@revikornmann/muka-ui 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +81 -60
  2. package/cli/bin/muka-ui.js +12 -5
  3. package/cli/commands/brand.js +33 -14
  4. package/cli/commands/init.js +21 -17
  5. package/cli/commands/install-skill.js +84 -26
  6. package/cli/templates/AGENTS.md +128 -0
  7. package/cli/templates/CLAUDE.md +8 -88
  8. package/cli/templates/muka-ui-guidelines.md +50 -14
  9. package/dist/cjs/components/ActionSheet/ActionSheet.css +4 -4
  10. package/dist/cjs/components/BottomBar/BottomBar.css +0 -3
  11. package/dist/cjs/components/Breadcrumb/Breadcrumb.css +26 -2
  12. package/dist/cjs/components/Combobox/Combobox.css +116 -132
  13. package/dist/cjs/components/Combobox/Combobox.js +175 -52
  14. package/dist/cjs/components/Combobox/Combobox.js.map +1 -1
  15. package/dist/cjs/components/Combobox/index.js.map +1 -1
  16. package/dist/cjs/components/ContextSelect/ContextSelect.css +4 -4
  17. package/dist/cjs/components/DocumentViewer/DocumentViewer.css +2 -2
  18. package/dist/cjs/components/DropdownSelect/DropdownSelect.css +225 -0
  19. package/dist/cjs/components/DropdownSelect/DropdownSelect.js +106 -0
  20. package/dist/cjs/components/DropdownSelect/DropdownSelect.js.map +1 -0
  21. package/dist/cjs/components/DropdownSelect/index.js +6 -0
  22. package/dist/cjs/components/DropdownSelect/index.js.map +1 -0
  23. package/dist/cjs/components/EmptyState/EmptyState.css +8 -8
  24. package/dist/cjs/components/FAB/FAB.css +1 -1
  25. package/dist/cjs/components/FileUpload/FileUpload.css +10 -10
  26. package/dist/cjs/components/Icon/custom/ArrowReturnIcon.js +11 -0
  27. package/dist/cjs/components/Icon/custom/ArrowReturnIcon.js.map +1 -0
  28. package/dist/cjs/components/Icon/iconRegistry.js +14 -1
  29. package/dist/cjs/components/Icon/iconRegistry.js.map +1 -1
  30. package/dist/cjs/components/Input/Input.css +47 -10
  31. package/dist/cjs/components/Input/Input.js +14 -3
  32. package/dist/cjs/components/Input/Input.js.map +1 -1
  33. package/dist/cjs/components/Label/Label.css +1 -1
  34. package/dist/cjs/components/LicensePlateInput/LicensePlateInput.css +1 -1
  35. package/dist/cjs/components/Menu/Menu.css +124 -38
  36. package/dist/cjs/components/Menu/Menu.js +283 -25
  37. package/dist/cjs/components/Menu/Menu.js.map +1 -1
  38. package/dist/cjs/components/Pagination/Pagination.css +15 -15
  39. package/dist/cjs/components/PhotoUploader/PhotoUploader.css +4 -4
  40. package/dist/cjs/components/ProgressAccordeon/ProgressAccordeon.css +10 -6
  41. package/dist/cjs/components/ProgressAccordeon/ProgressAccordeon.js +27 -16
  42. package/dist/cjs/components/ProgressAccordeon/ProgressAccordeon.js.map +1 -1
  43. package/dist/cjs/components/ProgressTracker/ProgressTracker.css +273 -22
  44. package/dist/cjs/components/ProgressTracker/ProgressTracker.js +109 -9
  45. package/dist/cjs/components/ProgressTracker/ProgressTracker.js.map +1 -1
  46. package/dist/cjs/components/Scrollbar/Scrollbar.css +7 -6
  47. package/dist/cjs/components/SearchInput/SearchInput.css +4 -4
  48. package/dist/cjs/components/Select/Select.css +2 -2
  49. package/dist/cjs/components/SmsOtpField/SmsOtpField.css +2 -2
  50. package/dist/cjs/components/SpecList/SpecList.css +8 -8
  51. package/dist/cjs/components/Spinner/Spinner.css +1 -1
  52. package/dist/cjs/components/SwipeActions/SwipeActions.css +6 -6
  53. package/dist/cjs/components/Table/Table.css +3 -3
  54. package/dist/cjs/components/Table/TablePagination.css +1 -1
  55. package/dist/cjs/components/Tabs/Tabs.css +6 -6
  56. package/dist/cjs/components/Textarea/Textarea.css +2 -2
  57. package/dist/cjs/components/Waveform/Waveform.css +1 -1
  58. package/dist/cjs/components/index.js +5 -3
  59. package/dist/cjs/components/index.js.map +1 -1
  60. package/dist/esm/components/ActionSheet/ActionSheet.css +4 -4
  61. package/dist/esm/components/BottomBar/BottomBar.css +0 -3
  62. package/dist/esm/components/Breadcrumb/Breadcrumb.css +26 -2
  63. package/dist/esm/components/Combobox/Combobox.css +116 -132
  64. package/dist/esm/components/Combobox/Combobox.js +177 -54
  65. package/dist/esm/components/Combobox/Combobox.js.map +1 -1
  66. package/dist/esm/components/Combobox/index.js +1 -1
  67. package/dist/esm/components/Combobox/index.js.map +1 -1
  68. package/dist/esm/components/ContextSelect/ContextSelect.css +4 -4
  69. package/dist/esm/components/DocumentViewer/DocumentViewer.css +2 -2
  70. package/dist/esm/components/DropdownSelect/DropdownSelect.css +225 -0
  71. package/dist/esm/components/DropdownSelect/DropdownSelect.js +102 -0
  72. package/dist/esm/components/DropdownSelect/DropdownSelect.js.map +1 -0
  73. package/dist/esm/components/DropdownSelect/index.js +2 -0
  74. package/dist/esm/components/DropdownSelect/index.js.map +1 -0
  75. package/dist/esm/components/EmptyState/EmptyState.css +8 -8
  76. package/dist/esm/components/FAB/FAB.css +1 -1
  77. package/dist/esm/components/FileUpload/FileUpload.css +10 -10
  78. package/dist/esm/components/Icon/custom/ArrowReturnIcon.js +7 -0
  79. package/dist/esm/components/Icon/custom/ArrowReturnIcon.js.map +1 -0
  80. package/dist/esm/components/Icon/iconRegistry.js +14 -1
  81. package/dist/esm/components/Icon/iconRegistry.js.map +1 -1
  82. package/dist/esm/components/Input/Input.css +47 -10
  83. package/dist/esm/components/Input/Input.js +14 -3
  84. package/dist/esm/components/Input/Input.js.map +1 -1
  85. package/dist/esm/components/Label/Label.css +1 -1
  86. package/dist/esm/components/LicensePlateInput/LicensePlateInput.css +1 -1
  87. package/dist/esm/components/Menu/Menu.css +124 -38
  88. package/dist/esm/components/Menu/Menu.js +284 -26
  89. package/dist/esm/components/Menu/Menu.js.map +1 -1
  90. package/dist/esm/components/Pagination/Pagination.css +15 -15
  91. package/dist/esm/components/PhotoUploader/PhotoUploader.css +4 -4
  92. package/dist/esm/components/ProgressAccordeon/ProgressAccordeon.css +10 -6
  93. package/dist/esm/components/ProgressAccordeon/ProgressAccordeon.js +27 -16
  94. package/dist/esm/components/ProgressAccordeon/ProgressAccordeon.js.map +1 -1
  95. package/dist/esm/components/ProgressTracker/ProgressTracker.css +273 -22
  96. package/dist/esm/components/ProgressTracker/ProgressTracker.js +110 -10
  97. package/dist/esm/components/ProgressTracker/ProgressTracker.js.map +1 -1
  98. package/dist/esm/components/Scrollbar/Scrollbar.css +7 -6
  99. package/dist/esm/components/SearchInput/SearchInput.css +4 -4
  100. package/dist/esm/components/Select/Select.css +2 -2
  101. package/dist/esm/components/SmsOtpField/SmsOtpField.css +2 -2
  102. package/dist/esm/components/SpecList/SpecList.css +8 -8
  103. package/dist/esm/components/Spinner/Spinner.css +1 -1
  104. package/dist/esm/components/SwipeActions/SwipeActions.css +6 -6
  105. package/dist/esm/components/Table/Table.css +3 -3
  106. package/dist/esm/components/Table/TablePagination.css +1 -1
  107. package/dist/esm/components/Tabs/Tabs.css +6 -6
  108. package/dist/esm/components/Textarea/Textarea.css +2 -2
  109. package/dist/esm/components/Waveform/Waveform.css +1 -1
  110. package/dist/esm/components/index.js +1 -0
  111. package/dist/esm/components/index.js.map +1 -1
  112. package/dist/styles/components/ActionSheet.css +4 -4
  113. package/dist/styles/components/BottomBar.css +0 -3
  114. package/dist/styles/components/Breadcrumb.css +26 -2
  115. package/dist/styles/components/Combobox.css +116 -132
  116. package/dist/styles/components/ContextSelect.css +4 -4
  117. package/dist/styles/components/DocumentViewer.css +2 -2
  118. package/dist/styles/components/DropdownSelect.css +225 -0
  119. package/dist/styles/components/EmptyState.css +8 -8
  120. package/dist/styles/components/FAB.css +1 -1
  121. package/dist/styles/components/FileUpload.css +10 -10
  122. package/dist/styles/components/Input.css +47 -10
  123. package/dist/styles/components/Label.css +1 -1
  124. package/dist/styles/components/LicensePlateInput.css +1 -1
  125. package/dist/styles/components/Menu.css +124 -38
  126. package/dist/styles/components/Pagination.css +15 -15
  127. package/dist/styles/components/PhotoUploader.css +4 -4
  128. package/dist/styles/components/ProgressAccordeon.css +10 -6
  129. package/dist/styles/components/ProgressTracker.css +273 -22
  130. package/dist/styles/components/Scrollbar.css +7 -6
  131. package/dist/styles/components/SearchInput.css +4 -4
  132. package/dist/styles/components/Select.css +2 -2
  133. package/dist/styles/components/SmsOtpField.css +2 -2
  134. package/dist/styles/components/SpecList.css +8 -8
  135. package/dist/styles/components/Spinner.css +1 -1
  136. package/dist/styles/components/SwipeActions.css +6 -6
  137. package/dist/styles/components/Table.css +3 -3
  138. package/dist/styles/components/Tabs.css +6 -6
  139. package/dist/styles/components/Textarea.css +2 -2
  140. package/dist/styles/components/Typography.css +89 -0
  141. package/dist/styles/components/Waveform.css +1 -1
  142. package/dist/styles/index.css +1167 -462
  143. package/dist/styles/muka-dark.css +1167 -462
  144. package/dist/styles/muka-light.css +1167 -462
  145. package/dist/styles/tokens-bouwplan-dark.css +5 -4
  146. package/dist/styles/tokens-bouwplan-light.css +5 -4
  147. package/dist/styles/tokens-fscl-dark.css +25 -24
  148. package/dist/styles/tokens-fscl-light.css +25 -24
  149. package/dist/styles/tokens-grip-dark.css +4 -3
  150. package/dist/styles/tokens-grip-light.css +4 -3
  151. package/dist/styles/tokens-muka-dark.css +4 -3
  152. package/dist/styles/tokens-muka-light.css +4 -3
  153. package/dist/styles/tokens-wireframe-dark.css +49 -48
  154. package/dist/styles/tokens-wireframe-light.css +49 -48
  155. package/dist/styles/wireframe-dark.css +1212 -507
  156. package/dist/styles/wireframe-light.css +1212 -507
  157. package/dist/types/components/Combobox/Combobox.d.ts +38 -38
  158. package/dist/types/components/Combobox/Combobox.d.ts.map +1 -1
  159. package/dist/types/components/Combobox/index.d.ts +1 -1
  160. package/dist/types/components/Combobox/index.d.ts.map +1 -1
  161. package/dist/types/components/DropdownSelect/DropdownSelect.d.ts +71 -0
  162. package/dist/types/components/DropdownSelect/DropdownSelect.d.ts.map +1 -0
  163. package/dist/types/components/DropdownSelect/index.d.ts +2 -0
  164. package/dist/types/components/DropdownSelect/index.d.ts.map +1 -0
  165. package/dist/types/components/Icon/custom/ArrowReturnIcon.d.ts +9 -0
  166. package/dist/types/components/Icon/custom/ArrowReturnIcon.d.ts.map +1 -0
  167. package/dist/types/components/Icon/iconRegistry.d.ts.map +1 -1
  168. package/dist/types/components/Input/Input.d.ts +5 -0
  169. package/dist/types/components/Input/Input.d.ts.map +1 -1
  170. package/dist/types/components/Menu/Menu.d.ts +42 -2
  171. package/dist/types/components/Menu/Menu.d.ts.map +1 -1
  172. package/dist/types/components/ProgressAccordeon/ProgressAccordeon.d.ts +17 -12
  173. package/dist/types/components/ProgressAccordeon/ProgressAccordeon.d.ts.map +1 -1
  174. package/dist/types/components/ProgressTracker/ProgressTracker.d.ts +29 -8
  175. package/dist/types/components/ProgressTracker/ProgressTracker.d.ts.map +1 -1
  176. package/dist/types/components/index.d.ts +1 -0
  177. package/dist/types/components/index.d.ts.map +1 -1
  178. package/docs/consumers/README.md +116 -37
  179. package/docs/consumers/brand.md +261 -0
  180. package/docs/consumers/figma-console-mcp.md +116 -0
  181. package/docs/consumers/setup-instructions.md +98 -12
  182. package/docs/consumers/skills.md +79 -0
  183. package/package.json +5 -6
  184. package/scripts/postinstall-nudge.js +8 -4
  185. package/skills/add-brand/SKILL.md +204 -0
  186. package/skills/add-brand/reference.md +160 -0
  187. package/skills/figma-to-code/SKILL.md +127 -0
  188. package/skills/pull-from-figma/SKILL.md +123 -0
  189. package/skills/push-to-figma/SKILL.md +172 -0
  190. package/skills/setup-muka/SKILL.md +73 -13
  191. package/tokens/README.md +39 -17
  192. package/tokens/t2-alias/brand/bouwplan/fonts.json +1 -1
  193. package/tokens/t2-alias/brand/fscl/fonts.json +3 -3
  194. package/tokens/t2-alias/brand/wireframe/fonts.json +2 -2
  195. package/tokens/t4-components/menu.json +8 -3
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: figma-to-code
3
+ description: Build a screen in this app from a Figma link, composing Muka UI components and design tokens instead of writing new styled markup.
4
+ disable-model-invocation: true
5
+ argument-hint: "[figma-url]"
6
+ ---
7
+
8
+ # Figma link → Muka UI screen
9
+
10
+ Turn a Figma design into working code in **this app** by assembling it from Muka
11
+ UI components. `$ARGUMENTS` is the Figma URL.
12
+
13
+ The goal is assembly, not reproduction. A Figma frame drawn with the Muka UI
14
+ Figma Library is already made of Muka components, so the right output is those
15
+ same components with props — not a pixel-matched rebuild in bespoke CSS. If the
16
+ result is a pile of `<div>`s with hardcoded colours, the design was
17
+ re-implemented instead of mapped, and it will not follow brand or theme changes.
18
+
19
+ **Prerequisite:** `@revikornmann/muka-ui` is installed (`/setup-muka`) and a
20
+ Figma MCP server is connected.
21
+
22
+ ## Step 1: Read the design
23
+
24
+ Extract `fileKey` and `nodeId` from the URL:
25
+
26
+ - `figma.com/design/:fileKey/:fileName?node-id=:nodeId` — convert `-` to `:` in
27
+ the nodeId (`472-4248` → `472:4248`)
28
+ - `figma.com/design/:fileKey/branch/:branchKey/…` — use `branchKey` as the
29
+ fileKey
30
+
31
+ Then pull the design context:
32
+
33
+ ```
34
+ get_code_connect_map(fileKey, nodeId) # FIRST — Figma node → real component names
35
+ get_design_context(fileKey, nodeId) # structure, tokens, variants
36
+ get_screenshot(fileKey, nodeId) # visual reference to check yourself against
37
+ ```
38
+
39
+ Read `get_code_connect_map` **first**. Muka publishes Code Connect mappings for
40
+ its components, so this is what tells you a node is a Muka `Button` rather than a
41
+ rounded rectangle with a label. Treat its answer as authoritative over your own
42
+ reading of the layers.
43
+
44
+ Treat `get_design_context` output as a **reference, not final code**. It
45
+ describes the design in generic terms; your job is the translation.
46
+
47
+ ## Step 2: Inventory what Muka already provides
48
+
49
+ Before writing any JSX, find out what exists. In order of reliability:
50
+
51
+ 1. **Storybook MCP**, if Muka's Storybook is running — the component list and
52
+ live prop tables are the most accurate source there is.
53
+ 2. **The published Storybook** at <https://muka.kornmann.com> — component APIs,
54
+ Playgrounds, and usage patterns.
55
+ 3. **The installed types**:
56
+ ```bash
57
+ grep 'export' node_modules/@revikornmann/muka-ui/dist/types/components/index.d.ts
58
+ ```
59
+ 4. **`npx muka-ui components -v`** — names and prop summaries from the package
60
+ manifest.
61
+ 5. **`docs/muka-ui-guidelines.md`** in this repo, if `npx muka-ui init` has run.
62
+
63
+ Write out the mapping before you build, so the gaps are visible up front:
64
+
65
+ | Figma layer | Muka component | Props |
66
+ |---|---|---|
67
+ | "Primary CTA" | `Button` | `variant="primary" size="md"` |
68
+ | "Search field" | `Input` | `placeholder`, leading icon slot |
69
+ | "Nav bar" | `TopBar` | title, actions |
70
+
71
+ ## Step 3: Compose the screen
72
+
73
+ Build from the outside in: page shell, then regions, then controls.
74
+
75
+ - **Use Muka components for anything Muka has.** Reach for a raw element only
76
+ when nothing fits.
77
+ - **Compose, don't fork.** Prefer slots and composition over copying a
78
+ component's source to tweak it. A forked component stops receiving upgrades
79
+ and brand changes.
80
+ - **Only Muka's CSS custom properties for styling.** For app-specific layout,
81
+ read T3 semantic properties:
82
+ ```css
83
+ .checkout-summary {
84
+ background-color: var(--color-surface-level1);
85
+ color: var(--color-text-default-default);
86
+ padding: var(--spacing-4);
87
+ border-radius: var(--radius-md);
88
+ }
89
+ ```
90
+ Never paste a hex, px type size, or radius out of Figma. Those values belong to
91
+ one brand in one theme; the token survives both.
92
+ - **Match the design's intent, not its pixels.** If Figma shows 15px of padding
93
+ and the nearest token is 16, use the token. Report differences larger than a
94
+ step rather than hardcoding around them.
95
+ - **Mobile first.** Muka is designed mobile-up with the desktop breakpoint at
96
+ `1024px`. Build the narrow layout first, then enhance.
97
+
98
+ ## Step 4: Handle what Muka does not have
99
+
100
+ When a design needs something Muka does not ship, say so explicitly rather than
101
+ quietly inventing a lookalike — a bespoke component that mimics a Muka one is
102
+ the most expensive outcome, because it looks correct until the brand changes.
103
+
104
+ 1. Re-check whether composing existing primitives covers it.
105
+ 2. If not, build it in **this repo** (not in Muka) styled entirely with Muka's
106
+ token custom properties, so it tracks brand and theme.
107
+ 3. Tell the user it is app-local and worth requesting upstream at
108
+ <https://github.com/revikornmann/muka/issues> if it is generic.
109
+
110
+ ## Step 5: Verify
111
+
112
+ - The screen renders and type-checks.
113
+ - **No hardcoded colours, type sizes, or radii.** Grep your diff for `#`,
114
+ `rgb(`, and `px` and justify every remaining hit.
115
+ - Compare against `get_screenshot` for structure, hierarchy, and spacing rhythm.
116
+ - **Switch theme and confirm nothing breaks.** Load the dark stylesheet and
117
+ re-check: anything that does not react was hardcoded. This is the single most
118
+ effective check that the mapping was done properly.
119
+ - Keyboard reachability: every interactive element is tabbable with a visible
120
+ focus ring.
121
+ - If this repo has a custom brand, confirm the screen picks it up.
122
+
123
+ ## Next
124
+
125
+ - `/add-brand` — if the design is in a brand this repo does not have yet.
126
+ - `/pull-from-figma` — if a designer changed brand colours in Figma and the code
127
+ needs to catch up.
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: pull-from-figma
3
+ description: Pull brand colour and font changes a designer made in Figma back into this repo's brand token files.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Pull brand changes from Figma
8
+
9
+ A designer retuned your brand's colours in Figma; bring those edits back into
10
+ `brand/light.json`, `brand/dark.json`, and `brand/fonts.json`.
11
+
12
+ **Direction:** Figma → Code. **Scope:** only your brand's `alias/*` tokens.
13
+ Nothing is created or deleted — existing `$value` fields are updated in place.
14
+
15
+ ## Prerequisites
16
+
17
+ 1. This repo has a custom brand with modes pushed to Figma — run `/add-brand`
18
+ then `/push-to-figma` first.
19
+ 2. **Figma Console MCP** is configured and the Desktop Bridge plugin is running.
20
+ One-time setup:
21
+ `node_modules/@revikornmann/muka-ui/docs/consumers/figma-console-mcp.md`
22
+ (also online at
23
+ <https://github.com/revikornmann/muka/blob/main/docs/consumers/figma-console-mcp.md>).
24
+
25
+ ## Why only the alias layer
26
+
27
+ Your repo owns exactly one layer of the token system. Semantics and component
28
+ tokens come from the installed package and are overwritten on the next upgrade,
29
+ so pulling them in would produce changes that silently disappear. If a designer
30
+ changed `color/surface/level1` or a `button/*` variable, that is a **library**
31
+ change: it belongs in a pull request against
32
+ <https://github.com/revikornmann/muka>, and you should say so rather than writing
33
+ it into `brand/`.
34
+
35
+ ## Step 1: Confirm the bridge is live
36
+
37
+ ```
38
+ figma_get_status { probe: true }
39
+ ```
40
+
41
+ ## Step 2: Find your brand's modes
42
+
43
+ Look collections and modes up by name — never hardcode IDs:
44
+
45
+ ```javascript
46
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
47
+ const theme = collections.find((c) => c.name === 'Theme');
48
+ const light = theme.modes.find((m) => m.name === 'Acme Light').modeId;
49
+ const dark = theme.modes.find((m) => m.name === 'Acme Dark').modeId;
50
+ ```
51
+
52
+ ## Step 3: Read the alias variables
53
+
54
+ Read every variable whose name starts with `alias/`, taking the value for your
55
+ two modes only. Ignore all other modes — they are other brands.
56
+
57
+ ## Step 4: Resolve each value back to a token reference
58
+
59
+ - **Variable alias** (the normal case, pointing into Primitives): resolve the
60
+ target variable's name and convert slashes to dots.
61
+ `color/violet/9` → `{color.violet.9}`
62
+ - **Concrete string** (font family or weight): keep the string, converting
63
+ Figma's style names back to CSS numerics — `SemiBold` → `600`, `Bold` → `700`,
64
+ `Medium` → `500`, `Regular` → `400`.
65
+ - **Raw colour value**: this is a problem, not a value to pull. It means the
66
+ variable was detached from the ramp. Report it and re-push that token as an
67
+ alias instead of writing a hex into the token files, which would opt it out of
68
+ the system permanently.
69
+
70
+ ## Step 5: Map to files
71
+
72
+ | Variable prefix | Target file |
73
+ |---|---|
74
+ | `alias/color/*` read at your **light** mode | `brand/light.json` |
75
+ | `alias/color/*` read at your **dark** mode | `brand/dark.json` |
76
+ | `alias/font/*` | `brand/fonts.json` |
77
+
78
+ Figma slash-paths map to token dot-paths directly:
79
+ `alias/color/accent/default` → `alias.color.accent.default`.
80
+
81
+ ## Step 6: Apply the diff
82
+
83
+ For each token that already exists in the target file:
84
+
85
+ 1. Compare the current `$value` with the resolved Figma value.
86
+ 2. **Show the user the full diff before writing anything.**
87
+ 3. Update only `$value`. Never touch `$type`, and preserve the `$schema` entry
88
+ and key order so the diff stays reviewable.
89
+
90
+ Safety rules:
91
+
92
+ - Only tokens already present in the files are updated.
93
+ - No tokens are created or deleted. A variable in Figma with no counterpart in
94
+ the files is reported, not added — it usually means the designer added
95
+ something that needs a code-side decision first.
96
+ - A token missing from Figma is left alone, not blanked.
97
+
98
+ ## Step 7: Rebuild and check
99
+
100
+ ```bash
101
+ npm run build:tokens
102
+ ```
103
+
104
+ Then verify:
105
+
106
+ - No `undefined` in `styles/tokens-<brand>-*.css` — a failed reference resolution.
107
+ - Both light and dark still read correctly; a designer tuning light mode in
108
+ isolation is the usual way dark-mode contrast regresses.
109
+ - Muka components pick up the change in the app.
110
+ - The dark values reference `Dark` ramps (`{color.violetDark.9}`). If a dark
111
+ token came back pointing at a light ramp, the designer edited the wrong mode.
112
+
113
+ Commit `brand/**` with a message naming what the designer changed.
114
+
115
+ ## Troubleshooting
116
+
117
+ | Symptom | Cause |
118
+ |---|---|
119
+ | Values come back as raw colours | The variable was detached from the ramp — re-push it as an alias |
120
+ | Everything appears unchanged | Reading the wrong modes; confirm the mode names match your brand |
121
+ | Dark values reference light ramps | The wrong Figma mode was edited |
122
+ | Semibold and bold pull the same weight | They were pushed as numerics and collapsed in Figma |
123
+ | `figma-console` tools absent | Session predates `.mcp.json`; approve via `/mcp` and restart |
@@ -0,0 +1,172 @@
1
+ ---
2
+ name: push-to-figma
3
+ description: Push this repo's custom brand tokens into Figma as variables, so designers can design in your brand using the Muka UI Figma Library.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Push your brand to Figma
8
+
9
+ Publish this repo's brand layer (`brand/light.json`, `brand/dark.json`,
10
+ `brand/fonts.json`) into Figma as variables, so designers see your brand in the
11
+ Muka UI Figma Library instead of one of Muka's.
12
+
13
+ **Direction:** Code → Figma. **The codebase is the source of truth.** Use
14
+ `/pull-from-figma` for the other direction.
15
+
16
+ ## Prerequisites
17
+
18
+ 1. This repo has a custom brand — run `/add-brand` first.
19
+ 2. **Figma Console MCP** is configured and the Desktop Bridge plugin is running.
20
+ One-time setup:
21
+ `node_modules/@revikornmann/muka-ui/docs/consumers/figma-console-mcp.md`
22
+ (also online at
23
+ <https://github.com/revikornmann/muka/blob/main/docs/consumers/figma-console-mcp.md>).
24
+ 3. You can edit the target Figma file (your own copy or branch of the Muka UI
25
+ Figma Library).
26
+
27
+ ## What gets pushed, and what does not
28
+
29
+ Only your **brand alias layer** — the ~78 `alias/color/*` tokens plus
30
+ `alias/font/*`. That is the whole point: primitives, semantics, and component
31
+ tokens belong to the Muka library and are inherited, exactly as in code.
32
+
33
+ ```
34
+ component (button/color/primary/background/default) → library, do not touch
35
+ semantic (color/surface/level0, color/action/default) → library, do not touch
36
+ alias (alias/color/accent/default) → YOURS, pushed here
37
+ Primitives (color/violet/9) → library, raw values
38
+ ```
39
+
40
+ If you find yourself pushing a `button/*` or `color/surface/*` variable, stop —
41
+ that is a library-level change and belongs in a Muka pull request, not your
42
+ brand.
43
+
44
+ ## Step 1: Confirm the bridge is live
45
+
46
+ ```
47
+ figma_get_status { probe: true }
48
+ ```
49
+
50
+ Expect `setup.valid: true`, `probeResult.success: true`, and the connected file
51
+ name. If the tools are missing entirely, the session started before the MCP
52
+ server was registered — restart it.
53
+
54
+ ## Step 2: Locate the collections
55
+
56
+ Do **not** hardcode collection or mode IDs; look them up by name in the file you
57
+ are connected to. Muka's own IDs differ in every copy and branch of the library.
58
+
59
+ ```javascript
60
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
61
+ for (const c of collections) {
62
+ console.log(c.name, c.id, c.modes.map((m) => `${m.name}=${m.modeId}`));
63
+ }
64
+ ```
65
+
66
+ You need two:
67
+
68
+ - **Primitives** — the raw ramps (`color/violet/9`). One mode. Read-only for you.
69
+ - **Theme** — one mode per brand/theme combination (`Muka Light`, `Muka Dark`, …).
70
+ This is where your brand's modes live.
71
+
72
+ Build a name → id map across **both** collections; you will alias into Primitives
73
+ by name.
74
+
75
+ ## Step 3: Add modes for your brand
76
+
77
+ Your brand needs two modes in the Theme collection, named to match the existing
78
+ convention:
79
+
80
+ ```javascript
81
+ const theme = collections.find((c) => c.name === 'Theme');
82
+ const lightModeId = theme.addMode('Acme Light');
83
+ const darkModeId = theme.addMode('Acme Dark');
84
+ ```
85
+
86
+ Skip this if the modes already exist — find them by name and reuse their
87
+ `modeId`. Figma has a per-collection mode limit that depends on the plan; if
88
+ `addMode` throws, the file has hit it and modes must be freed before continuing.
89
+
90
+ ## Step 4: Bind values — as aliases, never raw colours
91
+
92
+ **This is the step that goes wrong.** Every alias token must be a variable
93
+ **alias** pointing at a Primitive, not a resolved hex. A raw value is a
94
+ disconnected duplicate: it will not cascade when the ramp is edited, and it
95
+ drifts silently from your `{color.violet.9}` references in code.
96
+
97
+ Your `brand/light.json` already stores exactly the reference you need
98
+ (`{color.violet.9}` → the Primitives variable `color/violet/9`), so convert the
99
+ path and bind:
100
+
101
+ ```javascript
102
+ // '{color.violet.9}' → 'color/violet/9'
103
+ const target = value.replace(/[{}]/g, '').split('.').join('/');
104
+
105
+ const variable = await figma.variables.getVariableByIdAsync(aliasVarId);
106
+ variable.setValueForMode(lightModeId, {
107
+ type: 'VARIABLE_ALIAS',
108
+ id: nameToId[target],
109
+ });
110
+ ```
111
+
112
+ Each mode gets a **different** Primitives target — light mode points at
113
+ `color/violet/9`, dark mode at `color/violetDark/9`. Per-mode cross-collection
114
+ aliases do cascade, and that is what makes mode switching work.
115
+
116
+ > `figma_batch_create_variables` and `figma_batch_update_variables` accept only
117
+ > scalar/hex `valuesByMode` — they **cannot** set alias bindings. Use them to
118
+ > create bare variables in bulk, then bind in a separate `figma_execute` pass.
119
+
120
+ Raw `{r, g, b, a}` values are correct only in the Primitives collection. If you
121
+ ever must write one, pass an object — **never a hex string**, which drops the
122
+ alpha channel and flattens the `black-alpha` / `white-alpha` ramps to opaque.
123
+
124
+ ## Step 5: Push the fonts
125
+
126
+ `alias/font/*` variables are strings, not aliases, so set them directly — with
127
+ two conversions:
128
+
129
+ - **Family**: reduce a CSS stack to the single installed face name. Pushing
130
+ `'IBM Plex Sans', ui-sans-serif, system-ui` makes every bound text node render
131
+ as a **missing font**; push `IBM Plex Sans`.
132
+ - **Weight**: Figma `fontStyle` bindings need style **names**, not the CSS
133
+ numerics your tokens hold — `600` → `SemiBold`, `700` → `Bold`, `500` →
134
+ `Medium`, `400` → `Regular`. Pushing numerics collapses semibold and bold onto
135
+ the same face.
136
+
137
+ The font must be installed locally or available to the file, or every bound text
138
+ node shows as missing.
139
+
140
+ > Repairing a font-family variable that *already* holds a missing font is
141
+ > blocked, because Figma cannot re-render bound text in a font that will not
142
+ > load. Temporarily flip the consuming containers' mode
143
+ > (`node.explicitVariableModes`), set the variable, then flip back. Do **not**
144
+ > override `node.fontName` on bound instance text — that detaches the binding and
145
+ > breaks the other modes.
146
+
147
+ ## Step 6: Verify
148
+
149
+ 1. **Every pushed token reads as an _Alias_** in the inspector — an alias pill,
150
+ not a colour swatch. Programmatically, assert every `valuesByMode` entry has
151
+ `type === 'VARIABLE_ALIAS'`. A swatch means Step 4 was done wrong.
152
+ 2. **Resolve and diff against the source.** Follow each alias to a concrete
153
+ colour and confirm it matches `styles/tokens-<brand>-light.css`. A
154
+ concatenated-hex checksum per variable across both modes is a cheap way to
155
+ compare in bulk.
156
+ 3. **Switch modes in Figma** and confirm the design visibly changes: your light
157
+ mode shows your accent colour, dark mode shows dark surfaces with readable
158
+ text.
159
+ 4. **Spot-check a component instance.** A primary Button in your mode should be
160
+ your accent colour, which proves the whole component → semantic → alias →
161
+ primitive chain resolved.
162
+
163
+ ## Troubleshooting
164
+
165
+ | Symptom | Cause |
166
+ |---|---|
167
+ | Tokens show a colour swatch, not an alias pill | Pushed as raw hex — rebind per Step 4 |
168
+ | Mode switching changes nothing | Every mode was bound to the same target; each needs its own |
169
+ | Text renders as missing font | A CSS stack or an uninstalled family was pushed |
170
+ | Semibold and bold look identical | Numeric weights were pushed instead of style names |
171
+ | Transparency ramps look opaque | A hex string was pushed instead of `{r, g, b, a}` |
172
+ | `figma-console` tools absent | Session predates `.mcp.json`; approve via `/mcp` and restart |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-muka
3
- description: Set up a consumer repo to use Muka UI from npm — install the package, wire up the auto-update workflow, and register as a consumer.
3
+ description: Install Muka UI in this repo from npm, wire up styles and fonts, add the auto-update workflow, and register as a consumer.
4
4
  disable-model-invocation: true
5
5
  argument-hint: "[brand]"
6
6
  ---
@@ -8,16 +8,17 @@ argument-hint: "[brand]"
8
8
  # Set up Muka UI in this repo
9
9
 
10
10
  Configure the current repo to consume the **public npm package**
11
- `@revikornmann/muka-ui` and stay current with releases automatically. The
12
- optional `$ARGUMENTS` is the brand to theme with (e.g. `fscl`, `grip`,
13
- `bouwplan`); default brand is `muka`.
11
+ `@revikornmann/muka-ui`. The optional `$ARGUMENTS` is the brand to theme with
12
+ (`muka`, `wireframe`, `grip`, `fscl`, `bouwplan`); default is `muka`.
14
13
 
15
14
  Muka UI is a public npm package — **no `.npmrc`, registry config, or auth token
16
- is required**. When the package is installed, the version-locked copy of these
17
- steps lives at
15
+ is required**. Once installed, the version-locked copy of these steps lives at
18
16
  `node_modules/@revikornmann/muka-ui/docs/consumers/setup-instructions.md`; read
19
17
  it and prefer it when present, since it always matches the installed version.
20
18
 
19
+ To build a **custom brand** for this repo instead of using one of Muka's five,
20
+ finish this skill first and then run `/add-brand`.
21
+
21
22
  ## Step 1: Install the package
22
23
 
23
24
  If a previous git dependency exists (e.g.
@@ -39,26 +40,59 @@ npm ls @revikornmann/muka-ui # or: pnpm why … / yarn why …
39
40
 
40
41
  ## Step 2: Import styles and components
41
42
 
42
- In the app's root/entry, import the stylesheet once, then components anywhere:
43
+ In the app's root/entry, import one stylesheet, then components anywhere:
43
44
 
44
45
  ```ts
45
46
  import '@revikornmann/muka-ui/styles';
46
47
  import { Button, Card, Input } from '@revikornmann/muka-ui';
47
48
  ```
48
49
 
49
- For a brand theme, also import the brand token CSS and set the data attributes.
50
- Replace `<brand>` with `$ARGUMENTS` (or `muka`) and pick `light`/`dark`:
50
+ `/styles` is the **`muka-light`** bundle: base styles, the muka-light tokens,
51
+ every component's CSS, and muka's self-hosted fonts. Four pre-bundled
52
+ alternatives ship alongside it and are drop-in replacements:
53
+
54
+ ```ts
55
+ import '@revikornmann/muka-ui/styles/muka-dark.css';
56
+ import '@revikornmann/muka-ui/styles/wireframe-light.css';
57
+ import '@revikornmann/muka-ui/styles/wireframe-dark.css';
58
+ ```
59
+
60
+ For `grip`, `fscl`, or `bouwplan` there is no pre-bundled file, so compose it
61
+ from three imports — base styles, the brand's raw token CSS, and the brand's
62
+ fonts (the raw token CSS carries no `@font-face`):
51
63
 
52
64
  ```ts
65
+ import '@revikornmann/muka-ui/styles/base.css';
53
66
  import '@revikornmann/muka-ui/styles/tokens-<brand>-light.css';
67
+ import '@revikornmann/muka-ui/styles/fonts-<brand>.css';
54
68
  ```
55
69
 
56
- ```html
57
- <html data-brand="<brand>" data-theme="light">
70
+ ### How brand and theme selection actually works
71
+
72
+ Every token stylesheet declares its variables on `:root`. There are **no
73
+ `[data-brand]` or `[data-theme]` selectors**, so you select a brand and theme by
74
+ **choosing which stylesheet is loaded**, and importing two of them means the
75
+ last one wins. Do not add `data-brand` / `data-theme` attributes expecting them
76
+ to switch anything.
77
+
78
+ For a runtime light/dark toggle, swap a `<link>` instead of importing both:
79
+
80
+ ```ts
81
+ const link = document.getElementById('muka-theme') as HTMLLinkElement;
82
+ link.href = `/styles/tokens-${brand}-${mode}.css`;
58
83
  ```
59
84
 
60
- Apps that already centralize CSS (e.g. a `src/styles/index.css`) may instead
61
- `@import '@revikornmann/muka-ui/styles/base.css'` and the brand token CSS there.
85
+ Copy the brand's token CSS into the app's static/public directory (or let the
86
+ bundler emit it with a stable URL) so those hrefs resolve. This is exactly what
87
+ Muka's own Storybook does.
88
+
89
+ ### Fonts come from Muka — do not load them yourself
90
+
91
+ Muka self-hosts every brand's fonts as `@font-face` backed by bundled WOFF2.
92
+ Do **not** load them again via `next/font`, a Google Fonts `<link>`, or another
93
+ loader, and do **not** override the `--alias-font-*-family` tokens — the token
94
+ values are the exact `@font-face` family names, so overriding them only breaks
95
+ resolution.
62
96
 
63
97
  ## Step 3: Add the auto-update workflow
64
98
 
@@ -82,6 +116,10 @@ lockfile, `lockfileVersion: 9.0` → pnpm 9). Without it, CI's corepack pulls th
82
116
  `minimumReleaseAge` default rejecting a just-published version). Add it if
83
117
  missing.
84
118
 
119
+ Muka is under active development and a new version can change component
120
+ behaviour or appearance. Keep CI checks in place so the update workflow's bump
121
+ is reviewed rather than trusted.
122
+
85
123
  ## Step 4: Register this repo as a consumer
86
124
 
87
125
  So releases dispatch updates here, add `owner/repo` to Muka's
@@ -90,9 +128,31 @@ Open a PR against `revikornmann/muka` adding the line (skip if it is already
90
128
  listed). The Muka `CONSUMER_DISPATCH_TOKEN` PAT must have **write** access to
91
129
  this repo for the `repository_dispatch` to succeed.
92
130
 
131
+ ## Step 5: Give future agents the house rules
132
+
133
+ ```bash
134
+ npx muka-ui init
135
+ ```
136
+
137
+ This writes `AGENTS.md`, `CLAUDE.md`, and `docs/muka-ui-guidelines.md` into the
138
+ repo (it prompts before overwriting any of them) so any agent working here knows
139
+ to compose Muka components and use token custom properties instead of hardcoded
140
+ values. It also warns about styling libraries that conflict with Muka.
141
+
142
+ `AGENTS.md` carries the rules — it is the filename most agents read — and
143
+ `CLAUDE.md` is a short pointer to it, so there is only one copy to keep current.
144
+
93
145
  ## Verify
94
146
 
95
147
  - `npm ls @revikornmann/muka-ui` shows a semver version resolved from npmjs.
96
148
  - The app builds/type-checks and Muka components render with brand tokens.
149
+ - A component's computed `background-color` resolves to a real value, not the
150
+ `var(...)` fallback — that is the check that the token CSS actually loaded.
97
151
  - `.github/workflows/update-muka.yml` exists and is valid YAML.
98
152
  - This repo appears in Muka's `.github/consumers.txt` (or a PR is open for it).
153
+
154
+ ## Next
155
+
156
+ - `/add-brand` — build a custom brand that overrides Muka's brand layer.
157
+ - `/figma-to-code` — turn a Figma link into a screen built from Muka components.
158
+ - `npx muka-ui install-skill --list` — every skill shipped with the package.
package/tokens/README.md CHANGED
@@ -1,26 +1,37 @@
1
- `# Design Tokens
1
+ # Design Tokens
2
2
 
3
3
  ## Directory Structure
4
4
 
5
5
  ```
6
6
  tokens/
7
- ├── t1-primitives/ # Raw values: color.json, scale.json, typography.json
7
+ ├── t1-primitives/ # Raw values: color.json, scale.json, typography.json, motion.json
8
8
  ├── t2-alias/
9
9
  │ ├── base.json # Brand-agnostic aliases
10
- │ ├── brand/
11
- │ │ ├── muka/ # fonts.json, light.json, dark.json
12
- │ │ ├── wireframe/ # fonts.json, light.json, dark.json
13
- │ │ └── grip/ # fonts.json, light.json, dark.json
10
+ │ ├── brand/ # One directory per brand, each with fonts/light/dark.json
11
+ │ │ ├── muka/ # + radius.json
12
+ │ │ ├── wireframe/
13
+ │ │ ├── grip/
14
+ │ │ ├── fscl/
15
+ │ │ └── bouwplan/
14
16
  │ └── layout/
15
17
  │ ├── mobile.json # Mobile-first base (default)
16
18
  │ ├── tablet.json # md breakpoint (768px) overrides
17
- │ └── desktop.json # lg breakpoint (1024px) overrides
19
+ │ ├── desktop.json # lg breakpoint (1024px) overrides
20
+ │ └── wide.json # xl breakpoint (1280px) overrides
18
21
  ├── t3-semantics/ # ui.json — usage-intent tokens
19
- ├── t4-components/ # 28 component token files
22
+ ├── t4-components/ # 35 component token files
20
23
  ├── $themes.json # Tokens Studio theme config
21
24
  └── $metadata.json # Token metadata
22
25
  ```
23
26
 
27
+ Five brands × light/dark = **10 theme builds**, each emitting one
28
+ `styles/tokens-<brand>-<theme>.css`. The authoritative list is
29
+ `build/manifest.json`.
30
+
31
+ The whole `tokens/` tree ships in the npm package, so consumers can build a
32
+ custom brand against these primitives without forking. See
33
+ `docs/consumers/brand.md`.
34
+
24
35
  ## Naming Convention
25
36
 
26
37
  ```
@@ -38,14 +49,18 @@ tokens/
38
49
  { "$value": "{color.gray.11}" }
39
50
  ```
40
51
 
52
+ Dark-mode ramps append a **camelCase `Dark`** — `{color.mauveDark.1}`,
53
+ `{color.indigoDark.9}`. References are case-sensitive, so `{color.mauvedark.1}`
54
+ resolves to nothing.
55
+
41
56
  ## Inheritance Order
42
57
 
43
58
  1. T1 Primitives (base values)
44
59
  2. T2 Alias (brand-agnostic references)
45
60
  3. T3 Semantic (meaningful abstractions)
46
61
  4. T4 Component (component-specific)
47
- 5. Brand overrides (muka/, wireframe/, grip/)
48
- 6. Theme overrides (light.json, dark.json)
62
+ 5. Brand overrides (`brand/<name>/`)
63
+ 6. Theme overrides (`light.json`, `dark.json`)
49
64
 
50
65
  ## CSS Output
51
66
 
@@ -55,17 +70,23 @@ tokens/
55
70
 
56
71
  ## Brand/Theme Switching
57
72
 
58
- ```html
59
- <html data-theme="light" data-brand="muka">
60
- ```
73
+ Every generated stylesheet declares its variables on `:root`, so the active
74
+ brand and theme are determined by **which stylesheet is loaded** — there are no
75
+ `[data-brand]` / `[data-theme]` selectors, and loading two means the last one
76
+ wins.
77
+
78
+ Storybook switches by swapping a `<link href>` (`.storybook/ThemeDecorator.ts`);
79
+ consumers do the same, or import a single theme statically.
61
80
 
62
81
  ## Build Commands
63
82
 
64
83
  ```bash
65
- node scripts/build-tokens.js # Build all tokens → CSS
66
- node scripts/export-for-figma.js # Export for Figma Variables
67
- node scripts/export-for-penpot.js # Export for Penpot
84
+ npm run build:tokens # Build all themes → styles/tokens-*.css
85
+ npm run build:fonts # Self-host brand fonts → styles/fonts-*.css
68
86
  npm run validate:tokens # Validate JSON structure
87
+ node scripts/export-figma-variables.js # Export for Figma Variables
88
+ node scripts/export-for-penpot.js # Export for Penpot
89
+ node scripts/export-for-webflow.js # Export for Webflow
69
90
  ```
70
91
 
71
92
  ## Rules
@@ -92,5 +113,6 @@ to flow upward by reference; a brand file cannot override a `{component}.*` toke
92
113
  ## Human Docs
93
114
  <!-- SYNC: Update paths when Storybook docs change -->
94
115
  - Token architecture guide: stories/tokens/01-Introduction.mdx
95
- - Design Tokens in Storybook: stories/tokens/*.mdx (6 pages)
116
+ - Design Tokens in Storybook: stories/tokens/*.mdx (7 pages)
117
+ - Consumer brand guide: docs/consumers/brand.md
96
118
  - All Storybook docs: `npm run dev` → localhost:6006
@@ -18,7 +18,7 @@
18
18
  "family": { "$type": "fontFamilies", "$value": "Red Hat Text" },
19
19
  "weight": {
20
20
  "regular": { "$type": "fontWeights", "$value": "400" },
21
- "medium": { "$type": "fontWeights", "$value": "400" },
21
+ "medium": { "$type": "fontWeights", "$value": "500" },
22
22
  "semibold": { "$type": "fontWeights", "$value": "500" },
23
23
  "bold": { "$type": "fontWeights", "$value": "600" }
24
24
  }