division-ds 0.1.2 → 0.2.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 (98) hide show
  1. package/AGENTS.md +23 -3
  2. package/README.md +11 -0
  3. package/division-ds-0.2.1.tgz +0 -0
  4. package/package.json +2 -2
  5. package/public/docs/components/accordion.md +207 -0
  6. package/public/docs/components/ai-label.md +90 -0
  7. package/public/docs/components/alert-dialog.md +292 -0
  8. package/public/docs/components/alert.md +175 -0
  9. package/public/docs/components/approval-request.md +148 -0
  10. package/public/docs/components/auth-form.md +132 -0
  11. package/public/docs/components/avatar-group.md +152 -0
  12. package/public/docs/components/avatar.md +179 -0
  13. package/public/docs/components/badge.md +178 -0
  14. package/public/docs/components/breadcrumbs.md +112 -0
  15. package/public/docs/components/button.md +204 -0
  16. package/public/docs/components/card.md +257 -0
  17. package/public/docs/components/carousel.md +130 -0
  18. package/public/docs/components/cart.md +172 -0
  19. package/public/docs/components/chart-panel.md +358 -0
  20. package/public/docs/components/chat-message.md +157 -0
  21. package/public/docs/components/chat-thread.md +244 -0
  22. package/public/docs/components/checkbox.md +149 -0
  23. package/public/docs/components/checkout-form.md +128 -0
  24. package/public/docs/components/command-palette.md +147 -0
  25. package/public/docs/components/data-table.md +401 -0
  26. package/public/docs/components/description-list.md +103 -0
  27. package/public/docs/components/dialog.md +197 -0
  28. package/public/docs/components/divider.md +97 -0
  29. package/public/docs/components/dropdown-menu.md +222 -0
  30. package/public/docs/components/empty-state.md +145 -0
  31. package/public/docs/components/footer.md +397 -0
  32. package/public/docs/components/form-field.md +216 -0
  33. package/public/docs/components/icon-button.md +127 -0
  34. package/public/docs/components/input.md +200 -0
  35. package/public/docs/components/kanban-board.md +300 -0
  36. package/public/docs/components/kbd.md +72 -0
  37. package/public/docs/components/link.md +98 -0
  38. package/public/docs/components/list-item.md +241 -0
  39. package/public/docs/components/loading-animation.md +72 -0
  40. package/public/docs/components/multi-select.md +202 -0
  41. package/public/docs/components/navbar.md +144 -0
  42. package/public/docs/components/newsletter-form.md +112 -0
  43. package/public/docs/components/page-header.md +151 -0
  44. package/public/docs/components/pagination.md +212 -0
  45. package/public/docs/components/product-card.md +172 -0
  46. package/public/docs/components/progress.md +101 -0
  47. package/public/docs/components/prompt-input.md +151 -0
  48. package/public/docs/components/quantity-stepper.md +140 -0
  49. package/public/docs/components/radio.md +177 -0
  50. package/public/docs/components/rating.md +106 -0
  51. package/public/docs/components/score-ring.md +95 -0
  52. package/public/docs/components/search.md +116 -0
  53. package/public/docs/components/section-heading.md +113 -0
  54. package/public/docs/components/segmented-control.md +126 -0
  55. package/public/docs/components/select.md +224 -0
  56. package/public/docs/components/sheet.md +145 -0
  57. package/public/docs/components/sidebar.md +193 -0
  58. package/public/docs/components/skeleton.md +135 -0
  59. package/public/docs/components/slider.md +114 -0
  60. package/public/docs/components/sparkline.md +128 -0
  61. package/public/docs/components/spinner.md +98 -0
  62. package/public/docs/components/stat.md +106 -0
  63. package/public/docs/components/status-tracker.md +145 -0
  64. package/public/docs/components/stepper.md +147 -0
  65. package/public/docs/components/switch.md +153 -0
  66. package/public/docs/components/tabs.md +180 -0
  67. package/public/docs/components/tag.md +105 -0
  68. package/public/docs/components/textarea.md +148 -0
  69. package/public/docs/components/timeline.md +152 -0
  70. package/public/docs/components/toast.md +194 -0
  71. package/public/docs/components/tool-call.md +107 -0
  72. package/public/docs/components/tooltip.md +108 -0
  73. package/public/manifest/components.json +198 -15
  74. package/public/manifest/tokens.json +75 -5
  75. package/scripts/division-ds.mjs +78 -42
  76. package/scripts/lib/agent-docs.mjs +80 -6
  77. package/scripts/lib/ai-copy.mjs +120 -0
  78. package/scripts/lib/button-spacing.mjs +5 -5
  79. package/scripts/lib/check-text.mjs +57 -0
  80. package/scripts/lib/contrast.mjs +111 -0
  81. package/scripts/lib/host-inline.mjs +59 -0
  82. package/scripts/lib/mcp-server.mjs +116 -0
  83. package/scripts/lib/mcp-tools.mjs +266 -0
  84. package/scripts/lib/props-validator.mjs +3 -2
  85. package/scripts/lib/rules-installer.mjs +42 -4
  86. package/scripts/lib/token-discipline.mjs +10 -3
  87. package/skills/division-ds/SKILL.md +90 -0
  88. package/skills/division-ds/commands/add.md +25 -0
  89. package/skills/division-ds/commands/build.md +46 -0
  90. package/skills/division-ds/commands/fix.md +28 -0
  91. package/skills/division-ds/commands/review.md +25 -0
  92. package/skills/division-ds/topics/ai.md +8 -0
  93. package/skills/division-ds/topics/colour.md +11 -0
  94. package/skills/division-ds/topics/feedback.md +9 -0
  95. package/skills/division-ds/topics/forms.md +22 -0
  96. package/skills/division-ds/topics/layout.md +36 -0
  97. package/skills/division-ds/topics/motion.md +10 -0
  98. package/skills/division-ds/topics/overlays.md +9 -0
