@misoto22/design 0.5.0 → 0.6.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 (214) hide show
  1. package/README.md +124 -0
  2. package/bin/misoto22-design.mjs +205 -0
  3. package/dist/agent/Accordion.md +41 -0
  4. package/dist/agent/Alert.md +42 -0
  5. package/dist/agent/AppShell.md +40 -0
  6. package/dist/agent/AreaChart.md +68 -0
  7. package/dist/agent/Article.md +41 -0
  8. package/dist/agent/Avatar.md +32 -0
  9. package/dist/agent/Badge.md +40 -0
  10. package/dist/agent/BarChart.md +65 -0
  11. package/dist/agent/BarList.md +40 -0
  12. package/dist/agent/BigNumber.md +45 -0
  13. package/dist/agent/BoxPlot.md +58 -0
  14. package/dist/agent/Breadcrumb.md +32 -0
  15. package/dist/agent/BulletChart.md +48 -0
  16. package/dist/agent/Button.md +56 -0
  17. package/dist/agent/Calendar.md +42 -0
  18. package/dist/agent/Card.md +49 -0
  19. package/dist/agent/Checkbox.md +35 -0
  20. package/dist/agent/Collapsible.md +46 -0
  21. package/dist/agent/Combobox.md +50 -0
  22. package/dist/agent/Command.md +93 -0
  23. package/dist/agent/ComposedChart.md +57 -0
  24. package/dist/agent/ContextMenu.md +59 -0
  25. package/dist/agent/DatePicker.md +75 -0
  26. package/dist/agent/Diagram.md +46 -0
  27. package/dist/agent/Dialog.md +52 -0
  28. package/dist/agent/DropdownMenu.md +70 -0
  29. package/dist/agent/EmptyState.md +37 -0
  30. package/dist/agent/ErrorState.md +41 -0
  31. package/dist/agent/Facet.md +82 -0
  32. package/dist/agent/Field.md +37 -0
  33. package/dist/agent/FigureBand.md +37 -0
  34. package/dist/agent/FloatingIconButton.md +38 -0
  35. package/dist/agent/FunnelChart.md +52 -0
  36. package/dist/agent/Heatmap.md +53 -0
  37. package/dist/agent/Histogram.md +63 -0
  38. package/dist/agent/Input.md +30 -0
  39. package/dist/agent/Kbd.md +29 -0
  40. package/dist/agent/LineChart.md +60 -0
  41. package/dist/agent/LinkArrow.md +29 -0
  42. package/dist/agent/NativeSelect.md +40 -0
  43. package/dist/agent/NavItem.md +35 -0
  44. package/dist/agent/Pagination.md +43 -0
  45. package/dist/agent/PieChart.md +55 -0
  46. package/dist/agent/Popover.md +57 -0
  47. package/dist/agent/Progress.md +33 -0
  48. package/dist/agent/RadarChart.md +54 -0
  49. package/dist/agent/RadialChart.md +57 -0
  50. package/dist/agent/RadioGroup.md +44 -0
  51. package/dist/agent/SankeyChart.md +61 -0
  52. package/dist/agent/ScatterChart.md +54 -0
  53. package/dist/agent/ScrollArea.md +40 -0
  54. package/dist/agent/SearchableMenu.md +46 -0
  55. package/dist/agent/Select.md +75 -0
  56. package/dist/agent/Separator.md +35 -0
  57. package/dist/agent/Sheet.md +57 -0
  58. package/dist/agent/Skeleton.md +73 -0
  59. package/dist/agent/Slider.md +39 -0
  60. package/dist/agent/Sparkline.md +45 -0
  61. package/dist/agent/Spinner.md +39 -0
  62. package/dist/agent/StatusDot.md +36 -0
  63. package/dist/agent/StatusPill.md +28 -0
  64. package/dist/agent/Steps.md +44 -0
  65. package/dist/agent/Switch.md +29 -0
  66. package/dist/agent/Table.md +85 -0
  67. package/dist/agent/Tabs.md +48 -0
  68. package/dist/agent/Tag.md +35 -0
  69. package/dist/agent/Textarea.md +26 -0
  70. package/dist/agent/Toast.md +26 -0
  71. package/dist/agent/ToggleGroup.md +44 -0
  72. package/dist/agent/Tooltip.md +51 -0
  73. package/dist/agent/TreemapChart.md +52 -0
  74. package/dist/agent/WaterfallChart.md +62 -0
  75. package/dist/agent/catalog.json +1791 -0
  76. package/dist/agent/index.md +131 -0
  77. package/dist/charts/AreaChart/AreaChart.d.ts +191 -0
  78. package/dist/charts/AreaChart/AreaChart.js +480 -0
  79. package/dist/charts/AreaChart/AreaChart.js.map +1 -0
  80. package/dist/charts/BarChart/BarChart.d.ts +179 -0
  81. package/dist/charts/BarChart/BarChart.js +591 -0
  82. package/dist/charts/BarChart/BarChart.js.map +1 -0
  83. package/dist/charts/BarList/BarList.d.ts +57 -0
  84. package/dist/charts/BarList/BarList.js +71 -0
  85. package/dist/charts/BarList/BarList.js.map +1 -0
  86. package/dist/charts/BigNumber/BigNumber.d.ts +56 -0
  87. package/dist/charts/BigNumber/BigNumber.js +31 -0
  88. package/dist/charts/BigNumber/BigNumber.js.map +1 -0
  89. package/dist/charts/BoxPlot/BoxPlot.d.ts +177 -0
  90. package/dist/charts/BoxPlot/BoxPlot.js +495 -0
  91. package/dist/charts/BoxPlot/BoxPlot.js.map +1 -0
  92. package/dist/charts/BulletChart/BulletChart.d.ts +100 -0
  93. package/dist/charts/BulletChart/BulletChart.js +162 -0
  94. package/dist/charts/BulletChart/BulletChart.js.map +1 -0
  95. package/dist/charts/ComposedChart/ComposedChart.d.ts +171 -0
  96. package/dist/charts/ComposedChart/ComposedChart.js +667 -0
  97. package/dist/charts/ComposedChart/ComposedChart.js.map +1 -0
  98. package/dist/charts/Facet/Facet.d.ts +240 -0
  99. package/dist/charts/Facet/Facet.js +133 -0
  100. package/dist/charts/Facet/Facet.js.map +1 -0
  101. package/dist/charts/Facet/panels.d.ts +187 -0
  102. package/dist/charts/Facet/panels.js +203 -0
  103. package/dist/charts/Facet/panels.js.map +1 -0
  104. package/dist/charts/FunnelChart/FunnelChart.d.ts +101 -0
  105. package/dist/charts/FunnelChart/FunnelChart.js +147 -0
  106. package/dist/charts/FunnelChart/FunnelChart.js.map +1 -0
  107. package/dist/charts/Heatmap/Heatmap.d.ts +78 -0
  108. package/dist/charts/Heatmap/Heatmap.js +108 -0
  109. package/dist/charts/Heatmap/Heatmap.js.map +1 -0
  110. package/dist/charts/Histogram/Histogram.d.ts +165 -0
  111. package/dist/charts/Histogram/Histogram.js +350 -0
  112. package/dist/charts/Histogram/Histogram.js.map +1 -0
  113. package/dist/charts/LineChart/LineChart.d.ts +167 -0
  114. package/dist/charts/LineChart/LineChart.js +486 -0
  115. package/dist/charts/LineChart/LineChart.js.map +1 -0
  116. package/dist/charts/PieChart/PieChart.d.ts +128 -0
  117. package/dist/charts/PieChart/PieChart.js +309 -0
  118. package/dist/charts/PieChart/PieChart.js.map +1 -0
  119. package/dist/charts/RadarChart/RadarChart.d.ts +123 -0
  120. package/dist/charts/RadarChart/RadarChart.js +299 -0
  121. package/dist/charts/RadarChart/RadarChart.js.map +1 -0
  122. package/dist/charts/RadialChart/RadialChart.d.ts +124 -0
  123. package/dist/charts/RadialChart/RadialChart.js +257 -0
  124. package/dist/charts/RadialChart/RadialChart.js.map +1 -0
  125. package/dist/charts/SankeyChart/SankeyChart.d.ts +137 -0
  126. package/dist/charts/SankeyChart/SankeyChart.js +501 -0
  127. package/dist/charts/SankeyChart/SankeyChart.js.map +1 -0
  128. package/dist/charts/ScatterChart/ScatterChart.d.ts +128 -0
  129. package/dist/charts/ScatterChart/ScatterChart.js +269 -0
  130. package/dist/charts/ScatterChart/ScatterChart.js.map +1 -0
  131. package/dist/charts/Sparkline/Sparkline.d.ts +53 -0
  132. package/dist/charts/Sparkline/Sparkline.js +93 -0
  133. package/dist/charts/Sparkline/Sparkline.js.map +1 -0
  134. package/dist/charts/TreemapChart/TreemapChart.d.ts +93 -0
  135. package/dist/charts/TreemapChart/TreemapChart.js +118 -0
  136. package/dist/charts/TreemapChart/TreemapChart.js.map +1 -0
  137. package/dist/charts/WaterfallChart/WaterfallChart.d.ts +164 -0
  138. package/dist/charts/WaterfallChart/WaterfallChart.js +319 -0
  139. package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -0
  140. package/dist/charts/index.d.ts +41 -0
  141. package/dist/charts/index.js +128 -0
  142. package/dist/charts/index.js.map +1 -0
  143. package/dist/charts/lib/annotations.d.ts +103 -0
  144. package/dist/charts/lib/annotations.js +99 -0
  145. package/dist/charts/lib/annotations.js.map +1 -0
  146. package/dist/charts/lib/axis.d.ts +18 -0
  147. package/dist/charts/lib/axis.js +21 -0
  148. package/dist/charts/lib/axis.js.map +1 -0
  149. package/dist/charts/lib/background.d.ts +33 -0
  150. package/dist/charts/lib/background.js +173 -0
  151. package/dist/charts/lib/background.js.map +1 -0
  152. package/dist/charts/lib/brush.d.ts +94 -0
  153. package/dist/charts/lib/brush.js +535 -0
  154. package/dist/charts/lib/brush.js.map +1 -0
  155. package/dist/charts/lib/chart.d.ts +94 -0
  156. package/dist/charts/lib/chart.js +134 -0
  157. package/dist/charts/lib/chart.js.map +1 -0
  158. package/dist/charts/lib/dot.d.ts +29 -0
  159. package/dist/charts/lib/dot.js +78 -0
  160. package/dist/charts/lib/dot.js.map +1 -0
  161. package/dist/charts/lib/empty.d.ts +31 -0
  162. package/dist/charts/lib/empty.js +13 -0
  163. package/dist/charts/lib/empty.js.map +1 -0
  164. package/dist/charts/lib/export.d.ts +103 -0
  165. package/dist/charts/lib/export.js +204 -0
  166. package/dist/charts/lib/export.js.map +1 -0
  167. package/dist/charts/lib/figure.d.ts +61 -0
  168. package/dist/charts/lib/figure.js +59 -0
  169. package/dist/charts/lib/figure.js.map +1 -0
  170. package/dist/charts/lib/format.d.ts +46 -0
  171. package/dist/charts/lib/format.js +56 -0
  172. package/dist/charts/lib/format.js.map +1 -0
  173. package/dist/charts/lib/legend.d.ts +45 -0
  174. package/dist/charts/lib/legend.js +117 -0
  175. package/dist/charts/lib/legend.js.map +1 -0
  176. package/dist/charts/lib/loading.d.ts +48 -0
  177. package/dist/charts/lib/loading.js +105 -0
  178. package/dist/charts/lib/loading.js.map +1 -0
  179. package/dist/charts/lib/paint.d.ts +154 -0
  180. package/dist/charts/lib/paint.js +551 -0
  181. package/dist/charts/lib/paint.js.map +1 -0
  182. package/dist/charts/lib/sonify-control.d.ts +163 -0
  183. package/dist/charts/lib/sonify-control.js +172 -0
  184. package/dist/charts/lib/sonify-control.js.map +1 -0
  185. package/dist/charts/lib/sonify.d.ts +248 -0
  186. package/dist/charts/lib/sonify.js +262 -0
  187. package/dist/charts/lib/sonify.js.map +1 -0
  188. package/dist/charts/lib/toolbar.d.ts +143 -0
  189. package/dist/charts/lib/toolbar.js +156 -0
  190. package/dist/charts/lib/toolbar.js.map +1 -0
  191. package/dist/charts/lib/tooltip.d.ts +59 -0
  192. package/dist/charts/lib/tooltip.js +135 -0
  193. package/dist/charts/lib/tooltip.js.map +1 -0
  194. package/dist/charts/lib/values.d.ts +56 -0
  195. package/dist/charts/lib/values.js +71 -0
  196. package/dist/charts/lib/values.js.map +1 -0
  197. package/dist/charts/lib/zoom.d.ts +139 -0
  198. package/dist/charts/lib/zoom.js +314 -0
  199. package/dist/charts/lib/zoom.js.map +1 -0
  200. package/dist/semantic.css +19 -0
  201. package/dist/styles.css +1 -1
  202. package/dist/themes.css +50 -0
  203. package/dist/tokens.css +118 -0
  204. package/dist/tokens.d.ts +17 -0
  205. package/dist/tokens.js +101 -0
  206. package/dist/tokens.json +101 -0
  207. package/package.json +26 -4
  208. package/skills/misoto22-design/SKILL.md +171 -0
  209. package/skills/misoto22-design/evals/claims.json +95 -0
  210. package/skills/misoto22-design/rules/a11y.md +115 -0
  211. package/skills/misoto22-design/rules/composition.md +108 -0
  212. package/skills/misoto22-design/rules/forms.md +128 -0
  213. package/skills/misoto22-design/rules/naming.md +155 -0
  214. package/skills/misoto22-design/rules/tokens.md +131 -0
