@sableui/react 0.0.0-stage → 0.1.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 (247) hide show
  1. package/AGENTS.md +157 -0
  2. package/CHANGELOG.md +55 -0
  3. package/CONTRIBUTING.md +221 -0
  4. package/DESIGN.md +548 -0
  5. package/LICENSE +21 -0
  6. package/README.md +194 -2
  7. package/THIRD_PARTY_NOTICES.md +44 -0
  8. package/bin/sable-lint.mjs +0 -0
  9. package/bin/sable.mjs +1223 -0
  10. package/dist/_contract-CYScOdf5.d.ts +150 -0
  11. package/dist/chunk-3IWZYBGH.js +398 -0
  12. package/dist/chunk-5Z5KLOAU.js +167 -0
  13. package/dist/chunk-7MG4SHYX.js +15 -0
  14. package/dist/chunk-ADLLDWLW.js +15 -0
  15. package/dist/chunk-EP55KSYA.js +14 -0
  16. package/dist/chunk-J23ELQQZ.js +9396 -0
  17. package/dist/chunk-JI7WJP2L.js +286 -0
  18. package/dist/chunk-K7RUHUGC.js +14 -0
  19. package/dist/chunk-KO3NAEBF.js +15 -0
  20. package/dist/chunk-L4IEIQQA.js +14 -0
  21. package/dist/chunk-L6Y46C62.js +270 -0
  22. package/dist/chunk-NEQNXI2G.js +25 -0
  23. package/dist/chunk-PAOZBZME.js +40 -0
  24. package/dist/chunk-RWP4XBSK.js +139 -0
  25. package/dist/chunk-SRQWQUTW.js +63 -0
  26. package/dist/chunk-YXBOYWBN.js +15 -0
  27. package/dist/foundations/index.d.ts +88 -0
  28. package/dist/foundations/index.js +792 -0
  29. package/dist/icon/index.d.ts +60 -0
  30. package/dist/icon/index.js +2 -0
  31. package/dist/icon-sets/brands.d.ts +44 -0
  32. package/dist/icon-sets/brands.js +147 -0
  33. package/dist/icon-sets/reicon.d.ts +8 -0
  34. package/dist/icon-sets/reicon.js +1 -0
  35. package/dist/index.d.ts +7013 -0
  36. package/dist/index.js +15462 -0
  37. package/dist/ios/chrome.css +83 -0
  38. package/dist/ios/frame.css +5 -0
  39. package/dist/ios/index.d.ts +70 -0
  40. package/dist/ios/index.js +313 -0
  41. package/dist/ios/preview.d.ts +64 -0
  42. package/dist/ios/preview.js +133 -0
  43. package/dist/ios/styles.css +77 -0
  44. package/dist/layout/index.d.ts +280 -0
  45. package/dist/layout/index.js +2 -0
  46. package/dist/meta-6dUzgC6M.d.ts +205 -0
  47. package/dist/patterns/index.d.ts +36 -0
  48. package/dist/patterns/index.js +934 -0
  49. package/dist/presets/index.d.ts +37 -0
  50. package/dist/presets/index.js +117 -0
  51. package/dist/registry-CyL28OzJ.d.ts +28 -0
  52. package/dist/styles.css +1893 -0
  53. package/dist/theme-provider-BWcizhAt.d.ts +105 -0
  54. package/dist/themes/atelier/atelier.css +213 -0
  55. package/dist/themes/atelier/index.d.ts +9 -0
  56. package/dist/themes/atelier/index.js +1 -0
  57. package/dist/themes/base/base.css +443 -0
  58. package/dist/themes/base/index.d.ts +9 -0
  59. package/dist/themes/base/index.js +1 -0
  60. package/dist/themes/ember/ember.css +270 -0
  61. package/dist/themes/ember/index.d.ts +9 -0
  62. package/dist/themes/ember/index.js +1 -0
  63. package/dist/themes/index.d.ts +10 -0
  64. package/dist/themes/index.js +8 -0
  65. package/dist/themes/meridian/index.d.ts +9 -0
  66. package/dist/themes/meridian/index.js +1 -0
  67. package/dist/themes/meridian/meridian.css +255 -0
  68. package/dist/themes/poppy/index.d.ts +6 -0
  69. package/dist/themes/poppy/index.js +1 -0
  70. package/dist/themes/poppy/poppy.css +174 -0
  71. package/dist/themes/prism/index.d.ts +6 -0
  72. package/dist/themes/prism/index.js +1 -0
  73. package/dist/themes/prism/prism.css +183 -0
  74. package/dist/themes/verdant/index.d.ts +9 -0
  75. package/dist/themes/verdant/index.js +1 -0
  76. package/dist/themes/verdant/verdant.css +210 -0
  77. package/dist/themes.css +30 -0
  78. package/llms/components/accordion.md +93 -0
  79. package/llms/components/app-header.md +70 -0
  80. package/llms/components/app-shell.md +117 -0
  81. package/llms/components/attachment.md +105 -0
  82. package/llms/components/avatar.md +141 -0
  83. package/llms/components/badge.md +117 -0
  84. package/llms/components/banner.md +119 -0
  85. package/llms/components/bottom-nav.md +80 -0
  86. package/llms/components/breadcrumbs.md +75 -0
  87. package/llms/components/browser.md +67 -0
  88. package/llms/components/bubble.md +119 -0
  89. package/llms/components/button.md +199 -0
  90. package/llms/components/calendar.md +75 -0
  91. package/llms/components/card.md +135 -0
  92. package/llms/components/carousel.md +87 -0
  93. package/llms/components/chat-composer.md +126 -0
  94. package/llms/components/checkbox-card.md +98 -0
  95. package/llms/components/checkbox.md +90 -0
  96. package/llms/components/code-block.md +81 -0
  97. package/llms/components/code.md +62 -0
  98. package/llms/components/collapsible.md +83 -0
  99. package/llms/components/color-field.md +100 -0
  100. package/llms/components/combobox.md +119 -0
  101. package/llms/components/command-menu.md +158 -0
  102. package/llms/components/context-card.md +73 -0
  103. package/llms/components/context-menu.md +79 -0
  104. package/llms/components/data-table.md +99 -0
  105. package/llms/components/date-picker.md +97 -0
  106. package/llms/components/description.md +96 -0
  107. package/llms/components/destructive-action-modal.md +76 -0
  108. package/llms/components/dialog.md +127 -0
  109. package/llms/components/dots-menu.md +64 -0
  110. package/llms/components/empty-state.md +113 -0
  111. package/llms/components/entity.md +67 -0
  112. package/llms/components/feedback.md +63 -0
  113. package/llms/components/fieldset.md +106 -0
  114. package/llms/components/file-dropzone.md +103 -0
  115. package/llms/components/form.md +111 -0
  116. package/llms/components/gliding-highlight.md +84 -0
  117. package/llms/components/inline-status.md +62 -0
  118. package/llms/components/input.md +127 -0
  119. package/llms/components/kbd.md +57 -0
  120. package/llms/components/label.md +73 -0
  121. package/llms/components/marker.md +99 -0
  122. package/llms/components/marquee.md +66 -0
  123. package/llms/components/menu.md +122 -0
  124. package/llms/components/message-scroller.md +110 -0
  125. package/llms/components/message.md +114 -0
  126. package/llms/components/meter.md +124 -0
  127. package/llms/components/middle-truncate.md +62 -0
  128. package/llms/components/note.md +81 -0
  129. package/llms/components/notification-badge.md +67 -0
  130. package/llms/components/number-field.md +70 -0
  131. package/llms/components/otp-field.md +101 -0
  132. package/llms/components/page-header.md +76 -0
  133. package/llms/components/page-loader.md +67 -0
  134. package/llms/components/pagination.md +92 -0
  135. package/llms/components/panel-group.md +120 -0
  136. package/llms/components/panel-section.md +122 -0
  137. package/llms/components/phone.md +70 -0
  138. package/llms/components/pill.md +73 -0
  139. package/llms/components/popover.md +79 -0
  140. package/llms/components/progress.md +98 -0
  141. package/llms/components/radio-card.md +90 -0
  142. package/llms/components/radio.md +86 -0
  143. package/llms/components/relative-time.md +60 -0
  144. package/llms/components/scroller.md +88 -0
  145. package/llms/components/scrub-field.md +97 -0
  146. package/llms/components/search-input.md +84 -0
  147. package/llms/components/select.md +106 -0
  148. package/llms/components/separator.md +70 -0
  149. package/llms/components/settings-menu.md +139 -0
  150. package/llms/components/sheet.md +97 -0
  151. package/llms/components/show-more.md +59 -0
  152. package/llms/components/side-nav.md +106 -0
  153. package/llms/components/sidebar.md +87 -0
  154. package/llms/components/skeleton.md +62 -0
  155. package/llms/components/slider.md +114 -0
  156. package/llms/components/spinner.md +87 -0
  157. package/llms/components/status-bar.md +91 -0
  158. package/llms/components/status-dot.md +70 -0
  159. package/llms/components/stepper.md +125 -0
  160. package/llms/components/streaming-text.md +86 -0
  161. package/llms/components/switch.md +87 -0
  162. package/llms/components/table.md +112 -0
  163. package/llms/components/tabs.md +84 -0
  164. package/llms/components/textarea.md +88 -0
  165. package/llms/components/theme-selector.md +95 -0
  166. package/llms/components/theme-switcher.md +71 -0
  167. package/llms/components/thinking-orb.md +91 -0
  168. package/llms/components/toast.md +130 -0
  169. package/llms/components/toggle.md +95 -0
  170. package/llms/components/toolbar.md +168 -0
  171. package/llms/components/tooltip.md +93 -0
  172. package/llms/components/tree.md +227 -0
  173. package/llms/components/video.md +66 -0
  174. package/llms/components/workspace.md +138 -0
  175. package/llms/foundations/accessibility.md +60 -0
  176. package/llms/foundations/motion.md +96 -0
  177. package/llms/foundations/scrolling.md +41 -0
  178. package/llms/foundations/shape.md +73 -0
  179. package/llms/foundations/spacing.md +82 -0
  180. package/llms/foundations/surfaces.md +69 -0
  181. package/llms/foundations/typography.md +120 -0
  182. package/llms/layout/aspect-box.md +37 -0
  183. package/llms/layout/bleed.md +31 -0
  184. package/llms/layout/center.md +37 -0
  185. package/llms/layout/columns.md +51 -0
  186. package/llms/layout/grid-overlay.md +40 -0
  187. package/llms/layout/grid.md +47 -0
  188. package/llms/layout/page.md +39 -0
  189. package/llms/layout/region.md +49 -0
  190. package/llms/layout/row.md +40 -0
  191. package/llms/layout/safe-area.md +37 -0
  192. package/llms/layout/spacer.md +39 -0
  193. package/llms/layout/split-pane.md +41 -0
  194. package/llms/layout/stack.md +39 -0
  195. package/llms/patterns/auth-page.md +68 -0
  196. package/llms/patterns/brand-landing-page.md +423 -0
  197. package/llms/patterns/chat-panel.md +210 -0
  198. package/llms/patterns/dashboard-page.md +170 -0
  199. package/llms/patterns/feed-page.md +103 -0
  200. package/llms/patterns/landing-page.md +122 -0
  201. package/llms/patterns/list-detail-page.md +100 -0
  202. package/llms/patterns/settings-page.md +121 -0
  203. package/llms/reference/token-intent.md +324 -0
  204. package/llms/reference/utilities.md +129 -0
  205. package/llms/tasks/adding-a-component.md +113 -0
  206. package/llms/tasks/auditing-an-app.md +216 -0
  207. package/llms/tasks/building-a-page.md +109 -0
  208. package/llms/tasks/building-a-tool.md +141 -0
  209. package/llms/tasks/building-an-ios-prototype.md +105 -0
  210. package/llms/tasks/building-with-sable.md +73 -0
  211. package/llms/tasks/choosing-a-component.md +109 -0
  212. package/llms/tasks/converting-an-app.md +139 -0
  213. package/llms/tasks/creating-a-preset.md +43 -0
  214. package/llms/tasks/creating-design-assets.md +96 -0
  215. package/llms/tasks/designing-a-surface.md +115 -0
  216. package/llms/tasks/developing-taste.md +157 -0
  217. package/llms/tasks/getting-started.md +63 -0
  218. package/llms/tasks/learning-from-builds.md +74 -0
  219. package/llms/tasks/reviewing-a-design.md +114 -0
  220. package/llms/tasks/reviewing-a-journey.md +84 -0
  221. package/llms/tasks/reviewing-motion.md +92 -0
  222. package/llms/tasks/using-presets.md +87 -0
  223. package/llms/taste/asset-plan.template.json +53 -0
  224. package/llms/taste/composition-follows-content.md +20 -0
  225. package/llms/taste/hierarchy-is-a-relationship.md +22 -0
  226. package/llms/taste/lessons.json +57 -0
  227. package/llms/taste/motion-needs-a-reason.md +21 -0
  228. package/llms/taste/surface-states-travel-together.md +20 -0
  229. package/llms-full.txt +14906 -0
  230. package/llms.txt +405 -0
  231. package/package.json +185 -3
  232. package/registry.json +18812 -0
  233. package/scripts/agent-setup.mjs +54 -0
  234. package/scripts/design-atlas.mjs +221 -0
  235. package/scripts/design-directions.mjs +123 -0
  236. package/scripts/design-memory.mjs +166 -0
  237. package/scripts/design-preferences.mjs +227 -0
  238. package/scripts/design-presets.mjs +48 -0
  239. package/scripts/grade.mjs +509 -0
  240. package/scripts/lint-rules.mjs +1065 -0
  241. package/scripts/review-evidence.mjs +120 -0
  242. package/scripts/skeleton.mjs +248 -0
  243. package/skills/entry/SKILL.md +34 -0
  244. package/skills/sable/SKILL.md +187 -0
  245. package/skills/sable/agents/openai.yaml +6 -0
  246. package/targets/ios.json +982 -0
  247. package/third-party/reicon-LICENSE.txt +20 -0
