@sableui/react 0.0.0-stage → 0.1.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 (247) hide show
  1. package/AGENTS.md +157 -0
  2. package/CHANGELOG.md +50 -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,119 @@
1
+ # Banner
2
+
3
+ **Status:** stable
4
+
5
+ Full-width, page-level announcement bar. A tone (neutral, info, success, warning, edition, destructive) × an appearance (soft tint or loud solid fill), and an optional leading scope label. Centers its message, or — with a trailing action — switches to a leading-text + trailing-button layout. Optionally sticky and dismissible.
6
+
7
+ **Keywords:** banner, announcement, alert, status, message, warning, success, offline, sticky
8
+
9
+ ## Usage
10
+
11
+ Use Banner for page-level or app-level messages that affect the current experience: outages, maintenance, success confirmations, policy notices, or major warnings. Prefer Toast for transient feedback from a specific action, and prefer InlineStatus for local row or form state.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use soft appearance for contextual notices and solid appearance for urgent, blocking, or system-wide states.
16
+ - Do: Set live='polite' or live='assertive' only when the banner appears or changes dynamically.
17
+ - Do: Include one clear action when the user can resolve or inspect the state.
18
+ - Don't: Use multiple stacked solid banners unless the page is genuinely blocked by multiple independent states.
19
+ - Don't: Use Banner for field-level validation, row status, or small local hints; use Field, InlineStatus, or Badge instead.
20
+ - Don't: Use Banner inside a dialog, a form, or a section to qualify the content around it — that is Note. Banner is a full-width announcement strip for page- or app-level state, so in a modal it reads as a slab heavier than the fields it warns about, and its soft appearance has no border and no tone-colored icon: the severity survives only as a faint wash.
21
+
22
+ ### Anatomy
23
+
24
+ | Part | Required | Description |
25
+ | --- | --- | --- |
26
+ | Message | yes | The short announcement text. It should explain the state in plain language. |
27
+ | Action | no | Optional trailing action that changes the layout from centered copy to text plus action. |
28
+ | Dismiss | no | Optional close control for notices the user can safely hide. |
29
+
30
+ ## Props
31
+
32
+ | Name | Type | Default | Description |
33
+ | --- | --- | --- | --- |
34
+ | tone | "neutral" \| "info" \| "success" \| "warning" \| "edition" \| "destructive" | "neutral" | Neutral surface or a tone that encodes the announcement's intent (info / success / warning / edition / destructive). |
35
+ | appearance | "soft" \| "solid" | "soft" | "soft" is a quiet tinted fill; "solid" is the full tone with its paired ink — for loud, can't-miss states (offline, chain-down, RPC errors). |
36
+ | label | ReactNode | — | A short scope label ("Preview", "Staging", a project name) in a leading chip — a Badge that reads on soft and solid fills. |
37
+ | action | ReactNode | — | A trailing action (e.g. a Button). Switches the bar to leading text + trailing action instead of centered text. |
38
+ | sticky | boolean | false | Stick the banner to the top of its scroll container (sticky top-0, z-30). |
39
+ | live | "off" \| "polite" \| "assertive" | "off" | ARIA live-region politeness for banners shown/updated at runtime (offline, RPC-down, "N new posts"). "polite" → role=status; "assertive" → role=alert. "off" keeps a static labelled region. |
40
+ | dismissible | boolean | false | Render a trailing close button that hides the banner when clicked. |
41
+ | onDismiss | () => void | — | Called after the banner is dismissed. |
42
+
43
+ ## Variants
44
+
45
+ - **tone**: neutral, info, success, warning, edition, destructive
46
+ - **appearance**: soft, solid
47
+
48
+ ## Tokens used
49
+
50
+ `secondary`, `secondary-foreground`, `foreground`, `background`, `tone-info`, `tone-info-foreground`, `success`, `success-foreground`, `tone-warning`, `tone-warning-foreground`, `tone-edition`, `tone-edition-foreground`, `destructive`, `destructive-foreground`
51
+
52
+ ## Accessibility
53
+
54
+ - Static banners render as a labelled region (role=region); set `live` to announce dynamic status bars (role=status / role=alert) when they appear or change.
55
+ - The dismiss control is Button (on-color-ghost, icon-xs) with an aria-label; it takes the bar's own ink, so its glyph and focus ring stay legible on soft and solid fills alike.
56
+ - Solid fills pair each tone with its theme-adaptive ink token, so text stays legible in light and dark.
57
+
58
+ ## Related
59
+
60
+ - Components: Note, AppShell, Toast, InlineStatus, EmptyState, Button
61
+ - Patterns: DashboardPage
62
+
63
+ ## Examples
64
+
65
+ ### Tones
66
+
67
+ ```tsx
68
+ import { Banner } from "@sableui/react"
69
+
70
+ <Banner tone="info">Maintenance window tonight at 02:00 UTC.</Banner>
71
+ ```
72
+
73
+ ### Solid (loud)
74
+
75
+ ```tsx
76
+ import { Banner } from "@sableui/react"
77
+
78
+ <Banner tone="destructive" appearance="solid" live="assertive">Blockchain unavailable. Collect and buy are temporarily disabled.</Banner>
79
+ ```
80
+
81
+ ### Success
82
+
83
+ ```tsx
84
+ import { Banner } from "@sableui/react"
85
+
86
+ <Banner tone="success" live="polite">Credits authorized for Studio.</Banner>
87
+ ```
88
+
89
+ ### Action
90
+
91
+ ```tsx
92
+ import { Banner, Button } from "@sableui/react"
93
+
94
+ <Banner sticky action={<Button variant="outline" size="sm">Refresh</Button>}>12 new posts</Banner>
95
+ ```
96
+
97
+ ### Dismissible
98
+
99
+ ```tsx
100
+ import { Banner } from "@sableui/react"
101
+
102
+ <Banner tone="warning" dismissible>Your session expires soon.</Banner>
103
+ ```
104
+
105
+ ### Recoverable warning
106
+
107
+ ```tsx
108
+ import { Banner, Button } from "@sableui/react"
109
+
110
+ <Banner tone="warning" action={<Button variant="outline" size="sm">Reconnect</Button>}>Wallet connection paused.</Banner>
111
+ ```
112
+
113
+ ### App-shell outage
114
+
115
+ ```tsx
116
+ import { AppShell, Banner, EmptyState, Region } from "@sableui/react"
117
+
118
+ <AppShell banners={<Banner tone="destructive" appearance="solid" live="assertive">Uploads are temporarily unavailable.</Banner>}><Region max="feed"><EmptyState title="Uploads paused" description="Existing posts are still visible while the service recovers." /></Region></AppShell>
119
+ ```
@@ -0,0 +1,80 @@
1
+ # BottomNav
2
+
3
+ **Status:** stable
4
+
5
+ Mobile bottom tab bar (fixed, hidden at lg), with home-indicator safe-area padding and evenly distributed BottomNavItem tabs. Items are router-agnostic via `render`.
6
+
7
+ **Keywords:** bottom nav, mobile nav, tabs, app navigation
8
+
9
+ ## Usage
10
+
11
+ Use BottomNav for a small set of top-level mobile destinations that should remain reachable with one thumb. Mount it through `<AppShell bottomNav={…}>` so the content area reserves matching bottom space; it hides at lg, where the Sidebar takes over.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Limit the nav to the primary destinations users switch between often.
16
+ - Do: Give every item a clear label (it is the accessible name), and turn on showLabel when the icons alone are not obvious.
17
+ - Don't: Do not use BottomNav for desktop navigation, dense menus, or page-local actions.
18
+ - Don't: Do not omit the safe-area padding by overriding its padding-bottom. On a device with a home indicator the last row of targets ends up under the gesture area, where taps dismiss the app instead.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | BottomNav | yes | The fixed or anchored navigation container. |
25
+ | BottomNavItem | yes | One destination with label, icon, and active state. |
26
+
27
+ ## Subcomponents
28
+
29
+ | Name | Type | Description |
30
+ | --- | --- | --- |
31
+ | BottomNavItem | { label: string; icon?: ReactNode; active?: boolean; badge?: ReactNode; showLabel?: boolean; render?: useRender.RenderProp } + button props | A single tab, placed as a child of BottomNav. `label` is the accessible name (aria-label) and optional visible label; `icon` is the glyph node (consumer swaps fill weight for the active state); `active` marks the current route (active ink + aria-current); `badge` overlays a corner dot/count on the icon; `showLabel` shows the label text beneath the icon — defaults to false (icon-only); turn it on when the icons alone are not self-evident; `render` renders as another element, e.g. render={<Link to="/x" />}. |
32
+
33
+ ## Tokens used
34
+
35
+ `background`, `border`, `muted-foreground`, `foreground`, `ring`
36
+
37
+ ## Accessibility
38
+
39
+ - BottomNav renders a <nav> landmark.
40
+ - Each tab is a 44px minimum touch target with an aria-label.
41
+ - Active tab carries aria-current="page".
42
+
43
+ ## Related
44
+
45
+ - Components: Sidebar, SideNav, AppHeader
46
+
47
+ ## Examples
48
+
49
+ ### Tab bar
50
+
51
+ ```tsx
52
+ import { BottomNav, BottomNavItem, Icon, NotificationBadge } from "@sableui/react"
53
+ // <Link> = your router's link (react-router, next/link, @tanstack/router, …)
54
+
55
+ <BottomNav><BottomNavItem icon={<Icon name="home" />} label="Home" active render={<Link to="/" />} /><BottomNavItem icon={<Icon name="bell" />} label="Notifications" badge={<NotificationBadge dot />} render={<Link to="/notifications" />} /></BottomNav>
56
+ ```
57
+
58
+ ### When NOT to use it
59
+
60
+ ```tsx
61
+ import { AppShell, BottomNav, BottomNavItem, Icon, Region } from "@sableui/react"
62
+
63
+ // BottomNav is for 3–5 PEER destinations on a small viewport — the mobile
64
+ // counterpart to SideNav, not an action bar.
65
+
66
+ // ✗ Actions. A bottom bar of verbs (Save / Share / Delete) is a toolbar; it
67
+ // competes with the OS gesture area and has no "current" state to show.
68
+ // ✗ More than five items. Past that, labels truncate and targets shrink below
69
+ // the 44px floor — use a "More" destination that opens a Sheet.
70
+ // ✗ Nested navigation. BottomNav switches top-level sections only; drilling in
71
+ // happens inside the section, with Breadcrumbs or a back affordance.
72
+
73
+ <AppShell bottomNav={<BottomNav>
74
+ <BottomNavItem label="Home" icon={<Icon name="home" />} active />
75
+ <BottomNavItem label="Search" icon={<Icon name="search" />} />
76
+ <BottomNavItem label="Account" icon={<Icon name="user" />} />
77
+ </BottomNav>}>
78
+ <Region max="feed">Route content</Region>
79
+ </AppShell>
80
+ ```
@@ -0,0 +1,75 @@
1
+ # Breadcrumbs
2
+
3
+ **Status:** stable
4
+
5
+ Navigation trail showing the path to the current page. A labelled <nav> wraps an inline <ol> of crumbs — interactive ancestor links, separators, and a non-interactive current page. No Base UI.
6
+
7
+ **Keywords:** breadcrumbs, hierarchy, path, navigation
8
+
9
+ ## Usage
10
+
11
+ Use Breadcrumbs to show the user's location inside a hierarchy and provide lightweight parent navigation.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use breadcrumbs when users can arrive deep in a nested structure.
16
+ - Do: Keep the current page as text so it reads as location, not another link.
17
+ - Don't: Do not use breadcrumbs as a substitute for primary navigation or step progress.
18
+ - Don't: Do not make the last crumb a link. It points at the page you are already on, and screen readers announce it as a destination; render it as the current page with aria-current.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | BreadcrumbItem | yes | A single step in the path. |
25
+ | BreadcrumbLink | no | A linked ancestor destination. |
26
+ | BreadcrumbPage | yes | The current non-linked page. |
27
+ | BreadcrumbSeparator | no | The visual separator between steps. |
28
+
29
+ ## Subcomponents
30
+
31
+ | Name | Type | Description |
32
+ | --- | --- | --- |
33
+ | BreadcrumbItem | ComponentPropsWithRef<'li'> | A single crumb slot (<li>) holding a link or the current page. |
34
+ | BreadcrumbLink | ComponentPropsWithRef<'a'> | An interactive ancestor crumb (<a>). Muted by default, brightens to foreground on hover/focus. |
35
+ | BreadcrumbPage | ComponentPropsWithRef<'span'> | The current-page crumb (<span aria-current="page">). Non-interactive, full-contrast. |
36
+ | BreadcrumbSeparator | ComponentPropsWithRef<'li'> + { children? } | Decorative divider (<li aria-hidden>) between crumbs. Defaults to a chevron-right icon; pass children to swap the glyph. |
37
+
38
+ ## Props
39
+
40
+ | Name | Type | Default | Description |
41
+ | --- | --- | --- | --- |
42
+ | Breadcrumbs | ComponentPropsWithRef<'nav'> | — | The trail root. Renders <nav aria-label="Breadcrumb"> around a flex <ol>. Pass the crumb parts as children. |
43
+
44
+ ## Tokens used
45
+
46
+ `muted-foreground`, `foreground`
47
+
48
+ ## Accessibility
49
+
50
+ - The <nav> carries aria-label="Breadcrumb" so assistive tech announces the landmark.
51
+ - Crumbs sit in an ordered list (<ol>) to convey hierarchy and position.
52
+ - The current page uses aria-current="page" and is rendered as plain text, not a link.
53
+ - Separators are aria-hidden so the chevron glyph is not announced.
54
+
55
+ ## Related
56
+
57
+ - Components: SideNav, PageHeader
58
+
59
+ ## Examples
60
+
61
+ ### Three-level trail
62
+
63
+ ```tsx
64
+ import { BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, Breadcrumbs, BreadcrumbSeparator } from "@sableui/react"
65
+
66
+ <Breadcrumbs><BreadcrumbItem><BreadcrumbLink href="/">Home</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator /><BreadcrumbItem><BreadcrumbLink href="/library">Library</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator /><BreadcrumbItem><BreadcrumbPage>Editions</BreadcrumbPage></BreadcrumbItem></Breadcrumbs>
67
+ ```
68
+
69
+ ### Custom separator
70
+
71
+ ```tsx
72
+ import { BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, Breadcrumbs, BreadcrumbSeparator } from "@sableui/react"
73
+
74
+ <Breadcrumbs><BreadcrumbItem><BreadcrumbLink href="/">Home</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator>/</BreadcrumbSeparator><BreadcrumbItem><BreadcrumbPage>Settings</BreadcrumbPage></BreadcrumbItem></Breadcrumbs>
75
+ ```
@@ -0,0 +1,67 @@
1
+ # Browser
2
+
3
+ **Status:** stable
4
+
5
+ A generic, current browser window: one compact toolbar (window controls, back/forward/reload, a centered address field) around page content.
6
+
7
+ **Keywords:** browser, mockup, preview, website
8
+
9
+ ## Usage
10
+
11
+ Use Browser to frame a website or product surface preview when the browser chrome is part of the story.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use it for documentation, product examples, and visual previews where the page context matters.
16
+ - Do: Keep the framed content simple enough to remain legible at the rendered size.
17
+ - Don't: Do not use Browser for real navigation, real forms, or arbitrary card decoration.
18
+ - Don't: Do not put a real, working URL in the address pill. It reads as a link, is not one, and invites a click that does nothing.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | url | string | — | Address shown in the centered address pill. Omit for a blank pill. |
25
+ | children | ReactNode | — | Page content rendered inside the content region. |
26
+
27
+ ## Tokens used
28
+
29
+ `card`, `background`, `border`, `muted`, `muted-foreground`
30
+
31
+ ## Accessibility
32
+
33
+ - Purely presentational chrome; the framed content carries its own semantics.
34
+ - The toolbar is drawn, not interactive, and hidden from assistive technology; the address is decorative text.
35
+
36
+ ## Related
37
+
38
+ - Components: Phone, Card
39
+
40
+ ## Examples
41
+
42
+ ### Framing a page
43
+
44
+ ```tsx
45
+ import { Browser, PageHeader } from "@sableui/react"
46
+
47
+ <Browser url="studio.example/explore"><div className="p-8"><PageHeader title="Explore" description="Trending posts from every chain." /></div></Browser>
48
+ ```
49
+
50
+ ### Mockup, not chrome
51
+
52
+ ```tsx
53
+ import { Browser, Region } from "@sableui/react"
54
+
55
+ // Browser is DECORATIVE framing for a screenshot or a demo — it belongs on a
56
+ // marketing page, never around your actual app.
57
+
58
+ <Region max="wide">
59
+ <Browser url="studio.example">
60
+ <img src="/screenshots/editor.png" alt="The editor" />
61
+ </Browser>
62
+ </Region>
63
+
64
+ // ✗ Wrapping live UI in it. The frame implies "this is a picture of software";
65
+ // putting real controls inside makes users hunt for a browser that isn't there.
66
+ // ✗ Using it to fake a URL bar for credibility. Show the real product.
67
+ ```
@@ -0,0 +1,119 @@
1
+ # Bubble
2
+
3
+ **Status:** stable
4
+
5
+ The surface a message's content sits on: four treatments, an align-aware tail corner, a readable measure cap, and an optional row of reaction pills.
6
+
7
+ **Keywords:** chat bubble, message bubble, speech bubble, reactions, conversation, messaging, ai chat
8
+
9
+ ## Usage
10
+
11
+ The tinted (or untinted) surface a turn's content sits on. Kept separate from Message because the row's layout and the content's surface change independently — an assistant's markdown answer wants no surface at all in exactly the same row a one-line reply wants a bubble.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use `muted` for the other party and `primary` for the reader's own messages. Two treatments is the whole vocabulary a conversation needs to say who is speaking.
16
+ - Do: Reach for `variant="plain"` for long-form assistant output — rendered markdown, code blocks, tables. A tinted box around three paragraphs reads as a quote block rather than as a turn, and `plain` drops the measure cap with the surface.
17
+ - Do: Pass the same `align` you gave the enclosing Message, so the flattened corner lands on the speaker's side.
18
+ - Do: Set `tail={false}` on the continuation bubbles in a grouped run — the tail marks where a speaker starts, and repeating it on every line makes a block of one person's messages look like four separate turns.
19
+ - Do: Let the register size it. Inside `<Density value="compact">` a Bubble sets itself in the workspace prose tier automatically — a docked agent panel needs no per-call-site sizing, and hardcoding one defeats the mechanism.
20
+ - Don't: Do not remove the measure cap to fill a wide window. ~42rem is a reading measure; a bubble spanning a 1600px viewport is a paragraph at 140 characters per line.
21
+ - Don't: Do not put the timestamp or the author name inside the bubble. Those are Message's `header` / `footer`, which stay outside the surface and so still work under `variant="plain"`.
22
+ - Don't: Do not use a Bubble for a system note or a streaming status. Those are `Marker`, which is deliberately quieter than the message tier.
23
+ - Don't: Do not render reactions as static text. `BubbleReaction` is a toggle and reports `aria-pressed`, which is how a screen-reader user knows whether pressing it adds or removes their own reaction.
24
+
25
+ ### Anatomy
26
+
27
+ | Part | Required | Description |
28
+ | --- | --- | --- |
29
+ | Bubble | yes | The surface itself. Holds the variant, the side, and the flattened tail corner. |
30
+ | BubbleReactions | no | The row of reaction pills, just under the bubble at the message column's small gap, so it reads as belonging to that message rather than the next row. |
31
+ | BubbleReaction | no | One reaction pill — emoji, count (hidden at 1), and pressed state. |
32
+
33
+ ## Subcomponents
34
+
35
+ | Name | Type | Description |
36
+ | --- | --- | --- |
37
+ | BubbleReactions | { align?: "start" \| "end" } & ComponentPropsWithRef<"div"> | Row container for reaction pills. Match `align` to the Bubble so the row hangs off the same edge. |
38
+ | BubbleReaction | { emoji: ReactNode; count?: number; active?: boolean } & ComponentPropsWithRef<"button"> | One reaction pill. A toggle, not a label: `active` drives `aria-pressed` as well as the styling, and `count` is hidden at 1 because the pill already says "one". |
39
+
40
+ ## Props
41
+
42
+ | Name | Type | Default | Description |
43
+ | --- | --- | --- | --- |
44
+ | variant | "muted" \| "primary" \| "outline" \| "plain" | "muted" | Surface treatment. `muted` for the other party, `primary` for the reader's own messages, `outline` for a quieter own-side, `plain` for long-form output that should not sit in a box (also drops the measure cap and padding). |
45
+ | align | "start" \| "end" | "start" | Which side of the conversation the bubble is on — flattens the corner nearest the speaker. Match it to the enclosing Message. |
46
+ | tail | boolean | true | Flatten the speaker-side corner. A flattened corner rather than a drawn arrow, so it holds up in every theme's radius language (flat through fully pilled) with no per-theme geometry. Turn it off for the continuation bubbles in a grouped run. |
47
+ | ...div props | ComponentPropsWithRef<"div"> | — | Spread onto the surface element. |
48
+
49
+ ## Variants
50
+
51
+ - **variant**: muted, primary, outline, plain
52
+ - **align**: start, end
53
+ - **tail**: true, false
54
+
55
+ ## Tokens used
56
+
57
+ `muted`, `foreground`, `primary`, `primary-foreground`, `border`, `card`, `card-foreground`, `accent`, `accent-foreground`, `muted-foreground`, `ring`
58
+
59
+ ## Accessibility
60
+
61
+ - The surface is a plain container with no imposed role — a bubble is presentation, and announcing one as a region would put a landmark around every line of a conversation.
62
+ - Reaction pills are real buttons reporting `aria-pressed`, so the reader's own reaction is state rather than colour alone; the emoji is hidden from AT and the count is left readable.
63
+ - `whitespace-pre-wrap` and `break-words` keep pasted logs, long URLs, and hard-wrapped text inside the surface instead of overflowing the conversation.
64
+ - Every variant meets AA against its own surface, including `primary`, which pairs the primary fill with its paired foreground token rather than a hardcoded white.
65
+
66
+ ## Related
67
+
68
+ - Components: Message, MessageScroller, Marker, Attachment
69
+
70
+ ## Examples
71
+
72
+ ### Two sides
73
+
74
+ ```tsx
75
+ import { Bubble, Message } from "@sableui/react"
76
+
77
+ <>
78
+ <Message messageId="m1"><Bubble>Did the deploy finish?</Bubble></Message>
79
+ <Message messageId="m2" align="end"><Bubble variant="primary" align="end">Green across the board.</Bubble></Message>
80
+ </>
81
+ ```
82
+
83
+ ### Long-form assistant output
84
+
85
+ ```tsx
86
+ import { Avatar, Bubble, CodeBlock, Message } from "@sableui/react"
87
+
88
+ <Message messageId="a1" avatar={<Avatar size="sm" fallback="AI" />}>
89
+ <Bubble variant="plain" className="space-y-3">
90
+ <p className="text-body-md text-foreground">Three things changed in this release:</p>
91
+ <CodeBlock code={diff} filename="CHANGELOG.md" />
92
+ </Bubble>
93
+ </Message>
94
+ ```
95
+
96
+ ### With reactions
97
+
98
+ ```tsx
99
+ import { Bubble, BubbleReaction, BubbleReactions, Message } from "@sableui/react"
100
+
101
+ <Message messageId="m3">
102
+ <Bubble>Shipped 0.45.0.</Bubble>
103
+ <BubbleReactions>
104
+ <BubbleReaction emoji="🎉" count={3} active onClick={() => toggle("🎉")} />
105
+ <BubbleReaction emoji="👀" count={1} onClick={() => toggle("👀")} />
106
+ </BubbleReactions>
107
+ </Message>
108
+ ```
109
+
110
+ ### A grouped run drops the repeated tail
111
+
112
+ ```tsx
113
+ import { Bubble, Message } from "@sableui/react"
114
+
115
+ <>
116
+ <Message messageId="m1" align="end"><Bubble variant="primary" align="end">One more thing.</Bubble></Message>
117
+ <Message messageId="m2" align="end" grouped><Bubble variant="primary" align="end" tail={false}>The staging cert expires Friday.</Bubble></Message>
118
+ </>
119
+ ```
@@ -0,0 +1,199 @@
1
+ # Button
2
+
3
+ **Status:** stable
4
+
5
+ Pill-shaped action primitive (theme-driven radius via --button-radius, defaults to full). Neutral tiers (primary/secondary/ghost/outline), plus destructive and link, and three on-color tiers (on-color/on-color-outline/on-color-ghost) that paint with the surrounding ink so a button reads on any tone or brand fill. Optional loading state shows a spinner and disables the button.
6
+
7
+ **Keywords:** button, action, cta, submit, save, cancel, destructive, loading, link, icon button, on color, inverse, band, brand fill, tone fill
8
+
9
+ ## Usage
10
+
11
+ Use Button when the user commits an action, submits a form, opens UI, or confirms a decision. Use the visual tier to communicate priority: one primary action per local decision area, then secondary, ghost, outline, or link for lower-emphasis choices.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use a single primary button in a view or dense panel so the action hierarchy stays obvious.
16
+ - Do: Write labels as actions, such as "Save changes" or "Delete draft", instead of vague labels like "OK".
17
+ - Do: Use loading with loadingLabel for async work so the action cannot double-fire and the state is announced.
18
+ - Do: Put icons in iconStart / iconEnd rather than as children, so the button applies its optical padding. Start icons say what the action is (plus, share); end icons say where it goes (chevron-right, external).
19
+ - Do: On a coloured fill — a solid Banner, a bg-tone-* or bg-primary band, an inverse (bg-foreground) section, a brand panel — use variant="on-color" for the main action and on-color-outline / on-color-ghost for the rest. They paint with the band's inherited ink (currentColor), so the surface must set its paired ink (text-tone-info-foreground, text-primary-foreground, text-background…), which it already should. Name the fill once on the surface, e.g. [--on-color-fill:var(--tone-info)], and the solid button's label takes the fill colour; unnamed, it falls back to black or white against the ink. Both clear 4.5:1 in every shipped theme and mode.
20
+ - Don't: Use destructive for reversible or low-risk actions; reserve it for operations with real loss or danger.
21
+ - Don't: Use a button for plain navigation. Compose with render={<a />} when the element should behave like a link.
22
+ - Don't: Use secondary on a tinted surface — inside a Note, a soft Banner, or any tone fill. `secondary` is a near-background surface token (in some themes within 2% of the page), so on a tint it reads as a hole punched in the fill rather than as a control sitting on it. On a soft tint use outline or ghost; on a full-strength fill use the on-color tiers.
23
+ - Don't: Use the on-color tiers on the page, a Card, a Note, or a soft tint — there the ink is the page foreground and the normal tiers already fit. And don't restyle a normal variant with bg-*/text-* classNames to make it read on a band; that hard-codes colours per theme and skips the contrast and focus handling on-color carries.
24
+
25
+ ### Anatomy
26
+
27
+ | Part | Required | Description |
28
+ | --- | --- | --- |
29
+ | Container | yes | The rounded hit target that carries the visual tier, focus ring, disabled state, and loading affordance. |
30
+ | Label | yes | Visible action text and the default accessible name. Icon-only buttons still need an aria-label. |
31
+ | Leading icon | no | An optional semantic Icon that reinforces the label without replacing it. |
32
+ | Spinner | no | Displayed during loading and paired with aria-busy plus disabled activation. |
33
+
34
+ ## Props
35
+
36
+ | Name | Type | Default | Description |
37
+ | --- | --- | --- | --- |
38
+ | variant | "primary" \| "secondary" \| "ghost" \| "outline" \| "destructive" \| "link" \| "floating" \| "on-color" \| "on-color-outline" \| "on-color-ghost" | "primary" | Visual emphasis tier. One primary per screen. The on-color tiers are for buttons ON a coloured fill: they paint with the inherited text colour (the fill's ink) — on-color is solid (ink background, fill-coloured label via the optional --on-color-fill var, else black/white), on-color-outline and on-color-ghost are transparent with an ink label. Never use them on page or card surfaces. floating is for a control that sits OVER content (carousel arrows, a marquee pause, a jump-to-latest over a conversation): its own edge, lift and translucent popover fill, so it reads on arbitrary content beneath. |
39
+ | size | "xs" \| "sm" \| "md" \| "cta" \| "icon" \| "icon-sm" \| "icon-xs" | "md" | Reads the density ladder, so every value differs by register: page 28/32/40/44, workspace 24/28/32/36. xs and icon-xs were added in 0.31.0 for the workspace control floor and are the sizes tool chrome wants; md is the default everywhere. icon/icon-sm/icon-xs are square. |
40
+ | render | ReactElement \| (props, state) => ReactElement | — | Compose with another element/component through the Base UI render prop. |
41
+ | loading | boolean | false | Disable the button and show a leading Spinner while an action is in flight. Sets aria-busy; a native button is disabled, a render element (e.g. an <a>) gets aria-disabled. |
42
+ | badge | ReactNode | — | A corner indicator (usually <NotificationBadge dot /> or a count) overlaid on the top-end corner of the glyph. For icon-only sizes; never hand-position a badge over a Button with absolute offsets. |
43
+ | loadingLabel | ReactNode | — | Optional text shown in place of children while loading. |
44
+ | iconStart | ReactNode | — | Icon before the label. The button takes one 2px step off its start padding so icon + label read as centred. Replaced by the spinner while loading. |
45
+ | iconEnd | ReactNode | — | Icon after the label (disclosure, external link, next). Same optical correction on the end side. |
46
+
47
+ ## Variants
48
+
49
+ - **variant**: primary, secondary, ghost, outline, destructive, link, floating, on-color, on-color-outline, on-color-ghost
50
+ - **size**: xs, sm, md, cta, icon, icon-sm, icon-xs
51
+
52
+ ## Tokens used
53
+
54
+ `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`, `input`, `destructive`, `destructive-foreground`, `ring`
55
+
56
+ ## Motion
57
+
58
+ - **arrival** · Root · _no motion_ — A button belongs to the layout it ships in. Whatever staged that region owns the entrance; a button that fades in on its own is a button that is briefly un-clickable for no stated reason.
59
+ - **response** · Root · `motion-interactive` — Hover, focus ring and disabled fade all ease on one declaration. This is the reference case for the whole recipe: it lists scale and translate alongside the colours specifically so motion-press can add a transform without a second, conflicting transition-property.
60
+ - **response** · Root · `motion-press` — The 0.97 settle on pointer-down is the only confirmation the press registered before the action's own result arrives — which on a slow network is the difference between waiting and clicking again. It skips disabled elements, so a dead button stays visibly dead.
61
+ - **transition** · Root · _no motion_ — A button has no state of its own to move between. A control that toggles is a Switch or a Toggle, and those carry the transition.
62
+ - **activity** · Root · _no motion_ — Pending work is passed in via `loading`, which renders a Spinner — the activity motion lives there rather than being duplicated here.
63
+
64
+ ## Accessibility
65
+
66
+ - Renders a native <button> by default (keyboard + role free).
67
+ - Focus-visible ring uses the --ring token at 50% opacity.
68
+ - Use render={<a/>} for link semantics rather than onClick navigation.
69
+ - On-color tiers replace the --ring focus ring with one drawn in the band's ink past a transparent 2px gap, so focus stays visible on any fill (ink-on-fill is the theme's own tested pair).
70
+ - On-color contrast is measured in button.test.tsx for every shipped theme and mode on each tone, primary, destructive, success, highlight and inverse fill: solid label at rest and hover, and outline/ghost label on the hover wash, all at least 4.5:1.
71
+ - loading sets aria-busy and disables activation (disabled on a native button, aria-disabled on a render element) so the action can't double-fire.
72
+
73
+ ## Related
74
+
75
+ - Components: Menu, DotsMenu, Icon, Banner
76
+ - Patterns: SettingsPage, AuthPage, DashboardPage
77
+
78
+ ## Examples
79
+
80
+ ### Primary
81
+
82
+ ```tsx
83
+ import { Button } from "@sableui/react"
84
+
85
+ <Button>Collect</Button>
86
+ ```
87
+
88
+ ### Secondary + ghost
89
+
90
+ ```tsx
91
+ import { Button, Row } from "@sableui/react"
92
+
93
+ <Row gap={1}><Button variant="secondary">Cancel</Button><Button variant="ghost">Skip</Button></Row>
94
+ ```
95
+
96
+ ### Loading
97
+
98
+ ```tsx
99
+ import { Button } from "@sableui/react"
100
+
101
+ <Button loading loadingLabel="Saving…">Save</Button>
102
+ ```
103
+
104
+ ### Main action with options
105
+
106
+ ```tsx
107
+ import { Button, Icon, Menu, MenuContent, MenuItem, MenuTrigger, Row } from "@sableui/react"
108
+
109
+ <Row gap={2}><Button onClick={deploy}>Deploy</Button><Menu><MenuTrigger render={<Button variant="secondary" size="icon" aria-label="Deploy options"><Icon name="chevron-down" /></Button>} /><MenuContent align="end"><MenuItem>Deploy to preview</MenuItem><MenuItem>Deploy to production</MenuItem></MenuContent></Menu></Row>
110
+ ```
111
+
112
+ ### Copy to clipboard
113
+
114
+ ```tsx
115
+ import { Button, Icon, useCopy } from "@sableui/react"
116
+
117
+ const { copy, copied } = useCopy(address)
118
+ <Button variant="ghost" size="icon-sm" aria-label="Copy address" onClick={copy}><Icon name={copied ? "check" : "copy"} /></Button>
119
+ ```
120
+
121
+ ### As link
122
+
123
+ ```tsx
124
+ import { Button } from "@sableui/react"
125
+
126
+ <Button render={<a href="/explore" />}>Explore</Button>
127
+ ```
128
+
129
+ ### Primary + secondary decision row
130
+
131
+ ```tsx
132
+ import { Button, Row } from "@sableui/react"
133
+
134
+ <Row gap={1}><Button>Publish</Button><Button variant="secondary">Save draft</Button><Button variant="ghost">Cancel</Button></Row>
135
+ ```
136
+
137
+ ### Destructive with confirmation copy
138
+
139
+ ```tsx
140
+ import { Button, Row } from "@sableui/react"
141
+
142
+ <Row gap={1}><Button variant="secondary">Cancel</Button><Button variant="destructive">Delete edition</Button></Row>
143
+ ```
144
+
145
+ ### Leading icon
146
+
147
+ ```tsx
148
+ import { Button, Icon } from "@sableui/react"
149
+
150
+ <Button iconStart={<Icon name="plus" />}>New project</Button>
151
+ ```
152
+
153
+ ### Trailing icon
154
+
155
+ ```tsx
156
+ import { Button, Icon } from "@sableui/react"
157
+
158
+ <Button variant="secondary" iconEnd={<Icon name="chevron-right" />}>Continue</Button>
159
+ ```
160
+
161
+ ### Both icons
162
+
163
+ ```tsx
164
+ import { Button, Icon } from "@sableui/react"
165
+
166
+ <Button variant="outline" iconStart={<Icon name="file" />} iconEnd={<Icon name="chevron-down" />}>Export</Button>
167
+ ```
168
+
169
+ ### Icon-only
170
+
171
+ ```tsx
172
+ import { Button, Icon } from "@sableui/react"
173
+
174
+ <Button size="icon" variant="ghost" aria-label="Share"><Icon name="share" /></Button>
175
+ ```
176
+
177
+ ### Icon button with an unread badge
178
+
179
+ ```tsx
180
+ import { Button, Icon, NotificationBadge } from "@sableui/react"
181
+
182
+ <Button size="icon" variant="ghost" aria-label="Notifications" badge={<NotificationBadge dot />}><Icon name="bell" /></Button>
183
+ ```
184
+
185
+ ### On a coloured band
186
+
187
+ ```tsx
188
+ import { Button } from "@sableui/react"
189
+
190
+ <div className="flex gap-3 bg-tone-info p-6 text-tone-info-foreground [--on-color-fill:var(--tone-info)]"><Button variant="on-color">Join the beta</Button><Button variant="on-color-outline">Read the notes</Button></div>
191
+ ```
192
+
193
+ ### Action in a solid Banner
194
+
195
+ ```tsx
196
+ import { Banner, Button } from "@sableui/react"
197
+
198
+ <Banner tone="warning" appearance="solid" action={<Button variant="on-color" size="sm">Reconnect</Button>}>Wallet connection paused.</Banner>
199
+ ```