@@ -0,0 +1,115 @@
1
+ # Accessibility
2
+
3
+ The package keeps these promises so a call site does not have to. What is left
4
+ is the handful of things only the call site knows — and every one of them is a
5
+ prop that is easy to leave off and impossible to notice afterwards.
6
+
7
+ ## Props the type makes required, and why
8
+
9
+ These are required in TypeScript because forgetting them ships a control that
10
+ cannot be used without sight. Do not satisfy the type with a placeholder.
11
+
12
+ | Prop | What it is |
13
+ | --- | --- |
14
+ | `Table.caption` | What the table is a table *of*. Announced before the rows. |
15
+ | `Progress.label` | What is progressing. `aria-valuetext` has nothing else to say. |
16
+ | `Avatar.alt` + `Avatar.fallback` | Who it is, and the initials shown when the image fails. |
17
+ | `FloatingIconButton.label` | There is no text in the button at all. |
18
+ | `Select.label` | The trigger is a button; this is its accessible name. |
19
+ | `Combobox.label` | Same. |
20
+ | `SheetContent.title` | A panel with no name drops a screen reader into an unnamed region. |
21
+ | `AccordionItem.title` | The heading that is also the trigger. |
22
+ | `EmptyState.title` | The state's own name. |
23
+ | `Alert.title` | Optional, but an alert with no title is a coloured paragraph. |
24
+
25
+ ```tsx
26
+ // Incorrect — the type is satisfied and the table is still anonymous
27
+ <Table caption="">
28
+
29
+ // Incorrect — the caption is not a label for the reader, it is the reader's only orientation
30
+ <Table caption="Table">
31
+
32
+ // Correct
33
+ <Table caption="Invoices awaiting payment">
34
+ ```
35
+
36
+ Use `showCaption={false}` when the design has no room for a visible caption. The
37
+ caption still reaches assistive tech.
38
+
39
+ ## An icon-only control needs a name
40
+
41
+ ```tsx
42
+ // Incorrect — reads as "button"
43
+ <Button iconOnly><TrashIcon /></Button>
44
+
45
+ // Correct
46
+ <Button iconOnly aria-label="Delete invoice"><TrashIcon /></Button>
47
+ ```
48
+
49
+ The same applies to any `Button` whose children are only an icon, whether or not
50
+ `iconOnly` is set. `FloatingIconButton` cannot get this wrong — its `label` is
51
+ required.
52
+
53
+ ## Decoration is `aria-hidden`
54
+
55
+ An icon that repeats the words beside it is noise; an arrow inside a link is
56
+ read out as "north east arrow".
57
+
58
+ ```tsx
59
+ // Incorrect
60
+ <StatusPill tone="success"><CheckIcon /> Available</StatusPill>
61
+
62
+ // Correct
63
+ <StatusPill tone="success"><CheckIcon aria-hidden="true" /> Available</StatusPill>
64
+ ```
65
+
66
+ ## Colour is never the only carrier
67
+
68
+ Every status tone in the system is doubled by an icon, by the words, or by both.
69
+ Keep that when you compose.
70
+
71
+ ```tsx
72
+ // Incorrect — the only difference between these two rows is a hue
73
+ <TD><span className="text-(--danger)">●</span></TD>
74
+
75
+ // Correct
76
+ <TD><StatusPill tone="danger">Overdue</StatusPill></TD>
77
+ ```
78
+
79
+ ## Do not re-implement a pattern Radix already owns
80
+
81
+ Focus traps, roving tabindex, typeahead, portal placement and scroll locking are
82
+ where a hand-rolled component quietly becomes unusable with a keyboard. `Dialog`,
83
+ `Sheet`, `Popover`, `DropdownMenu`, `ContextMenu`, `Tabs`, `Accordion`,
84
+ `RadioGroup`, `ToggleGroup`, `Select`, `Slider` and `Tooltip` already wrap it.
85
+
86
+ ```tsx
87
+ // Incorrect
88
+ <div role="dialog" className="fixed inset-0 …" onKeyDown={handleEscape}>
89
+
90
+ // Correct
91
+ <Dialog><DialogContent title="…">…</DialogContent></Dialog>
92
+ ```
93
+
94
+ ## Motion is gated
95
+
96
+ Anything animated is behind `motion-safe` and carries `data-m22-animated`, so
97
+ the one reduced-motion rule in the package can reach it. If you animate
98
+ something yourself, do both.
99
+
100
+ ```tsx
101
+ // Correct — the package's keyframes are all prefixed `m22-`
102
+ <div data-m22-animated className="motion-safe:animate-[m22-fade-in_200ms_var(--ease-out-expo)]">
103
+ ```
104
+
105
+ `data-motion="still"` on any ancestor stops motion in that subtree.
106
+
107
+ ## The keyboard contract is documented per component
108
+
109
+ ```bash
110
+ npx misoto22-design docs Combobox
111
+ ```
112
+
113
+ prints the key-by-key contract. It is the part that quietly regresses — a
114
+ wrapper that swallows a key looks identical until someone tries it — so check it
115
+ before you wrap one of these in your own handler.
@@ -0,0 +1,108 @@
1
+ # Composition
2
+
3
+ ## `className` adjusts layout, never appearance
4
+
5
+ The caller's `className` is merged last through `cn()`, so it *can* override
6
+ anything. That is what makes the rule necessary rather than enforced.
7
+
8
+ ```tsx
9
+ // Incorrect — re-skins the component, and only in this one place
10
+ <Button className="bg-blue-600 text-white rounded-full px-8 text-lg">
11
+
12
+ // Correct — the appearance is a variant, the layout is the class
13
+ <Button variant="primary" size="lg" className="w-full">
14
+ ```
15
+
16
+ Legitimate `className`: `w-`, `flex-`, `grid-`, `col-span-`, `m*`, `self-`,
17
+ `order-`. Not legitimate: colour, border, radius, font size, shadow. If the
18
+ appearance you want is not reachable through props and the theme axes, that is a
19
+ gap in the system — say so rather than patching it at one call site.
20
+
21
+ ## Use `cn()` for conditional classes
22
+
23
+ ```tsx
24
+ import { cn } from '@misoto22/design'
25
+
26
+ // Incorrect — emits both sides of the conflict; stylesheet order picks a winner
27
+ <div className={`p-6 ${dense ? 'p-2' : ''}`}>
28
+
29
+ // Correct — twMerge resolves by utility group, so p-2 replaces p-6
30
+ <div className={cn('p-6', dense && 'p-2')}>
31
+ ```
32
+
33
+ ## Card uses its full set
34
+
35
+ ```tsx
36
+ // Incorrect
37
+ <Card><div className="p-6"><h3 className="font-bold">Usage</h3><p>…</p></div></Card>
38
+
39
+ // Correct
40
+ <Card variant="outline">
41
+ <CardHeader><CardTitle>Usage</CardTitle></CardHeader>
42
+ <CardBody>…</CardBody>
43
+ <CardFooter><Button>Manage</Button></CardFooter>
44
+ </Card>
45
+ ```
46
+
47
+ `variant`: `outline` (default) · `plate` · `flat`. The part is `CardBody`, not
48
+ `CardContent` — see [naming.md](./naming.md).
49
+
50
+ ## Reach for a component before writing markup
51
+
52
+ | Instead of | Use |
53
+ | --- | --- |
54
+ | a styled div with an icon and a message | `Alert` |
55
+ | a centred "nothing here yet" block | `EmptyState` |
56
+ | an error page or panel | `ErrorState` |
57
+ | `<hr>` or `border-t` | `Separator` |
58
+ | a custom `animate-pulse` div | `Skeleton` (+ `SkeletonLine` / `SkeletonText` / `SkeletonPage`) |
59
+ | a styled span for a count or state | `Badge`, or `StatusPill` for a live state |
60
+ | a styled span for a keyboard key | `Kbd` |
61
+ | a hand-built breadcrumb trail | `Breadcrumb` |
62
+ | a hand-built page/sidebar frame | `AppShell` |
63
+ | a hand-built numbered process | `Steps` |
64
+ | long-form prose you did not style yourself | `Article` |
65
+
66
+ ## Navigation goes through `asChild`
67
+
68
+ No component in this package imports a router. A control that navigates takes
69
+ its own element from the call site.
70
+
71
+ ```tsx
72
+ // Incorrect in a Next.js app — a full page load
73
+ <Button href="/settings">Settings</Button>
74
+
75
+ // Correct
76
+ <Button asChild><Link href="/settings">Settings</Link></Button>
77
+ ```
78
+
79
+ `href` on `Button` renders a plain `<a>`. That is right for an external link and
80
+ wrong for in-app navigation.
81
+
82
+ ## Overlays own their own stacking
83
+
84
+ `Dialog`, `Sheet`, `Popover`, `DropdownMenu`, `ContextMenu` and `Tooltip` portal
85
+ themselves and read `--z-overlay` / `--z-dropdown`.
86
+
87
+ ```tsx
88
+ // Incorrect
89
+ <DialogContent title="Edit" className="z-50">
90
+
91
+ // Correct
92
+ <DialogContent title="Edit">
93
+ ```
94
+
95
+ `TooltipProvider` goes once at the app root; `Toaster` too.
96
+
97
+ ## Logical properties, always
98
+
99
+ `ps-` / `pe-` / `ms-` / `me-` / `start-` / `end-` / `text-start` / `border-s`.
100
+ A physical property breaks RTL, and the package is RTL-correct everywhere else.
101
+
102
+ ## Server and client
103
+
104
+ The package is compiled per-file, so `'use client'` sits on the components that
105
+ need it. `Button`, `Card`, `Badge`, `Table`, `Alert`, `Separator` and the other
106
+ static primitives stay server-renderable; anything wrapping Radix carries the
107
+ directive itself. Do not add `'use client'` to a page just because it imports
108
+ from here.
@@ -0,0 +1,128 @@
1
+ # Forms
2
+
3
+ `Field` is the form row. It does the ARIA wiring that a hand-rolled label +
4
+ input + error div silently skips, and skipping it is invisible in a browser and
5
+ total for a screen reader.
6
+
7
+ ## A labelled control is a `Field`
8
+
9
+ ```tsx
10
+ // Incorrect — the hint is drawn and never announced, and required is decorative
11
+ <div className="space-y-2">
12
+ <label htmlFor="email">Email *</label>
13
+ <Input id="email" />
14
+ <p className="text-xs text-red-500">Enter a valid email.</p>
15
+ </div>
16
+
17
+ // Correct
18
+ <Field label="Email" required error="Enter a valid email.">
19
+ <Input type="email" />
20
+ </Field>
21
+ ```
22
+
23
+ `Field` generates the id, points the label at it, and sets `aria-describedby`,
24
+ `aria-required` and `aria-invalid` on the control.
25
+
26
+ ## Do not repeat what `Field` already sets
27
+
28
+ Passing `error` sets `aria-invalid` on the control for you. Writing `invalid`
29
+ as well is redundant, and writing a red border class is wrong.
30
+
31
+ ```tsx
32
+ // Redundant
33
+ <Field label="Email" error={message}><Input invalid /></Field>
34
+
35
+ // Wrong
36
+ <Field label="Email" error={message}><Input className="border-red-500" /></Field>
37
+
38
+ // Correct
39
+ <Field label="Email" error={message}><Input /></Field>
40
+ ```
41
+
42
+ Reach for `invalid` on the control only when there is no `Field` around it.
43
+
44
+ ## `hint` and `error` are one slot
45
+
46
+ They are not two stacked messages. `error` replaces `hint` when present: when a
47
+ field is wrong, the thing to read is what is wrong with it.
48
+
49
+ ```tsx
50
+ // Correct — one of the two shows, decided by whether there is an error
51
+ <Field label="Password" hint="At least 12 characters." error={errors.password}>
52
+ <Input type="password" />
53
+ </Field>
54
+ ```
55
+
56
+ ## One control per `Field`
57
+
58
+ The wiring clones a **single** element child. Two children, or a wrapper div,
59
+ and nothing is wired — the label points at an id that is on nothing.
60
+
61
+ ```tsx
62
+ // Incorrect — the div is the child, so the input gets no id, no describedby
63
+ <Field label="Amount">
64
+ <div className="flex gap-2"><Input /><Button>Max</Button></div>
65
+ </Field>
66
+
67
+ // Correct — one Field per control; the row is laid out outside it
68
+ <div className="flex items-end gap-2">
69
+ <Field label="Amount" className="flex-1"><Input /></Field>
70
+ <Button>Max</Button>
71
+ </div>
72
+ ```
73
+
74
+ ## The controls
75
+
76
+ | Need | Component | Note |
77
+ | --- | --- | --- |
78
+ | Text, email, number | `Input` | |
79
+ | Multi-line | `Textarea` | |
80
+ | A short list, styled | `Select` | `label` is **required** |
81
+ | A short list, native | `NativeSelect` | Use on mobile-first forms |
82
+ | A long, searchable list | `Combobox` | `options` array, `label` **required** |
83
+ | Boolean, in a form | `Checkbox` | |
84
+ | Boolean, applied immediately | `Switch` | |
85
+ | One of 2–7 visible options | `RadioGroup` + `RadioGroupItem` | |
86
+ | One of 2–7, as a toolbar | `ToggleGroup` + `ToggleGroupItem` | |
87
+ | A number in a range | `Slider` | |
88
+ | A date | `DatePicker` / `DateRangePicker` | |
89
+
90
+ `Select` and `Combobox` take `label` even inside a `Field` — it is the
91
+ accessible name of the trigger, which is a button, not a labellable control.
92
+
93
+ ## Custom controls read the shared base
94
+
95
+ Do not re-derive the text-control look. Three exports carry it, and they respond
96
+ to `data-density` and `data-mode` for free.
97
+
98
+ ```tsx
99
+ import { CONTROL_BASE, CONTROL_BORDER, cn, isInvalid } from '@misoto22/design'
100
+
101
+ function MoneyInput({ invalid, 'aria-invalid': ariaInvalid, className, ...rest }) {
102
+ const bad = isInvalid(invalid, ariaInvalid)
103
+ return (
104
+ <input
105
+ aria-invalid={bad || undefined}
106
+ className={cn(CONTROL_BASE, bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting, className)}
107
+ {...rest}
108
+ />
109
+ )
110
+ }
111
+ ```
112
+
113
+ `isInvalid()` reads both spellings — `invalid` is the ergonomic prop, and
114
+ `aria-invalid` is what a form library sets. A control that honours only one of
115
+ them loses the error styling for half the ecosystem.
116
+
117
+ ## Layout
118
+
119
+ `Field` already stacks its own parts. For the gap between fields, use flex with
120
+ `gap`, not margin utilities on the children.
121
+
122
+ ```tsx
123
+ // Correct
124
+ <form className="flex flex-col gap-4">
125
+ <Field label="Name"><Input /></Field>
126
+ <Field label="Email"><Input type="email" /></Field>
127
+ </form>
128
+ ```
@@ -0,0 +1,155 @@
1
+ # Naming — where habit produces an import that does not exist
2
+
3
+ Most models have read far more shadcn/ui and Radix than they have read this
4
+ package. Those habits produce identifiers that are not exported here, and the
5
+ failure is a TypeScript error at the import line rather than something subtle.
6
+
7
+ This is the whole divergence list. Everything not on it is spelled the way you
8
+ would guess.
9
+
10
+ ## Compound parts renamed
11
+
12
+ | Habit | Here |
13
+ | --- | --- |
14
+ | `CardContent` | `CardBody` |
15
+ | `TableHeader` | `THead` |
16
+ | `TableBody` | `TBody` |
17
+ | `TableRow` | `TR` |
18
+ | `TableHead` | `TH` |
19
+ | `TableCell` | `TD` |
20
+
21
+ ```tsx
22
+ // Incorrect
23
+ <Card><CardContent>…</CardContent></Card>
24
+
25
+ // Correct
26
+ <Card><CardBody>…</CardBody></Card>
27
+ ```
28
+
29
+ ## Compounds that are one component with props
30
+
31
+ These have no sub-parts at all. Reaching for `<XTrigger>` / `<XContent>` fails.
32
+
33
+ ### Dialog and Sheet — the title is a prop
34
+
35
+ ```tsx
36
+ // Incorrect — DialogTitle and DialogHeader are not exported
37
+ <DialogContent>
38
+ <DialogHeader><DialogTitle>Delete project</DialogTitle></DialogHeader>
39
+
40
+ </DialogContent>
41
+
42
+ // Correct
43
+ <DialogContent title="Delete project" description="This cannot be undone.">
44
+
45
+ </DialogContent>
46
+ ```
47
+
48
+ `Sheet` is the same shape, and `SheetTitle` does not exist either:
49
+
50
+ ```tsx
51
+ // Incorrect
52
+ <SheetContent><SheetTitle>Filters</SheetTitle>…</SheetContent>
53
+
54
+ // Correct
55
+ <SheetContent side="end" title="Filters">…</SheetContent>
56
+ ```
57
+
58
+ `SheetContent.title` is required by the type. `DialogContent.title` is optional
59
+ — Radix demands a title either way, so omitting it renders a visually-hidden
60
+ placeholder rather than warning. Pass it anyway: the placeholder keeps the modal
61
+ legal, not useful. When the design calls for no visible heading, pass the title
62
+ and add `hideTitle`, so the accessible name is a real one.
63
+
64
+ ### Tooltip — the content is a prop, and it wraps its trigger
65
+
66
+ ```tsx
67
+ // Incorrect
68
+ <Tooltip>
69
+ <TooltipTrigger asChild><Button /></TooltipTrigger>
70
+ <TooltipContent>Copy</TooltipContent>
71
+ </Tooltip>
72
+
73
+ // Correct — put TooltipProvider once at the app root
74
+ <Tooltip content="Copy"><Button iconOnly aria-label="Copy"><CopyIcon /></Button></Tooltip>
75
+ ```
76
+
77
+ ### Alert and EmptyState — tone and title are props
78
+
79
+ ```tsx
80
+ // Incorrect
81
+ <Alert><AlertTitle>Saved</AlertTitle><AlertDescription>…</AlertDescription></Alert>
82
+
83
+ // Correct
84
+ <Alert tone="success" title="Saved">Your changes are live.</Alert>
85
+ ```
86
+
87
+ `EmptyState` is the same shape: `icon` (a Lucide component, not an element),
88
+ `title`, `description`, `action`.
89
+
90
+ ### Accordion — the item carries its own title
91
+
92
+ ```tsx
93
+ // Incorrect
94
+ <AccordionItem value="a">
95
+ <AccordionTrigger>Billing</AccordionTrigger>
96
+ <AccordionContent>…</AccordionContent>
97
+ </AccordionItem>
98
+
99
+ // Correct
100
+ <AccordionItem value="a" title="Billing">…</AccordionItem>
101
+ ```
102
+
103
+ ### Select — one component, `SelectItem` children
104
+
105
+ ```tsx
106
+ // Incorrect
107
+ <Select>
108
+ <SelectTrigger><SelectValue placeholder="Choose" /></SelectTrigger>
109
+ <SelectContent><SelectItem value="au">Australia</SelectItem></SelectContent>
110
+ </Select>
111
+
112
+ // Correct — label is required
113
+ <Select label="Region" placeholder="Choose">
114
+ <SelectItem value="au">Australia</SelectItem>
115
+ </Select>
116
+ ```
117
+
118
+ `SelectRoot` is the raw Radix root, exported for the rare case that needs it.
119
+ Reach for `Select` unless you know why you are not.
120
+
121
+ ### Combobox — an options array, not children
122
+
123
+ ```tsx
124
+ // Correct
125
+ <Combobox
126
+ label="Assignee"
127
+ options={[{ value: 'ada', label: 'Ada Lovelace' }]}
128
+ value={value}
129
+ onValueChange={setValue}
130
+ />
131
+ ```
132
+
133
+ ## Compounds that ARE compounds
134
+
135
+ Spelled as expected, no surprises: `Popover`, `DropdownMenu`, `ContextMenu`,
136
+ `Command`, `Tabs`, `Collapsible`, `RadioGroup`, `ToggleGroup`, `Skeleton`.
137
+
138
+ ## Imports that come from here, not from upstream
139
+
140
+ | Habit | Here |
141
+ | --- | --- |
142
+ | `import { toast } from 'sonner'` | `import { toast } from '@misoto22/design'` |
143
+ | `import { cn } from '@/lib/utils'` | `import { cn } from '@misoto22/design'` |
144
+
145
+ `<Toaster />` goes once at the app root, and it is exported from here too.
146
+
147
+ ## When in doubt
148
+
149
+ ```bash
150
+ npx misoto22-design docs <Component>
151
+ ```
152
+
153
+ That prints the component's real exports, every prop with its type and default,
154
+ and its keyboard contract, read out of the installed source. It is cheaper than
155
+ a wrong import and a retry.
@@ -0,0 +1,131 @@
1
+ # Colour, theming and spacing
2
+
3
+ The system is monochrome by construction: paper ground, near-black mark, and
4
+ status is the only chroma. A raw colour class is not a style choice here, it is
5
+ a hole in the theme — it survives the light/dark swap unchanged and it ignores
6
+ every surface axis.
7
+
8
+ ## Never write a raw colour
9
+
10
+ ```tsx
11
+ // Incorrect
12
+ <div className="bg-white text-gray-500 border-neutral-200">
13
+ <div className="bg-neutral-900 dark:bg-white">
14
+ <div style={{ color: '#101010' }}>
15
+
16
+ // Correct
17
+ <div className="bg-(--background) text-(--foreground-muted) border-(--border-color)">
18
+ ```
19
+
20
+ The semantic layer is the one to read. It is shipped as its own entry point
21
+ (`@misoto22/design/semantic.css`) and it is what the components themselves use.
22
+
23
+ | Role | Token |
24
+ | --- | --- |
25
+ | Page ground | `--background` |
26
+ | One step off the ground | `--background-elevated` |
27
+ | Card ground | `--card-background` |
28
+ | Body text | `--foreground` |
29
+ | Quieter text | `--foreground-muted` |
30
+ | Quietest text that still clears AA | `--secondary-text` |
31
+ | Edge | `--border-color` |
32
+ | Hairline | `--border-subtle` |
33
+ | A chosen state — primary button, checked box, active tab | `--accent` |
34
+ | Text on an accent fill | `--accent-foreground` |
35
+ | Text on an accent wash | `--accent-on-muted` |
36
+ | Status | `--success` `--warning` `--danger` `--info`, each with a `-wash` |
37
+ | Text over a photograph | `--on-dark` |
38
+
39
+ Prefer these over the primitive layer (`--paper`, `--ink`, `--rule`, `--stone`).
40
+ Primitives are values; semantics are roles, and a role can be re-pointed without
41
+ a component changing. `--card-background` is already an alias for a surface that
42
+ was retired — that is the layer doing its job.
43
+
44
+ ## Never write a `dark:` variant
45
+
46
+ ```tsx
47
+ // Incorrect — freezes one side of the swap
48
+ <div className="bg-white dark:bg-neutral-900">
49
+
50
+ // Correct — the token already resolves in both
51
+ <div className="bg-(--background)">
52
+ ```
53
+
54
+ Dark mode is a value swap on the primitives behind `[data-mode="dark"]`. Any
55
+ semantic token re-resolves on its own. A `dark:` class is a second, competing
56
+ theme that will disagree with the first the moment a surface axis is set.
57
+
58
+ ## Theme with the seven axes
59
+
60
+ Each is an attribute, each is independent, and each works on **any element** —
61
+ not just `:root`. An unset axis is the default.
62
+
63
+ | Attribute | Values | Unset means |
64
+ | --- | --- | --- |
65
+ | `data-mode` | `light` `dark` | follows the app |
66
+ | `data-surface` | `warm` `cool` `glass` | paper |
67
+ | `data-radius` | `sharp` `round` | the default ladder |
68
+ | `data-rules` | `quiet` `firm` | hairline |
69
+ | `data-type` | `grotesk` `bookish` | editorial |
70
+ | `data-motion` | `still` `snappy` | calm |
71
+ | `data-density` | `compact` | comfortable |
72
+
73
+ ```tsx
74
+ // A warm, square, dense panel inside an otherwise default page.
75
+ <section data-surface="warm" data-radius="sharp" data-density="compact">…</section>
76
+ ```
77
+
78
+ There is **no `data-accent` attribute.** `--accent` is a single CSS custom
79
+ property; re-skin the system by re-pointing it in your own stylesheet, not by
80
+ setting an attribute.
81
+
82
+ ```css
83
+ /* Correct way to re-accent */
84
+ :root { --accent: var(--ok); --accent-hover: var(--ok-ink); }
85
+ ```
86
+
87
+ ## Radius, duration and control size come from tokens
88
+
89
+ ```tsx
90
+ // Incorrect
91
+ <div className="rounded-lg duration-150 min-h-11">
92
+
93
+ // Correct
94
+ <div className="rounded-(--radius) duration-(--duration-fast) min-h-(--control-h-md)">
95
+ ```
96
+
97
+ `--control-h-md` is 44px comfortable / 36px compact. Hard-coding `min-h-11`
98
+ means the control never responds to `data-density`, and `min-height` is a floor,
99
+ so it also never shrinks.
100
+
101
+ Durations: `--duration-fast` `--duration-base` `--duration-slow`. Easing:
102
+ `--ease-out-expo`.
103
+
104
+ ## No blurred shadow
105
+
106
+ `--shadow`, `--shadow-sm` and `--shadow-lg` all resolve to `none` on purpose.
107
+ The system has no elevation ramp. Depth is a hairline, a change of ground, or
108
+ `--lift` — a hard ink offset with no blur.
109
+
110
+ ```tsx
111
+ // Incorrect
112
+ <div className="shadow-lg">
113
+
114
+ // Correct
115
+ <div className="shadow-(--lift)">
116
+ ```
117
+
118
+ ## Logical properties, always
119
+
120
+ The package is RTL-correct and a physical property breaks it.
121
+
122
+ ```tsx
123
+ // Incorrect
124
+ <div className="pl-4 ml-2 text-left border-l">
125
+
126
+ // Correct
127
+ <div className="ps-4 ms-2 text-start border-s">
128
+ ```
129
+
130
+ Tailwind has no logical `translate`, so anything sliding along the inline axis
131
+ needs an explicit `rtl:` counterpart.