@@ -0,0 +1,93 @@
1
+ # Accordion
2
+
3
+ **Status:** stable
4
+
5
+ Stacked, collapsible disclosure sections over Base UI Accordion — roving focus, arrow-key navigation, ARIA region/heading/button wiring, and an animated height transition. Each item pairs a full-width trigger (with a rotating chevron) and a panel.
6
+
7
+ **Keywords:** accordion, disclosure, collapse, faq
8
+
9
+ ## Usage
10
+
11
+ Use Accordion to reveal a vertical set of related sections while keeping the surrounding page scannable.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use concise trigger text that describes the content revealed below it.
16
+ - Do: Keep section content focused so expanding one item does not feel like navigating to a new page.
17
+ - Don't: Do not hide required fields, primary actions, or critical errors inside collapsed sections.
18
+ - Don't: Do not nest an Accordion inside an Accordion. Two layers of roving focus fight each other, and the arrow keys become ambiguous — flatten the hierarchy or use a Tree.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | AccordionItem | yes | One expandable section in the group. |
25
+ | AccordionTrigger | yes | The button-like control that opens the section. |
26
+ | AccordionPanel | yes | The content revealed by the trigger. |
27
+
28
+ ## Subcomponents
29
+
30
+ | Name | Type | Description |
31
+ | --- | --- | --- |
32
+ | AccordionItem | Base UI Accordion.Item props + className (value, disabled, onOpenChange) | Pairs a header with its panel. Give each a stable `value` to control or set initial open state. |
33
+ | AccordionTrigger | Base UI Accordion.Trigger props + className + indicator?: ReactNode | The header button (Header + Trigger composed). Renders children plus a chevron that rotates via data-[panel-open] by default; pass `indicator` to replace the icon and its open-state behavior entirely (e.g. a plus that becomes an × for a FAQ-style toggle). |
34
+ | AccordionPanel | Base UI Accordion.Panel props + className (keepMounted, hiddenUntilFound) | The collapsible body. Animates height using the --accordion-panel-height CSS var; fades on data-[starting-style]/data-[ending-style]. |
35
+
36
+ ## Props
37
+
38
+ | Name | Type | Default | Description |
39
+ | --- | --- | --- | --- |
40
+ | Accordion (Root) | Base UI Accordion.Root props (value, defaultValue, multiple, disabled, onValueChange, orientation, loopFocus, keepMounted, hiddenUntilFound) | — | Groups all items and owns the open value(s). Uncontrolled via defaultValue, controlled via value. |
41
+ | defaultValue | Value[] | [] | Uncontrolled set of item values that start expanded (e.g. ['faq-1']). |
42
+ | value | Value[] | — | Controlled set of expanded item values; pair with onValueChange. |
43
+ | onValueChange | (value: Value[]) => void | — | Called with the new set of expanded item values when an item opens or closes. |
44
+ | multiple | boolean | false | Whether more than one item can be open at the same time. When false, opening one closes the others. |
45
+
46
+ ## Tokens used
47
+
48
+ `border`, `foreground`, `muted-foreground`, `ring`
49
+
50
+ ## Motion
51
+
52
+ - **arrival** · Root · _no motion_ — An accordion is page furniture that is present from first paint. Animating it in would stage content the reader is already scanning past.
53
+ - **response** · AccordionTrigger · `motion-interactive` — The whole header row is the hit target, and it looks like a heading rather than a control. The hover shift is what tells the reader it can be clicked before they click it.
54
+ - **transition** · AccordionPanel · `motion-disclosure` — Panel height is the only cue that content came from THIS header rather than appearing at the bottom of the page. Snapping it open moves everything below by a screenful with nothing connecting cause to effect.
55
+ - **activity** · Root · _no motion_ — Nothing here is indeterminate — the panel content is already resident. A panel that loads on expand should put its own loader inside the panel.
56
+
57
+ ## Accessibility
58
+
59
+ - Base UI renders the trigger inside an <h3> heading and wires aria-expanded / aria-controls to the panel region.
60
+ - Arrow keys move roving focus between triggers; Home/End jump to the first/last; loopFocus wraps by default.
61
+ - Each trigger toggles its panel with Enter/Space; the panel is announced as a labelled region.
62
+ - A disabled AccordionItem dims and is skipped by keyboard focus while neighbouring items stay interactive.
63
+ - The chevron rotation is driven by data-[panel-open] and the focus ring by focus-visible — both honor reduced-motion via the consumer's transitions.
64
+
65
+ ## Related
66
+
67
+ - Components: Collapsible, Tabs
68
+
69
+ ## Examples
70
+
71
+ ### Single (default)
72
+
73
+ ```tsx
74
+ import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "@sableui/react"
75
+
76
+ <Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger>Is it permanent?</AccordionTrigger><AccordionPanel>Yes — stored on Arweave.</AccordionPanel></AccordionItem><AccordionItem value="b"><AccordionTrigger>How much?</AccordionTrigger><AccordionPanel>A one-time mint fee.</AccordionPanel></AccordionItem></Accordion>
77
+ ```
78
+
79
+ ### Multiple open
80
+
81
+ ```tsx
82
+ import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "@sableui/react"
83
+
84
+ <Accordion multiple defaultValue={["a", "b"]}><AccordionItem value="a"><AccordionTrigger>First</AccordionTrigger><AccordionPanel>One.</AccordionPanel></AccordionItem><AccordionItem value="b"><AccordionTrigger>Second</AccordionTrigger><AccordionPanel>Two.</AccordionPanel></AccordionItem></Accordion>
85
+ ```
86
+
87
+ ### Custom indicator (plus-becomes-×)
88
+
89
+ ```tsx
90
+ import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Icon } from "@sableui/react"
91
+
92
+ <Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger indicator={<Icon name="plus" className="text-xs text-muted-foreground transition-transform [[data-panel-open]_&]:rotate-45" />}>Is it permanent?</AccordionTrigger><AccordionPanel>Yes — stored on Arweave.</AccordionPanel></AccordionItem></Accordion>
93
+ ```
@@ -0,0 +1,70 @@
1
+ # AppHeader
2
+
3
+ **Status:** stable
4
+
5
+ Mobile top bar (sticky, hidden at lg) with leading / title / actions slots on a 3-column grid that keeps the title optically centered. Grows into the top safe-area.
6
+
7
+ **Keywords:** app header, top bar, mobile header, chrome
8
+
9
+ ## Usage
10
+
11
+ Use AppHeader for compact application chrome at the top of a screen, usually with navigation, identity, and a small action set. Pass it to AppShell's `header` slot so screen content clears it on mobile (it is sticky and hides itself at lg).
12
+
13
+ ### Best practices
14
+
15
+ - Do: Keep the header actions narrow and predictable so the page title remains legible.
16
+ - Do: Use it once per app view as chrome, not as repeated content inside page sections.
17
+ - Don't: Do not overload it with filters, forms, or page-specific controls that belong in the content area.
18
+ - Don't: Do not render more than one per app, or put a second one inside a route. It is sticky chrome, so two of them stack and permanently eat the top of the viewport.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | leading | ReactNode | — | Leading slot — back button, create action, or menu trigger. |
25
+ | title | ReactNode | — | Center slot — brand wordmark or contextual page title. |
26
+ | actions | ReactNode | — | Trailing slot — wallet trigger, action buttons. |
27
+
28
+ ## Tokens used
29
+
30
+ `background`, `border`, `foreground`
31
+
32
+ ## Accessibility
33
+
34
+ - Renders a <header> landmark.
35
+ - Pair with a leading control that has an accessible label (e.g. a BackButton).
36
+
37
+ ## Related
38
+
39
+ - Components: AppShell, PageHeader, BottomNav
40
+
41
+ ## Examples
42
+
43
+ ### Contextual header
44
+
45
+ ```tsx
46
+ import { AppHeader, Button, Icon } from "@sableui/react"
47
+
48
+ <AppHeader leading={<Button size="icon" variant="ghost" aria-label="Back"><Icon name="chevron-left" /></Button>} title="Notifications" />
49
+ ```
50
+
51
+ ### Choosing between AppHeader and PageHeader
52
+
53
+ ```tsx
54
+ import { AppHeader, AppShell, PageHeader, Region } from "@sableui/react"
55
+
56
+ // AppHeader is APP chrome — it persists across routes and holds global
57
+ // affordances (brand, search, account). One per app.
58
+ <AppShell header={<AppHeader title="Studio" />}>
59
+ <Region max="content">Route content</Region>
60
+ </AppShell>
61
+
62
+ // PageHeader is CONTENT — it introduces the current route and scrolls away
63
+ // with it. One per page, INSIDE the region.
64
+ <Region max="content">
65
+ <PageHeader title="Billing" description="Plans, invoices, payment methods." />
66
+ </Region>
67
+
68
+ // ✗ Putting route-specific actions in AppHeader. They then persist onto routes
69
+ // where they mean nothing, and the header becomes a dumping ground.
70
+ ```
@@ -0,0 +1,117 @@
1
+ # AppShell
2
+
3
+ **Status:** stable
4
+
5
+ The application frame: slots for status banners, a mobile header, a desktop sidebar, main content, a mobile bottom nav, and floating overlays — plus a built-in skip link and #main-content target.
6
+
7
+ **Keywords:** app shell, layout, frame, sidebar, header, bottom nav, mobile, banners, main
8
+
9
+ ## Usage
10
+
11
+ Use AppShell as the outer product frame for authenticated or app-like experiences. It coordinates responsive chrome slots and guarantees a skip link plus main landmark; put route content inside the children and let AppHeader, Sidebar, BottomNav, and Banner own their own semantics.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use AppShell once around the route tree or major app area so the skip link and main landmark stay stable.
16
+ - Do: Pass app-wide outage or status messages through banners so they remain above the frame.
17
+ - Do: Use Region inside the main content to choose each route's reading width.
18
+ - Do: Wrap EVERY return of a route in its own Page or Region — including early returns for loading, empty and error states. AppShell's <main> deliberately carries no padding (it is a frame, and padding belongs to the region that chose the width), so a route that returns a bare spinner or error renders flush against the chrome. This is the most common conversion bug: the happy path gets a Page, the three early returns do not.
19
+ - Don't: Nest AppShell inside route cards or panels. It is page chrome, not local content.
20
+ - Don't: Put desktop-only navigation in the mobile header slot or mobile-only controls in the sidebar slot.
21
+
22
+ ### Anatomy
23
+
24
+ | Part | Required | Description |
25
+ | --- | --- | --- |
26
+ | Banners | no | Full-width app status messages rendered above the shell. |
27
+ | Header | no | Mobile top chrome, usually AppHeader. |
28
+ | Sidebar | no | Desktop navigation chrome, usually Sidebar. |
29
+ | Main | yes | The route content landmark with id main-content. |
30
+ | Bottom nav | no | Mobile fixed navigation with reserved bottom space. |
31
+
32
+ ## Props
33
+
34
+ | Name | Type | Default | Description |
35
+ | --- | --- | --- | --- |
36
+ | banners | ReactNode | — | Full-width status banners pinned in flow above the frame (offline / chain-down). |
37
+ | header | ReactNode | — | Mobile top header (e.g. AppHeader); expected to hide itself at lg. |
38
+ | sidebar | ReactNode | — | Desktop side navigation (e.g. Sidebar); expected to hide itself below lg. Leading column of the body row. |
39
+ | bottomNav | ReactNode | — | Mobile bottom navigation (e.g. BottomNav); fixed to the bottom edge. main reserves space for it. |
40
+ | overlays | ReactNode | — | Floating overlays — a message launcher, FAB, or modal mount points. |
41
+
42
+ ## Tokens used
43
+
44
+ `background`, `foreground`, `primary`, `primary-foreground`
45
+
46
+ ## Accessibility
47
+
48
+ - Renders a skip link to #main-content as the first focusable element.
49
+ - Wraps content in a <main id="main-content"> landmark.
50
+ - Slot components own their own ARIA (nav landmarks, labels).
51
+
52
+ ## Related
53
+
54
+ - Components: AppHeader, Sidebar, SideNav, BottomNav, Banner
55
+ - Patterns: DashboardPage, FeedPage, SettingsPage
56
+
57
+ ## Examples
58
+
59
+ ### Full frame
60
+
61
+ ```tsx
62
+ import { AppHeader, AppShell, Banner, BottomNav, BottomNavItem, Icon, PageHeader, Region, Sidebar, SidebarItem, SidebarNav } from "@sableui/react"
63
+ // <Link> = your router's link (react-router, next/link, @tanstack/router, …)
64
+
65
+ <AppShell
66
+ banners={<Banner tone="warning">Network congested — mints may be slow.</Banner>}
67
+ header={<AppHeader title="Studio" />}
68
+ sidebar={
69
+ <Sidebar>
70
+ <SidebarNav>
71
+ <SidebarItem icon={<Icon name="home" />} label="Home" active render={<Link to="/" />} />
72
+ <SidebarItem icon={<Icon name="bell" />} label="Notifications" render={<Link to="/notifications" />} />
73
+ <SidebarItem icon={<Icon name="gear" />} label="Settings" render={<Link to="/settings" />} />
74
+ </SidebarNav>
75
+ </Sidebar>
76
+ }
77
+ bottomNav={
78
+ <BottomNav>
79
+ <BottomNavItem icon={<Icon name="home" />} label="Home" active render={<Link to="/" />} />
80
+ <BottomNavItem icon={<Icon name="bell" />} label="Alerts" render={<Link to="/notifications" />} />
81
+ </BottomNav>
82
+ }
83
+ >
84
+ <Region max="feed">
85
+ <PageHeader title="Home" description="Latest posts from artists you follow." />
86
+ </Region>
87
+ </AppShell>
88
+ ```
89
+
90
+ ### Minimal (sidebar + content only)
91
+
92
+ ```tsx
93
+ import { AppShell, Icon, PageHeader, Region, Sidebar, SidebarItem, SidebarNav } from "@sableui/react"
94
+ // <Link> = your router's link (react-router, next/link, @tanstack/router, …)
95
+
96
+ <AppShell
97
+ sidebar={
98
+ <Sidebar>
99
+ <SidebarNav>
100
+ <SidebarItem icon={<Icon name="home" />} label="Dashboard" active render={<Link to="/" />} />
101
+ </SidebarNav>
102
+ </Sidebar>
103
+ }
104
+ >
105
+ <Region max="wide">
106
+ <PageHeader title="Dashboard" />
107
+ </Region>
108
+ </AppShell>
109
+ ```
110
+
111
+ ### Route content width
112
+
113
+ ```tsx
114
+ import { AppShell, Card, CardContent, CardHeader, CardTitle, Field, Icon, Input, PageHeader, Region, Sidebar, SidebarItem, SidebarNav } from "@sableui/react"
115
+
116
+ <AppShell sidebar={<Sidebar><SidebarNav><SidebarItem icon={<Icon name="home" />} label="Home" active /></SidebarNav></Sidebar>}><Region max="content"><PageHeader title="Settings" description="Manage account and workspace preferences." /><Card><CardHeader><CardTitle>Profile</CardTitle></CardHeader><CardContent><Field label="Display name"><Input defaultValue="Nova" /></Field></CardContent></Card></Region></AppShell>
117
+ ```
@@ -0,0 +1,105 @@
1
+ # Attachment
2
+
3
+ **Status:** stable
4
+
5
+ A file or image attached to a message: media or icon lead, name and metadata, upload and error states, an actions slot, and a full-card trigger that keeps those actions separately clickable.
6
+
7
+ **Keywords:** attachment, file, upload, image preview, chat file, document, drag and drop, file card
8
+
9
+ ## Usage
10
+
11
+ The card a file takes in a conversation — in the composer while it uploads, and in the transcript once it has. It renders all four states an attachment actually moves through (queued, uploading, failed, delivered) rather than only the delivered one, because the other three are the states that are hard to get right and would otherwise fall back on every caller.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use `variant="media"` for images and `variant="card"` for documents. A filename tells you nothing about a photo, and a thumbnail tells you nothing about a contract.
16
+ - Do: Pass `onOpen` rather than wrapping the card in your own button. It renders a stretched trigger underneath the actions, so the card is one tab stop, each action is its own, and clicking "remove" never opens the file.
17
+ - Do: Give `status="uploading"` a numeric `progress` when you have one and omit it when you do not — the bar goes indeterminate rather than lying about 0%.
18
+ - Do: Put the reason in `meta` when `status="error"`. The card re-colors but keeps its shape, so a failed upload stays recognisable as the file it was — the text is what says why.
19
+ - Don't: Do not nest interactive controls inside a card you have wrapped in a link or button yourself. That is invalid HTML and unreachable in the order it looks like; `onOpen` exists to avoid it.
20
+ - Don't: Do not use it as a generic file-list row. `Tree` is the file listing; this one is shaped for a conversation, where an attachment is a message payload.
21
+ - Don't: Do not rely on the error colour alone to signal failure. Colour is not a status for anyone who cannot see it — say what happened in `meta`.
22
+
23
+ ### Anatomy
24
+
25
+ | Part | Required | Description |
26
+ | --- | --- | --- |
27
+ | Media | yes | Thumbnail crop or file glyph. Fills the tile in `media`, a lead square in `card`. |
28
+ | Label | yes | Name and metadata. A row beside the media in `card`; a scrim strip over the picture in `media`, shown only when there is something to say. |
29
+ | Actions | no | Remove / retry / download controls, stacked above the stretched trigger so they stay separately clickable. |
30
+ | Progress | no | A hairline bar along the bottom edge while `status="uploading"`; determinate with `progress`, indeterminate without. |
31
+
32
+ ## Props
33
+
34
+ | Name | Type | Default | Description |
35
+ | --- | --- | --- | --- |
36
+ | name | string | — | File name. Truncated rather than wrapped — the stem truncates and a short extension stays visible — and used as the default label for the stretched trigger and the media tile's alt text. |
37
+ | meta | ReactNode | — | Size, type, upload progress, or the failure reason — the quiet second line. |
38
+ | thumbnail | string | — | Image preview URL. Present means the card shows a crop of the file instead of a glyph. |
39
+ | icon | string | "file" | Semantic icon name for a non-image file (resolved through IconProvider). Defaults to "image-stack" when a thumbnail is set. |
40
+ | variant | "card" \| "media" | "card" | `card` is a name-led horizontal row for documents; `media` is a square tile that is mostly picture. |
41
+ | status | "idle" \| "uploading" \| "error" | "idle" | Transfer state. `uploading` adds a progress bar; `error` re-colors the card without changing its shape. |
42
+ | progress | number | — | Percentage 0–100 while uploading. Omit for an indeterminate bar. |
43
+ | actions | ReactNode | — | Remove / retry / download controls. Stay clickable when `onOpen` makes the card a trigger. On a `media` tile they float on the top-end corner over the picture, so pass `variant="floating"` buttons there. |
44
+ | onOpen | () => void | — | Makes the whole card activate, via a stretched button rendered beneath the actions rather than by wrapping the card. |
45
+ | openLabel | string | `Open ${name}` | Accessible label for that stretched trigger. |
46
+ | ...div props | Omit<ComponentPropsWithRef<"div">, "onClick"> | — | Spread onto the card. Use `onOpen` rather than `onClick` — a click handler on a div is not a control. |
47
+
48
+ ## Variants
49
+
50
+ - **variant**: card, media
51
+ - **status**: idle, uploading, error
52
+
53
+ ## Tokens used
54
+
55
+ `card`, `border`, `border-strong`, `muted`, `muted-foreground`, `foreground`, `destructive`, `overlay`, `overlay-foreground`, `ring`
56
+
57
+ ## Accessibility
58
+
59
+ - The full-card trigger is a real stretched `<button>` with an accessible label, not a click handler on a div — so it is one tab stop, keyboard-activatable, and focus-ringed.
60
+ - Actions sit above that trigger in the stacking order, which keeps them individually focusable and stops a click on `remove` from also opening the file. Text is only made click-through when a trigger is present, so a filename stays selectable otherwise.
61
+ - A thumbnail is described as the attachment in `media` (where the picture is the only label) and left decorative in `card` (where the filename is right beside it), so AT never reads the same name twice.
62
+ - The upload bar carries an accessible name via Progress; the error state is carried by text in `meta` as well as colour.
63
+ - Label text on a media tile sits on `overlay` / `overlay-foreground` — a fixed pairing that stays legible over arbitrary imagery, where a theme surface token would not.
64
+
65
+ ## Related
66
+
67
+ - Components: Message, Bubble, MessageScroller, Progress
68
+
69
+ ## Examples
70
+
71
+ ### Document with a remove action
72
+
73
+ ```tsx
74
+ import { Attachment, Button, Icon } from "@sableui/react"
75
+
76
+ <Attachment name="proposal.pdf" meta="2.4 MB · PDF" onOpen={() => open(file)} actions={<Button variant="ghost" size="icon-xs" aria-label="Remove"><Icon name="xmark" /></Button>} />
77
+ ```
78
+
79
+ ### Image tile, uploading
80
+
81
+ ```tsx
82
+ import { Attachment } from "@sableui/react"
83
+
84
+ <Attachment variant="media" name="cover.png" thumbnail={previewUrl} status="uploading" progress={62} meta="Uploading… 62%" />
85
+ ```
86
+
87
+ ### Failed upload with a retry
88
+
89
+ ```tsx
90
+ import { Attachment, Button } from "@sableui/react"
91
+
92
+ <Attachment name="recording.mov" meta="Upload failed — file is over the 50 MB limit" status="error" icon="warning" actions={<Button variant="ghost" size="xs" onClick={retry}>Retry</Button>} />
93
+ ```
94
+
95
+ ### Attachments as a message payload
96
+
97
+ ```tsx
98
+ import { Attachment, Message, RelativeTime } from "@sableui/react"
99
+
100
+ <Message messageId="m7" align="end" footer={<RelativeTime date={sentAt} />}>
101
+ <div className="flex flex-wrap gap-2">
102
+ {files.map((f) => (<Attachment key={f.id} variant="media" name={f.name} thumbnail={f.url} onOpen={() => open(f)} />))}
103
+ </div>
104
+ </Message>
105
+ ```
@@ -0,0 +1,141 @@
1
+ # Avatar
2
+
3
+ **Status:** stable
4
+
5
+ Circular profile image over Base UI Avatar — automatic image-load tracking with an initials/glyph fallback. Ergonomic Avatar plus low-level AvatarRoot/AvatarImage/AvatarFallback parts, and AvatarGroup for an overlapping stack with a "+N" overflow count.
6
+
7
+ **Keywords:** avatar, profile, user, identity, initials, fallback, group, team, stack, overflow, collaborators, facepile
8
+
9
+ ## Usage
10
+
11
+ Use Avatar to represent a person, team, or account with a compact visual identity. Wrap several in AvatarGroup for collaborators, assignees, or attendees — it overlaps them, rings each in the surface colour, and collapses the rest into a "+N" count; use the low-level parts only when you need custom image/fallback layout.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Provide meaningful alt text for profile images, or an empty alt when the adjacent text already names the person.
16
+ - Do: Use initials or a simple glyph fallback so missing images still communicate identity.
17
+ - Do: Use consistent sizes within a group; reserve lg for profile headers or major identity moments.
18
+ - Do: Use AvatarGroup for a stack of people, and give it an aria-label with the real count ("12 collaborators"). Set `surface` (or `--avatar-group-ring`) to whatever the group sits on, so the separating ring matches a card or popover, not the page. When it shares a row with actions, set `fit` so it gives up avatars to the "+N" count before the row has to wrap.
19
+ - Don't: Hand-build a stack with negative margins and per-avatar ring classes, or fake the overflow as an Avatar with "+4" initials — screen readers hear "plus 4" with no context. Use AvatarGroup's `max`/`total`, or AvatarGroupCount.
20
+ - Don't: Use avatars as decoration without nearby identity context. They should help users recognize a person or account.
21
+ - Don't: Overload a dense table with large avatars; use sm or omit images when scanning data is more important.
22
+
23
+ ### Anatomy
24
+
25
+ | Part | Required | Description |
26
+ | --- | --- | --- |
27
+ | Root | yes | The circular frame that clips the image, owns size, and contains image plus fallback. |
28
+ | Image | no | The loaded profile image, cropped to fill the root. |
29
+ | Fallback | yes | Initials, a glyph, or short text shown while loading, on error, or when no image exists. |
30
+ | Group | no | AvatarGroup: the overlapping stack. Sets one size for every avatar and rings each in the surface colour. |
31
+ | Count | no | AvatarGroupCount: the "+N" avatar ending a group, read as "and N more". Rendered automatically by `max`/`total`. |
32
+
33
+ ## Subcomponents
34
+
35
+ | Name | Type | Description |
36
+ | --- | --- | --- |
37
+ | AvatarRoot | Base UI Avatar.Root props + size + className | Low-level circular frame for full composition control — clips the image and holds the `size` axis. Wraps AvatarImage and AvatarFallback. |
38
+ | AvatarImage | Base UI Avatar.Image props + className | The profile image. Fills and crops the frame; hidden by Base UI until it loads. |
39
+ | AvatarFallback | Base UI Avatar.Fallback props (delay) + className | Shown when no image is available — typically initials or a glyph. Accepts a delay prop. |
40
+ | AvatarGroup | { size?: "sm" \| "md" \| "lg"; surface?: "background" \| "card" \| "popover" \| "muted"; max?: number; fit?: boolean; total?: number; countLabel?: (count: number) => string; "aria-label"?: string; className?: string; children: ReactNode } | Overlapping stack (role=group) — name it with aria-label ("12 collaborators"). `size` applies to every avatar inside. `max` shows that many and collapses the rest into a "+N" count; `total` is the real head-count when you only pass some people. `fit` shows only as many avatars as fit the width the group is given (it fills the space left in a flex row) and folds the rest into the count, so a tight row reads "MO SR +5" instead of wrapping. The separating ring reads the inherited `--avatar-group-ring` (default: the page background); `surface` sets it to card, popover, or muted. |
41
+ | AvatarGroupCount | { count: number; label?: string; size?: "sm" \| "md" \| "lg"; className?: string } | The "+N" avatar. Shows "+4" and reads "and 4 more" (override with label). AvatarGroup renders it for you from max/total; use it directly only when composing the stack yourself. |
42
+
43
+ ## Props
44
+
45
+ | Name | Type | Default | Description |
46
+ | --- | --- | --- | --- |
47
+ | src | string | — | Image source. When omitted, still loading, or failed, the fallback shows. |
48
+ | alt | string | "" | Alt text for the image. Without an image it becomes the avatar's accessible name (role=img), so the initials are never read out as the name. |
49
+ | fallback | ReactNode | — | Initials or a glyph rendered when no image is displayed. |
50
+ | fallbackDelay | number | — | Delay before showing the fallback, in ms — avoids a flash on fast loads. |
51
+ | status | ReactNode | — | A presence indicator pinned to the bottom-end edge, usually <StatusDot tone="success" />. It sits outside the clipped circle and wears a ring in the surface colour (--avatar-group-ring, else the page background). Use this instead of absolutely positioning a dot over an Avatar. |
52
+ | size | "sm" \| "md" \| "lg" | "md" | Square footprint: 32px / 40px / 48px. |
53
+
54
+ ## Variants
55
+
56
+ - **size**: sm, md, lg
57
+
58
+ ## Tokens used
59
+
60
+ `muted`, `muted-foreground`, `background`, `card`, `popover`
61
+
62
+ ## Accessibility
63
+
64
+ - Base UI tracks the image loading status and swaps to the fallback on missing/loading/error — no manual onError wiring.
65
+ - Provide meaningful alt text via the alt prop; pass an empty alt for decorative avatars.
66
+ - The fallback (initials/glyph) gives a non-image identity cue for screen readers and slow connections.
67
+ - AvatarGroup renders role=group; give it an aria-label naming the whole set ("12 collaborators"), since the visible stack only shows some of them.
68
+ - The "+N" count is hidden from screen readers and replaced by "and N more" (AvatarGroupCount `label`, or AvatarGroup `countLabel`).
69
+
70
+ ## Related
71
+
72
+ - Components: Entity, StatusDot, Badge, Row
73
+ - Patterns: DashboardPage, ListDetailPage
74
+
75
+ ## Examples
76
+
77
+ ### With image
78
+
79
+ ```tsx
80
+ import { Avatar } from "@sableui/react"
81
+
82
+ <Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" />
83
+ ```
84
+
85
+ ### Fallback only
86
+
87
+ ```tsx
88
+ import { Avatar } from "@sableui/react"
89
+
90
+ <Avatar fallback="AM" alt="Alex Morgan" />
91
+ ```
92
+
93
+ ### Sizes
94
+
95
+ ```tsx
96
+ import { Avatar } from "@sableui/react"
97
+
98
+ <Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="sm" />
99
+ <Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="md" />
100
+ <Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="lg" />
101
+ ```
102
+
103
+ ### Low-level parts
104
+
105
+ ```tsx
106
+ import { AvatarFallback, AvatarImage, AvatarRoot } from "@sableui/react"
107
+
108
+ <AvatarRoot size="lg"><AvatarImage src="/pfp.png" alt="Mara Okafor" /><AvatarFallback>MO</AvatarFallback></AvatarRoot>
109
+ ```
110
+
111
+ ### Group with overflow
112
+
113
+ ```tsx
114
+ import { Avatar, AvatarGroup } from "@sableui/react"
115
+
116
+ <AvatarGroup aria-label="6 collaborators" max={3} size="sm" surface="card"><Avatar src="/ana.png" alt="Ana Chen" fallback="AC" /><Avatar src="/mika.png" alt="Mika Lee" fallback="ML" /><Avatar fallback="JL" alt="Jo Lund" /><Avatar fallback="AK" alt="Ari Kim" /><Avatar fallback="SR" alt="Sam Reyes" /><Avatar fallback="MO" alt="Mo Ortiz" /></AvatarGroup>
117
+ ```
118
+
119
+ ### Group of a larger set
120
+
121
+ ```tsx
122
+ import { Avatar, AvatarGroup } from "@sableui/react"
123
+
124
+ <AvatarGroup aria-label="12 attendees" total={12} countLabel={(n) => `and ${n} more attendees`}><Avatar fallback="MO" alt="Mo Ortiz" /><Avatar fallback="SR" alt="Sam Reyes" /><Avatar fallback="JL" alt="Jo Lund" /></AvatarGroup>
125
+ ```
126
+
127
+ ### Composed count
128
+
129
+ ```tsx
130
+ import { Avatar, AvatarGroup, AvatarGroupCount } from "@sableui/react"
131
+
132
+ <AvatarGroup aria-label="7 reviewers"><Avatar fallback="AC" alt="Ana Chen" /><Avatar fallback="ML" alt="Mika Lee" /><AvatarGroupCount count={5} label="and 5 more reviewers" /></AvatarGroup>
133
+ ```
134
+
135
+ ### With status
136
+
137
+ ```tsx
138
+ import { Avatar, Entity, StatusDot } from "@sableui/react"
139
+
140
+ <Entity leading={<Avatar src="/pfp.png" alt="Nova Reed" fallback="NR" status={<StatusDot tone="success" />} />} title="Nova Reed" subtitle="Online" />
141
+ ```
@@ -0,0 +1,117 @@
1
+ # Badge
2
+
3
+ **Status:** stable
4
+
5
+ Compact static label, set on two axes: appearance (soft, outline, solid, overlay on media, plain text) and tone (neutral or a semantic tone). An optional leading icon, or a status dot (with pulse) for state.
6
+
7
+ **Keywords:** badge, tag, label, status, metadata, chip, pill, tone, category
8
+
9
+ ## Usage
10
+
11
+ Use Badge for any short static label: metadata, categories, tone-coded labels, a label on artwork (appearance="overlay"), or a status line (appearance="plain" with dot). Use Pill for a chip someone clicks, NotificationBadge for an unread count on an icon, and Banner or Toast for messages that need attention.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Keep badge text short: one to three words or a compact count.
16
+ - Do: Use tone variants when the badge carries meaning, such as success, warning, destructive, edition, or collectible.
17
+ - Do: Pair icons with text; the icon reinforces meaning but should not be the only cue.
18
+ - Do: Choose appearance and tone separately: appearance is how loud it is (soft by default, solid for emphasis, overlay on images, plain for a status line); tone is what it means.
19
+ - Don't: Use a Badge as a button. If it triggers an action, use Button, Toggle, or a menu item.
20
+ - Don't: Stack many loud tone badges in one row; too many semantic colors compete with the actual content.
21
+
22
+ ### Anatomy
23
+
24
+ | Part | Required | Description |
25
+ | --- | --- | --- |
26
+ | Container | yes | The compact pill surface carrying the tone or neutral treatment. |
27
+ | Label | yes | Short visible text that names the category, state, role, or metadata value. |
28
+ | Icon | no | Optional leading Icon used to reinforce the label. |
29
+
30
+ ## Props
31
+
32
+ | Name | Type | Default | Description |
33
+ | --- | --- | --- | --- |
34
+ | appearance | "soft" \| "outline" \| "solid" \| "overlay" \| "plain" | "soft" | How it is drawn. soft: a tinted fill (neutral is the secondary surface). outline: a hairline. solid: the tone at full strength (neutral is the inverted chip). overlay: legible on images — a dark scrim, or the solid tone. plain: no fill or padding, a status line. |
35
+ | tone | "neutral" \| "standard" \| "collectible" \| "edition" \| "info" \| "success" \| "warning" \| "destructive" | "neutral" | What it means. Every appearance takes every tone. |
36
+ | size | "sm" \| "md" | "md" | "sm" uses tighter padding + text-label-sm; "md" is the default look. |
37
+ | icon | ReactNode | — | Optional leading glyph (e.g. <Icon name="lock" />), rendered before the children with the base gap. Ignored when dot is set. |
38
+ | dot | boolean | false | A leading status dot in the badge's tone. With appearance="plain" it is a status line (● Ready). |
39
+ | pulse | boolean | false | Pulse the dot, for a state in progress. Needs dot. |
40
+
41
+ ## Variants
42
+
43
+ - **appearance**: soft, outline, solid, overlay, plain
44
+ - **tone**: neutral, standard, collectible, edition, info, success, warning, destructive
45
+ - **size**: sm, md
46
+
47
+ ## Tokens used
48
+
49
+ `secondary`, `secondary-foreground`, `border`, `foreground`, `background`, `muted`, `muted-foreground`, `tone-standard`, `tone-collectible`, `tone-edition`, `tone-info`, `success`, `tone-warning`, `destructive`, `overlay`, `overlay-foreground`, `tone-standard-foreground`, `tone-collectible-foreground`, `tone-edition-foreground`, `tone-info-foreground`, `success-foreground`, `tone-warning-foreground`, `destructive-foreground`
50
+
51
+ ## Accessibility
52
+
53
+ - Badges are static text by default; do not rely on color alone for meaning.
54
+ - Use clear label text for semantic tones so assistive tech receives the same status information sighted users see.
55
+
56
+ ## Related
57
+
58
+ - Components: Pill, StatusDot, NotificationBadge, Banner, Toast
59
+ - Patterns: DashboardPage, ListDetailPage
60
+
61
+ ## Examples
62
+
63
+ ### Tones
64
+
65
+ ```tsx
66
+ import { Badge, Row } from "@sableui/react"
67
+
68
+ <Row gap={1}><Badge tone="edition">Edition</Badge><Badge tone="collectible">Collectible</Badge><Badge tone="standard">Standard</Badge></Row>
69
+ ```
70
+
71
+ ### Solid
72
+
73
+ ```tsx
74
+ import { Badge } from "@sableui/react"
75
+
76
+ <Badge appearance="solid" size="sm">Admin</Badge>
77
+ ```
78
+
79
+ ### Success
80
+
81
+ ```tsx
82
+ import { Badge } from "@sableui/react"
83
+
84
+ <Badge tone="success">Resolved</Badge>
85
+ ```
86
+
87
+ ### With icon
88
+
89
+ ```tsx
90
+ import { Badge, Icon } from "@sableui/react"
91
+
92
+ <Badge size="sm" icon={<Icon name="lock" />}>Locked</Badge>
93
+ ```
94
+
95
+ ### Small
96
+
97
+ ```tsx
98
+ import { Badge } from "@sableui/react"
99
+
100
+ <Badge size="sm" tone="info">New</Badge>
101
+ ```
102
+
103
+ ### Metadata row
104
+
105
+ ```tsx
106
+ import { Badge, Icon, Row } from "@sableui/react"
107
+
108
+ <Row gap={1}><Badge>12 editions</Badge><Badge appearance="outline">Open edition</Badge><Badge tone="info" icon={<Icon name="info" />}>Curated</Badge></Row>
109
+ ```
110
+
111
+ ### Semantic state
112
+
113
+ ```tsx
114
+ import { Badge, Row } from "@sableui/react"
115
+
116
+ <Row gap={1}><Badge tone="success">Synced</Badge><Badge tone="warning">Review</Badge><Badge tone="destructive">Blocked</Badge></Row>
117
+ ```