@rfdtech/components 1.22.0 → 2.0.1

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 (218) hide show
  1. package/README.md +209 -81
  2. package/dist/adapters/next-adapter.d.ts.map +1 -1
  3. package/dist/components/app-header/AppHeader.d.ts.map +1 -1
  4. package/dist/components/app-header/AppHeaderNotificationItem.d.ts +3 -0
  5. package/dist/components/app-header/AppHeaderNotificationItem.d.ts.map +1 -0
  6. package/dist/components/app-header/AppHeaderProfile.d.ts +5 -0
  7. package/dist/components/app-header/AppHeaderProfile.d.ts.map +1 -1
  8. package/dist/components/app-header/index.d.ts +3 -1
  9. package/dist/components/app-header/index.d.ts.map +1 -1
  10. package/dist/components/app-switcher/AppIconTile.d.ts +10 -0
  11. package/dist/components/app-switcher/AppIconTile.d.ts.map +1 -0
  12. package/dist/components/app-switcher/AppSwitcher.d.ts +1 -1
  13. package/dist/components/app-switcher/AppSwitcher.d.ts.map +1 -1
  14. package/dist/components/app-switcher/AppSwitcherItem.d.ts.map +1 -1
  15. package/dist/components/app-switcher/IconPatterns.d.ts +8 -0
  16. package/dist/components/app-switcher/IconPatterns.d.ts.map +1 -0
  17. package/dist/components/app-switcher/SystemAppIcon.d.ts.map +1 -1
  18. package/dist/components/app-switcher/index.d.ts +2 -0
  19. package/dist/components/app-switcher/index.d.ts.map +1 -1
  20. package/dist/components/avatar/Avatar.d.ts.map +1 -1
  21. package/dist/components/bulk-import-modal/BulkImportModal.d.ts.map +1 -1
  22. package/dist/components/bulk-import-modal/steps/ValidateDataStep.d.ts.map +1 -1
  23. package/dist/components/button/Button.d.ts +1 -1
  24. package/dist/components/button/Button.d.ts.map +1 -1
  25. package/dist/components/button/Spinner.d.ts.map +1 -1
  26. package/dist/components/combobox/Combobox.d.ts +4 -0
  27. package/dist/components/combobox/Combobox.d.ts.map +1 -0
  28. package/dist/components/combobox/Combobox.test.d.ts +2 -0
  29. package/dist/components/combobox/Combobox.test.d.ts.map +1 -0
  30. package/dist/components/combobox/index.d.ts +3 -0
  31. package/dist/components/combobox/index.d.ts.map +1 -0
  32. package/dist/components/country-selector/CountrySelector.d.ts +7 -0
  33. package/dist/components/country-selector/CountrySelector.d.ts.map +1 -1
  34. package/dist/components/country-selector/index.d.ts +1 -0
  35. package/dist/components/country-selector/index.d.ts.map +1 -1
  36. package/dist/components/date-range-selector/DateRangeSelector.d.ts +2 -2
  37. package/dist/components/date-range-selector/DateRangeSelector.d.ts.map +1 -1
  38. package/dist/components/date-range-selector/index.d.ts +1 -1
  39. package/dist/components/date-range-selector/index.d.ts.map +1 -1
  40. package/dist/components/date-selector/DateSelector.d.ts.map +1 -1
  41. package/dist/components/dropdown/Dropdown.d.ts +1 -1
  42. package/dist/components/dropdown/Dropdown.d.ts.map +1 -1
  43. package/dist/components/export-button/ExportButton.d.ts +3 -0
  44. package/dist/components/export-button/ExportButton.d.ts.map +1 -0
  45. package/dist/components/export-button/ExportButton.test.d.ts +2 -0
  46. package/dist/components/export-button/ExportButton.test.d.ts.map +1 -0
  47. package/dist/components/export-button/index.d.ts +3 -0
  48. package/dist/components/export-button/index.d.ts.map +1 -0
  49. package/dist/components/launchpad/Launchpad.d.ts +4 -0
  50. package/dist/components/launchpad/Launchpad.d.ts.map +1 -0
  51. package/dist/components/launchpad/Launchpad.test.d.ts +2 -0
  52. package/dist/components/launchpad/Launchpad.test.d.ts.map +1 -0
  53. package/dist/components/launchpad/LaunchpadGridIcon.d.ts +2 -0
  54. package/dist/components/launchpad/LaunchpadGridIcon.d.ts.map +1 -0
  55. package/dist/components/launchpad/LaunchpadIconTile.d.ts +10 -0
  56. package/dist/components/launchpad/LaunchpadIconTile.d.ts.map +1 -0
  57. package/dist/components/launchpad/LaunchpadItem.d.ts +8 -0
  58. package/dist/components/launchpad/LaunchpadItem.d.ts.map +1 -0
  59. package/dist/components/launchpad/LaunchpadOverlays.d.ts +3 -0
  60. package/dist/components/launchpad/LaunchpadOverlays.d.ts.map +1 -0
  61. package/dist/components/launchpad/SystemLaunchpadIcon.d.ts +5 -0
  62. package/dist/components/launchpad/SystemLaunchpadIcon.d.ts.map +1 -0
  63. package/dist/components/launchpad/hooks/useLaunchpad.d.ts +3 -0
  64. package/dist/components/launchpad/hooks/useLaunchpad.d.ts.map +1 -0
  65. package/dist/components/launchpad/index.d.ts +10 -0
  66. package/dist/components/launchpad/index.d.ts.map +1 -0
  67. package/dist/components/metric-card/MetricCard.d.ts.map +1 -1
  68. package/dist/components/network-operator/NetworkOperator.d.ts +4 -0
  69. package/dist/components/network-operator/NetworkOperator.d.ts.map +1 -1
  70. package/dist/components/network-operator/index.d.ts +1 -0
  71. package/dist/components/network-operator/index.d.ts.map +1 -1
  72. package/dist/components/notice/Notice.d.ts +4 -0
  73. package/dist/components/notice/Notice.d.ts.map +1 -0
  74. package/dist/components/notice/Notice.test.d.ts +2 -0
  75. package/dist/components/notice/Notice.test.d.ts.map +1 -0
  76. package/dist/components/notice/index.d.ts +3 -0
  77. package/dist/components/notice/index.d.ts.map +1 -0
  78. package/dist/components/phone-number-input/PhoneNumberInput.d.ts.map +1 -1
  79. package/dist/components/popup/Popup.d.ts +15 -0
  80. package/dist/components/popup/Popup.d.ts.map +1 -0
  81. package/dist/components/popup/Popup.test.d.ts +2 -0
  82. package/dist/components/popup/Popup.test.d.ts.map +1 -0
  83. package/dist/components/popup/index.d.ts +3 -0
  84. package/dist/components/popup/index.d.ts.map +1 -0
  85. package/dist/components/profile-popover/ProfilePopover.d.ts +4 -0
  86. package/dist/components/profile-popover/ProfilePopover.d.ts.map +1 -0
  87. package/dist/components/profile-popover/ProfilePopover.test.d.ts +2 -0
  88. package/dist/components/profile-popover/ProfilePopover.test.d.ts.map +1 -0
  89. package/dist/components/profile-popover/index.d.ts +3 -0
  90. package/dist/components/profile-popover/index.d.ts.map +1 -0
  91. package/dist/components/role-select/RoleSelect.d.ts +4 -0
  92. package/dist/components/role-select/RoleSelect.d.ts.map +1 -0
  93. package/dist/components/role-select/RoleSelect.test.d.ts +2 -0
  94. package/dist/components/role-select/RoleSelect.test.d.ts.map +1 -0
  95. package/dist/components/role-select/index.d.ts +3 -0
  96. package/dist/components/role-select/index.d.ts.map +1 -0
  97. package/dist/components/section-header/SectionHeader.d.ts +7 -0
  98. package/dist/components/section-header/SectionHeader.d.ts.map +1 -0
  99. package/dist/components/section-header/SectionHeader.test.d.ts +2 -0
  100. package/dist/components/section-header/SectionHeader.test.d.ts.map +1 -0
  101. package/dist/components/section-header/index.d.ts +3 -0
  102. package/dist/components/section-header/index.d.ts.map +1 -0
  103. package/dist/components/sidebar/Sidebar.d.ts +1 -1
  104. package/dist/components/sidebar/Sidebar.d.ts.map +1 -1
  105. package/dist/components/sidebar/SidebarContext.d.ts +6 -0
  106. package/dist/components/sidebar/SidebarContext.d.ts.map +1 -1
  107. package/dist/components/sidebar/index.d.ts +1 -1
  108. package/dist/components/sidebar/index.d.ts.map +1 -1
  109. package/dist/components/switch/Switch.d.ts +4 -0
  110. package/dist/components/switch/Switch.d.ts.map +1 -0
  111. package/dist/components/switch/Switch.test.d.ts +2 -0
  112. package/dist/components/switch/Switch.test.d.ts.map +1 -0
  113. package/dist/components/switch/index.d.ts +3 -0
  114. package/dist/components/switch/index.d.ts.map +1 -0
  115. package/dist/components/table/Table.d.ts.map +1 -1
  116. package/dist/components/table/TableHeader.d.ts.map +1 -1
  117. package/dist/components/table/TablePagination.d.ts.map +1 -1
  118. package/dist/components/table/index.d.ts +1 -1
  119. package/dist/components/table/index.d.ts.map +1 -1
  120. package/dist/components/theme/ThemeProvider.d.ts.map +1 -1
  121. package/dist/components/theme/cletTheme.d.ts +11 -0
  122. package/dist/components/theme/cletTheme.d.ts.map +1 -0
  123. package/dist/components/theme/cletTheme.test-d.d.ts +2 -0
  124. package/dist/components/theme/cletTheme.test-d.d.ts.map +1 -0
  125. package/dist/components/theme/cletTheme.test.d.ts +2 -0
  126. package/dist/components/theme/cletTheme.test.d.ts.map +1 -0
  127. package/dist/components/theme/index.d.ts +3 -1
  128. package/dist/components/theme/index.d.ts.map +1 -1
  129. package/dist/components/upload-field/UploadField.d.ts +5 -0
  130. package/dist/components/upload-field/UploadField.d.ts.map +1 -1
  131. package/dist/components/upload-field/index.d.ts +2 -2
  132. package/dist/components/upload-field/index.d.ts.map +1 -1
  133. package/dist/generated/components.theme.d.ts +313 -0
  134. package/dist/generated/components.theme.d.ts.map +1 -0
  135. package/dist/hooks/index.d.ts +1 -0
  136. package/dist/hooks/index.d.ts.map +1 -1
  137. package/dist/hooks/useHasMounted.d.ts +10 -0
  138. package/dist/hooks/useHasMounted.d.ts.map +1 -0
  139. package/dist/hooks/useTableFilter.d.ts.map +1 -1
  140. package/dist/hooks/useTablePagination.d.ts.map +1 -1
  141. package/dist/hooks/useTableState.d.ts.map +1 -1
  142. package/dist/index.cjs +84 -56
  143. package/dist/index.css +1 -1
  144. package/dist/index.d.ts +12 -1
  145. package/dist/index.d.ts.map +1 -1
  146. package/dist/index.js +19025 -31594
  147. package/dist/mcp/cli.js +75 -0
  148. package/dist/mcp/docs.js +122 -0
  149. package/dist/mcp/generated/components.json +3601 -0
  150. package/dist/mcp/generated/examples.json +1233 -0
  151. package/dist/mcp/generated/index.meta.json +252 -0
  152. package/dist/mcp/generated/rules.json +629 -0
  153. package/dist/mcp/generated/search.json +1626 -0
  154. package/dist/mcp/generated/tokens.json +1022 -0
  155. package/dist/mcp/index.js +337 -0
  156. package/dist/mcp/indexer.js +370 -0
  157. package/dist/mcp/installers/claude.js +37 -0
  158. package/dist/mcp/installers/codex.js +24 -0
  159. package/dist/mcp/installers/cursor.js +33 -0
  160. package/dist/mcp/installers/doctor.js +36 -0
  161. package/dist/mcp/installers/opencode.js +36 -0
  162. package/dist/mcp/installers/setup.js +34 -0
  163. package/dist/mcp/installers/util.js +41 -0
  164. package/dist/mcp/paths.js +63 -0
  165. package/dist/mcp/skill/SKILL.md +36 -0
  166. package/dist/mcp/skill/image-to-components.md +49 -0
  167. package/dist/next-index.d.ts.map +1 -1
  168. package/dist/next.cjs +104 -94
  169. package/dist/next.css +1 -1
  170. package/dist/next.js +16275 -29977
  171. package/dist/types/app-header.d.ts +52 -13
  172. package/dist/types/app-header.d.ts.map +1 -1
  173. package/dist/types/app-switcher.d.ts +10 -1
  174. package/dist/types/app-switcher.d.ts.map +1 -1
  175. package/dist/types/avatar.d.ts +9 -0
  176. package/dist/types/avatar.d.ts.map +1 -1
  177. package/dist/types/combobox.d.ts +43 -0
  178. package/dist/types/combobox.d.ts.map +1 -0
  179. package/dist/types/country-selector.d.ts +4 -0
  180. package/dist/types/country-selector.d.ts.map +1 -1
  181. package/dist/types/date-range-selector.d.ts +11 -0
  182. package/dist/types/date-range-selector.d.ts.map +1 -1
  183. package/dist/types/dropdown.d.ts +8 -0
  184. package/dist/types/dropdown.d.ts.map +1 -1
  185. package/dist/types/export-button.d.ts +24 -0
  186. package/dist/types/export-button.d.ts.map +1 -0
  187. package/dist/types/launchpad.d.ts +79 -0
  188. package/dist/types/launchpad.d.ts.map +1 -0
  189. package/dist/types/metric-card.d.ts +4 -0
  190. package/dist/types/metric-card.d.ts.map +1 -1
  191. package/dist/types/network-operator.d.ts +8 -0
  192. package/dist/types/network-operator.d.ts.map +1 -1
  193. package/dist/types/notice.d.ts +24 -0
  194. package/dist/types/notice.d.ts.map +1 -0
  195. package/dist/types/popup.d.ts +43 -0
  196. package/dist/types/popup.d.ts.map +1 -0
  197. package/dist/types/profile-popover.d.ts +97 -0
  198. package/dist/types/profile-popover.d.ts.map +1 -0
  199. package/dist/types/role-select.d.ts +28 -0
  200. package/dist/types/role-select.d.ts.map +1 -0
  201. package/dist/types/section-header.d.ts +26 -0
  202. package/dist/types/section-header.d.ts.map +1 -0
  203. package/dist/types/sidebar.d.ts +5 -0
  204. package/dist/types/sidebar.d.ts.map +1 -1
  205. package/dist/types/switch.d.ts +24 -0
  206. package/dist/types/switch.d.ts.map +1 -0
  207. package/dist/types/table.d.ts +23 -1
  208. package/dist/types/table.d.ts.map +1 -1
  209. package/dist/types/theme.d.ts +28 -8
  210. package/dist/types/theme.d.ts.map +1 -1
  211. package/dist/types/upload-field.d.ts +23 -0
  212. package/dist/types/upload-field.d.ts.map +1 -1
  213. package/dist/utils/export.d.ts +15 -0
  214. package/dist/utils/export.d.ts.map +1 -0
  215. package/dist/utils/stringToColor.d.ts +25 -0
  216. package/dist/utils/stringToColor.d.ts.map +1 -1
  217. package/package.json +29 -10
  218. package/scripts/postinstall.mjs +40 -0
