@sableui/react 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +157 -0
- package/CHANGELOG.md +55 -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,130 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Imperative notification system over Base UI Toast — auto-dismiss timers, swipe-to-dismiss, focus management, ARIA live-region announcements, and a limit-aware stack. Wrap the app in ToastProvider, then push toasts from anywhere with the global, Sonner-shaped toast() (toast.success/error/info/warning/loading/promise/dismiss) or the useToast() hook inside components.
|
|
6
|
+
|
|
7
|
+
**Keywords:** toast, notification, snackbar, feedback, success, error, promise, loading, undo
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Toast for transient feedback caused by a user action: saved, failed, copied, queued, uploaded, or undone. Use Banner for persistent page/app state and InlineStatus or Field errors for feedback that must remain attached to content.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Mount one ToastProvider near the app root before any code calls toast() or useToast().
|
|
16
|
+
- Do: Use toast.promise for async actions so loading, success, and error states stay tied to one notification.
|
|
17
|
+
- Do: Keep toast copy short and outcome-focused. Add an action only when it is immediately useful, such as Undo.
|
|
18
|
+
- Don't: Use Toast for critical blockers or persistent outages. Use Banner or Dialog when the user must notice and act.
|
|
19
|
+
- Don't: Fire multiple toasts for one operation. Update the same toast when a task progresses.
|
|
20
|
+
- Don't: Put the only path to an action in a toast that auto-dismisses. An Undo that expires on a timer is data loss on a schedule, so error toasts and any toast carrying actionProps default to timeout 0 and wait to be read — pass an explicit timeout only when the action is also reachable somewhere else. When a timeout exists at all, 5s is the floor: anything shorter cannot be read and acted on.
|
|
21
|
+
|
|
22
|
+
### Anatomy
|
|
23
|
+
|
|
24
|
+
| Part | Required | Description |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Viewport | yes | The positioned live-region stack rendered by ToastProvider. |
|
|
27
|
+
| Surface | yes | The individual ToastRoot containing title, description, action, and close affordance. |
|
|
28
|
+
| Title | yes | Short outcome text, used as the accessible name. |
|
|
29
|
+
| Description | no | Optional detail or context beneath the title. |
|
|
30
|
+
| Action | no | Optional inline action such as Undo or Retry. |
|
|
31
|
+
|
|
32
|
+
## Subcomponents
|
|
33
|
+
|
|
34
|
+
| Name | Type | Description |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| ToastRoot | Base UI Toast.Root props + className (requires the `toast` object) | A single toast surface. Animates enter/leave via data-[starting-style]/data-[ending-style]; swipe-to-dismiss is wired by Base UI. |
|
|
37
|
+
| ToastTitle | Base UI Toast.Title props + className | The toast headline (text-title-sm). Auto-associated as the toast's accessible name. |
|
|
38
|
+
| ToastDescription | Base UI Toast.Description props + className | Supporting body copy beneath the title (text-body-sm, muted). Auto-associated as the toast's accessible description. |
|
|
39
|
+
| ToastClose | Base UI Toast.Close props + className | Dismiss button. Base UI handles the close and leave animation; labelled "Dismiss" by default. |
|
|
40
|
+
| ToastAction | Base UI Toast.Action props + className | Inline action button (e.g. Undo). The imperative path renders one on every toast and Base UI sources its label and handler from that toast's `actionProps`, rendering nothing when there are none — so `toast(msg, { actionProps })` is all an action needs. Reach for the component directly only when composing a toast by hand; compose with render={<Button … />}. |
|
|
41
|
+
|
|
42
|
+
## Props
|
|
43
|
+
|
|
44
|
+
| Name | Type | Default | Description |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| ToastProvider | { children, timeout?, limit?, closeIcon?, position?, viewportClassName? } | — | Wraps Base UI Toast.Provider and renders the themed Viewport + toast list. Mount once near the app root, above any caller of useToast(). |
|
|
47
|
+
| position | "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" | "top-right" | Screen anchor for the toast stack. |
|
|
48
|
+
| viewportClassName | string | — | Extra classes merged LAST onto the fixed viewport region — for safe-area insets or an offset (e.g. clearing a sidebar). |
|
|
49
|
+
| timeout | number | 5000 | Default auto-dismiss time in ms for toasts. 0 keeps a toast until it is dismissed. Error toasts, loading toasts, and any toast carrying actionProps default to 0 regardless of this value; a per-toast timeout overrides both. |
|
|
50
|
+
| limit | number | 3 | Maximum toasts shown at once; older toasts are pushed out when the limit is exceeded. |
|
|
51
|
+
| closeIcon | ReactNode | <Icon name="xmark" /> | Override the dismiss-button glyph. The default already follows the injected icon set. |
|
|
52
|
+
| useToast / useToastManager | () => { toasts, add, close, update, promise } | — | Manager hook for use inside components. add({ title, description, timeout?, type?, priority?, actionProps? }) returns the new toast id and applies the same persistence default as toast.* — an error, loading, or actionProps toast stays until dismissed unless you pass a timeout. close(id), update(id, opts), and promise(p, opts) round out the API. |
|
|
53
|
+
| toast | (message, options?) => string & { success, error, warning, info, message, loading, promise, dismiss, update, add, custom } | — | Imperative singleton callable OUTSIDE React (event handlers, utilities, non-React code) — the Sonner-shaped surface that makes migrating a lib/toast wrapper a near drop-in. Requires a mounted <ToastProvider> (it binds the shared manager). Each call returns the toast id; toast.dismiss(id?) closes one or all; toast.promise(p, { loading, success, error }) and toast.update(id, opts) handle async + in-place updates. |
|
|
54
|
+
| type (toast option) | "success" \| "error" \| "warning" \| "info" \| "loading" | — | Set by the toast.* helpers. Drives a semantic left accent (success→success, error→destructive, warning→tone-warning, info→tone-info); loading shows a spinner. Both loading and error persist until updated or dismissed unless the call supplies a timeout. |
|
|
55
|
+
|
|
56
|
+
## Tokens used
|
|
57
|
+
|
|
58
|
+
`popover`, `popover-foreground`, `border`, `muted-foreground`, `accent`, `accent-foreground`, `secondary`, `secondary-foreground`, `ring`, `success`, `tone-warning`, `tone-info`, `destructive`
|
|
59
|
+
|
|
60
|
+
## Motion
|
|
61
|
+
|
|
62
|
+
- **arrival** · ToastRoot · `motion-slide-left` — A toast interrupts without being asked, so where it came from is the reader's first question. Sliding in from the viewport edge answers it and marks the toast as belonging to the edge rather than to whatever it happens to cover.
|
|
63
|
+
- **response** · ToastAction · `motion-interactive` — Toasts are on a timer, so the action has to look actionable at a glance — there is no time to hunt for the hit target.
|
|
64
|
+
- **transition** · ToastRoot · `motion-slide-left` — Leaving the same way it entered keeps the stack legible when several dismiss at once; toasts that fade in place leave the remaining ones jumping to fill gaps with no visible cause.
|
|
65
|
+
- **activity** · ToastRoot · _no motion_ — A toast reports a result that has already happened. Anything still in flight belongs in the surface that started it, where the reader is still looking.
|
|
66
|
+
|
|
67
|
+
## Accessibility
|
|
68
|
+
|
|
69
|
+
- Base UI announces toasts through an ARIA live region — `low` priority is polite, `high` is assertive.
|
|
70
|
+
- Auto-dismiss timers pause on hover/focus and while the window is hidden.
|
|
71
|
+
- ToastClose is keyboard-focusable and labelled; Esc dismisses the focused toast.
|
|
72
|
+
- ToastClose draws a 24px box (21px against the desktop root) and carries a 24x24+ POINTER TARGET behind it, per WCAG 2.5.8 — the glyph stays quiet because a dismiss control should not be the loudest thing in a toast. Base UI keeps the button out of the accessibility tree until the toast is focused; the toast itself is the announced element.
|
|
73
|
+
- An action does NOT dismiss its toast — Base UI leaves that to the handler, so an action that opens a confirmation keeps the toast it is confirming. Call `toast.dismiss(id)` yourself when the action should close it.
|
|
74
|
+
- Animate enter/leave via data-[starting-style]/data-[ending-style] rather than mount/unmount flips.
|
|
75
|
+
|
|
76
|
+
## Related
|
|
77
|
+
|
|
78
|
+
- Components: Banner, InlineStatus, Button, Progress
|
|
79
|
+
- Patterns: DashboardPage, SettingsPage
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
### Imperative (from anywhere)
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { toast } from "@sableui/react"
|
|
87
|
+
|
|
88
|
+
toast.success("Saved", { description: "Your post is live." })
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Promise (loading → success / error)
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { toast } from "@sableui/react"
|
|
95
|
+
|
|
96
|
+
toast.promise(publish(), { loading: "Publishing…", success: "Published", error: "Failed to publish" })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Persistent loading, updated in place
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { toast } from "@sableui/react"
|
|
103
|
+
|
|
104
|
+
const id = toast.loading("Pinning to Arweave…"); /* later */ toast.update(id, { title: "Pinned", type: "success", timeout: 4000 })
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Custom toast markup (composed parts)
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { Icon, toast, ToastClose, ToastDescription, ToastRoot, ToastTitle, ToastViewport, useToast } from "@sableui/react"
|
|
111
|
+
|
|
112
|
+
const { toasts } = useToast()
|
|
113
|
+
<ToastViewport>{toasts.map((t) => (<ToastRoot key={t.id} toast={t}><div className="flex min-w-0 flex-col gap-1"><ToastTitle>{t.title}</ToastTitle><ToastDescription>{t.description}</ToastDescription></div><ToastClose><Icon name="xmark" /></ToastClose></ToastRoot>))}</ToastViewport>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Undo action
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { toast } from "@sableui/react"
|
|
120
|
+
|
|
121
|
+
toast.message("Post archived", { actionProps: { children: "Undo", onClick: () => restorePost() } })
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Provider with app chrome
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { AppShell, Button, Region, toast, ToastProvider } from "@sableui/react"
|
|
128
|
+
|
|
129
|
+
<ToastProvider position="bottom-right" limit={3}><AppShell><Region max="feed"><Button onClick={() => toast.success("Saved")}>Save</Button></Region></AppShell></ToastProvider>
|
|
130
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Toggle
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Pressed-state button over Base UI Toggle, plus ToggleGroup for shared single- or multi-select rows. Pressed styling reads from data-[pressed].
|
|
6
|
+
|
|
7
|
+
**Keywords:** toggle, pressed, button state, toolbar, segmented control, segmented, toggle group, button group, view switcher
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Toggle for button-like controls that can be pressed on or off within a toolbar or compact control set.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it for transient UI modes such as bold, preview, pin, or favorite.
|
|
16
|
+
- Do: Make the pressed state visually and programmatically clear.
|
|
17
|
+
- Do: Use ToggleGroup appearance="segmented" for a single-select view switch (List / Grid). It is the same capsule ThemeSwitcher wears — do not hand-build a track around a row of toggles.
|
|
18
|
+
- Don't: Do not use Toggle for persistent application settings; use Switch.
|
|
19
|
+
- Don't: Do not leave an icon-only Toggle without an accessible name. It announces as "button, pressed", which tells a screen-reader user the state but never what the state applies to.
|
|
20
|
+
|
|
21
|
+
### Anatomy
|
|
22
|
+
|
|
23
|
+
| Part | Required | Description |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Toggle | yes | A single pressed or unpressed control. |
|
|
26
|
+
| ToggleGroup | no | A coordinated group of toggle choices. |
|
|
27
|
+
| ToggleGroupItem | no | One toggle inside a group — an alias of Toggle, so <Toggle value="…"> inside a ToggleGroup is the same thing. |
|
|
28
|
+
|
|
29
|
+
## Subcomponents
|
|
30
|
+
|
|
31
|
+
| Name | Type | Description |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| ToggleGroup | Base UI ToggleGroup props (value, defaultValue, onValueChange, multiple, disabled, orientation, loopFocus) + appearance?: "default" \| "segmented" + className | Groups child Toggles and shares pressed state across them — each Toggle inside carries a value. Defaults to multiple: false (single-select) — the fit for view-mode switches and segmented controls; pass multiple to allow several pressed at once, e.g. a row of formatting toggles. |
|
|
34
|
+
| ToggleGroupItem | Alias of Toggle | Re-export of Toggle for group composition — a Toggle carrying a value is the group item. |
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
| Name | Type | Default | Description |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| appearance | "default" \| "segmented" | "default" | On ToggleGroup: `default` is a loose row of toggles; `segmented` draws the capsule segmented control (one track, the pressed option filled) shared with ThemeSwitcher. Toggles inside inherit it. |
|
|
41
|
+
| multiple | boolean | false | On ToggleGroup: allow several toggles pressed at once. The default is single-select, which behaves like a radio group. |
|
|
42
|
+
| Toggle | Base UI Toggle props (pressed, defaultPressed, onPressedChange, disabled, value) + className | defaultPressed: false | A two-state button. Uncontrolled via defaultPressed; controlled via pressed + onPressedChange. Pass value to identify it inside a ToggleGroup. |
|
|
43
|
+
|
|
44
|
+
## Variants
|
|
45
|
+
|
|
46
|
+
- **appearance**: default, segmented
|
|
47
|
+
|
|
48
|
+
## Tokens used
|
|
49
|
+
|
|
50
|
+
`muted-foreground`, `foreground`, `accent`, `accent-foreground`, `card`, `border`, `ring`
|
|
51
|
+
|
|
52
|
+
## Accessibility
|
|
53
|
+
|
|
54
|
+
- Base UI sets aria-pressed on each Toggle and manages the pressed/disabled states.
|
|
55
|
+
- ToggleGroup provides arrow-key roving focus across its items; loopFocus wraps at the ends.
|
|
56
|
+
- Pressed styling reads from the data-[pressed] attribute; disabled items are dimmed and non-interactive.
|
|
57
|
+
- Always supply an aria-label (or visible text) when a Toggle holds only an icon.
|
|
58
|
+
|
|
59
|
+
## Related
|
|
60
|
+
|
|
61
|
+
- Components: Switch, Button, Tabs
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
### Standalone
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { Toggle } from "@sableui/react"
|
|
69
|
+
|
|
70
|
+
<Toggle aria-label="Mute">Mute</Toggle>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Single-select group
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { Icon, Toggle, ToggleGroup } from "@sableui/react"
|
|
77
|
+
|
|
78
|
+
<ToggleGroup defaultValue={["list"]}><Toggle value="list" aria-label="List view"><Icon name="list" /></Toggle><Toggle value="grid" aria-label="Grid view"><Icon name="grid" /></Toggle></ToggleGroup>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Segmented view switch
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Icon, Toggle, ToggleGroup } from "@sableui/react"
|
|
85
|
+
|
|
86
|
+
<ToggleGroup appearance="segmented" defaultValue={["list"]} aria-label="View"><Toggle value="list" aria-label="List view"><Icon name="list" /></Toggle><Toggle value="grid" aria-label="Grid view"><Icon name="grid" /></Toggle></ToggleGroup>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Multi-select group
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { Toggle, ToggleGroup } from "@sableui/react"
|
|
93
|
+
|
|
94
|
+
<ToggleGroup multiple defaultValue={["bold"]}><Toggle value="bold">Bold</Toggle><Toggle value="italic">Italic</Toggle></ToggleGroup>
|
|
95
|
+
```
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Toolbar
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A row or column of controls that is one tab stop, with arrow-key movement between its items (role="toolbar"), over Base UI Toolbar. Items reuse Button's ghost variants and Input, so they follow density like every other control.
|
|
6
|
+
|
|
7
|
+
**Keywords:** toolbar, action bar, button row, icon buttons, formatting bar, roving focus, arrow keys, tool chrome, editor toolbar
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Toolbar when three or more related controls sit together and act on the same thing — a text selection, a canvas, a document, a table. Tab lands on the toolbar once and the arrow keys move along it, so a keyboard user crosses an editor's twelve buttons in one keystroke instead of twelve. It works in both registers: ghost sm buttons on a page, compact icon-xs buttons inside a Workspace.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Give every Toolbar an aria-label that names what it acts on ("Text formatting", "Canvas"). With more than one toolbar on screen, that label is the only way a screen-reader user tells them apart.
|
|
16
|
+
- Do: Use size="icon-sm" (page) or size="icon-xs" (workspace) with an aria-label for icon-only ToolbarButtons, and keep one size across the row so the controls line up.
|
|
17
|
+
- Do: Split a long row into ToolbarGroups with a ToolbarSeparator between them — undo/redo, text style, insert. Groups give the eye the same structure the arrow keys already cross.
|
|
18
|
+
- Do: Compose triggers and toggles through ToolbarButton's render prop — render={<MenuTrigger />}, render={<PopoverTrigger />}, or render={<Toggle value="bold" />} inside a ToggleGroup — so they join the toolbar's arrow-key order.
|
|
19
|
+
- Don't: Put a bare Button, Toggle or Input inside a Toolbar. It renders, but it is not a toolbar item: the arrow keys skip it and it takes its own tab stop, which is the inconsistency Toolbar exists to remove. Use ToolbarButton, ToolbarInput, or render through ToolbarButton.
|
|
20
|
+
- Don't: Wrap one or two buttons, or a dialog's Cancel/Confirm pair, in a Toolbar. Those are independent actions; arrow-key movement there is a surprise, not a shortcut. A plain row of Buttons is right.
|
|
21
|
+
- Don't: Use Toolbar for navigation between pages or views. Links that go somewhere are Tabs, SideNav or Breadcrumbs; ToolbarLink is for the odd link that sits among actions, like Help.
|
|
22
|
+
- Don't: Give toolbar buttons primary or destructive fills by default. A toolbar is a quiet strip of tools; the ghost default keeps it from competing with the page's one primary action.
|
|
23
|
+
|
|
24
|
+
### Anatomy
|
|
25
|
+
|
|
26
|
+
| Part | Required | Description |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| Toolbar | yes | The root (role="toolbar"). Owns orientation, loopFocus and disabled for everything inside it. |
|
|
29
|
+
| ToolbarButton | yes | An action. Button's ghost variants by default; `render` composes a menu trigger, popover trigger or Toggle into the toolbar. |
|
|
30
|
+
| ToolbarGroup | no | A run of related items. `disabled` disables the whole run. |
|
|
31
|
+
| ToolbarSeparator | no | A hairline between groups, oriented across the toolbar. Never a focus stop. |
|
|
32
|
+
| ToolbarInput | no | A compact Input that joins the arrow-key order — a search or find field in the bar. |
|
|
33
|
+
| ToolbarLink | no | An <a> styled like a ToolbarButton, for a link that sits among the actions. |
|
|
34
|
+
|
|
35
|
+
## Subcomponents
|
|
36
|
+
|
|
37
|
+
| Name | Type | Description |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| ToolbarButton | { variant?: buttonVariants variant (default "ghost"); size?: buttonVariants size (default "sm"); disabled?: boolean; focusableWhenDisabled?: boolean (default true); render?: ReactElement } + Base UI Toolbar.Button props | A toolbar action, styled exactly like <Button>. Disabled buttons stay focusable by default and carry aria-disabled. Pass size="icon-sm"/"icon-xs" plus aria-label for icon-only. |
|
|
40
|
+
| ToolbarLink | { href?: string; variant?: buttonVariants variant (default "ghost"); size?: buttonVariants size (default "sm"); render?: ReactElement } + Base UI Toolbar.Link props | An <a> in the toolbar's arrow-key order, styled like a ToolbarButton. |
|
|
41
|
+
| ToolbarInput | { disabled?: boolean; focusableWhenDisabled?: boolean (default true) } + Base UI Toolbar.Input / native input props | Sable's Input at the sm control height (w-40 by default; override with className). Arrow keys leave the field so it does not trap the row. |
|
|
42
|
+
| ToolbarGroup | { disabled?: boolean } + Base UI Toolbar.Group props | Groups related items; `disabled` disables all of them. |
|
|
43
|
+
| ToolbarSeparator | Base UI Toolbar.Separator props + className | A hairline-strong rule across the toolbar's axis, exposed as role="separator" and skipped by the arrow keys. |
|
|
44
|
+
|
|
45
|
+
## Props
|
|
46
|
+
|
|
47
|
+
| Name | Type | Default | Description |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| orientation | "horizontal" \| "vertical" | "horizontal" | Which arrow keys move focus (Left/Right or Up/Down) and which way the items stack. Separators turn to match. |
|
|
50
|
+
| loopFocus | boolean | true | Arrowing past the last item wraps to the first, and back. |
|
|
51
|
+
| disabled | boolean | false | Disables every item. Items stay focusable (aria-disabled) so the row keeps its shape under the keyboard. |
|
|
52
|
+
| aria-label | string | — | Names the toolbar for assistive technology. Always set it. |
|
|
53
|
+
| className | string | — | Layout overrides — e.g. `flex-1` to let the toolbar fill a bar. |
|
|
54
|
+
|
|
55
|
+
## Variants
|
|
56
|
+
|
|
57
|
+
- **orientation**: horizontal, vertical
|
|
58
|
+
- **variant**: ghost, secondary, outline, primary, destructive, link
|
|
59
|
+
- **size**: xs, sm, md, icon, icon-sm, icon-xs
|
|
60
|
+
|
|
61
|
+
## Tokens used
|
|
62
|
+
|
|
63
|
+
`foreground`, `accent`, `accent-foreground`, `ring`, `hairline-strong`, `input`, `card`
|
|
64
|
+
|
|
65
|
+
## Motion
|
|
66
|
+
|
|
67
|
+
- **arrival** · Toolbar · _no motion_ — A toolbar is fixed chrome. It is present when its surface is; animating it in would leave the tools briefly unusable for no reason the user can see.
|
|
68
|
+
- **response** · ToolbarButton · `motion-interactive` — Inherited from buttonVariants: hover fill, focus ring and disabled dim ease together, so the item under the arrow keys reads as the one that moved.
|
|
69
|
+
- **response** · ToolbarButton · `motion-press` — Inherited from buttonVariants. Many toolbar actions change something off to the side (the canvas, the selection), so the settle on press is the confirmation that lands where the pointer is.
|
|
70
|
+
- **transition** · Toolbar · _no motion_ — The toolbar has no state of its own. Pressed state belongs to a Toggle rendered through ToolbarButton, which carries its own transition.
|
|
71
|
+
- **activity** · Toolbar · _no motion_ — A toolbar never works on its own. Pending work belongs to the surface the action targets, or to a Spinner in the button that started it.
|
|
72
|
+
|
|
73
|
+
## Accessibility
|
|
74
|
+
|
|
75
|
+
- Base UI renders role="toolbar" with aria-orientation, and a roving tabindex: the toolbar is one tab stop, the arrow keys move between items, Home/End jump to the ends.
|
|
76
|
+
- Disabled items stay focusable by default and announce as disabled (aria-disabled), so arrowing along the row never silently skips a control. Set focusableWhenDisabled={false} on an item to opt out.
|
|
77
|
+
- ToolbarSeparator is role="separator" with the orientation opposite the toolbar's, and is not a focus stop.
|
|
78
|
+
- ToolbarInput lets the arrow keys move the caret and only leaves the field at either end of its text.
|
|
79
|
+
- Always set aria-label on the Toolbar, and on every icon-only ToolbarButton.
|
|
80
|
+
- A Toggle rendered through ToolbarButton keeps aria-pressed; a MenuTrigger keeps aria-haspopup/aria-expanded.
|
|
81
|
+
|
|
82
|
+
## Related
|
|
83
|
+
|
|
84
|
+
- Components: Button, Toggle, Menu, Popover, Tooltip, Workspace, PanelSection, Separator
|
|
85
|
+
|
|
86
|
+
## Examples
|
|
87
|
+
|
|
88
|
+
### Formatting toolbar
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { Icon, Toggle, ToggleGroup, Toolbar, ToolbarButton, ToolbarGroup, ToolbarLink, ToolbarSeparator } from "@sableui/react"
|
|
92
|
+
|
|
93
|
+
<Toolbar aria-label="Text formatting">
|
|
94
|
+
<ToolbarGroup aria-label="History">
|
|
95
|
+
<ToolbarButton size="icon-sm" aria-label="Undo">
|
|
96
|
+
<Icon name="arrow-left" />
|
|
97
|
+
</ToolbarButton>
|
|
98
|
+
<ToolbarButton size="icon-sm" aria-label="Redo">
|
|
99
|
+
<Icon name="arrow-right" />
|
|
100
|
+
</ToolbarButton>
|
|
101
|
+
</ToolbarGroup>
|
|
102
|
+
<ToolbarSeparator />
|
|
103
|
+
<ToggleGroup multiple aria-label="Style">
|
|
104
|
+
<ToolbarButton size="icon-sm" aria-label="Bold" render={<Toggle value="bold" />}>
|
|
105
|
+
<Icon name="bold" />
|
|
106
|
+
</ToolbarButton>
|
|
107
|
+
<ToolbarButton size="icon-sm" aria-label="Italic" render={<Toggle value="italic" />}>
|
|
108
|
+
<Icon name="italic" />
|
|
109
|
+
</ToolbarButton>
|
|
110
|
+
</ToggleGroup>
|
|
111
|
+
<ToolbarSeparator />
|
|
112
|
+
<ToolbarLink href="/help">Help</ToolbarLink>
|
|
113
|
+
</Toolbar>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### With a menu trigger and a search field
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { Menu, MenuContent, MenuItem, MenuTrigger, Toolbar, ToolbarButton, ToolbarInput, ToolbarSeparator } from "@sableui/react"
|
|
120
|
+
|
|
121
|
+
<Toolbar aria-label="Table">
|
|
122
|
+
<ToolbarButton>Export</ToolbarButton>
|
|
123
|
+
<Menu>
|
|
124
|
+
<ToolbarButton render={<MenuTrigger />}>View</ToolbarButton>
|
|
125
|
+
<MenuContent>
|
|
126
|
+
<MenuItem>Compact rows</MenuItem>
|
|
127
|
+
<MenuItem>Comfortable rows</MenuItem>
|
|
128
|
+
</MenuContent>
|
|
129
|
+
</Menu>
|
|
130
|
+
<ToolbarSeparator />
|
|
131
|
+
<ToolbarInput aria-label="Search rows" placeholder="Search" />
|
|
132
|
+
</Toolbar>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Vertical tool rail
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { Icon, Toolbar, ToolbarButton, ToolbarSeparator } from "@sableui/react"
|
|
139
|
+
|
|
140
|
+
<Toolbar orientation="vertical" aria-label="Tools">
|
|
141
|
+
<ToolbarButton size="icon-sm" aria-label="Select">
|
|
142
|
+
<Icon name="arrow-up" />
|
|
143
|
+
</ToolbarButton>
|
|
144
|
+
<ToolbarButton size="icon-sm" aria-label="Draw">
|
|
145
|
+
<Icon name="edit" />
|
|
146
|
+
</ToolbarButton>
|
|
147
|
+
<ToolbarSeparator />
|
|
148
|
+
<ToolbarButton size="icon-sm" aria-label="Delete" disabled>
|
|
149
|
+
<Icon name="trash" />
|
|
150
|
+
</ToolbarButton>
|
|
151
|
+
</Toolbar>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### In a workspace bar
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
import { Icon, Toolbar, ToolbarButton, WorkspaceBar } from "@sableui/react"
|
|
158
|
+
|
|
159
|
+
<WorkspaceBar>
|
|
160
|
+
<Toolbar aria-label="Document" className="flex-1">
|
|
161
|
+
<ToolbarButton size="xs">File</ToolbarButton>
|
|
162
|
+
<ToolbarButton size="xs">Edit</ToolbarButton>
|
|
163
|
+
<ToolbarButton size="icon-xs" aria-label="Share" className="ms-auto">
|
|
164
|
+
<Icon name="share" />
|
|
165
|
+
</ToolbarButton>
|
|
166
|
+
</Toolbar>
|
|
167
|
+
</WorkspaceBar>
|
|
168
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Hover/focus label over Base UI Tooltip (collision-aware positioning, ARIA wiring, delay grouping via TooltipProvider). Compact popover surface.
|
|
6
|
+
|
|
7
|
+
**Keywords:** tooltip, hint, hover, assistive
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Tooltip for brief nonessential hints tied to a specific control or piece of text.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it to name icon-only controls or clarify compact UI when the label cannot be visible.
|
|
16
|
+
- Do: Keep tooltip content short and noninteractive.
|
|
17
|
+
- Don't: Do not put critical instructions, errors, or interactive content inside Tooltip.
|
|
18
|
+
- Don't: Do not let a tooltip serve as an icon button's accessible name. A tooltip is not a name — without its own aria-label the button announces as "button", and the tooltip never opens for a screen reader at all.
|
|
19
|
+
|
|
20
|
+
### Anatomy
|
|
21
|
+
|
|
22
|
+
| Part | Required | Description |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| TooltipProvider | no | Optional provider for shared tooltip timing. |
|
|
25
|
+
| TooltipTrigger | yes | The element that owns the hint. |
|
|
26
|
+
| TooltipContent | yes | The short hint content. |
|
|
27
|
+
| TooltipArrow | no | An optional pointer toward the trigger. |
|
|
28
|
+
| SimpleTooltip | no | A convenience wrapper for common tooltip usage. |
|
|
29
|
+
|
|
30
|
+
## Subcomponents
|
|
31
|
+
|
|
32
|
+
| Name | Type | Description |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| TooltipProvider | Base UI Tooltip.Provider props (delay, closeDelay, timeout) | Wraps an app or group of Tooltips to share open/close delays. Once one tooltip is open, adjacent tooltips open instantly. |
|
|
35
|
+
| TooltipTrigger | Base UI Trigger props + render | The anchor element. Use render={<Button/>} to compose with another component. |
|
|
36
|
+
| TooltipContent | Base UI Popup props + side \| align \| sideOffset + className | Portals + positions + paints the popup. side ("top" \| "bottom" \| "left" \| "right" \| "inline-*") picks which side of the trigger to render against, defaults to "top", and auto-flips on collision; align ("start" \| "center" \| "end") defaults to "center"; sideOffset (gap between trigger and popup, in pixels) defaults to 6. |
|
|
37
|
+
| TooltipArrow | Base UI Arrow props + className | Optional pointer connecting the popup to its trigger — a popover-coloured square turned 45° that carries the border on its outward edges. Place inside TooltipContent. |
|
|
38
|
+
| SimpleTooltip | Base UI Tooltip.Root props + content: ReactNode + children: ReactElement + side \| align \| sideOffset | Shorthand wrapping Root + Trigger + Content for the common case. The single child becomes the trigger (via Base UI render); content sets the body; side / align / sideOffset forward to the content with the same defaults. Composition parts remain for advanced use. |
|
|
39
|
+
|
|
40
|
+
## Props
|
|
41
|
+
|
|
42
|
+
| Name | Type | Default | Description |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| side | "top" \| "right" \| "bottom" \| "left" | "top" | On TooltipContent: which side of the trigger to render against. Flips automatically when there is no room. |
|
|
45
|
+
| Tooltip (Root) | Base UI Tooltip.Root props (open, defaultOpen, onOpenChange, disabled) | — | Groups the trigger and content. Uncontrolled by default. |
|
|
46
|
+
|
|
47
|
+
## Tokens used
|
|
48
|
+
|
|
49
|
+
`popover`, `popover-foreground`, `border-floating`
|
|
50
|
+
|
|
51
|
+
## Motion
|
|
52
|
+
|
|
53
|
+
- **arrival** · Content · `motion-pop` — The transform-origin follows the anchor, so the tooltip appears to grow out of the element it describes. That is what binds it to its trigger — a bubble that materialises at full size near two possible triggers belongs to neither.
|
|
54
|
+
- **response** · Content · _no motion_ — The tooltip is not a hover target; pointing at it is how you dismiss it. Styling it for the pointer would suggest otherwise.
|
|
55
|
+
- **transition** · Content · `motion-pop` — Same recipe, exit direction — it collapses back toward the anchor. Content that fades from a fixed size on close reads as a second, unrelated element disappearing.
|
|
56
|
+
- **activity** · Content · _no motion_ — Tooltip copy is resident before the tooltip opens. Anything that has to be fetched is a Popover with its own loading state.
|
|
57
|
+
|
|
58
|
+
## Accessibility
|
|
59
|
+
|
|
60
|
+
- Base UI wires aria-describedby from trigger to popup and manages hover + focus open.
|
|
61
|
+
- Opens on keyboard focus, not just hover; dismisses on Esc and blur.
|
|
62
|
+
- Animate via data-[starting-style]/data-[ending-style] opacity attributes.
|
|
63
|
+
- Tooltip text is supplementary — never put essential-only actions inside a tooltip.
|
|
64
|
+
|
|
65
|
+
## Related
|
|
66
|
+
|
|
67
|
+
- Components: Popover, ContextCard
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
### Basic
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { Button, Icon, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@sableui/react"
|
|
75
|
+
|
|
76
|
+
<TooltipProvider><Tooltip><TooltipTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Info"><Icon name="info" /></Button>} /><TooltipContent>Stored permanently on Arweave.</TooltipContent></Tooltip></TooltipProvider>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Sides
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@sableui/react"
|
|
83
|
+
|
|
84
|
+
<Tooltip><TooltipTrigger render={<Button variant="secondary">Right</Button>} /><TooltipContent side="right">Opens to the right</TooltipContent></Tooltip>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Shorthand (SimpleTooltip)
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Button, Icon, SimpleTooltip } from "@sableui/react"
|
|
91
|
+
|
|
92
|
+
<SimpleTooltip content="Stored permanently on Arweave."><Button variant="ghost" size="icon-sm" aria-label="Info"><Icon name="info" /></Button></SimpleTooltip>
|
|
93
|
+
```
|