@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,75 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Date picker grid wrapping react-day-picker v9, themed entirely with semantic tokens (no external stylesheet). Supports single, multiple, and range selection; nav chevrons render through the injected icon set.
|
|
6
|
+
|
|
7
|
+
**Keywords:** calendar, date grid, month, schedule
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Calendar to browse or choose dates in a month grid, especially when relative date position matters.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it when users need to compare weekdays, ranges, or nearby dates.
|
|
16
|
+
- Do: Provide selected, disabled, and today states when date choice is interactive.
|
|
17
|
+
- Don't: Do not use Calendar when a compact DatePicker (with granularity="minute" for a time) would be faster.
|
|
18
|
+
- Don't: Do not use it as the only way to enter a date. A grid is slow for a date far from today and unusable for someone who knows the value — always pair it with a text input.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Name | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| mode | "single" \| "multiple" \| "range" | — | Selection behavior. Pair with the matching selected/onSelect shape (Date, Date[], or DateRange). |
|
|
25
|
+
| selected | Date \| Date[] \| DateRange \| undefined | — | The controlled selection. Shape follows mode. |
|
|
26
|
+
| onSelect | (value, triggerDate, modifiers, event) => void | — | Fires when a day is chosen. The first argument's type follows mode. |
|
|
27
|
+
| defaultMonth | Date | — | The month shown on first render (uncontrolled navigation). |
|
|
28
|
+
| month | Date | — | Controlled displayed month; pair with onMonthChange. |
|
|
29
|
+
| numberOfMonths | number | 1 | How many month grids to render side by side. |
|
|
30
|
+
| disabled | Matcher \| Matcher[] | — | Days matching this are dimmed and non-selectable (e.g. { before: new Date() }). |
|
|
31
|
+
| showOutsideDays | boolean | true | Render leading/trailing days from adjacent months, dimmed. |
|
|
32
|
+
| bare | boolean | false | Drop the card frame (border, fill, padding) so the grid sits flush inside a surface that already has one, such as a Popover. DatePicker renders its Calendar this way. |
|
|
33
|
+
| className | string | — | Merged onto the bordered card container that wraps the grid. |
|
|
34
|
+
| classNames | Partial<ClassNames> | — | Per-part class overrides merged over the themed defaults. Themed slots: months, month, nav, button_previous, button_next, month_caption, caption_label, month_grid, weekdays, weekday, week, day, day_button, range_start, range_middle, range_end, selected, today, outside, disabled, hidden. Selection/state slots (selected/today/range_*/outside/disabled) style the day CELL; the inner day button is targeted via the [&>button] descendant. |
|
|
35
|
+
| components | Partial<CustomComponents> | — | Render overrides for RDP parts. Chevron is supplied by default and merged with yours. |
|
|
36
|
+
|
|
37
|
+
## Variants
|
|
38
|
+
|
|
39
|
+
- **mode**: single, multiple, range
|
|
40
|
+
|
|
41
|
+
## Tokens used
|
|
42
|
+
|
|
43
|
+
`card`, `card-foreground`, `border`, `primary`, `primary-foreground`, `accent`, `accent-foreground`, `muted-foreground`, `ring`
|
|
44
|
+
|
|
45
|
+
## Accessibility
|
|
46
|
+
|
|
47
|
+
- react-day-picker manages the roving-tabindex grid, arrow-key navigation, and ARIA roles/labels for the calendar.
|
|
48
|
+
- Selected days use bg-primary/text-primary-foreground for AA contrast; today is marked with a dot under the number (not a ring, which is reserved for keyboard focus) rather than color alone.
|
|
49
|
+
- Disabled and outside days are dimmed and removed from the tab order.
|
|
50
|
+
- Nav chevrons resolve through the injected IconProvider; ensure chevron-left and chevron-right are registered in the icon set.
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- Components: DatePicker
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
### Single date
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { Calendar } from "@sableui/react"
|
|
62
|
+
|
|
63
|
+
const [date, setDate] = useState<Date>()
|
|
64
|
+
<Calendar mode="single" selected={date} onSelect={setDate} />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Range
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { Calendar } from "@sableui/react"
|
|
71
|
+
import type { DateRange } from "react-day-picker"
|
|
72
|
+
|
|
73
|
+
const [range, setRange] = useState<DateRange>()
|
|
74
|
+
<Calendar mode="range" selected={range} onSelect={setRange} numberOfMonths={2} />
|
|
75
|
+
```
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Card
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Raised surface container (rounded-lg) with optional Header/Title/Description/Action/Content/Footer parts. A density knob trades the part padding (comfortable vs compact) for denser section panels. `render` makes the whole card one link or button, with a hover, a full-card focus ring and motion.
|
|
6
|
+
|
|
7
|
+
**Keywords:** card, surface, panel, container, tile, section, header, footer, settings, link card, clickable card, nav card
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Card for a discrete thing with its own boundary, such as one product, one notification, one metric, or one setting cluster. Do not use Card as the default way to group every page section; whitespace, headings, Stack, Section, and dividers often create cleaner hierarchy.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use Card when the item could be reordered, removed, selected, or compared independently.
|
|
16
|
+
- Do: Keep sibling cards on the same density and padding rhythm so lists and grids scan cleanly.
|
|
17
|
+
- Do: Use CardHeader, CardContent, and CardFooter when the card has a stable title/body/action structure.
|
|
18
|
+
- Do: Make the whole card one link (`render={<a href="…" />}`) when the card IS a single destination — a nav tile, a gallery item, a news teaser — and nothing inside it is interactive. All of the card text becomes the accessible name of the link, so keep it short: a title and a line.
|
|
19
|
+
- Don't: Make a card a link when it holds its own buttons, menus, switches or links. Interactive content nested in a link is invalid HTML and announces twice; keep the card a plain surface and put the link on its title or in CardFooter instead.
|
|
20
|
+
- Don't: Wrap every settings section or form group in a card; use headings, Stack, or the SettingsPage pattern instead.
|
|
21
|
+
- Don't: Nest cards inside cards. Flatten the hierarchy or use dividers and spacing inside one card.
|
|
22
|
+
|
|
23
|
+
### Anatomy
|
|
24
|
+
|
|
25
|
+
| Part | Required | Description |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Container | yes | The raised or flat surface that creates the item boundary. |
|
|
28
|
+
| Header | no | Optional title, description, and aligned action area for the card's identity. |
|
|
29
|
+
| Content | yes | The body region. It should contain the information or controls that belong to this one item. |
|
|
30
|
+
| Footer | no | Optional action or metadata row, usually lower-emphasis than the header. |
|
|
31
|
+
|
|
32
|
+
## Subcomponents
|
|
33
|
+
|
|
34
|
+
| Name | Type | Description |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| CardHeader | ComponentPropsWithRef<"div"> | Top section holding CardTitle/CardDescription (and optionally CardAction). Padding follows the density knob. |
|
|
37
|
+
| CardTitle | ComponentPropsWithRef<"div"> | The card's name (text-title-lg). |
|
|
38
|
+
| CardDescription | ComponentPropsWithRef<"div"> | Muted supporting line under the title. |
|
|
39
|
+
| CardAction | ComponentPropsWithRef<"div"> | Header-aligned top-right action slot (e.g. a dots menu, switch, or close button). Place inside CardHeader, which becomes a 2-column grid when present and collapses to a single column otherwise. |
|
|
40
|
+
| CardContent | ComponentPropsWithRef<"div"> & { flush?: boolean } | The body region. Padding follows the density knob on all sides — does not assume a preceding CardHeader, so it is safe to use standalone (e.g. no CardHeader in the card). When paired with CardHeader, the seam stays tight because CardHeader zeroes its own bottom padding. `flush` runs it edge to edge for a table: the Table insets its first and last columns by exactly the card padding, so it lines up with the header — never hand-set pl-6/pr-6 on columns. |
|
|
41
|
+
| CardList | ComponentPropsWithRef<"div"> | Rows that run edge to edge in the card (Entity rows, most often), divided by hairlines. Insets each row to the card's own padding, so a list card lines up with every other card at any density — no hand-tuned px-* on the rows. Under a CardHeader it opens with a hairline and a gap; alone it fills the card. |
|
|
42
|
+
| CardFooter | ComponentPropsWithRef<"div"> | Bottom row for actions/meta. Padding follows the density knob. |
|
|
43
|
+
|
|
44
|
+
## Props
|
|
45
|
+
|
|
46
|
+
| Name | Type | Default | Description |
|
|
47
|
+
| --- | --- | --- | --- |
|
|
48
|
+
| variant | "default" \| "flat" | "default" | "default" raises with bg-card + border + soft shadow; "flat" is transparent. |
|
|
49
|
+
| density | "comfortable" \| "compact" | "comfortable" | Padding of the Header/Content/Footer parts: comfortable = p-6 (default), compact = p-4 for denser section panels (pair compact with the flat variant for a settings / form section panel). Threaded to the parts via the inherited --card-p custom property. Card sets --card-p via inline style, which beats a plain (non-!important) stylesheet override of the same property on the same element — use this prop rather than a className override; if an external override is unavoidable, it needs !important. |
|
|
50
|
+
| render | useRender.RenderProp | — | Replace the rendered element through the Base UI render prop — most often `render={<a href="/x" />}` (or a router Link) to make the whole card one link. When the rendered element is a link (a[href]) or a button, the card gains a hover state (stronger border and shadow; a muted fill when flat), a full-card focus-visible ring and motion-interactive easing. Any other element (article, li) stays a static surface. Children can answer the hover with `group-hover/card:`. |
|
|
51
|
+
|
|
52
|
+
## Variants
|
|
53
|
+
|
|
54
|
+
- **variant**: default, flat
|
|
55
|
+
- **density**: comfortable, compact
|
|
56
|
+
|
|
57
|
+
## Tokens used
|
|
58
|
+
|
|
59
|
+
`card`, `card-foreground`, `border`, `border-strong`, `muted`, `muted-foreground`, `ring`
|
|
60
|
+
|
|
61
|
+
## Motion
|
|
62
|
+
|
|
63
|
+
- **arrival** · Container · _no motion_ — A card is part of the layout it ships in; whatever stages that region owns the entrance. A card that fades in on its own delays reading the thing it holds.
|
|
64
|
+
- **response** · Container · `motion-interactive` — Only when the card is rendered as a link or button. The border, shadow and ring ease in so the pointer reads as landing on one target, not as the grid flickering. No motion-press: a 0.97 settle on a surface this large reads as the layout lurching. A static card has no response, so it gets no transition.
|
|
65
|
+
- **transition** · Container · _no motion_ — A card has no states of its own to move between. Selection is CheckboxCard or RadioCard; disclosure is Collapsible or Accordion.
|
|
66
|
+
- **activity** · Container · _no motion_ — A card does not work on anything. Loading content inside it is a Skeleton's job.
|
|
67
|
+
|
|
68
|
+
## Accessibility
|
|
69
|
+
|
|
70
|
+
- Purely presentational by default; add headings/landmarks in content as needed.
|
|
71
|
+
- Rendered as a link or button, the whole card is one target: its full text is the accessible name, and the focus ring wraps the whole card. Nothing interactive may sit inside it — nested links or buttons inside a link are invalid and unreachable for keyboard users in some browsers.
|
|
72
|
+
- A Card is a container, not a landmark and not a heading. If its title is the only heading in the region, render it as a real heading element with a title tier — a visually-styled div gives screen-reader heading navigation nothing to find.
|
|
73
|
+
|
|
74
|
+
## Related
|
|
75
|
+
|
|
76
|
+
- Components: Region, Stack, Separator, Button, Badge
|
|
77
|
+
- Patterns: DashboardPage, SettingsPage, ListDetailPage
|
|
78
|
+
|
|
79
|
+
## Examples
|
|
80
|
+
|
|
81
|
+
### With parts
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sableui/react"
|
|
85
|
+
|
|
86
|
+
<Card><CardHeader><CardTitle>Edition</CardTitle><CardDescription>Limited run of 100.</CardDescription></CardHeader><CardContent><p className="text-body-md">Each collector receives a signed print and the on-chain edition.</p></CardContent></Card>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### With a header action
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { Button, Card, CardAction, CardContent, CardHeader, CardTitle, Icon } from "@sableui/react"
|
|
93
|
+
|
|
94
|
+
<Card><CardHeader><CardTitle>Edition</CardTitle><CardAction><Button variant="ghost" size="icon-sm" aria-label="More"><Icon name="ellipsis" /></Button></CardAction></CardHeader><CardContent><p className="text-body-md text-muted-foreground">24 of 100 collected.</p></CardContent></Card>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Compact section panel
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@sableui/react"
|
|
101
|
+
|
|
102
|
+
<Card variant="flat" density="compact"><CardHeader><CardTitle>Notifications</CardTitle></CardHeader><CardContent><p className="text-body-sm text-muted-foreground">Push and email are enabled.</p></CardContent></Card>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Product card with actions
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { Badge, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@sableui/react"
|
|
109
|
+
|
|
110
|
+
<Card><CardHeader><CardTitle>Midnight Archive</CardTitle><CardDescription>Edition #24 of 100</CardDescription><CardAction><Badge tone="edition">Edition</Badge></CardAction></CardHeader><CardContent><p className="text-body-sm text-muted-foreground">A signed print and token-gated download for collectors.</p></CardContent><CardFooter><Button size="sm">Collect</Button><Button size="sm" variant="ghost">Preview</Button></CardFooter></Card>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Whole card as a link
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { Card, CardContent, CardDescription, CardTitle, Icon } from "@sableui/react"
|
|
117
|
+
|
|
118
|
+
<Card render={<a href="/components" />}><CardContent className="flex items-center justify-between gap-4"><div className="min-w-0"><CardTitle>Components</CardTitle><CardDescription>Every building block, with live examples.</CardDescription></div><Icon name="chevron-right" className="text-sm text-muted-foreground motion-interactive group-hover/card:translate-x-0.5 group-hover/card:text-foreground" /></CardContent></Card>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Metric card
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sableui/react"
|
|
125
|
+
|
|
126
|
+
<Card density="compact"><CardHeader><CardTitle>Revenue</CardTitle><CardDescription>Last 30 days</CardDescription></CardHeader><CardContent><p className="text-heading-3">128.4 SOL</p><p className="text-body-sm text-success">+12.8% from previous period</p></CardContent></Card>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### List card
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { Avatar, Card, CardDescription, CardHeader, CardList, CardTitle, Entity } from "@sableui/react"
|
|
133
|
+
|
|
134
|
+
<Card><CardHeader><CardTitle>Members</CardTitle><CardDescription>3 people can edit this space.</CardDescription></CardHeader><CardList>{people.map((p) => <Entity key={p.id} interactive onSelect={() => open(p)} leading={<Avatar fallback={p.initials} alt={p.name} />} title={p.name} subtitle={p.role} />)}</CardList></Card>
|
|
135
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Carousel
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Generic, accessible, zero-dependency slide carousel. Transform track with measured slide pitch — multiple slides per view, infinite loop (cloned ends), pointer drag-to-swipe with snap, autoplay (with a pause control + hover/focus/reduced-motion pausing), arrows, dots, counter, and keyboard. Motion is token-driven.
|
|
6
|
+
|
|
7
|
+
**Keywords:** carousel, gallery, slides, horizontal
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Carousel for a curated, finite sequence of media or cards that benefits from horizontal browsing.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use obvious previous and next controls and make the current position understandable.
|
|
16
|
+
- Do: Keep slide content independent so users do not need every slide to complete a task.
|
|
17
|
+
- Don't: Do not hide critical information or primary conversion actions only inside later slides.
|
|
18
|
+
- Don't: Do not autoplay without a visible pause control. Motion that cannot be stopped fails WCAG 2.2.2 and makes the surrounding text hard to read.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Name | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| children | ReactNode | — | Each top-level child becomes one slide. |
|
|
25
|
+
| slidesToShow | number \| { base?: number; sm?: number; md?: number; lg?: number; xl?: number } | 1 | Slides visible per view. A number, or a mobile-first breakpoint set keyed to Tailwind's default min-widths (sm 640, md 768, lg 1024, xl 1280px); a missing key inherits from the next smaller defined one, and base (default 1) is also the server-render value. Resolved with matchMedia, so no hand-rolled resize hook is needed. |
|
|
26
|
+
| gap | string | "0px" | Gap between slides (any CSS length). Use the spacing scale — e.g. "calc(var(--spacing) * 4)" — so density moves it. |
|
|
27
|
+
| loop | boolean | false | Infinite loop via cloned ends (needs count > slidesToShow). |
|
|
28
|
+
| autoplay | number | 0 | Auto-advance interval in ms (0 = off). Pauses on hover/focus/reduced-motion and exposes a play/pause control. |
|
|
29
|
+
| arrows | boolean | true | Prev/next arrow buttons. |
|
|
30
|
+
| dots | boolean | true | Pagination dots. |
|
|
31
|
+
| counter | boolean | false | An N / M slide counter. |
|
|
32
|
+
| draggable | boolean | true | Pointer drag-to-swipe. |
|
|
33
|
+
| index | number | — | Controlled active index. |
|
|
34
|
+
| defaultIndex | number | 0 | Initial index (uncontrolled). |
|
|
35
|
+
| onIndexChange | (index: number) => void | — | Notified when the active index changes. |
|
|
36
|
+
| label | string | — | Accessible label for the carousel region. |
|
|
37
|
+
| slideClassName | string | — | Class for each slide wrapper (e.g. an aspect ratio). |
|
|
38
|
+
| arrowClassName | string | — | Class for both prev/next arrow buttons. Merged through cn(), so a radius or shadow utility replaces the default rounded-full shadow-md rather than stacking with it. |
|
|
39
|
+
|
|
40
|
+
## Tokens used
|
|
41
|
+
|
|
42
|
+
`border`, `ring`, `popover`, `popover-foreground`, `foreground`, `muted-foreground`
|
|
43
|
+
|
|
44
|
+
## Accessibility
|
|
45
|
+
|
|
46
|
+
- Region carries aria-roledescription='carousel'; each slide is a group with aria-roledescription='slide' and an 'N of M' label; clones are aria-hidden.
|
|
47
|
+
- Arrows and dots are real buttons (dots set aria-current); Left/Right arrow keys navigate when the viewport is focused.
|
|
48
|
+
- A polite live region announces the active slide; it goes silent while auto-rotating (APG).
|
|
49
|
+
- Autoplay exposes a pause/play control and pauses on hover, focus, and under prefers-reduced-motion (which also makes transitions instant).
|
|
50
|
+
|
|
51
|
+
## Related
|
|
52
|
+
|
|
53
|
+
- Components: Scroller, Dialog
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### Looping media
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Carousel } from "@sableui/react"
|
|
61
|
+
|
|
62
|
+
<Carousel loop dots slideClassName="aspect-video">{images.map((s) => <img key={s.id} src={s.url} alt={s.alt} className="size-full object-cover" />)}</Carousel>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Multiple per view
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { Carousel } from "@sableui/react"
|
|
69
|
+
|
|
70
|
+
<Carousel slidesToShow={3} gap="calc(var(--spacing) * 4)">{cards}</Carousel>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Responsive per view
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { Carousel } from "@sableui/react"
|
|
77
|
+
|
|
78
|
+
<Carousel slidesToShow={{ base: 1, sm: 2, lg: 3 }} gap="calc(var(--spacing) * 6)" label="Templates">{cards}</Carousel>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Autoplay
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Carousel } from "@sableui/react"
|
|
85
|
+
|
|
86
|
+
<Carousel loop autoplay={4000} counter>{slides}</Carousel>
|
|
87
|
+
```
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# ChatComposer
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
The input half of a conversation: one surface holding attachments, an auto-growing field, and its controls. Enter sends, Shift+Enter breaks the line, and send becomes stop while a reply streams.
|
|
6
|
+
|
|
7
|
+
**Keywords:** chat input, composer, message input, prompt input, send message, ai chat, support chat, textarea
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Pairs with MessageScroller to make a working chat. A single surface rather than an input with buttons parked beside it: attachments above, text in the middle, affordances along the bottom. Works controlled or uncontrolled — uncontrolled is the default because it is what the common case wants, and what lets a hook-free page shell ship a composer that actually types.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Pass `busy` while a reply streams, and `onStop` alongside it. Interrupting a long generation is the most common thing a reader wants during one, and the control belongs where their cursor already is.
|
|
16
|
+
- Do: Leave it uncontrolled unless you need the text elsewhere — a slash-command menu, a draft you persist. Uncontrolled clears itself on send; controlled does not.
|
|
17
|
+
- Do: Render pending uploads as `<Attachment>` chips in `attachments`, so the file and the message it belongs to stay in one box.
|
|
18
|
+
- Do: Put model pickers and tool toggles in `leading`, beside the attach button — they are composer state, not message content, and belong on the chrome row rather than in the field.
|
|
19
|
+
- Do: Building a docked agent panel inside a tool? Wrap the whole conversation — scroller, messages and composer — in `<Density value="compact">`. The entire set follows the workspace register from there: prose drops a tier, the rhythm tightens, and the controls come off the compact ladder. Do not hand-size it at the call site.
|
|
20
|
+
- Don't: Do not clear a controlled composer for the user on send. Only the caller knows whether the send succeeded, and clearing on their behalf loses the message when it did not.
|
|
21
|
+
- Don't: Do not add your own Enter-to-send handler on top. The built-in one already guards against firing mid-IME-composition, which is the line hand-rolled chat inputs miss.
|
|
22
|
+
- Don't: Do not use it as a general multi-line input. That is `Textarea` — this one submits on Enter, which is wrong for any field inside a larger form.
|
|
23
|
+
- Don't: Do not override the field's type utility to make it match your transcript. The field and `Bubble` both read the same tier already, which is what stops a message changing size the moment it is sent — overriding one breaks that pairing.
|
|
24
|
+
- Don't: Do not remove the height cap to let the field grow without limit. A pasted essay would push the conversation off the screen; past the cap it scrolls.
|
|
25
|
+
|
|
26
|
+
### Anatomy
|
|
27
|
+
|
|
28
|
+
| Part | Required | Description |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| Attachments | no | Pending files as chips, above the field, inside the same surface. |
|
|
31
|
+
| Field | yes | An auto-growing Textarea, stripped of its own border and ring — the surface carries both — capped before it eats the transcript. |
|
|
32
|
+
| Controls | yes | Attach and any `leading` controls on the left; `actions` and the send/stop button on the right. |
|
|
33
|
+
| Footer | no | Helper text or a character counter below the controls. |
|
|
34
|
+
|
|
35
|
+
## Props
|
|
36
|
+
|
|
37
|
+
| Name | Type | Default | Description |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| value | string | — | Controlled value. Omit for an uncontrolled composer that owns its own text. |
|
|
40
|
+
| defaultValue | string | "" | Initial value when uncontrolled. |
|
|
41
|
+
| onValueChange | (value: string) => void | — | Notified on every keystroke. Required when `value` is supplied. |
|
|
42
|
+
| onSend | (value: string) => void | — | Called with the trimmed message. An uncontrolled composer clears itself afterwards; a controlled one is the caller's to clear. |
|
|
43
|
+
| placeholder | string | "Send a message…" | Placeholder for the field. |
|
|
44
|
+
| busy | boolean | false | A reply is streaming. Disables send, and swaps it for a stop control when `onStop` is supplied. |
|
|
45
|
+
| onStop | () => void | — | Interrupt the in-flight reply. Supplying it turns send into stop while busy. |
|
|
46
|
+
| disabled | boolean | false | Block sending entirely — over a limit, disconnected, read-only thread. Distinct from `busy`, which is temporary and offers a way out (stop). |
|
|
47
|
+
| invalid | boolean | false | Mark the current text invalid. Colours the surface border and sets `aria-invalid` on the field. |
|
|
48
|
+
| attachments | ReactNode | — | Attachment chips, shown above the field. |
|
|
49
|
+
| leading | ReactNode | — | Controls on the left of the footer row — model picker, tool toggles. |
|
|
50
|
+
| actions | ReactNode | — | Extra controls left of the send button. |
|
|
51
|
+
| onAttach | () => void | — | Attach handler. Supplying it renders the built-in attach button. |
|
|
52
|
+
| label | string | "Message" | Accessible label for the field. |
|
|
53
|
+
| sendLabel | string | "Send message" | Accessible label for the send button. |
|
|
54
|
+
| stopLabel | string | "Stop generating" | Accessible label for the stop button. |
|
|
55
|
+
| footer | ReactNode | — | Helper text or counter below the controls. |
|
|
56
|
+
| fieldRef | Ref<HTMLTextAreaElement> | — | Ref to the underlying textarea — for focusing after a send, or on mount. |
|
|
57
|
+
| ...form props | Omit<ComponentPropsWithRef<"form">, "onSubmit" \| "defaultValue"> | — | Spread onto the form element. Use `onSend` rather than `onSubmit`. |
|
|
58
|
+
|
|
59
|
+
## Variants
|
|
60
|
+
|
|
61
|
+
- **invalid**: true, false
|
|
62
|
+
|
|
63
|
+
## Tokens used
|
|
64
|
+
|
|
65
|
+
`card`, `input`, `ring`, `destructive`, `foreground`, `muted-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`
|
|
66
|
+
|
|
67
|
+
## Accessibility
|
|
68
|
+
|
|
69
|
+
- A real `<form>`, so the send button is a submit button and the whole thing behaves as one control to assistive tech and to password managers alike.
|
|
70
|
+
- Enter never submits while an IME composition is open (`isComposing`), so a CJK reader confirming a candidate word does not mail a fragment of their first sentence.
|
|
71
|
+
- The focus ring is drawn on the surface via `focus-within`, not on the textarea, so the visible focus indicator matches the box a sighted user perceives as the input.
|
|
72
|
+
- Send is disabled — not merely styled down — until there is non-whitespace to send, so the affordance and the behaviour agree for keyboard users.
|
|
73
|
+
- Icon-only attach, send and stop buttons all carry accessible labels, each overridable for localisation.
|
|
74
|
+
|
|
75
|
+
## Related
|
|
76
|
+
|
|
77
|
+
- Components: MessageScroller, Message, Bubble, Attachment, Textarea
|
|
78
|
+
- Patterns: ChatPanel
|
|
79
|
+
|
|
80
|
+
## Examples
|
|
81
|
+
|
|
82
|
+
### Uncontrolled — the common case
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { ChatComposer } from "@sableui/react"
|
|
86
|
+
|
|
87
|
+
<ChatComposer onSend={(text) => send(text)} onAttach={pickFile} />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Streaming, with a stop control
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { ChatComposer } from "@sableui/react"
|
|
94
|
+
|
|
95
|
+
<ChatComposer onSend={send} busy={isStreaming} onStop={stop} placeholder="Ask a follow-up…" />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Controlled, with pending attachments
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { Attachment, Button, ChatComposer, Icon } from "@sableui/react"
|
|
102
|
+
|
|
103
|
+
<ChatComposer
|
|
104
|
+
value={draft}
|
|
105
|
+
onValueChange={setDraft}
|
|
106
|
+
onSend={(text) => { post(text, files); setDraft("") }}
|
|
107
|
+
onAttach={pickFile}
|
|
108
|
+
attachments={files.map((f) => (<Attachment key={f.id} variant="media" name={f.name} thumbnail={f.url} actions={<Button variant="floating" size="icon-xs" aria-label="Remove" onClick={() => drop(f.id)}><Icon name="xmark" /></Button>} />))}
|
|
109
|
+
footer={`${draft.length} / 4000`}
|
|
110
|
+
invalid={draft.length > 4000}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### A working chat, end to end
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { Bubble, ChatComposer, Marker, Message, MessageScroller } from "@sableui/react"
|
|
118
|
+
|
|
119
|
+
<div className="flex h-128 flex-col gap-3">
|
|
120
|
+
<MessageScroller className="flex-1 min-h-0">
|
|
121
|
+
{messages.map((m) => (<Message key={m.id} messageId={m.id} align={m.mine ? "end" : "start"}><Bubble variant={m.mine ? "primary" : "muted"} align={m.mine ? "end" : "start"}>{m.body}</Bubble></Message>))}
|
|
122
|
+
{streaming ? <Marker icon="spinner" spin shimmer>Thinking…</Marker> : null}
|
|
123
|
+
</MessageScroller>
|
|
124
|
+
<ChatComposer onSend={send} busy={streaming} onStop={stop} />
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# CheckboxCard
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A multi-select selection card — a card-sized Base UI Checkbox with a checkbox box, title, and optional description. The checkbox twin of RadioCard (same tone table). CheckboxGroup lays a column out and can own the selection: give each card a `value` and the group holds the checked values as an array, with an optional parent "select all" Checkbox.
|
|
6
|
+
|
|
7
|
+
**Keywords:** checkbox card, select card, multi select, option card
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use CheckboxCard when an option needs more visual weight, supporting text, or iconography than a plain checkbox row.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it for a small number of options where details help users compare choices.
|
|
16
|
+
- Do: Keep the checked state visible across the full card, not only inside the indicator.
|
|
17
|
+
- Do: Let CheckboxGroup own the selection when the cards answer one question: give each card a `value` and read one array from `onValueChange` instead of wiring a toggle per card.
|
|
18
|
+
- Do: Use a plain Checkbox with `parent` for "select all", and list every child value in the group's `allValues`. The group works out its checked and mixed states.
|
|
19
|
+
- Don't: Do not use CheckboxCard for long lists; use Checkbox rows or a Combobox with `multiple` instead.
|
|
20
|
+
- Don't: Do not put a link or a button inside the card. The whole card is the checkbox's label, so a nested interactive element is either unreachable or steals the click that should toggle it.
|
|
21
|
+
|
|
22
|
+
### Anatomy
|
|
23
|
+
|
|
24
|
+
| Part | Required | Description |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| CheckboxCard | yes | One card-shaped checkbox option. |
|
|
27
|
+
| CheckboxGroup | no | A grouped set of related checkbox cards. Optionally owns the selected values and a parent "select all" checkbox. |
|
|
28
|
+
|
|
29
|
+
## Subcomponents
|
|
30
|
+
|
|
31
|
+
| Name | Type | Description |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| CheckboxGroup | { className?: string; children: ReactNode; value?: string[]; defaultValue?: string[]; onValueChange?: (value: string[]) => void; allValues?: string[]; disabled?: boolean } | Column wrapper (role=group) over Base UI's CheckboxGroup. With no group props it is layout only and each card stays independent. Pass value/defaultValue/onValueChange (string[]) and give each child a `value` to let the group own the selection — works for CheckboxCard and plain Checkbox children alike. Add allValues plus a `<Checkbox parent />` for a select-all box that turns mixed when only some are checked. `disabled` disables every child. onValueChange gets (value, eventDetails). Override the grid via className for multi-column layouts. |
|
|
34
|
+
|
|
35
|
+
## Props
|
|
36
|
+
|
|
37
|
+
| Name | Type | Default | Description |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| onCheckedChange | (checked: boolean, eventDetails) => void | — | Fires with the next checked state (Base UI passes eventDetails second). Pair with `checked` for a controlled card; omit both to let it manage its own state. |
|
|
40
|
+
| title | ReactNode | — | The card's primary label (text-title-sm). |
|
|
41
|
+
| description | ReactNode | — | Optional supporting copy beneath the title (text-body-sm, muted). |
|
|
42
|
+
| icon | ReactNode | — | Optional leading glyph (a solid <Icon>); takes the tone colour. |
|
|
43
|
+
| tone | "neutral" \| "standard" \| "collectible" \| "edition" \| "info" \| "warning" | "neutral" | Semantic colour the checked state carries across the border, checkbox box, and icon. |
|
|
44
|
+
| value | string | — | Names the card inside a group-owned CheckboxGroup: the card is checked when the group's value array includes it. Also the value submitted with a form. Inside a CheckboxGroup, `name` stands in for `value` when `value` is omitted. |
|
|
45
|
+
| checked / onCheckedChange / defaultChecked / disabled | Base UI Checkbox.Root props | — | Standard checkbox state. Without a group-owned value, each card is independent — drive checked/onCheckedChange per item for multi-select. |
|
|
46
|
+
|
|
47
|
+
## Variants
|
|
48
|
+
|
|
49
|
+
- **tone**: neutral, standard, collectible, edition, info, warning
|
|
50
|
+
|
|
51
|
+
## Tokens used
|
|
52
|
+
|
|
53
|
+
`input`, `card`, `ring`, `primary`, `primary-foreground`, `accent`, `foreground`, `muted-foreground`, `tone-standard`, `tone-standard-foreground`, `tone-collectible`, `tone-collectible-foreground`, `tone-edition`, `tone-edition-foreground`, `tone-info`, `tone-info-foreground`, `tone-warning`, `tone-warning-foreground`
|
|
54
|
+
|
|
55
|
+
## Accessibility
|
|
56
|
+
|
|
57
|
+
- Base UI provides the checkbox role, aria-checked, hidden native input, keyboard toggle, and data-[checked]/data-disabled state.
|
|
58
|
+
- The whole card is the hit target; focus draws a 2px outline outside the card (not a ring, so the checked tone ring survives focus); Space toggles the focused card.
|
|
59
|
+
- Selection never relies on colour alone — the filled checkbox box + check mark and the card border all shift.
|
|
60
|
+
- A parent select-all Checkbox gets aria-checked=mixed and aria-controls pointing at its children from Base UI; give it a visible label.
|
|
61
|
+
|
|
62
|
+
## Related
|
|
63
|
+
|
|
64
|
+
- Components: Checkbox, RadioCard
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
### Group-owned selection
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { CheckboxCard, CheckboxGroup } from "@sableui/react"
|
|
72
|
+
|
|
73
|
+
<CheckboxGroup value={selected} onValueChange={setSelected}>{REASONS.map((r) => (<CheckboxCard key={r} value={r} title={r} />))}</CheckboxGroup>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Select all
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { Checkbox, CheckboxCard, CheckboxGroup, Field } from "@sableui/react"
|
|
80
|
+
|
|
81
|
+
<CheckboxGroup allValues={REASONS} value={selected} onValueChange={setSelected}><Field label="All reasons" orientation="horizontal"><Checkbox parent /></Field>{REASONS.map((r) => (<CheckboxCard key={r} value={r} title={r} />))}</CheckboxGroup>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### A single independent card
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { CheckboxCard } from "@sableui/react"
|
|
88
|
+
|
|
89
|
+
<CheckboxCard title="Email me a receipt" description="Sent when the mint settles" checked={receipt} onCheckedChange={setReceipt} />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Toned with description
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { CheckboxCard } from "@sableui/react"
|
|
96
|
+
|
|
97
|
+
<CheckboxCard tone="edition" title="Editions" description="Sell as numbered NFT editions" defaultChecked />
|
|
98
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Checkbox
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Square toggle (18px at rest, on the spacing scale) over Base UI's Checkbox. Checked/indeterminate/disabled states; controlled or uncontrolled; form-ready via the hidden native input.
|
|
6
|
+
|
|
7
|
+
**Keywords:** checkbox, boolean, multi select, form control
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Checkbox for independent boolean choices or for selecting multiple options in a set.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use Checkbox when each option can be toggled independently.
|
|
16
|
+
- Do: Attach a visible label that explains the consequence of checking the control.
|
|
17
|
+
- Don't: Do not use Checkbox for mutually exclusive choices; use Radio or Select instead.
|
|
18
|
+
- Don't: Do not use it without a label or an aria-label. An 18px square with no accessible name is announced as just "checkbox", which tells a screen-reader user nothing about what they are agreeing to.
|
|
19
|
+
- Don't: Do not phrase the label as a negative, such as "Don't send read receipts". Paired with an unchecked box it becomes a double negative the reader has to solve before they can answer. Label the state being turned on.
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
| Name | Type | Default | Description |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| checked | boolean | — | Controlled checked state. Pair with onCheckedChange. Omit for uncontrolled. |
|
|
26
|
+
| defaultChecked | boolean | false | Initial checked state for an uncontrolled checkbox. |
|
|
27
|
+
| onCheckedChange | (checked: boolean, eventDetails) => void | — | Fires when the checkbox is ticked or unticked. Note: Base UI passes a second eventDetails arg — not the common (checked) => void, so a single-arg handler must widen its signature. |
|
|
28
|
+
| indeterminate | boolean | false | Mixed state (neither checked nor unchecked). Renders a dash instead of the check. For a "select all" box inside a CheckboxGroup, use `parent` instead and the group sets this for you. |
|
|
29
|
+
| parent | boolean | false | Makes this the "select all" box of a CheckboxGroup that has `allValues`. It checks or clears every child, and shows the dash when only some are checked. Only works inside a CheckboxGroup. |
|
|
30
|
+
| disabled | boolean | false | Ignores user interaction and dims the control. |
|
|
31
|
+
| name | string | — | Field name for the hidden input on form submit. |
|
|
32
|
+
| value | string | — | Value submitted with the form when checked. Inside a group-owned CheckboxGroup, it is the name the group's value array uses for this checkbox. |
|
|
33
|
+
| render | ReactElement \| (props, state) => ReactElement | — | Compose the root with another element through the Base UI render prop. |
|
|
34
|
+
|
|
35
|
+
## Tokens used
|
|
36
|
+
|
|
37
|
+
`card`, `input`, `primary`, `primary-foreground`, `ring`
|
|
38
|
+
|
|
39
|
+
## Accessibility
|
|
40
|
+
|
|
41
|
+
- Base UI renders a hidden native <input type=checkbox> for keyboard, focus, and form semantics.
|
|
42
|
+
- Space toggles; focus-visible draws a 2px ring in the --ring token.
|
|
43
|
+
- indeterminate sets aria-checked=mixed automatically; a `parent` checkbox in a CheckboxGroup gets aria-checked=mixed and aria-controls for its children from the group.
|
|
44
|
+
- Label it with Field orientation="horizontal": it wires the label's htmlFor to the checkbox, so the label text joins the click target, and adds the description and error plumbing.
|
|
45
|
+
|
|
46
|
+
## Related
|
|
47
|
+
|
|
48
|
+
- Components: CheckboxCard, Switch, Radio
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
### Default
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { Checkbox } from "@sableui/react"
|
|
56
|
+
|
|
57
|
+
<Checkbox defaultChecked />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Controlled
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Checkbox } from "@sableui/react"
|
|
64
|
+
|
|
65
|
+
<Checkbox checked={value} onCheckedChange={setValue} />
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Indeterminate
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Checkbox } from "@sableui/react"
|
|
72
|
+
|
|
73
|
+
<Checkbox indeterminate />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Select all in a group
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { Checkbox, CheckboxGroup, Field } from "@sableui/react"
|
|
80
|
+
|
|
81
|
+
<CheckboxGroup allValues={["email", "sms"]} defaultValue={["email"]} className="gap-3"><Field label="All channels" orientation="horizontal"><Checkbox parent /></Field><Field label="Email" orientation="horizontal" className="ps-7"><Checkbox value="email" /></Field><Field label="SMS" orientation="horizontal" className="ps-7"><Checkbox value="sms" /></Field></CheckboxGroup>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### With label
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { Checkbox, Field } from "@sableui/react"
|
|
88
|
+
|
|
89
|
+
<Field label="Remember me" orientation="horizontal"><Checkbox /></Field>
|
|
90
|
+
```
|