package/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Division DS: instructions for coding agents
2
2
 
3
- A React 19 + Tailwind 4 + Radix design system: 67 components, tokens in three layers, light and
3
+ A React 19 + Tailwind 4 + Radix design system: 68 components, tokens in three layers, light and
4
4
  dark themes, WCAG 2.2 AA. This file is the entry point. It is short on purpose; everything it
5
5
  points to is generated from the code, so it is current.
6
6
 
@@ -11,6 +11,7 @@ points to is generated from the code, so it is current.
11
11
  | The list of components, one line each | `public/llms.txt` |
12
12
  | One component: props, examples as code, keyboard, do and don't | `public/docs/components/<slug>.md` |
13
13
  | Every component's props in one compact file | `public/llms-components.txt` |
14
+ | The whole system in one short file (for a tool that takes one design file) | `public/DESIGN.md` |
14
15
  | Which token to use for what | `public/llms-tokens.txt` |
15
16
  | Shared helpers: form errors, money, links, theme, streamed text | `public/docs/helpers.md` |
16
17
  | The same as data | `public/manifest/components.json`, `public/manifest/tokens.json` |
@@ -42,10 +43,17 @@ npx division-ds check <files or dirs> (from npm; Node 22 or newer)
42
43
  node <path-to-division-ds>/scripts/division-ds.mjs check <files or dirs> (the same, from a clone)
43
44
  ```
44
45
 
45
- It runs the colour and button-spacing gates and checks every Division DS element's props against
46
+ It runs the colour, button-spacing and AI copy gates and checks every Division DS element's props against
46
47
  `public/manifest/components.json`: an unknown prop, or a value that is not in the prop's union.
47
48
  Exit 1 means a finding; each one says what to write instead. `division-ds rules --target <dir>`
48
49
  installs these rules into that project's `AGENTS.md`, `GEMINI.md` and Cursor rules.
50
+ `division-ds skill --target <dir>` installs the skill into `.agents/skills/` (Codex, Cursor) and
51
+ `.claude/skills/` (Claude Code).
52
+
53
+ An agent that supports MCP can have the docs and the same checks as tools instead:
54
+ `claude mcp add division-ds -- npx -y division-ds@0.2.1 mcp` (`list_components`, `get_component`,
55
+ `search`, `get_tokens`, `validate_code`, `check_contrast`, `get_add_command`,
56
+ `get_review_checklist`).
49
57
 
50
58
  <!-- rules:start -->
51
59
  ## Rules
@@ -108,7 +116,9 @@ The shell paints the page background (`surface.base`) and supplies the gutter; a
108
116
  own `max-w-*` and vertical spacing, and no page background. A form or an overlay is the
109
117
  exception: it paints `surface.base` on itself, so its field colours never depend on what is
110
118
  behind it. In a project that uses the components, your own root does this
111
- (step 4 of `install.md`).
119
+ (step 4 of `install.md`). Toggle it with `setTheme()` from `src/lib/theme.ts`.
120
+
121
+ **App navigation.** A few views: `Navbar` (`current: true` on the open one). Many: `Sidebar`.
112
122
 
113
123
  **Type.** Three families, as utilities: `font-display` (headings, figures), `font-body` (the
114
124
  default, set by the shell), `font-mono` (labels, code, tabular numbers). A page title is
@@ -143,6 +153,9 @@ bare and name it through its own `label` prop ("Role for Ilse Varga"). `Switch`,
143
153
  renders `ErrorSummary` at the top and focuses it; `useFieldErrors` holds the state (signature in
144
154
  `public/docs/helpers.md`).
145
155
 
156
+ A `RadioGroup` in a form sits in a `<fieldset>` whose `<legend>` names it (code on the Radio
157
+ page). A date is `<Input type="date">` in a `FormField`.
158
+
146
159
  **Buttons.** Two or more buttons side by side need a parent with a gap
147
160
  (`flex flex-wrap gap-3`); `DialogFooter` and `CardFooter` already are one. A `Button` is
148
161
  `type="button"` unless you say `type="submit"`. A busy button takes `loading`, not `disabled`.
@@ -175,6 +188,13 @@ element arriving, `anim-draw-on` for a hairline) and the motion tokens (`--durat
175
188
  hover, `--duration-base` by default, `--duration-slow` for a larger change, `--ease-snap`).