@@ -0,0 +1,629 @@
1
+ {
2
+ "accessibility": [
3
+ {
4
+ "id": "accessibility-decorative-icons",
5
+ "title": "Mark decorative icons aria-hidden",
6
+ "severity": "do",
7
+ "components": [
8
+ "toast"
9
+ ],
10
+ "body": "Decorative icons that carry no independent meaning — for example a toast's leading icon — should\nbe marked `aria-hidden`. Screen readers should not announce them separately from the text they\naccompany."
11
+ },
12
+ {
13
+ "id": "accessibility-prefer-library-primitives",
14
+ "title": "Prefer the library's accessible primitives over hand-rolled patterns",
15
+ "severity": "do",
16
+ "components": [
17
+ "checkbox",
18
+ "radio-group"
19
+ ],
20
+ "body": "Prefer this library's accessible primitives — `Checkbox`, `RadioGroup`, and similar — over\nhand-rolled `sr-only`-checkbox or custom-input patterns. The library primitives already handle\nkeyboard interaction, focus management, and ARIA wiring correctly."
21
+ },
22
+ {
23
+ "id": "accessibility-avoid-doubled-icons",
24
+ "title": "Check whether a component already renders its own icon before adding another",
25
+ "severity": "do",
26
+ "components": [],
27
+ "body": "Before adding an adjacent icon (e.g. a lucide icon) next to a library component, check whether\nthat component already renders its own icon internally. Doubling icons is both a visual\nredundancy and an accessibility redundancy — it can cause the same meaning to be announced twice."
28
+ }
29
+ ],
30
+ "components": [
31
+ {
32
+ "id": "components-button-variant-semantics",
33
+ "title": "Use variant to signal intent, not just style",
34
+ "severity": "do",
35
+ "components": [
36
+ "button"
37
+ ],
38
+ "body": "`variant=\"primary\"` is for create/assign/confirm actions. `variant=\"primary-destructive\"` is for\ndestructive actions that also need primary visual weight (e.g. \"Delete and notify\"). The full\nvariant set is `\"primary\" | \"secondary\" | \"outline\" | \"ghost\" | \"destructive\" | \"primary-destructive\" | \"success\"` —\npick the variant that matches the action's real-world consequence, not just the desired color."
39
+ },
40
+ {
41
+ "id": "components-button-footer-ordering",
42
+ "title": "Footer button order is Ghost, then Primary, then Destructive",
43
+ "severity": "do",
44
+ "components": [
45
+ "button"
46
+ ],
47
+ "body": "In a footer/action row, order buttons left to right as: ghost (Cancel/Close) first, primary\n(the main action) next, and destructive last (rightmost). Destructive actions should never be\nthe leftmost or default-focused button."
48
+ },
49
+ {
50
+ "id": "components-button-modal-actions-iconless",
51
+ "title": "Modal and dialog action buttons are iconless",
52
+ "severity": "do",
53
+ "components": [
54
+ "button",
55
+ "dialog",
56
+ "modal"
57
+ ],
58
+ "body": "Buttons inside modal/dialog footers (Cancel, Save, Delete, etc.) are conventionally rendered\nwithout a leading icon, even though buttons elsewhere in the app often carry one. Keep dialog\naction rows text-only."
59
+ },
60
+ {
61
+ "id": "components-button-navigate-vs-download-icon",
62
+ "title": "Match the leading icon to the actual action",
63
+ "severity": "do",
64
+ "components": [
65
+ "button"
66
+ ],
67
+ "body": "A button that navigates to another page or external resource (not downloads a file) should use\na navigation icon like `ExternalLink` or `ArrowRight`, not `Download`. Reserve `Download` for\nbuttons that actually trigger a file download."
68
+ },
69
+ {
70
+ "id": "components-section-header-actions-primary-icon",
71
+ "title": "SectionActions primary button always carries a leading icon",
72
+ "severity": "do",
73
+ "components": [
74
+ "section-header",
75
+ "button"
76
+ ],
77
+ "body": "The primary-variant button inside `SectionActions` — the section header's main, highest-emphasis\naction (e.g. \"Add user\", \"Create project\") — must always include a leading icon, e.g.\n`<Plus size={14} strokeWidth={1.5} />`. Other buttons in the same `SectionActions` row (outline,\nghost, secondary) are icon-optional."
78
+ },
79
+ {
80
+ "id": "components-dialog-no-padding-override",
81
+ "title": "Never override DialogContent padding",
82
+ "severity": "dont",
83
+ "components": [
84
+ "dialog"
85
+ ],
86
+ "body": "Do not pass `classNames={{ content: \"p-0 overflow-hidden\" }}` or any other override that strips\nor replaces `DialogContent`'s padding. `DialogContent` manages its own padding internally, and\noverriding it breaks the component's intended spacing."
87
+ },
88
+ {
89
+ "id": "components-dialog-no-inner-wrapper-divs",
90
+ "title": "Don't add inner wrapper divs with their own padding/gap inside DialogContent",
91
+ "severity": "dont",
92
+ "components": [
93
+ "dialog"
94
+ ],
95
+ "body": "`DialogTitle`, `DialogDescription`, and the actions row sit directly inside `DialogContent`,\nwhich already provides padding. Wrapping them in an extra `<div className=\"p-6 flex flex-col gap-4\">`\ndoubles the spacing and produces a visibly larger gap than intended."
96
+ },
97
+ {
98
+ "id": "components-dialog-actions-row",
99
+ "title": "Dialog actions row is a plain flex div, no footer component",
100
+ "severity": "do",
101
+ "components": [
102
+ "dialog"
103
+ ],
104
+ "body": "The actions row is simply `<div className=\"flex justify-end gap-2\">` placed directly inside\n`DialogContent` — there is no separate footer subcomponent and no extra nesting required."
105
+ },
106
+ {
107
+ "id": "components-dialog-leading-icon-with-title",
108
+ "title": "Pair a custom leading icon with the dialog title using a flex row",
109
+ "severity": "do",
110
+ "components": [
111
+ "dialog"
112
+ ],
113
+ "body": "For a custom leading icon next to the dialog title (e.g. a status icon), wrap the icon and title\ntogether: `<div className=\"flex items-center gap-2\">`, with the icon sized `size-5 shrink-0` so\nit doesn't compress when the title wraps."
114
+ },
115
+ {
116
+ "id": "components-modal-canonical-structure",
117
+ "title": "Canonical Modal composition",
118
+ "severity": "do",
119
+ "components": [
120
+ "modal"
121
+ ],
122
+ "body": "Compose modals as `Modal > ModalPortal > ModalOverlay + ModalContent(showCloseButton, size) >\nModalHeader(ModalTitle, ModalDescription) > Form > ModalBody > ModalFooter`. Don't skip layers or\nsubstitute plain divs for these subcomponents — each one carries required styling or behavior."
123
+ },
124
+ {
125
+ "id": "components-modal-no-form-element",
126
+ "title": "Don't wrap Modal content in a literal <form> element",
127
+ "severity": "do",
128
+ "components": [
129
+ "modal"
130
+ ],
131
+ "body": "No `<form>` element is needed inside a Modal. Instead, call `form.handleSubmit(onSubmit)` from\nthe submit button's `onClick` handler."
132
+ },
133
+ {
134
+ "id": "components-modal-field-textarea-full-width",
135
+ "title": "Field and Textarea need explicit w-full inside ModalBody",
136
+ "severity": "do",
137
+ "components": [
138
+ "modal",
139
+ "form-field"
140
+ ],
141
+ "body": "`Field` and `Textarea` do not automatically fill the modal body's width — give them\n`className=\"w-full\"` so they span the available space inside `ModalBody`."
142
+ },
143
+ {
144
+ "id": "components-modal-subtle-surface",
145
+ "title": "Use a muted, translucent background for nested cards inside a Modal",
146
+ "severity": "do",
147
+ "components": [
148
+ "modal"
149
+ ],
150
+ "body": "For a subtle card-like background inside a Modal, use `bg-surface-muted/20` rather than plain\n`bg-surface-muted`, which reads too harsh against the modal surface."
151
+ },
152
+ {
153
+ "id": "components-table-context-children",
154
+ "title": "Never pull Table's context-dependent children out of <Table>",
155
+ "severity": "dont",
156
+ "components": [
157
+ "table"
158
+ ],
159
+ "body": "`TableSearch`, `TableFilter`, `TableActions`, and view toggles all rely on the ancestor\n`<Table paramPrefix=\"...\">` context to function. Don't render them inside a plain div outside\nof `<Table>` — they will silently break."
160
+ },
161
+ {
162
+ "id": "components-table-header-consistency-across-views",
163
+ "title": "Keep TableHeader identical across list/tile view toggles",
164
+ "severity": "do",
165
+ "components": [
166
+ "table"
167
+ ],
168
+ "body": "When a page offers a list/tile view toggle, keep `TableHeader` (search, filters, view toggle)\nthe same for both modes. Only swap the body: `TableContent` for list view vs. a grid for tile\nview, both still inside the same `<Table>`."
169
+ },
170
+ {
171
+ "id": "components-no-border-override",
172
+ "title": "Don't override DateSelector/Dropdown trigger borders",
173
+ "severity": "dont",
174
+ "components": [
175
+ "date-selector",
176
+ "dropdown"
177
+ ],
178
+ "body": "Don't strip default borders with something like\n`classNames={{ trigger: \"!border-0 !shadow-none focus:!shadow-none\" }}`. Let these components\nkeep their default border and shadow treatment."
179
+ },
180
+ {
181
+ "id": "components-dropdown-react-hook-form-wiring",
182
+ "title": "Wire Dropdown to react-hook-form with null-coalescing value/onChange",
183
+ "severity": "do",
184
+ "components": [
185
+ "dropdown",
186
+ "form"
187
+ ],
188
+ "body": "Wire as `value={field.value || null}` and `onValueChange={(v) => field.onChange(v ?? \"\")}`, with\n`options` passed as a `{value, label}[]` array matching the `DropdownOption` type. This avoids\nuncontrolled/controlled warnings and keeps empty values consistent as `\"\"` rather than `null`\nin form state."
189
+ },
190
+ {
191
+ "id": "components-card-no-manual-padding",
192
+ "title": "Don't add manual padding utilities inside Card",
193
+ "severity": "dont",
194
+ "components": [
195
+ "card"
196
+ ],
197
+ "body": "Don't add `px-6`, `py-4`, or similar padding utilities inside Card content. Card has a built-in\n`--clet-card-padding` token that already supplies correct, consistent padding."
198
+ },
199
+ {
200
+ "id": "components-card-footer-no-border",
201
+ "title": "Don't add a top border to Card footers",
202
+ "severity": "dont",
203
+ "components": [
204
+ "card"
205
+ ],
206
+ "body": "Don't add `border-t` on card footers. The design keeps card content-to-footer transitions\nborderless."
207
+ },
208
+ {
209
+ "id": "components-card-scrollable-content",
210
+ "title": "Scrollable Card content needs flex-col plus min-h-0 on the scroll region",
211
+ "severity": "do",
212
+ "components": [
213
+ "card"
214
+ ],
215
+ "body": "For scrollable content inside a Card, use `Card className=\"flex flex-col\"` with an inner region\nstyled `flex-1 overflow-y-auto min-h-0`. The `min-h-0` is required — without it the flex child\ngrows instead of scrolling."
216
+ },
217
+ {
218
+ "id": "components-toast-provider-setup",
219
+ "title": "Set up ToastProvider once at the app root",
220
+ "severity": "do",
221
+ "components": [
222
+ "toast"
223
+ ],
224
+ "body": "Wrap the app once, at the root, as `<ToastProvider><App/><Toaster/></ToastProvider>`. Don't\nre-instantiate the provider or `Toaster` deeper in the tree."
225
+ },
226
+ {
227
+ "id": "components-toast-icon-sizing",
228
+ "title": "Toast leading icons must be size={18} strokeWidth={2} and aria-hidden",
229
+ "severity": "do",
230
+ "components": [
231
+ "toast"
232
+ ],
233
+ "body": "Match the toast's typography scale by sizing leading icons `size={18} strokeWidth={2}`, and mark\nthem `aria-hidden` since they are decorative."
234
+ },
235
+ {
236
+ "id": "components-toast-extract-handlers",
237
+ "title": "Extract toast(...) calls into named handler functions",
238
+ "severity": "do",
239
+ "components": [
240
+ "toast"
241
+ ],
242
+ "body": "Never call `toast(...)` inline inside a JSX lambda or render body. Extract it into a named\nhandler function (e.g. `handleSaveSuccess`) so the toast call site is testable and readable\nseparately from render logic."
243
+ },
244
+ {
245
+ "id": "components-toast-variants",
246
+ "title": "Toast variant catalog",
247
+ "severity": "info",
248
+ "components": [
249
+ "toast"
250
+ ],
251
+ "body": "Toast supports four variants: `success | error | warning | default`. Pick the variant that\nmatches the outcome being reported rather than relying on custom icon/color combinations."
252
+ },
253
+ {
254
+ "id": "components-metric-card-primary-shadow",
255
+ "title": "Use classNames root shadow-primary for a primary-tinted MetricCard shadow",
256
+ "severity": "do",
257
+ "components": [
258
+ "metric-card"
259
+ ],
260
+ "body": "MetricCard's default shadow is `none`. For a primary-tinted drop shadow, pass\n`classNames={{ root: \"shadow-primary\" }}` through the component's `classNames` prop rather than\nadding shadow utilities to a wrapping element."
261
+ },
262
+ {
263
+ "id": "components-form-field-canonical-composition",
264
+ "title": "Canonical Form/FormField/Field composition",
265
+ "severity": "do",
266
+ "components": [
267
+ "form",
268
+ "form-field"
269
+ ],
270
+ "body": "Compose as `Form > FormField(control, name, render) > Field(invalid) > FieldLabel + FieldControl(Input) + FieldError`.\nWire validation state explicitly: `<Field invalid={!!fieldState.error}>` and\n`<FieldError>{fieldState.error?.message}</FieldError>`."
271
+ },
272
+ {
273
+ "id": "components-form-field-no-unneeded-description",
274
+ "title": "Don't add FieldDescription unless the design calls for it",
275
+ "severity": "dont",
276
+ "components": [
277
+ "form",
278
+ "form-field"
279
+ ],
280
+ "body": "Don't add a `FieldDescription` under every field by default. Only include it when the design\nexplicitly specifies helper text for that field."
281
+ },
282
+ {
283
+ "id": "components-check-library-before-hand-rolling",
284
+ "title": "Check for an existing component before building a replacement",
285
+ "severity": "do",
286
+ "components": [],
287
+ "body": "Before building any UI element from scratch, check whether the library already provides it —\nfor example `UploadField`, `Checkbox`, `Dropdown`, or `CommandInput`. Don't hand-roll a\nreplacement for functionality the library already ships."
288
+ },
289
+ {
290
+ "id": "components-app-header-notification-item",
291
+ "title": "Use AppHeaderNotificationItem, never hand-rolled notification divs",
292
+ "severity": "dont",
293
+ "components": [
294
+ "app-header"
295
+ ],
296
+ "body": "Render each `AppHeaderNotifications` child as an `AppHeaderNotificationItem` (`text`, `time`,\n`unread`, optional `onClick`) — never hand-write `.clet-notif-popover__item` /\n`.clet-notif-popover__dot` / `.clet-notif-popover__body-text` divs directly. The component also\nmakes the row keyboard-focusable (Enter/Space) when `onClick` is passed, which hand-rolled divs\ndon't get for free."
297
+ },
298
+ {
299
+ "id": "components-table-row-actions-inline-preferred",
300
+ "title": "Prefer inline row-action buttons over the built-in kebab menu",
301
+ "severity": "do",
302
+ "components": [
303
+ "table"
304
+ ],
305
+ "body": "`TableContent`'s `rowActions` prop always renders a kebab (`...`) popover menu. For the current\ndesign system, prefer a custom `cell` on an \"actions\" column rendering plain icon buttons inline\ninstead (view/edit/delete side by side) — see `demo/pages/MembersPage.tsx` or\n`demo/pages/Dashboard2Page.tsx` for the pattern. Don't combine both on the same table: passing\n`selectable` with no `rowActions` renders no kebab column at all (selection is already handled by\nthe checkbox column), so there's no redundant empty menu next to your inline buttons."
306
+ },
307
+ {
308
+ "id": "components-new-design-system-variants",
309
+ "title": "Prefer the \"new design system\" variant on these components",
310
+ "severity": "do",
311
+ "components": [
312
+ "app-header",
313
+ "metric-card",
314
+ "sidebar",
315
+ "table"
316
+ ],
317
+ "body": "The current preferred variant set (demonstrated at `/dashboard2` vs. the frozen `/dashboard2/legacy`\nin the demo app): `AppHeader` `variant=\"plain\"` over `\"default\"`; `MetricCard` `variant=\"outline\"`\nover `\"default\"`; `SidebarGroup` `collapsible` (accordion groups) over flat non-collapsible groups;\n`TableContent` `variant=\"panel\"` over `\"default\"`; `TableFilter` `variant=\"spread\"` over the\ndefault `\"popover\"`. Default to these in new work. If asked to \"convert this app to the new design\nsystem,\" scan for usages of the components above and swap them to the preferred variant, then ask\nbefore overriding any of the consuming app's own `--clet-*` color token overrides."
318
+ }
319
+ ],
320
+ "forms": [
321
+ {
322
+ "id": "forms-required-banner",
323
+ "title": "One \"All fields are required\" banner, not per-label asterisks",
324
+ "severity": "dont",
325
+ "components": [
326
+ "form",
327
+ "form-field"
328
+ ],
329
+ "body": "Do not put a `*` on every required field's label. Instead add one line near the top of the form:\n`<p className=\"text-sm text-error -mt-2\">All fields are required</p>`. This keeps the label row\nclean and states the requirement once instead of repeating visual noise on every field."
330
+ },
331
+ {
332
+ "id": "forms-stack",
333
+ "title": "The library's form stack",
334
+ "severity": "info",
335
+ "components": [
336
+ "form",
337
+ "form-field"
338
+ ],
339
+ "body": "Forms are built with `react-hook-form` + `zod` + `@hookform/resolvers/zod`, composed with this\nlibrary's `Form` / `FormField` / `Field` family. Validate with a zod schema and resolve it via\n`zodResolver`, then drive rendering through `FormField`'s `render` prop."
340
+ },
341
+ {
342
+ "id": "forms-field-array-and-watch",
343
+ "title": "Use useFieldArray for repeatable sections, form.watch for conditional fields",
344
+ "severity": "do",
345
+ "components": [
346
+ "form"
347
+ ],
348
+ "body": "Use `useFieldArray` for repeatable field sections (e.g. a dynamic list of contacts). Use\n`form.watch(...)` to conditionally reveal or hide fields based on the current value of another\nfield, rather than tracking a duplicate piece of local state."
349
+ },
350
+ {
351
+ "id": "forms-no-unnecessary-wrappers",
352
+ "title": "Don't wrap fields in unnecessary divs or add labels the design doesn't call for",
353
+ "severity": "dont",
354
+ "components": [
355
+ "form",
356
+ "form-field"
357
+ ],
358
+ "body": "Don't add extra wrapping `<div>`s around fields, and don't add a `FieldLabel` or other label text\nthat the design doesn't specify. Keep the field tree as flat as the `Form`/`FormField`/`Field`\ncomposition requires and nothing more."
359
+ },
360
+ {
361
+ "id": "forms-never-native-controls",
362
+ "title": "Never use a native select/button/input as a visible filter or form control",
363
+ "severity": "dont",
364
+ "components": [
365
+ "table",
366
+ "dropdown",
367
+ "form",
368
+ "form-field"
369
+ ],
370
+ "body": "Never render a raw `<select>`, native `<button>`, or native `<input>` as the visible control in a\nfilter or form — use the library's own components (`Dropdown`, `Button`, `Input`, etc.) instead.\nThis applies everywhere, but especially inside `TableFilter`: its `handleApply` reads `FormData`\nfrom named, form-associated elements, so pass `name=\"role\"` straight to `Dropdown` — it\nparticipates in native form submission on its own (backed by Radix's hidden bubble `<select>`),\nno separate hidden input needed. Never a bare `<select name=\"role\">`. For other custom inputs\nwithout built-in form participation, pair a hidden `<input type=\"hidden\" name=\"...\" value={...} />`\n(captured by `FormData`) driving the same value instead — it's invisible plumbing, not a visible\nnative control."
371
+ },
372
+ {
373
+ "id": "forms-table-filter-spread-autoapply",
374
+ "title": "TableFilter's spread variant has no Apply button — it auto-applies",
375
+ "severity": "info",
376
+ "components": [
377
+ "table"
378
+ ],
379
+ "body": "`<TableFilter variant=\"spread\">` removes the popover and lays fields out inline with just a\n\"clear\" action — there is no Apply button. Fields apply as soon as their value changes (the\ncomponent diffs a `FormData` snapshot after every render — a safety net for any custom input that\ndoesn't fire a native change event; `Dropdown`'s own `name`-driven hidden field does fire one).\nDon't add your own Apply button next to a spread filter — it's redundant."
380
+ }
381
+ ],
382
+ "layout": [
383
+ {
384
+ "id": "layout-no-inline-style-sizing",
385
+ "title": "Don't use inline style={{}} for layout sizing — use Tailwind arbitrary values",
386
+ "severity": "dont",
387
+ "components": [],
388
+ "body": "Don't use inline `style={{}}` in React for layout sizing. Use Tailwind arbitrary values instead,\ne.g. `h-[calc(100vh-80px)]`, so sizing stays inspectable in markup and consistent with the rest\nof the styling approach."
389
+ },
390
+ {
391
+ "id": "layout-min-h-0",
392
+ "title": "min-h-0 is required on a flex child for overflow-y-auto to scroll",
393
+ "severity": "do",
394
+ "components": [],
395
+ "body": "A flex child needs `min-h-0` for `overflow-y-auto` to actually scroll. Without it, the flex item\ngrows to fit its content and pushes the parent taller instead of producing an internal scroll\nregion.\n\n```\n<div className=\"flex flex-col h-full\">\n <div className=\"flex-1 overflow-y-auto min-h-0\">...</div>\n</div>\n```"
396
+ },
397
+ {
398
+ "id": "layout-no-mixed-grid-flex-row",
399
+ "title": "Don't mix CSS grid and flexbox for the same row",
400
+ "severity": "dont",
401
+ "components": [],
402
+ "body": "Pick one layout model per row. Don't apply `display: grid` and `display: flex` behavior to the\nsame row by nesting a flex row's children with grid-specific utilities — it produces\ninconsistent alignment and makes the row hard to reason about."
403
+ },
404
+ {
405
+ "id": "layout-no-absolute-positioning-in-grid",
406
+ "title": "Don't use absolute positioning for inline elements inside a grid layout",
407
+ "severity": "dont",
408
+ "components": [],
409
+ "body": "Don't use absolute positioning to place inline elements inside a grid layout. Use a flex row for\ninline element groupings instead — absolute positioning inside a grid breaks the grid's own flow\nand sizing."
410
+ },
411
+ {
412
+ "id": "layout-icon-column-alignment",
413
+ "title": "Use a fixed grid template to keep an icon column aligned across repeated rows",
414
+ "severity": "do",
415
+ "components": [],
416
+ "body": "Use `grid grid-cols-[1fr_auto_1fr_auto] items-center gap-2` (or a similar explicit column\ntemplate) to keep an icon column aligned across repeated rows, rather than relying on flexbox\nspacing that can drift row to row."
417
+ }
418
+ ],
419
+ "page-composition": [
420
+ {
421
+ "id": "page-composition-layout-shell",
422
+ "title": "Adopt AppLayout variant=\"stacked\" + AppHeader variant=\"plain\" + Sidebar variant=\"plain\" together",
423
+ "severity": "do",
424
+ "components": [
425
+ "app-layout",
426
+ "app-header",
427
+ "sidebar"
428
+ ],
429
+ "body": "Adopt the new layout shell as a set, not individually: `AppLayout variant=\"stacked\"` (header spans\nfull width on top, sidebar + content side by side below), `AppHeader variant=\"plain\"` (square\ncorners, `--clet-primary` background, on-primary text, continuous top bar), and `Sidebar\nvariant=\"plain\"` (transparent background + right border instead of a panel surface). All required\nfor new/touched screens — none required for existing screens to keep compiling."
430
+ },
431
+ {
432
+ "id": "page-composition-sidebar-groups-collapsible",
433
+ "title": "Use collapsible SidebarGroups, not flat nav lists",
434
+ "severity": "do",
435
+ "components": [
436
+ "sidebar"
437
+ ],
438
+ "body": "All nav groups use `SidebarGroup collapsible` accordion sections, not flat lists. Initialize with\nthe `Main` group expanded and the rest collapsed."
439
+ },
440
+ {
441
+ "id": "page-composition-sidebar-footer-required",
442
+ "title": "Every Sidebar needs a SidebarFooter hosting a ProfilePopover",
443
+ "severity": "do",
444
+ "components": [
445
+ "sidebar",
446
+ "profile-popover"
447
+ ],
448
+ "body": "Never omit `SidebarFooter`. It must host a `ProfilePopover` in `fullName`/`email` mode (not\n`user`/`variant`) — full name, email, and role get a stable, real-estate-rich home there. This is\nrequired on the new layout shell, unlike the old default shell (`DemoLayout`), which never\nrequired one."
449
+ },
450
+ {
451
+ "id": "page-composition-no-sidebar-trigger",
452
+ "title": "Don't render SidebarTrigger in new layouts",
453
+ "severity": "dont",
454
+ "components": [
455
+ "sidebar"
456
+ ],
457
+ "body": "The old default layout shell renders `<SidebarTrigger>Menu</SidebarTrigger>` to manually toggle\nthe sidebar. The new shell drops it entirely — don't render `SidebarTrigger` in new or touched\nlayouts. Use `SidebarProvider` with its defaults and no manual collapse control."
458
+ },
459
+ {
460
+ "id": "page-composition-sidebar-badge-numeric",
461
+ "title": "SidebarBadge shows a real numeric count, not decorative text",
462
+ "severity": "do",
463
+ "components": [
464
+ "sidebar"
465
+ ],
466
+ "body": "Compose `SidebarBadge` inside a `SidebarLink`'s children (`{link.badge && <SidebarBadge>{link.badge}</SidebarBadge>}`).\nReserve it for a live count of actionable items on that nav destination (e.g. `8` unread alerts) —\nthe number must reflect real pending/important items, not a static placeholder. The one exception\nis a short text badge like `\"New\"` for a feature callout (e.g. a newly-added nav link) — don't\ninvent other text badges beyond that pattern."
467
+ },
468
+ {
469
+ "id": "page-composition-branding-in-app-header",
470
+ "title": "Branding lives in AppHeader via AppHeaderBranding, never in the sidebar",
471
+ "severity": "do",
472
+ "components": [
473
+ "app-header",
474
+ "sidebar"
475
+ ],
476
+ "body": "Put branding in `AppHeader` via `AppHeaderBranding` (`logo`/`title`/`subtitle`), never in the\nsidebar. `Sidebar` has no `SidebarHeader`/`SidebarBrand` when `AppHeaderBranding` is used — the\nsame logo must not appear twice on screen. Use the current brand logo (`clet-logo.png`) for any\nnew system — `gsl-logo.png` is the older mark used only by the legacy default layout shell."
477
+ },
478
+ {
479
+ "id": "page-composition-profile-popover-default-items",
480
+ "title": "Don't rebuild My Profile/Account Settings/Help and Support via items",
481
+ "severity": "dont",
482
+ "components": [
483
+ "profile-popover"
484
+ ],
485
+ "body": "`ProfilePopover`'s `items` defaults to \"My Profile\", \"Account Settings\", and \"Help and Support\",\nwired via `onMyProfile`/`onAccountSettings`/`onHelpAndSupport`. Don't hand-build those three rows\nvia a passed-in `items` array — only pass `items` when the system genuinely needs rows other than\nthe three defaults, since a passed-in array fully replaces them rather than merging."
486
+ },
487
+ {
488
+ "id": "page-composition-role-select-three-locations",
489
+ "title": "Wire the same RoleSelect state into Launchpad, header ProfilePopover, and sidebar footer ProfilePopover",
490
+ "severity": "do",
491
+ "components": [
492
+ "role-select",
493
+ "launchpad",
494
+ "profile-popover"
495
+ ],
496
+ "body": "Whenever a system allows a user to have more than one role — true for most systems — wire the same\n`RoleSelect` state (`roles`/`selectedRole`/`onClickRole`) into all three locations: `Launchpad`,\nthe header `ProfilePopover`, and the sidebar footer `ProfilePopover`. Don't add it to only one or\ntwo of the three. The header `ProfilePopover` uses `variant=\"avatar\"` when a sidebar footer\npopover also exists; reserve `variant=\"full\"` for headers with no sidebar footer."
497
+ },
498
+ {
499
+ "id": "page-composition-launchpad-required-role-select",
500
+ "title": "Launchpad's children (RoleSelect) is required, not optional",
501
+ "severity": "do",
502
+ "components": [
503
+ "launchpad"
504
+ ],
505
+ "body": "`Launchpad`'s `children` prop is required at the type level and only accepts a single `RoleSelect`\nelement — omitting it is a type error, not a lint warning. There is no bordered \"See all\" footer\nbutton anymore: a small ghost `sm` expand button sits at the top right of the panel next to the\ntitle, its expand icon always visible, its \"See more\" text label appearing only once `apps.length`\nexceeds the 9-app cap."
506
+ },
507
+ {
508
+ "id": "page-composition-launchpad-over-app-switcher",
509
+ "title": "Use Launchpad, not the deprecated AppSwitcher, for new app-switching UI",
510
+ "severity": "dont",
511
+ "components": [
512
+ "app-switcher",
513
+ "launchpad"
514
+ ],
515
+ "body": "`AppSwitcher` is deprecated and gets no new features. Use `Launchpad` for all new app-switching\nwork. Stay on `AppSwitcher` only if the system genuinely needs flexible column count, an uncapped\ngrid without an expand modal, or a fully custom footer slot — and check with the team first."
516
+ },
517
+ {
518
+ "id": "page-composition-table-family-variants",
519
+ "title": "Adopt TableContent variant=\"panel\" + TableFilter variant=\"spread\" + no-border TableFooter together",
520
+ "severity": "do",
521
+ "components": [
522
+ "table"
523
+ ],
524
+ "body": "New or touched `Table`s use all three together: `TableContent variant=\"panel\"` (not `\"default\"`),\n`TableFilter variant=\"spread\"` (not the default `\"popover\"`), and `TableFooter noBorder` (a real\n`TableFooterProps` prop, not a demo hack)."
525
+ },
526
+ {
527
+ "id": "page-composition-other-preferred-variants",
528
+ "title": "Prefer MetricCard variant=\"outline\" and Tabs variant=\"pill\" on new usage",
529
+ "severity": "do",
530
+ "components": [
531
+ "metric-card",
532
+ "tabs"
533
+ ],
534
+ "body": "Use `MetricCard variant=\"outline\"` over `\"default\"`, and `Tabs variant=\"pill\"` over `\"default\"`,\non any new or substantially-touched screen."
535
+ },
536
+ {
537
+ "id": "page-composition-section-header",
538
+ "title": "Use SectionHeader for page headers, never a hand-rolled div",
539
+ "severity": "do",
540
+ "components": [
541
+ "section-header"
542
+ ],
543
+ "body": "Use `SectionHeader` (with `SectionTitle`/`SectionDescription`/`SectionActions`) to compose page\nheaders — never a hand-rolled `<div>`."
544
+ },
545
+ {
546
+ "id": "page-composition-builtin-loading-states",
547
+ "title": "Use each component's real loading prop, never hand-rolled placeholders",
548
+ "severity": "do",
549
+ "components": [
550
+ "launchpad",
551
+ "app-header",
552
+ "profile-popover",
553
+ "sidebar",
554
+ "metric-card",
555
+ "table"
556
+ ],
557
+ "body": "Every component that supports a real `loading` prop (with a shimmering skeleton) should use it —\n`Launchpad`, `AppHeaderNotifications`, `ProfilePopover` (`loading`/`loadingLabel`), `SidebarLink`\n(`loading`/`loadingLabel`), `MetricCard` (`loading`/`loadingLabel`), `TableContent`\n(`loading`/`loadingRows`). Never render `\"...\"` placeholders, a hand-rolled `<Spinner />`, or omit\ncontent until data is ready."
558
+ }
559
+ ],
560
+ "theming": [
561
+ {
562
+ "id": "theming-no-hardcoded-colors",
563
+ "title": "Never hardcode colors — always use theme tokens",
564
+ "severity": "dont",
565
+ "components": [],
566
+ "body": "Never hardcode colors such as `#fff` or Tailwind palette classes like `text-blue-600`. Always use\nthis library's theme tokens instead — `text-foreground`, `bg-surface-muted`, `text-error`, and\nsimilar. This holds even for small stylistic touches like a link-styled button; reach for a token\nclass, not a raw palette color, in every case."
567
+ },
568
+ {
569
+ "id": "theming-no-global-token-mutation-for-one-element",
570
+ "title": "Don't mutate global accent tokens to reskin a single element",
571
+ "severity": "dont",
572
+ "components": [],
573
+ "body": "Don't change global accent/theme tokens just to restyle one specific element. Instead apply a\nscoped Tailwind color class directly to that element, leaving the shared tokens untouched for the\nrest of the app."
574
+ },
575
+ {
576
+ "id": "theming-subtle-surface-opacity",
577
+ "title": "Prefer a translucent muted surface over the harsher plain one",
578
+ "severity": "do",
579
+ "components": [
580
+ "modal",
581
+ "card"
582
+ ],
583
+ "body": "For a subtle or muted surface — for example a card background nested inside a modal — prefer\n`bg-surface-muted/20` over the plain `bg-surface-muted`, which reads too harsh in that context."
584
+ },
585
+ {
586
+ "id": "theming-use-clettheme-for-token-overrides",
587
+ "title": "Use cletTheme() to override design tokens, not hand-written CSS",
588
+ "severity": "do",
589
+ "components": [
590
+ "theme"
591
+ ],
592
+ "body": "To override any `--clet-*` token (global, like `primary`/`radiusBase`, or component-scoped, like\nCard's `padding` or AppHeader's `bg`), call the `cletTheme()` runtime helper exported from\n`@rfdtech/components` — pass `all`/`light`/`dark` for global tokens, or `components: { ComponentName:\n{...} }` for a specific component's own tokens. It's fully type-checked against the real token\nregistry (unknown keys and mismatched value shapes are compile errors), and it injects one `<style>`\ntag with no build config or separate CSS file needed. Reach for a hand-written CSS override file\n(documented as a fallback) only when you need conditional/media-query logic `cletTheme()` can't\nexpress. See `get_component(\"theme\")` for the full API and `get_tokens()` for every valid token name.\nPut the call in a `src/clet.theme.ts` file, side-effect-imported once in the app entry point (see\n`get_component(\"theme\")` for the recommended-file convention)."
593
+ },
594
+ {
595
+ "id": "theming-migration-ask-about-existing-token-overrides",
596
+ "title": "When migrating, ask the user about each existing `--clet-*` token override — don't silently keep or drop any",
597
+ "severity": "do",
598
+ "components": [
599
+ "theme"
600
+ ],
601
+ "body": "When migrating a project onto the new design system defaults, first find every existing override\nof a **known `--clet-*` token** — a `cletTheme()` call (in `src/clet.theme.ts` or elsewhere), or\nhand-written CSS setting a real `--clet-*` custom property. This does not include custom/arbitrary\nCSS variables or component-local one-off styling that isn't a recognized library token — only\noverrides of tokens the library itself defines (cross-check against `get_tokens()`).\n\nList every overridden token found, then ask the user about each one individually — keep the\nexisting override (preserve current branding for that token), or drop it and adopt the new\napproved default. Don't decide this silently and don't batch it into a single yes/no — a token like\n`--clet-primary` might be an intentional brand choice worth keeping, while another was only ever set\nto match the *old* pre-rebrand defaults and should be dropped now that those defaults themselves\nchanged. See the \"Rebrand\" section of `get_component(\"migration-v2\")` for the specific old→new\ndefault values that changed."
602
+ }
603
+ ],
604
+ "tokens": [
605
+ {
606
+ "id": "tokens-radius-xl",
607
+ "title": "--clet-radius-xl backs Tailwind's rounded-xl",
608
+ "severity": "info",
609
+ "components": [],
610
+ "body": "`--clet-radius-xl` is the design token that backs Tailwind's `rounded-xl` utility in this design\nsystem. Use `rounded-xl` in markup rather than a hardcoded border-radius value."
611
+ },
612
+ {
613
+ "id": "tokens-card-padding",
614
+ "title": "--clet-card-padding is Card's built-in padding token",
615
+ "severity": "info",
616
+ "components": [
617
+ "card"
618
+ ],
619
+ "body": "`--clet-card-padding` is the token that supplies Card's built-in content padding. Don't\nre-implement it with utility padding classes like `px-6 py-4` — rely on the token."
620
+ },
621
+ {
622
+ "id": "tokens-catalog",
623
+ "title": "Token catalog observed in real usage",
624
+ "severity": "info",
625
+ "components": [],
626
+ "body": "Common tokens/utilities in use across the library and consuming apps: `text-foreground`,\n`text-foreground-muted`, `bg-surface-muted`, `bg-surface-muted/20`, `bg-surface-subtle`,\n`border-border`, `text-error`, `text-warning`, `bg-warning-bg`, `bg-primary`, `text-primary`\n(backed by `--clet-primary`), `rounded-xl` (backed by `--clet-radius-xl`), and Card's\n`--clet-card-padding`. Prefer these over hardcoded colors or spacing values."
627
+ }
628
+ ]
629
+ }