@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.
- package/AGENTS.md +157 -0
- package/CHANGELOG.md +50 -0
- package/CONTRIBUTING.md +221 -0
- package/DESIGN.md +548 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/THIRD_PARTY_NOTICES.md +44 -0
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +1223 -0
- package/dist/_contract-CYScOdf5.d.ts +150 -0
- package/dist/chunk-3IWZYBGH.js +398 -0
- package/dist/chunk-5Z5KLOAU.js +167 -0
- package/dist/chunk-7MG4SHYX.js +15 -0
- package/dist/chunk-ADLLDWLW.js +15 -0
- package/dist/chunk-EP55KSYA.js +14 -0
- package/dist/chunk-J23ELQQZ.js +9396 -0
- package/dist/chunk-JI7WJP2L.js +286 -0
- package/dist/chunk-K7RUHUGC.js +14 -0
- package/dist/chunk-KO3NAEBF.js +15 -0
- package/dist/chunk-L4IEIQQA.js +14 -0
- package/dist/chunk-L6Y46C62.js +270 -0
- package/dist/chunk-NEQNXI2G.js +25 -0
- package/dist/chunk-PAOZBZME.js +40 -0
- package/dist/chunk-RWP4XBSK.js +139 -0
- package/dist/chunk-SRQWQUTW.js +63 -0
- package/dist/chunk-YXBOYWBN.js +15 -0
- package/dist/foundations/index.d.ts +88 -0
- package/dist/foundations/index.js +792 -0
- package/dist/icon/index.d.ts +60 -0
- package/dist/icon/index.js +2 -0
- package/dist/icon-sets/brands.d.ts +44 -0
- package/dist/icon-sets/brands.js +147 -0
- package/dist/icon-sets/reicon.d.ts +8 -0
- package/dist/icon-sets/reicon.js +1 -0
- package/dist/index.d.ts +7013 -0
- package/dist/index.js +15462 -0
- package/dist/ios/chrome.css +83 -0
- package/dist/ios/frame.css +5 -0
- package/dist/ios/index.d.ts +70 -0
- package/dist/ios/index.js +313 -0
- package/dist/ios/preview.d.ts +64 -0
- package/dist/ios/preview.js +133 -0
- package/dist/ios/styles.css +77 -0
- package/dist/layout/index.d.ts +280 -0
- package/dist/layout/index.js +2 -0
- package/dist/meta-6dUzgC6M.d.ts +205 -0
- package/dist/patterns/index.d.ts +36 -0
- package/dist/patterns/index.js +934 -0
- package/dist/presets/index.d.ts +37 -0
- package/dist/presets/index.js +117 -0
- package/dist/registry-CyL28OzJ.d.ts +28 -0
- package/dist/styles.css +1893 -0
- package/dist/theme-provider-BWcizhAt.d.ts +105 -0
- package/dist/themes/atelier/atelier.css +213 -0
- package/dist/themes/atelier/index.d.ts +9 -0
- package/dist/themes/atelier/index.js +1 -0
- package/dist/themes/base/base.css +443 -0
- package/dist/themes/base/index.d.ts +9 -0
- package/dist/themes/base/index.js +1 -0
- package/dist/themes/ember/ember.css +270 -0
- package/dist/themes/ember/index.d.ts +9 -0
- package/dist/themes/ember/index.js +1 -0
- package/dist/themes/index.d.ts +10 -0
- package/dist/themes/index.js +8 -0
- package/dist/themes/meridian/index.d.ts +9 -0
- package/dist/themes/meridian/index.js +1 -0
- package/dist/themes/meridian/meridian.css +255 -0
- package/dist/themes/poppy/index.d.ts +6 -0
- package/dist/themes/poppy/index.js +1 -0
- package/dist/themes/poppy/poppy.css +174 -0
- package/dist/themes/prism/index.d.ts +6 -0
- package/dist/themes/prism/index.js +1 -0
- package/dist/themes/prism/prism.css +183 -0
- package/dist/themes/verdant/index.d.ts +9 -0
- package/dist/themes/verdant/index.js +1 -0
- package/dist/themes/verdant/verdant.css +210 -0
- package/dist/themes.css +30 -0
- package/llms/components/accordion.md +93 -0
- package/llms/components/app-header.md +70 -0
- package/llms/components/app-shell.md +117 -0
- package/llms/components/attachment.md +105 -0
- package/llms/components/avatar.md +141 -0
- package/llms/components/badge.md +117 -0
- package/llms/components/banner.md +119 -0
- package/llms/components/bottom-nav.md +80 -0
- package/llms/components/breadcrumbs.md +75 -0
- package/llms/components/browser.md +67 -0
- package/llms/components/bubble.md +119 -0
- package/llms/components/button.md +199 -0
- package/llms/components/calendar.md +75 -0
- package/llms/components/card.md +135 -0
- package/llms/components/carousel.md +87 -0
- package/llms/components/chat-composer.md +126 -0
- package/llms/components/checkbox-card.md +98 -0
- package/llms/components/checkbox.md +90 -0
- package/llms/components/code-block.md +81 -0
- package/llms/components/code.md +62 -0
- package/llms/components/collapsible.md +83 -0
- package/llms/components/color-field.md +100 -0
- package/llms/components/combobox.md +119 -0
- package/llms/components/command-menu.md +158 -0
- package/llms/components/context-card.md +73 -0
- package/llms/components/context-menu.md +79 -0
- package/llms/components/data-table.md +99 -0
- package/llms/components/date-picker.md +97 -0
- package/llms/components/description.md +96 -0
- package/llms/components/destructive-action-modal.md +76 -0
- package/llms/components/dialog.md +127 -0
- package/llms/components/dots-menu.md +64 -0
- package/llms/components/empty-state.md +113 -0
- package/llms/components/entity.md +67 -0
- package/llms/components/feedback.md +63 -0
- package/llms/components/fieldset.md +106 -0
- package/llms/components/file-dropzone.md +103 -0
- package/llms/components/form.md +111 -0
- package/llms/components/gliding-highlight.md +84 -0
- package/llms/components/inline-status.md +62 -0
- package/llms/components/input.md +127 -0
- package/llms/components/kbd.md +57 -0
- package/llms/components/label.md +73 -0
- package/llms/components/marker.md +99 -0
- package/llms/components/marquee.md +66 -0
- package/llms/components/menu.md +122 -0
- package/llms/components/message-scroller.md +110 -0
- package/llms/components/message.md +114 -0
- package/llms/components/meter.md +124 -0
- package/llms/components/middle-truncate.md +62 -0
- package/llms/components/note.md +81 -0
- package/llms/components/notification-badge.md +67 -0
- package/llms/components/number-field.md +70 -0
- package/llms/components/otp-field.md +101 -0
- package/llms/components/page-header.md +76 -0
- package/llms/components/page-loader.md +67 -0
- package/llms/components/pagination.md +92 -0
- package/llms/components/panel-group.md +120 -0
- package/llms/components/panel-section.md +122 -0
- package/llms/components/phone.md +70 -0
- package/llms/components/pill.md +73 -0
- package/llms/components/popover.md +79 -0
- package/llms/components/progress.md +98 -0
- package/llms/components/radio-card.md +90 -0
- package/llms/components/radio.md +86 -0
- package/llms/components/relative-time.md +60 -0
- package/llms/components/scroller.md +88 -0
- package/llms/components/scrub-field.md +97 -0
- package/llms/components/search-input.md +84 -0
- package/llms/components/select.md +106 -0
- package/llms/components/separator.md +70 -0
- package/llms/components/settings-menu.md +139 -0
- package/llms/components/sheet.md +97 -0
- package/llms/components/show-more.md +59 -0
- package/llms/components/side-nav.md +106 -0
- package/llms/components/sidebar.md +87 -0
- package/llms/components/skeleton.md +62 -0
- package/llms/components/slider.md +114 -0
- package/llms/components/spinner.md +87 -0
- package/llms/components/status-bar.md +91 -0
- package/llms/components/status-dot.md +70 -0
- package/llms/components/stepper.md +125 -0
- package/llms/components/streaming-text.md +86 -0
- package/llms/components/switch.md +87 -0
- package/llms/components/table.md +112 -0
- package/llms/components/tabs.md +84 -0
- package/llms/components/textarea.md +88 -0
- package/llms/components/theme-selector.md +95 -0
- package/llms/components/theme-switcher.md +71 -0
- package/llms/components/thinking-orb.md +91 -0
- package/llms/components/toast.md +130 -0
- package/llms/components/toggle.md +95 -0
- package/llms/components/toolbar.md +168 -0
- package/llms/components/tooltip.md +93 -0
- package/llms/components/tree.md +227 -0
- package/llms/components/video.md +66 -0
- package/llms/components/workspace.md +138 -0
- package/llms/foundations/accessibility.md +60 -0
- package/llms/foundations/motion.md +96 -0
- package/llms/foundations/scrolling.md +41 -0
- package/llms/foundations/shape.md +73 -0
- package/llms/foundations/spacing.md +82 -0
- package/llms/foundations/surfaces.md +69 -0
- package/llms/foundations/typography.md +120 -0
- package/llms/layout/aspect-box.md +37 -0
- package/llms/layout/bleed.md +31 -0
- package/llms/layout/center.md +37 -0
- package/llms/layout/columns.md +51 -0
- package/llms/layout/grid-overlay.md +40 -0
- package/llms/layout/grid.md +47 -0
- package/llms/layout/page.md +39 -0
- package/llms/layout/region.md +49 -0
- package/llms/layout/row.md +40 -0
- package/llms/layout/safe-area.md +37 -0
- package/llms/layout/spacer.md +39 -0
- package/llms/layout/split-pane.md +41 -0
- package/llms/layout/stack.md +39 -0
- package/llms/patterns/auth-page.md +68 -0
- package/llms/patterns/brand-landing-page.md +423 -0
- package/llms/patterns/chat-panel.md +210 -0
- package/llms/patterns/dashboard-page.md +170 -0
- package/llms/patterns/feed-page.md +103 -0
- package/llms/patterns/landing-page.md +122 -0
- package/llms/patterns/list-detail-page.md +100 -0
- package/llms/patterns/settings-page.md +121 -0
- package/llms/reference/token-intent.md +324 -0
- package/llms/reference/utilities.md +129 -0
- package/llms/tasks/adding-a-component.md +113 -0
- package/llms/tasks/auditing-an-app.md +216 -0
- package/llms/tasks/building-a-page.md +109 -0
- package/llms/tasks/building-a-tool.md +141 -0
- package/llms/tasks/building-an-ios-prototype.md +105 -0
- package/llms/tasks/building-with-sable.md +73 -0
- package/llms/tasks/choosing-a-component.md +109 -0
- package/llms/tasks/converting-an-app.md +139 -0
- package/llms/tasks/creating-a-preset.md +43 -0
- package/llms/tasks/creating-design-assets.md +96 -0
- package/llms/tasks/designing-a-surface.md +115 -0
- package/llms/tasks/developing-taste.md +157 -0
- package/llms/tasks/getting-started.md +63 -0
- package/llms/tasks/learning-from-builds.md +74 -0
- package/llms/tasks/reviewing-a-design.md +114 -0
- package/llms/tasks/reviewing-a-journey.md +84 -0
- package/llms/tasks/reviewing-motion.md +92 -0
- package/llms/tasks/using-presets.md +87 -0
- package/llms/taste/asset-plan.template.json +53 -0
- package/llms/taste/composition-follows-content.md +20 -0
- package/llms/taste/hierarchy-is-a-relationship.md +22 -0
- package/llms/taste/lessons.json +57 -0
- package/llms/taste/motion-needs-a-reason.md +21 -0
- package/llms/taste/surface-states-travel-together.md +20 -0
- package/llms-full.txt +14906 -0
- package/llms.txt +405 -0
- package/package.json +185 -3
- package/registry.json +18812 -0
- package/scripts/agent-setup.mjs +54 -0
- package/scripts/design-atlas.mjs +221 -0
- package/scripts/design-directions.mjs +123 -0
- package/scripts/design-memory.mjs +166 -0
- package/scripts/design-preferences.mjs +227 -0
- package/scripts/design-presets.mjs +48 -0
- package/scripts/grade.mjs +509 -0
- package/scripts/lint-rules.mjs +1065 -0
- package/scripts/review-evidence.mjs +120 -0
- package/scripts/skeleton.mjs +248 -0
- package/skills/entry/SKILL.md +34 -0
- package/skills/sable/SKILL.md +187 -0
- package/skills/sable/agents/openai.yaml +6 -0
- package/targets/ios.json +982 -0
- 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
|
+
```
|