176
189
  Anything you animate yourself stops under `prefers-reduced-motion` (`motion-reduce:` in
177
190
  Tailwind), and nothing moves on its own: no autoplay.
191
+
192
+ <!-- topic: ai -->
193
+
194
+ **Assistant copy.** A button names the action ("Summarize"), never "AI". Say "response", not
195
+ "answer". No "please" or "!" in assistant labels. The interface says "Assistant"; only its own
196
+ messages say "I". Response feedback: `ResponseFeedback` in `actions`.
197
+ A value the assistant filled is `aiValue` on that `Input` or `Textarea` only; leaving it accepts it.
178
198
  <!-- rules:end -->
179
199
 
180
200
  ## Changing the system itself
package/README.md CHANGED
@@ -16,4 +16,15 @@ npx division-ds rules # writes the Division DS rules into AGENTS.md / CLAUDE
16
16
  npx division-ds help
17
17
  ```
18
18
 
19
+ ## For agents: the MCP server
20
+
21
+ ```bash
22
+ claude mcp add division-ds -- npx -y division-ds mcp
23
+ ```
24
+
25
+ Any MCP client works the same way: the command is `npx -y division-ds mcp`, on stdio. It gives the
26
+ agent eight tools: `list_components`, `get_component`, `search`, `get_tokens`,
27
+ `validate_code` (the checks above, on code the agent just wrote), `check_contrast` (WCAG 2.2 AA,
28
+ both themes), `get_add_command` and `get_review_checklist`.
29
+
19
30
  The components, the docs and the agent skill: https://github.com/Victor-Ilioiu/division-ds
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "division-ds",
3
- "version": "0.1.2",
4
- "description": "Checks a project that uses Division DS components: raw colours, invented tokens, touching buttons, unknown props.",
3
+ "version": "0.2.1",
4
+ "description": "Checks a project that uses Division DS components (raw colours, invented tokens, touching buttons, unknown props), and serves the docs and the same checks to agents over MCP.",
5
5
  "license": "MIT",
6
6
  "author": "Victor Ilioiu",
7
7
  "homepage": "https://victor-ilioiu.vercel.app/systems/division-ds/",
@@ -0,0 +1,207 @@
1
+ # Accordion
2
+
3
+ > Sections that open and close in place, each under its own heading. You can allow one open at a time or several, and draw them as plain rows split by thin lines or as a panel per section.
4
+
5
+ - **Best for:** Sections people scan by heading and open one or two of: frequently asked questions (FAQ), grouped settings, long specifications.
6
+ - **Not for:** Steps in a sequence: use Stepper. Equal views of the same thing: use Tabs.
7
+ - Molecule. Source: `src/components/molecules/accordion/Accordion.tsx`
8
+ - Rendered docs page: `/components/accordion`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'src/components/molecules/accordion/Accordion'
14
+ ```
15
+
16
+ Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `type` | `'single' \| 'multiple'` | | Required on the root. single: opening one section closes the other. multiple: any number can be open. |
23
+ | `collapsible` | `boolean` | `false` | single mode only. Lets the open section be closed again by its own trigger. You'll want it almost always. |
24
+ | `variant` | `'plain' \| 'boxed'` | `'plain'` | plain: thin separator lines, for a long run of questions. boxed: a panel per section, for a few sections that each stand alone. |
25
+ | `value (Root)` | `string \| string[]` | | The open section when the page controls it: a string in single mode, an array in multiple mode. |
26
+ | `defaultValue` | `string \| string[]` | | The section or sections open at first, when the accordion keeps track itself (uncontrolled). Set on the root. |
27
+ | `onValueChange` | `(value: string \| string[]) => void` | | Called when a section opens or closes (on the root). The argument has the same shape as type. |
28
+ | `value (Item)` | `string` | | Required. The section’s own value. |
29
+ | `disabled (Item)` | `boolean` | `false` | The section can't be opened or closed. |
30
+ | `level (Trigger)` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | The level of the heading the trigger sits in. Pick one below the heading the accordion appears under. |
31
+ | `forceMount (Content)` | `true` | | Keeps a closed panel in the page's code (the DOM, the browser's live model of the page), so the browser's find-in-page search can reach its content. It then skips the closing animation. |
32
+ | `className` | `string` | | Merged with each part’s classes. On AccordionContent it goes on the inner padded box, not the animated clipping box around it. |
33
+ | `ref` | `Ref<HTMLElement>` | | Passed through to the DOM element. Every part has one; the trigger’s is the button. |
34
+
35
+ ## Types and helpers
36
+
37
+ Exported beside the component, from the same file. Import a type with `import type { ... }`.
38
+
39
+ ```ts
40
+ export type AccordionVariant = 'plain' | 'boxed'
41
+ ```
42
+
43
+ ## Examples
44
+
45
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
46
+
47
+ ### Frequently asked questions (FAQ): plain style, one answer open at a time, and the open one can be closed again
48
+
49
+ ```tsx
50
+ <div className="w-full max-w-lg">
51
+ <Accordion type="single" collapsible>
52
+ <AccordionItem value="shipping">
53
+ <AccordionTrigger>How long does shipping take?</AccordionTrigger>
54
+ <AccordionContent>
55
+ Two to four working days inside the EU. Every parcel leaves the depot with a tracking
56
+ number, sent to you by email the same day.
57
+ </AccordionContent>
58
+ </AccordionItem>
59
+ <AccordionItem value="returns">
60
+ <AccordionTrigger>Can I return a replica?</AccordionTrigger>
61
+ <AccordionContent>
62
+ Within 30 days, unfired and in its original box. Open a return from your order page and
63
+ print the label.
64
+ </AccordionContent>
65
+ </AccordionItem>
66
+ <AccordionItem value="age">
67
+ <AccordionTrigger>Do I need to prove my age?</AccordionTrigger>
68
+ <AccordionContent>
69
+ Yes, once. We check an ID document at the first order of a replica; accessories need no
70
+ check.
71
+ </AccordionContent>
72
+ </AccordionItem>
73
+ <AccordionItem value="custom" disabled>
74
+ <AccordionTrigger>Custom builds (paused)</AccordionTrigger>
75
+ <AccordionContent>The workshop takes no new builds this season.</AccordionContent>
76
+ </AccordionItem>
77
+ </Accordion>
78
+ </div>
79
+ ```
80
+
81
+ ### Settings sections: boxed style, several open at once, and each section header is a level-2 heading (h2)
82
+
83
+ ```tsx
84
+ function SettingRow({ id, label, defaultChecked }: { id: string; label: string; defaultChecked?: boolean }) {
85
+ return (
86
+ <div className="flex items-center justify-between gap-4 py-2">
87
+ <label htmlFor={id} className="text-sm text-[var(--color-text-primary)]">
88
+ {label}
89
+ </label>
90
+ <Switch id={id} defaultChecked={defaultChecked} />
91
+ </div>
92
+ )
93
+ }
94
+
95
+ <div className="w-full max-w-lg">
96
+ <Accordion type="multiple" variant="boxed" defaultValue={['notifications']}>
97
+ <AccordionItem value="notifications">
98
+ <AccordionTrigger level={2}>Notifications</AccordionTrigger>
99
+ <AccordionContent>
100
+ <SettingRow id="acc-set-shipping" label="Shipping updates" defaultChecked />
101
+ <SettingRow id="acc-set-restock" label="Restock alerts" />
102
+ <SettingRow id="acc-set-news" label="Monthly newsletter" />
103
+ </AccordionContent>
104
+ </AccordionItem>
105
+ <AccordionItem value="privacy">
106
+ <AccordionTrigger level={2}>Privacy</AccordionTrigger>
107
+ <AccordionContent>
108
+ <SettingRow id="acc-set-profile" label="Show my squad profile" defaultChecked />
109
+ <SettingRow id="acc-set-history" label="Keep order history" defaultChecked />
110
+ </AccordionContent>
111
+ </AccordionItem>
112
+ <AccordionItem value="display">
113
+ <AccordionTrigger level={2}>Display</AccordionTrigger>
114
+ <AccordionContent>
115
+ <SettingRow id="acc-set-motion" label="Reduce motion" />
116
+ </AccordionContent>
117
+ </AccordionItem>
118
+ </Accordion>
119
+ </div>
120
+ ```
121
+
122
+ ### Controlled: the page keeps track of which sections are open
123
+
124
+ ```tsx
125
+ function ControlledExample() {
126
+ const [open, setOpen] = useState<string[]>(['battery'])
127
+ return (
128
+ <div className="w-full max-w-lg">
129
+ <p className="mb-3 font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-text-secondary)]">
130
+ Open: {open.length ? open.join(', ') : 'none'}
131
+ </p>
132
+ <Accordion type="multiple" value={open} onValueChange={setOpen}>
133
+ <AccordionItem value="battery">
134
+ <AccordionTrigger>Battery</AccordionTrigger>
135
+ <AccordionContent>11.1V LiPo, 1450mAh, with a deans connector.</AccordionContent>
136
+ </AccordionItem>
137
+ <AccordionItem value="hop-up">
138
+ <AccordionTrigger>Hop-up</AccordionTrigger>
139
+ <AccordionContent>Rotary adjustment, 60 degree bucking.</AccordionContent>
140
+ </AccordionItem>
141
+ </Accordion>
142
+ </div>
143
+ )
144
+ }
145
+
146
+ <ControlledExample />
147
+ ```
148
+
149
+ ## Keyboard
150
+
151
+ | Keys | Result |
152
+ |---|---|
153
+ | `Tab` | Moves between triggers, and into an open section’s content. |
154
+ | `Enter` `Space` | Opens or closes the focused section. |
155
+ | `↓` `↑` | Goes to the next or previous trigger, wrapping round at the ends and skipping disabled ones. |
156
+ | `Home` `End` | Goes to the first or last trigger. |
157
+
158
+ ## Do
159
+
160
+ - Use it for content people scan by its headings and open one or two of: an FAQ, a group of settings, long specifications.
161
+ - Write each trigger as the question or the section name, so the closed list reads like a table of contents.
162
+ - Set collapsible in single mode.
163
+ - Use boxed for a few sections that each hold controls, and plain for a long list of text answers.
164
+
165
+ ## Don't
166
+
167
+ - Don't hide the one thing everyone needs inside a closed section. Show it.
168
+ - Don't use it for steps in a sequence. That's Stepper.
169
+ - Don't use it to switch between equal views of the same thing. That's Tabs.
170
+ - Don't nest an accordion inside another accordion's content.
171
+
172
+ ## Common mistakes
173
+
174
+ Wrong:
175
+
176
+ ```tsx
177
+ <Accordion type="single">
178
+ ```
179
+
180
+ Right:
181
+
182
+ ```tsx
183
+ <Accordion type="single" collapsible>
184
+ ```
185
+
186
+ Without collapsible, a single-mode accordion can never be fully closed again once a section is open. That surprises people.
187
+
188
+ Wrong:
189
+
190
+ ```tsx
191
+ <AccordionTrigger>Shipping</AccordionTrigger> // under an h3 section title
192
+ ```
193
+
194
+ Right:
195
+
196
+ ```tsx
197
+ <AccordionTrigger level={4}>Shipping</AccordionTrigger>
198
+ ```
199
+
200
+ Every trigger (the clickable section header) sits inside a heading, h3 (a level-3 heading) by default. Set level so the triggers nest one level below the heading they appear under.
201
+
202
+ ## Anatomy
203
+
204
+ - **Accordion**: The root. It holds which sections are open and the variant, and every part below reads both.
205
+ - **AccordionItem**: One section. plain: a thin line under it. boxed: its own raised panel with the cut corner. The panel is drawn on a backing layer marked aria-hidden (hidden from screen readers), so the item itself is never clipped.
206
+ - **AccordionTrigger**: A button inside a heading of the level you choose. It uses the display typeface, has a chevron that turns half a revolution when the section is open, and is underlined on hover.
207
+ - **AccordionContent**: A region labelled by its trigger, in secondary text. It grows open over the base duration and shrinks shut faster (M7). When someone has asked their device for reduced motion, it snaps open and shut instead.
@@ -0,0 +1,90 @@
1
+ # AiLabel
2
+
3
+ > Marks content as made by AI, and on request says why. Given an explanation, the mark is a button that opens a short note right under it, with an optional list of sources. It is a disclosure (a button that shows and hides a region), not a tooltip or a popover: a tooltip can't hold links and never shows on a touch screen, and the system has no popover.
4
+
5
+ - **Best for:** Marking a block of text, a value or a field that an AI wrote, so the person knows to check it.
6
+ - **Not for:** A status or a category: use Badge. A field the AI filled: give the field aiValue, which marks it itself.
7
+ - Atom. Source: `src/components/atoms/ai-label/AiLabel.tsx`
8
+ - Rendered docs page: `/components/ai-label`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { AiLabel } from 'src/components/atoms/ai-label/AiLabel'
14
+ ```
15
+
16
+ Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `label` | `string` | `'Generated by AI'` | The mark's words, and the button's name. Say what the AI made: 'AI summary', 'AI draft'. |
23
+ | `explanation` | `ReactNode` | | Why this is AI content and what to do about it, in a sentence or two: what it was made from, and that it needs checking. Without it the mark is static text and nothing opens. |
24
+ | `sources` | `AiLabelSource[]` | | { label, href } for each thing the content was made from. Listed under the explanation; ignored without one. |
25
+ | `renderLink` | `(props: LinkRenderProps) => ReactNode` | `a plain anchor` | How each source link is drawn. Pass your router's link component for sources inside the app. |
26
+ | `className` | `string` | | Merged with the root's classes. When Tailwind utility classes clash, the later one wins. |
27
+ | `ref` | `Ref<HTMLDivElement>` | | Passed through to the root div. |
28
+ | `...rest` | `HTMLAttributes<HTMLDivElement>` | | Every native attribute, onto the root div. |
29
+
30
+ ## Types and helpers
31
+
32
+ Exported beside the component, from the same file. Import a type with `import type { ... }`.
33
+
34
+ ```ts
35
+ export type AiLabelSource = { label: string; href: string }
36
+ ```
37
+
38
+ ## Examples
39
+
40
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
41
+
42
+ ### Static mark
43
+
44
+ ```tsx
45
+ <AiLabel />
46
+ ```
47
+
48
+ ### With an explanation and sources
49
+
50
+ ```tsx
51
+ <div className="flex flex-col gap-2 max-w-md">
52
+ <AiLabel
53
+ label="AI summary"
54
+ explanation="Summarised from the three most recent reviews of this replica. It can miss details or get them wrong: verify before you rely on it."
55
+ sources={[
56
+ { label: 'Review by Ilse Varga', href: '#review-1' },
57
+ { label: 'Review by Tomas Brandt', href: '#review-2' },
58
+ ]}
59
+ />
60
+ <p className="text-sm text-[var(--color-text-primary)]">
61
+ Reviewers find the gearbox quiet and the hop-up easy to tune; two mention a loose stock.
62
+ </p>
63
+ </div>
64
+ ```
65
+
66
+ ## Keyboard
67
+
68
+ | Keys | Result |
69
+ |---|---|
70
+ | `Tab` | Moves focus to the mark when it has an explanation, then into any source links once open. |
71
+ | `Enter` `Space` | Opens or closes the explanation. Focus stays on the button. |
72
+
73
+ ## Do
74
+
75
+ - Put it right above or beside the AI content it marks, not in a page header far away.
76
+ - Say in the explanation what the AI used and that the person should check it, the way GitLab's 'Verify before use' does.
77
+ - Name the sources when there are any. Showing where it came from is how a reader decides whether to trust it.
78
+
79
+ ## Don't
80
+
81
+ - Don't put it inside a <label>. A button inside a label is read into the field's name; put it beside the label.
82
+ - Don't mark everything. A label on every line is a label on nothing.
83
+ - Don't rely on the colour. The words say AI; ai.text only helps you find them.
84
+
85
+ ## Anatomy
86
+
87
+ - **Mark**: A sparkle and the label in uppercase mono, in ai.text. Static text when there is no explanation.
88
+ - **Disclosure button**: With an explanation, the mark is a button with aria-expanded and a chevron that turns when open. Its name is the label.
89
+ - **Explanation**: A region right under the button, edged in ai.border, in text.secondary. Hidden until asked for, then next in the reading order.
90
+ - **Sources**: Optional. A list of links under the explanation, drawn through renderLink.