@sableui/react 0.15.0 → 0.17.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/CHANGELOG.md +99 -0
- package/bin/sable-lint.mjs +5 -1
- package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
- package/dist/blocks/approval-block.js +30 -48
- package/dist/blocks/approval-block.meta.js +7 -8
- package/dist/blocks/index.d.ts +1 -1
- package/dist/blocks/wallet-block.js +2 -2
- package/dist/components/app-shell/app-shell-frame.js +16 -0
- package/dist/components/app-shell/app-shell-main.js +12 -0
- package/dist/components/app-shell/app-shell.js +28 -4
- package/dist/components/app-shell/app-shell.meta.js +88 -4
- package/dist/components/area-chart/area-chart.meta.js +6 -1
- package/dist/components/attachment/attachment.meta.js +2 -1
- package/dist/components/attachment/attachment.variants.js +1 -1
- package/dist/components/badge/badge.meta.js +8 -1
- package/dist/components/badge/badge.variants.js +7 -7
- package/dist/components/banner/banner.meta.js +6 -1
- package/dist/components/banner/banner.variants.js +5 -5
- package/dist/components/bar-chart/bar-chart.meta.js +6 -1
- package/dist/components/caret-menu/caret-menu.js +5 -6
- package/dist/components/chat-composer/chat-composer.meta.js +2 -2
- package/dist/components/chat-composer/chat-composer.variants.js +1 -1
- package/dist/components/color-field/color-field.js +6 -8
- package/dist/components/combobox/combobox.js +27 -5
- package/dist/components/combobox/combobox.meta.js +10 -2
- package/dist/components/command-menu/command-menu.js +14 -3
- package/dist/components/command-menu/command-menu.meta.js +9 -1
- package/dist/components/date-picker/date-picker.meta.js +2 -2
- package/dist/components/feedback/feedback.js +1 -1
- package/dist/components/feedback/feedback.meta.js +2 -1
- package/dist/components/gradient-art/gradient-art.js +8 -5
- package/dist/components/gradient-art/gradient-art.meta.js +35 -2
- package/dist/components/input/input.meta.js +11 -3
- package/dist/components/input-group/input-group.js +1 -1
- package/dist/components/input-group/input-group.meta.js +1 -1
- package/dist/components/line-chart/line-chart.meta.js +6 -1
- package/dist/components/menu/menu.js +1 -1
- package/dist/components/meter/meter.js +53 -7
- package/dist/components/meter/meter.meta.js +51 -4
- package/dist/components/meter/meter.variants.js +43 -7
- package/dist/components/note/note.meta.js +5 -1
- package/dist/components/note/note.variants.js +4 -4
- package/dist/components/number-field/number-field.meta.js +2 -2
- package/dist/components/otp-field/otp-field.meta.js +2 -2
- package/dist/components/panel-group/panel-group.js +5 -8
- package/dist/components/pie-chart/pie-chart.meta.js +6 -1
- package/dist/components/radar-chart/radar-chart.meta.js +6 -1
- package/dist/components/radial-chart/radial-chart.meta.js +6 -1
- package/dist/components/search-input/search-input.meta.js +11 -3
- package/dist/components/section-rail/index.js +7 -0
- package/dist/components/section-rail/section-rail.js +153 -0
- package/dist/components/section-rail/section-rail.meta.js +189 -0
- package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
- package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.meta.js +2 -2
- package/dist/components/sheet/sheet.js +5 -4
- package/dist/components/sidebar/sidebar.js +16 -1
- package/dist/components/sidebar/sidebar.meta.js +7 -2
- package/dist/components/sparkline/sparkline.meta.js +8 -1
- package/dist/components/status-dot/status-dot.meta.js +8 -1
- package/dist/components/status-dot/status-dot.variants.js +7 -7
- package/dist/components/stepper/stepper.meta.js +2 -1
- package/dist/components/stepper/stepper.variants.js +1 -1
- package/dist/components/table/table.meta.js +4 -4
- package/dist/components/textarea/textarea.js +5 -0
- package/dist/components/textarea/textarea.meta.js +3 -3
- package/dist/file-hashes.json +99 -92
- package/dist/foundations/accessibility.js +5 -0
- package/dist/index.d.ts +227 -25
- package/dist/index.js +12 -1
- package/dist/lib/chart/types.js +8 -8
- package/dist/lib/chart/use-arrival.js +5 -4
- package/dist/lib/field-shell.js +1 -1
- package/dist/lib/highlight-glide.js +6 -6
- package/dist/lib/scrub-gesture.js +6 -6
- package/dist/lib/taxonomy.js +1 -0
- package/dist/lib/use-active-section.js +59 -0
- package/dist/lib/use-caret-trigger.js +11 -18
- package/dist/lib/use-match-highlight.js +69 -0
- package/dist/lib/use-smooth-wheel.js +10 -8
- package/dist/presets/index.d.ts +13 -2
- package/dist/presets/index.js +17 -1
- package/dist/styles.css +100 -0
- package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
- package/dist/themes/_contract.js +14 -0
- package/dist/themes/atelier/atelier.css +18 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/contrast.js +48 -1
- package/dist/themes/ember/ember.css +18 -0
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +18 -0
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +18 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +18 -0
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +18 -0
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +18 -0
- package/llms/blocks/approval-block.md +40 -55
- package/llms/blocks/wallet-block.md +5 -4
- package/llms/components/app-shell.md +72 -3
- package/llms/components/area-chart.md +1 -1
- package/llms/components/attachment.md +1 -1
- package/llms/components/badge.md +1 -1
- package/llms/components/banner.md +1 -1
- package/llms/components/bar-chart.md +1 -1
- package/llms/components/chat-composer.md +1 -1
- package/llms/components/combobox.md +2 -1
- package/llms/components/command-menu.md +3 -2
- package/llms/components/date-picker.md +1 -1
- package/llms/components/feedback.md +1 -1
- package/llms/components/gradient-art.md +28 -1
- package/llms/components/input-group.md +1 -1
- package/llms/components/input.md +4 -2
- package/llms/components/line-chart.md +1 -1
- package/llms/components/meter.md +33 -4
- package/llms/components/note.md +1 -1
- package/llms/components/number-field.md +1 -1
- package/llms/components/otp-field.md +1 -1
- package/llms/components/pie-chart.md +1 -1
- package/llms/components/radar-chart.md +1 -1
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/search-input.md +20 -2
- package/llms/components/section-rail.md +101 -0
- package/llms/components/segmented-bar.md +1 -1
- package/llms/components/select.md +1 -1
- package/llms/components/sidebar.md +3 -2
- package/llms/components/sparkline.md +1 -1
- package/llms/components/status-dot.md +1 -1
- package/llms/components/stepper.md +1 -1
- package/llms/components/table.md +2 -2
- package/llms/components/textarea.md +2 -2
- package/llms/foundations/accessibility.md +1 -0
- package/llms/reference/token-intent.md +22 -2
- package/llms/reference/utilities.md +5 -2
- package/llms-full.txt +367 -105
- package/llms.txt +6 -5
- package/package.json +1 -1
- package/registry.json +609 -67
- package/scripts/lint-rules.mjs +6 -3
- package/targets/ios.json +9 -0
package/llms-full.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sable (@sableui/react)
|
|
2
2
|
|
|
3
|
-
> An owned, themeable React component system. Default theme: **Sable** (dark-by-default, content-first, fluid product UI). Behavior from Base UI; styling via Tailwind v4 + semantic CSS-variable tokens; Reicon icons via a pluggable adapter.
|
|
3
|
+
> An owned, themeable React component system. Default theme: **Sable** (dark-by-default, content-first, fluid product UI). Behavior from Base UI; styling via Tailwind v4 + semantic CSS-variable tokens; Reicon icons via a pluggable adapter. 122 components, 13 layout primitives, and a **patterns** tier of canonical page shells. The full, per-component contract is the generated `registry.json`.
|
|
4
4
|
|
|
5
5
|
## Start here — brief, composition, evidence
|
|
6
6
|
|
|
@@ -4083,6 +4083,7 @@ Sable's components ship accessible behavior from Base UI. These rules cover what
|
|
|
4083
4083
|
- **Never:** Color as the only signal (a red border with no message). **Instead:** Color plus text or an icon. Roughly 1 in 12 men cannot tell red from green.
|
|
4084
4084
|
- **Never:** Opacity on the focus ring. **Instead:** ring-ring at full opacity; tune the token in the theme. A faded ring loses the contrast the theme tested. *Enforced by sable-lint: faded-focus-ring.*
|
|
4085
4085
|
- **Never:** A brand or status fill retuned under its old label — dark ink on a bright red that passes 4.5:1 at APCA Lc 42. **Instead:** A label that clears 4.5:1 and APCA Lc 60 in both modes. Move the fill's lightness, not its hue, keeping the ink its colour takes. A status colour on a surface is its own token (`--destructive-text`), so the fill can move freely. The ratio alone passes labels that read muddy. A deliberate brand pair is allowed with a recorded reason (lint disable or preset exceptions). *Enforced by sable-lint: label-contrast.*
|
|
4086
|
+
- **Never:** A label fill painted on a mark that carries no text — a meter, a segmented bar, a status dot, a chart series, a soft tint. **Instead:** The colour's `-mark` token (`bg-success-mark`, `bg-tone-warning-mark/15`): the same hue lifted and saturated, held to 2:1 on the page. Text contrast is for text; a graphic owes visibility, and 3:1 (WCAG 1.4.11) only when it is the sole carrier of its meaning. A fill is pinned dark enough for a white label. On a bar or dot with nothing to label, that darkness only reads as mud: a brown warning, a forest-green success.
|
|
4086
4087
|
- **Never:** Blocking paste, or type="number" for codes and card numbers. **Instead:** type="text" inputMode="numeric" with the right autocomplete. Blocking paste breaks password managers; number inputs scroll-change identifiers.
|
|
4087
4088
|
- **Never:** forced-color-adjust: none to 'fix' high-contrast mode. **Instead:** Let the system colors apply; verify the result. It removes the user's chosen colors exactly where they need them.
|
|
4088
4089
|
- **Never:** outline: none without a replacement. **Instead:** focus-visible:ring-2 ring-ring. Keyboard users lose their place.
|
|
@@ -7092,49 +7093,46 @@ export function ApiKeysBlock({ className, framed = true, onCreate, onKeyAction }
|
|
|
7092
7093
|
|
|
7093
7094
|
# ApprovalBlock
|
|
7094
7095
|
|
|
7095
|
-
An agent asking permission for one tool call: the question, what
|
|
7096
|
+
An agent asking permission for one tool call: the question, what is at stake, the call itself as a ToolCall with its exact input, and Deny / Always allow / Allow once.
|
|
7096
7097
|
|
|
7097
|
-
**Intent:** Reach for this in a chat or agent run when a tool call waits on the person — sending, paying, deleting, publishing.
|
|
7098
|
+
**Intent:** Reach for this in a chat or agent run when a tool call waits on the person — running a command, sending, paying, deleting, publishing. It wraps the call's own ToolCall, which shows status="awaiting" until the person answers and then "complete" or "denied". A destructive action outside an agent is DestructiveActionModal.
|
|
7098
7099
|
|
|
7099
7100
|
**Block:** AI & agents · card · designed for 18rem–28rem wide (fills its container up to the max)
|
|
7100
7101
|
|
|
7101
7102
|
## Notes
|
|
7102
7103
|
|
|
7103
|
-
- The title is the question in plain words ("
|
|
7104
|
-
- The
|
|
7105
|
-
-
|
|
7106
|
-
- After a decision the buttons give way to one InlineStatus line saying what happened, so the thread keeps a record of the answer.
|
|
7104
|
+
- The title is the question in plain words ("Run this command?"); the description says what is at stake. That sentence is what the person is deciding on.
|
|
7105
|
+
- The call is shown as the agent made it — tool name and raw input, open on arrival — because the exact arguments are what a careful person checks. Do not paraphrase them into a form.
|
|
7106
|
+
- Three answers, as agent products settle on: Deny on the start edge, then Always allow and Allow once, the primary on the end. Neither is focused on arrival: an approval that a stray Enter can grant is not an approval.
|
|
7107
|
+
- After a decision the buttons give way to one InlineStatus line saying what happened, and the ToolCall shows the outcome, so the thread keeps a record of the answer.
|
|
7107
7108
|
|
|
7108
7109
|
## Accessibility
|
|
7109
7110
|
|
|
7110
7111
|
- Focus moves to the outcome line when the buttons leave, instead of dropping to the page; the line is a role=status region, so the decision is announced.
|
|
7111
|
-
- The
|
|
7112
|
-
- The remember Checkbox has its own Label, so the tool name inside it is part of its accessible name.
|
|
7112
|
+
- The ToolCall announces its own status change, and its input is a labelled code view.
|
|
7113
7113
|
|
|
7114
7114
|
## Source
|
|
7115
7115
|
|
|
7116
7116
|
```tsx
|
|
7117
|
-
import { useState, useEffect, useRef
|
|
7118
|
-
import { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
|
|
7117
|
+
import { useState, useEffect, useRef } from "react"
|
|
7118
|
+
import { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, cn, InlineStatus, ToolCall } from "@sableui/react"
|
|
7119
7119
|
|
|
7120
7120
|
const request = {
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
allowed: "Allowed. Sending the email.",
|
|
7130
|
-
denied: "Denied. The agent won't send it.",
|
|
7121
|
+
title: "Run this command?",
|
|
7122
|
+
description: "The agent wants to migrate the production database in acme/api. It changes live data.",
|
|
7123
|
+
tool: "run_command",
|
|
7124
|
+
action: "Run a command",
|
|
7125
|
+
// A command reads as the command; a structured call would pass its arguments object.
|
|
7126
|
+
input: "pnpm db:migrate --env production",
|
|
7127
|
+
allowed: "Allowed. Running the migration.",
|
|
7128
|
+
denied: "Denied. The agent won't run it.",
|
|
7131
7129
|
}
|
|
7132
7130
|
|
|
7133
7131
|
export interface ApprovalBlockProps {
|
|
7134
7132
|
className?: string
|
|
7135
7133
|
/** false drops the card's surface and border — for a chat column that already frames it. */
|
|
7136
7134
|
framed?: boolean
|
|
7137
|
-
/** The person allowed the call. `remember` is
|
|
7135
|
+
/** The person allowed the call. `remember` is true for "Always allow". */
|
|
7138
7136
|
onApprove?: (options: { remember: boolean }) => void
|
|
7139
7137
|
/** The person declined the call. */
|
|
7140
7138
|
onDeny?: () => void
|
|
@@ -7142,8 +7140,6 @@ export interface ApprovalBlockProps {
|
|
|
7142
7140
|
|
|
7143
7141
|
export function ApprovalBlock({ className, framed = true, onApprove, onDeny }: ApprovalBlockProps) {
|
|
7144
7142
|
const [decision, setDecision] = useState<"allowed" | "denied" | null>(null)
|
|
7145
|
-
const [remember, setRemember] = useState(false)
|
|
7146
|
-
const id = useId()
|
|
7147
7143
|
const outcome = useRef<HTMLDivElement>(null)
|
|
7148
7144
|
// The buttons leave with the decision, so focus moves to the line that
|
|
7149
7145
|
// replaces them instead of falling back to the top of the page.
|
|
@@ -7151,37 +7147,30 @@ export function ApprovalBlock({ className, framed = true, onApprove, onDeny }: A
|
|
|
7151
7147
|
if (decision) outcome.current?.focus()
|
|
7152
7148
|
}, [decision])
|
|
7153
7149
|
|
|
7150
|
+
const approve = (remember: boolean) => {
|
|
7151
|
+
setDecision("allowed")
|
|
7152
|
+
onApprove?.({ remember })
|
|
7153
|
+
}
|
|
7154
|
+
|
|
7154
7155
|
return (
|
|
7155
7156
|
<Card variant={framed ? "default" : "flat"} className={cn("w-full max-w-md", className)}>
|
|
7156
7157
|
<CardHeader>
|
|
7157
7158
|
<CardTitle>{request.title}</CardTitle>
|
|
7158
7159
|
<CardDescription>{request.description}</CardDescription>
|
|
7159
7160
|
</CardHeader>
|
|
7160
|
-
<CardContent
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
<Code>{request.tool}</Code>
|
|
7172
|
-
</dd>
|
|
7173
|
-
</div>
|
|
7174
|
-
</dl>
|
|
7175
|
-
{decision ? null : (
|
|
7176
|
-
<div className="flex items-center gap-2">
|
|
7177
|
-
<Checkbox id={`${id}-remember`} checked={remember} onCheckedChange={(v: boolean) => setRemember(v)} />
|
|
7178
|
-
<Label htmlFor={`${id}-remember`} className="text-body-sm">
|
|
7179
|
-
Allow <Code>{request.tool}</Code> for the rest of this chat
|
|
7180
|
-
</Label>
|
|
7181
|
-
</div>
|
|
7182
|
-
)}
|
|
7161
|
+
<CardContent>
|
|
7162
|
+
{/* The call itself, exactly as the agent made it. It answers with the
|
|
7163
|
+
person: held, then done or denied. */}
|
|
7164
|
+
<ToolCall
|
|
7165
|
+
name={request.tool}
|
|
7166
|
+
title={request.action}
|
|
7167
|
+
status={decision === "allowed" ? "complete" : decision === "denied" ? "denied" : "awaiting"}
|
|
7168
|
+
statusLabel={decision === "allowed" ? "Allowed" : undefined}
|
|
7169
|
+
input={request.input}
|
|
7170
|
+
defaultOpen
|
|
7171
|
+
/>
|
|
7183
7172
|
</CardContent>
|
|
7184
|
-
<CardFooter actions
|
|
7173
|
+
<CardFooter actions>
|
|
7185
7174
|
{decision ? (
|
|
7186
7175
|
<InlineStatus
|
|
7187
7176
|
ref={outcome}
|
|
@@ -7195,7 +7184,8 @@ export function ApprovalBlock({ className, framed = true, onApprove, onDeny }: A
|
|
|
7195
7184
|
) : (
|
|
7196
7185
|
<>
|
|
7197
7186
|
<Button
|
|
7198
|
-
variant="
|
|
7187
|
+
variant="ghost"
|
|
7188
|
+
className="me-auto"
|
|
7199
7189
|
onClick={() => {
|
|
7200
7190
|
setDecision("denied")
|
|
7201
7191
|
onDeny?.()
|
|
@@ -7203,14 +7193,10 @@ export function ApprovalBlock({ className, framed = true, onApprove, onDeny }: A
|
|
|
7203
7193
|
>
|
|
7204
7194
|
Deny
|
|
7205
7195
|
</Button>
|
|
7206
|
-
<Button
|
|
7207
|
-
|
|
7208
|
-
setDecision("allowed")
|
|
7209
|
-
onApprove?.({ remember })
|
|
7210
|
-
}}
|
|
7211
|
-
>
|
|
7212
|
-
Allow
|
|
7196
|
+
<Button variant="outline" onClick={() => approve(true)}>
|
|
7197
|
+
Always allow
|
|
7213
7198
|
</Button>
|
|
7199
|
+
<Button onClick={() => approve(false)}>Allow once</Button>
|
|
7214
7200
|
</>
|
|
7215
7201
|
)}
|
|
7216
7202
|
</CardFooter>
|
|
@@ -10269,7 +10255,10 @@ const activity = [
|
|
|
10269
10255
|
{ title: "Subscription", detail: "Team plan · 2h ago", amount: "+49.00", tone: "1" },
|
|
10270
10256
|
{ title: "Refund", detail: "Order 1042 · Yesterday", amount: "-24.00", tone: "2" },
|
|
10271
10257
|
{ title: "Payout", detail: "To checking · Mon", amount: "-850.00", tone: "3" },
|
|
10272
|
-
]
|
|
10258
|
+
] as const
|
|
10259
|
+
|
|
10260
|
+
/** Each row's mark: its tone at graphic strength, which every theme tunes. */
|
|
10261
|
+
const TONE_MARK = { "1": "bg-tone-1-mark", "2": "bg-tone-2-mark", "3": "bg-tone-3-mark" } as const
|
|
10273
10262
|
|
|
10274
10263
|
export interface WalletBlockProps {
|
|
10275
10264
|
className?: string
|
|
@@ -10303,9 +10292,7 @@ export function WalletBlock({ className, framed = true, onSend, onReceive }: Wal
|
|
|
10303
10292
|
leading={
|
|
10304
10293
|
<span
|
|
10305
10294
|
aria-hidden
|
|
10306
|
-
className="block size-8 rounded-full"
|
|
10307
|
-
// design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
|
|
10308
|
-
style={{ background: `oklch(from var(--tone-${item.tone}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h)` }}
|
|
10295
|
+
className={cn("block size-8 rounded-full", TONE_MARK[item.tone])}
|
|
10309
10296
|
/>
|
|
10310
10297
|
}
|
|
10311
10298
|
title={item.title}
|
|
@@ -11102,7 +11089,7 @@ import { AppHeader, AppShell, PageHeader, Region } from "@sableui/react"
|
|
|
11102
11089
|
|
|
11103
11090
|
The application frame: slots for status banners, a mobile header, a desktop sidebar, main content, a mobile bottom nav, a persistent dock (a now-playing or call bar), and floating overlays — plus a built-in skip link and #main-content target.
|
|
11104
11091
|
|
|
11105
|
-
**Keywords:** app shell, layout, frame, sidebar, header, bottom nav, mobile, banners, main, dock, player bar, now playing, persistent
|
|
11092
|
+
**Keywords:** app shell, layout, frame, sidebar, header, bottom nav, mobile, banners, main, dock, player bar, now playing, persistent, inset, inset frame, floating panel, rounded content, scroll container
|
|
11106
11093
|
|
|
11107
11094
|
## Usage
|
|
11108
11095
|
|
|
@@ -11114,7 +11101,10 @@ Use AppShell as the outer product frame for authenticated or app-like experience
|
|
|
11114
11101
|
- Do: Pass app-wide outage or status messages through banners so they remain above the frame.
|
|
11115
11102
|
- Do: Use Region inside the main content to choose each route's reading width.
|
|
11116
11103
|
- Do: Wrap EVERY return of a route in its own Page or Region — including early returns for loading, empty and error states. AppShell's <main> deliberately carries no padding (it is a frame, and padding belongs to the region that chose the width), so a route that returns a bare spinner or error renders flush against the chrome. This is the most common conversion bug: the happy path gets a Page, the three early returns do not.
|
|
11104
|
+
- Do: Use frame="inset" for dense, multi-pane product apps — a CRM, an issue tracker, an AI workspace — where framing the content as one raised panel on the rail's ground helps it read as the workspace and the rail as chrome. Keep the Sidebar on surface="rail" so its tint is the ground. Inset moves the page scroll from the window to <main id="main-content"> on desktop: point scroll listeners, scroll restoration and scroll-to-top at that element, not window.
|
|
11117
11105
|
- Don't: Nest AppShell inside route cards or panels. It is page chrome, not local content.
|
|
11106
|
+
- Don't: Wrap a route in its own h-full overflow-y-auto box inside an inset shell. main is already the page's scroll container; a second one traps the scrollbar inside a smaller box and breaks sticky headers and the skip link's scroll target. Scroll a genuinely separate pane (a list beside a detail view) with Scroller. → use **Scroller**
|
|
11107
|
+
- Don't: Combine frame="inset" with a full-width header bar above the rail. The inset ground is the rail running the full height of the window; a bar across the top cuts it off. Use the flush frame with Sidebar surface="page" for that layout.
|
|
11118
11108
|
- Don't: Put desktop-only navigation in the mobile header slot or mobile-only controls in the sidebar slot.
|
|
11119
11109
|
|
|
11120
11110
|
### Anatomy
|
|
@@ -11124,7 +11114,7 @@ Use AppShell as the outer product frame for authenticated or app-like experience
|
|
|
11124
11114
|
| Banners | no | Full-width app status messages rendered above the shell. |
|
|
11125
11115
|
| Header | no | Mobile top chrome, usually AppHeader. |
|
|
11126
11116
|
| Sidebar | no | Desktop navigation chrome, usually Sidebar. |
|
|
11127
|
-
| Main | yes | The route content landmark with id main-content. |
|
|
11117
|
+
| Main | yes | The route content landmark with id main-content. With frame="inset", from lg it is a rounded, bordered panel that scrolls inside itself. |
|
|
11128
11118
|
| Bottom nav | no | Mobile fixed navigation with reserved bottom space. |
|
|
11129
11119
|
| Dock | no | Persistent bottom chrome that outlives routes — a now-playing bar, a call bar. Sticky above the bottom nav; the desktop Sidebar shortens to clear it. |
|
|
11130
11120
|
|
|
@@ -11144,10 +11134,11 @@ Use AppShell as the outer product frame for authenticated or app-like experience
|
|
|
11144
11134
|
| bottomNav | ReactNode | — | Mobile bottom navigation (e.g. BottomNav); fixed to the bottom edge. main reserves space for it. |
|
|
11145
11135
|
| dock | ReactNode | — | Persistent chrome kept on screen across routes (a now-playing bar, an active-call bar, an upload tray). Full width, sticky to the bottom edge and above bottomNav on mobile; its height is published as --dock-height and the app Sidebar subtracts it. In flow, so main needs no reserved space. The contents paint their own surface and top border, and name themselves (e.g. <section aria-label="Player">). Mount AppShell in a layout that survives navigation, or the playback restarts. |
|
|
11146
11136
|
| overlays | ReactNode | — | Floating overlays — a message launcher, FAB, or modal mount points. |
|
|
11137
|
+
| frame | "flush" \| "inset" | "flush" | How the desktop frame (lg and up) holds the content; below lg both are the same mobile layout with page scroll. "flush": main sits on the page beside the rail and the document scrolls. "inset": the shell fills the viewport and paints the shell-ground token (one step off the rail's surface, tunable with --theme-shell-ground) behind the rail and around the panel; main becomes a rounded-lg panel with a hairline border and shadow-sm, inset by a spacing step from the window edges, that scrolls inside itself (scrollbar-quiet) while the rail stays put. The Sidebar in the sidebar slot drops its right border and fills the row. main stays the #main-content target (tabIndex -1, so the skip link lands focus in the scroller) and the inline-size query container; sticky children stick to the panel's top; banners and the dock stay in flow, so the panel's bottom gap sits above a dock. With Sidebar surface="page" the ground is the page colour and the panel's border alone lifts it. |
|
|
11147
11138
|
|
|
11148
11139
|
## Tokens used
|
|
11149
11140
|
|
|
11150
|
-
`background`, `foreground`, `primary`, `primary-foreground`
|
|
11141
|
+
`background`, `foreground`, `primary`, `primary-foreground`, `sidebar`, `shell-ground`, `border`
|
|
11151
11142
|
|
|
11152
11143
|
## Accessibility
|
|
11153
11144
|
|
|
@@ -11155,6 +11146,7 @@ Use AppShell as the outer product frame for authenticated or app-like experience
|
|
|
11155
11146
|
- Wraps content in a <main id="main-content"> landmark.
|
|
11156
11147
|
- Slot components own their own ARIA (nav landmarks, labels).
|
|
11157
11148
|
- The dock follows <main> in DOM order, so keyboard and screen-reader users reach the player after the page content, not before it.
|
|
11149
|
+
- With frame="inset", <main> is the desktop scroll container and takes tabIndex -1: the skip link moves focus into it, so PageDown and the arrow keys scroll the content, without adding a tab stop.
|
|
11158
11150
|
|
|
11159
11151
|
## Related
|
|
11160
11152
|
|
|
@@ -11194,6 +11186,70 @@ import { AppHeader, AppShell, Banner, BottomNav, BottomNavItem, Icon, PageHeader
|
|
|
11194
11186
|
</AppShell>
|
|
11195
11187
|
```
|
|
11196
11188
|
|
|
11189
|
+
### Inset frame
|
|
11190
|
+
|
|
11191
|
+
```tsx
|
|
11192
|
+
import { AppHeader, AppShell, BottomNav, BottomNavItem, Button, Card, CardContent, Icon, Page, PageHeader, Sidebar, SidebarBrand, SidebarHeader, SidebarItem, SidebarNav, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@sableui/react"
|
|
11193
|
+
// <Link> = your router's link (react-router, next/link, @tanstack/router, …)
|
|
11194
|
+
|
|
11195
|
+
<AppShell
|
|
11196
|
+
frame="inset"
|
|
11197
|
+
header={<AppHeader title="Pipeline" />}
|
|
11198
|
+
sidebar={
|
|
11199
|
+
<Sidebar>
|
|
11200
|
+
<SidebarHeader>
|
|
11201
|
+
<SidebarBrand mark={<Icon name="gem" variant="solid" />} label="Relay" render={<Link to="/" />} />
|
|
11202
|
+
</SidebarHeader>
|
|
11203
|
+
<SidebarNav>
|
|
11204
|
+
<SidebarItem icon={<Icon name="inbox" />} label="Inbox" render={<Link to="/inbox" />} />
|
|
11205
|
+
<SidebarItem icon={<Icon name="user" />} label="People" active render={<Link to="/people" />} />
|
|
11206
|
+
<SidebarItem icon={<Icon name="folder" />} label="Companies" render={<Link to="/companies" />} />
|
|
11207
|
+
</SidebarNav>
|
|
11208
|
+
</Sidebar>
|
|
11209
|
+
}
|
|
11210
|
+
bottomNav={
|
|
11211
|
+
<BottomNav>
|
|
11212
|
+
<BottomNavItem icon={<Icon name="inbox" />} label="Inbox" render={<Link to="/inbox" />} />
|
|
11213
|
+
<BottomNavItem icon={<Icon name="user" />} label="People" active render={<Link to="/people" />} />
|
|
11214
|
+
<BottomNavItem icon={<Icon name="folder" />} label="Companies" render={<Link to="/companies" />} />
|
|
11215
|
+
</BottomNav>
|
|
11216
|
+
}
|
|
11217
|
+
>
|
|
11218
|
+
<Page max="wide" gap={4} className="py-6">
|
|
11219
|
+
<PageHeader
|
|
11220
|
+
title="People"
|
|
11221
|
+
description="Everyone in your pipeline, newest first."
|
|
11222
|
+
actions={<Button><Icon name="plus" />Add person</Button>}
|
|
11223
|
+
/>
|
|
11224
|
+
<Card>
|
|
11225
|
+
<CardContent flush>
|
|
11226
|
+
<Table>
|
|
11227
|
+
<TableHeader>
|
|
11228
|
+
<TableRow>
|
|
11229
|
+
<TableHead>Name</TableHead>
|
|
11230
|
+
<TableHead>Company</TableHead>
|
|
11231
|
+
<TableHead numeric>Deals</TableHead>
|
|
11232
|
+
</TableRow>
|
|
11233
|
+
</TableHeader>
|
|
11234
|
+
<TableBody>
|
|
11235
|
+
<TableRow>
|
|
11236
|
+
<TableCell>Ada Lovelace</TableCell>
|
|
11237
|
+
<TableCell>Northwind</TableCell>
|
|
11238
|
+
<TableCell numeric>4</TableCell>
|
|
11239
|
+
</TableRow>
|
|
11240
|
+
<TableRow>
|
|
11241
|
+
<TableCell>Grace Hopper</TableCell>
|
|
11242
|
+
<TableCell>Harbor Labs</TableCell>
|
|
11243
|
+
<TableCell numeric>2</TableCell>
|
|
11244
|
+
</TableRow>
|
|
11245
|
+
</TableBody>
|
|
11246
|
+
</Table>
|
|
11247
|
+
</CardContent>
|
|
11248
|
+
</Card>
|
|
11249
|
+
</Page>
|
|
11250
|
+
</AppShell>
|
|
11251
|
+
```
|
|
11252
|
+
|
|
11197
11253
|
### SkipLink in a layout of your own
|
|
11198
11254
|
|
|
11199
11255
|
```tsx
|
|
@@ -11292,7 +11348,7 @@ Use AreaChart for volume over time — visitors, revenue, usage — where the fi
|
|
|
11292
11348
|
|
|
11293
11349
|
## Tokens used
|
|
11294
11350
|
|
|
11295
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
11351
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
11296
11352
|
|
|
11297
11353
|
## Motion
|
|
11298
11354
|
|
|
@@ -11415,7 +11471,7 @@ The card a file takes in a conversation — in the composer while it uploads, an
|
|
|
11415
11471
|
|
|
11416
11472
|
## Tokens used
|
|
11417
11473
|
|
|
11418
|
-
`card`, `border`, `border-strong`, `muted`, `muted-foreground`, `foreground`, `destructive`, `overlay`, `overlay-foreground`, `ring`
|
|
11474
|
+
`card`, `border`, `border-strong`, `muted`, `muted-foreground`, `foreground`, `destructive`, `overlay`, `overlay-foreground`, `ring`, `destructive-mark`
|
|
11419
11475
|
|
|
11420
11476
|
## Motion
|
|
11421
11477
|
|
|
@@ -11702,7 +11758,7 @@ Use Badge for any short static label: metadata, categories, tone-coded labels, a
|
|
|
11702
11758
|
|
|
11703
11759
|
## Tokens used
|
|
11704
11760
|
|
|
11705
|
-
`palette-wash`, `palette-wash-strong`, `palette-edge`, `palette-dot`, `palette-solid`, `palette-text`, `palette-solid-ink`, `secondary`, `secondary-foreground`, `border`, `foreground`, `background`, `muted`, `muted-foreground`, `tone-1`, `tone-2`, `tone-3`, `tone-info`, `success`, `tone-warning`, `destructive`, `overlay`, `overlay-foreground`, `tone-1-foreground`, `tone-2-foreground`, `tone-3-foreground`, `tone-info-foreground`, `success-foreground`, `tone-warning-foreground`, `destructive-foreground`
|
|
11761
|
+
`palette-wash`, `palette-wash-strong`, `palette-edge`, `palette-dot`, `palette-solid`, `palette-text`, `palette-solid-ink`, `secondary`, `secondary-foreground`, `border`, `foreground`, `background`, `muted`, `muted-foreground`, `tone-1`, `tone-2`, `tone-3`, `tone-info`, `success`, `tone-warning`, `destructive`, `overlay`, `overlay-foreground`, `tone-1-foreground`, `tone-2-foreground`, `tone-3-foreground`, `tone-info-foreground`, `success-foreground`, `tone-warning-foreground`, `destructive-foreground`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
11706
11762
|
|
|
11707
11763
|
## Accessibility
|
|
11708
11764
|
|
|
@@ -11831,7 +11887,7 @@ Use Banner for page-level or app-level messages that affect the current experien
|
|
|
11831
11887
|
|
|
11832
11888
|
## Tokens used
|
|
11833
11889
|
|
|
11834
|
-
`secondary`, `secondary-foreground`, `foreground`, `background`, `tone-info`, `tone-info-foreground`, `success`, `success-foreground`, `tone-warning`, `tone-warning-foreground`, `tone-1`, `tone-1-foreground`, `destructive`, `destructive-foreground`
|
|
11890
|
+
`secondary`, `secondary-foreground`, `foreground`, `background`, `tone-info`, `tone-info-foreground`, `success`, `success-foreground`, `tone-warning`, `tone-warning-foreground`, `tone-1`, `tone-1-foreground`, `destructive`, `destructive-foreground`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`
|
|
11835
11891
|
|
|
11836
11892
|
## Accessibility
|
|
11837
11893
|
|
|
@@ -11955,7 +12011,7 @@ Use BarChart to compare amounts across categories — the default chart for "how
|
|
|
11955
12011
|
|
|
11956
12012
|
## Tokens used
|
|
11957
12013
|
|
|
11958
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
12014
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
11959
12015
|
|
|
11960
12016
|
## Motion
|
|
11961
12017
|
|
|
@@ -13214,7 +13270,7 @@ Pairs with MessageScroller to make a working chat. A single surface rather than
|
|
|
13214
13270
|
|
|
13215
13271
|
## Tokens used
|
|
13216
13272
|
|
|
13217
|
-
`
|
|
13273
|
+
`field`, `field-border`, `ring`, `destructive`, `foreground`, `muted-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`
|
|
13218
13274
|
|
|
13219
13275
|
## Motion
|
|
13220
13276
|
|
|
@@ -13930,13 +13986,14 @@ Use Combobox when users choose from a list that benefits from typing, filtering,
|
|
|
13930
13986
|
| items | readonly any[] \| readonly ComboboxItemGroup[] | — | The pool of options, flat or grouped ({ value, items }). Drives type-to-filter and is required for ComboboxEmpty to render its fallback. |
|
|
13931
13987
|
| multiple | boolean | false | Select several items instead of one. `value` / `defaultValue` / `onValueChange` become arrays, choosing an item toggles its membership, and the popup stays open between picks. Pair it with ComboboxChips in place of ComboboxInput; every other part is unchanged. |
|
|
13932
13988
|
| filter | null \| ((item, query, itemToString?) => boolean) | Base UI's locale-aware contains | How items match the query. `null` turns matching off, for items a server already filtered: pair it with `onInputValueChange` to fetch. |
|
|
13989
|
+
| highlightMatches | boolean | true | Paints the typed text wherever it appears in a row's label, on the `highlight` / `highlight-foreground` pair, through the CSS Custom Highlight API — no `<mark>` in the row, and the accessible name is unchanged. Only typed text counts: a field still holding the selected item's label lists everything and lights nothing. A literal, case-insensitive match within the label; a `filter` that matches differently leaves rows it kept unpainted. `false` turns it off. |
|
|
13933
13990
|
| onInputValueChange | (inputValue: string, eventDetails) => void | — | Fires as the reader types. The place to fetch results for a server-backed combobox. |
|
|
13934
13991
|
| itemToStringLabel | (item) => string | item.label for { value, label } items | The text an object item shows in the input once chosen, and is matched by. Needed for object items without a `label` field. |
|
|
13935
13992
|
| virtualized | boolean | false | Tells Base UI the DOM holds only part of the list. Required when using ComboboxVirtualList. |
|
|
13936
13993
|
|
|
13937
13994
|
## Tokens used
|
|
13938
13995
|
|
|
13939
|
-
`
|
|
13996
|
+
`highlight`, `highlight-foreground`, `field-border`, `field`, `foreground`, `muted-foreground`, `ring`, `destructive`, `popover`, `popover-foreground`, `border`, `accent`, `accent-foreground`, `primary`, `secondary`, `secondary-foreground`
|
|
13940
13997
|
|
|
13941
13998
|
## Motion
|
|
13942
13999
|
|
|
@@ -14058,7 +14115,7 @@ Use CommandMenu for global or contextual keyboard-driven actions: navigation, cr
|
|
|
14058
14115
|
|
|
14059
14116
|
| Name | Type | Description |
|
|
14060
14117
|
| --- | --- | --- |
|
|
14061
|
-
| CommandMenuTrigger | { onOpen?: () => void; placeholder?: string; shortcut?: string \| false; size?: "sm" \| "md"; disabled?: boolean } | The search field in a header that is really a button. Wears SearchInput's shell (
|
|
14118
|
+
| CommandMenuTrigger | { onOpen?: () => void; placeholder?: string; shortcut?: string \| false; size?: "sm" \| "md"; disabled?: boolean } | The search field in a header that is really a button. Wears SearchInput's shell (the field fill and outline, leading magnifier, muted placeholder) on the control ladder, with a trailing Kbd: ⌘K on macOS, Ctrl K elsewhere. onOpen runs on click and on the shortcut, which it listens for page-wide (useCommandMenuShortcut). shortcut is the key (default "k"); false drops both the hint and the listener. size is sm in a header beside small buttons, md (default) beside Input and SearchInput. It sits outside CommandMenu, so the Dialog returns focus to it on close. |
|
|
14062
14119
|
| useCommandMenuShortcut | (onShortcut: (event: KeyboardEvent) => void, key?: string \| false) => void | The page-wide ⌘K / Ctrl+K listener CommandMenuTrigger installs, for a palette with no visible trigger. Ignored while focus is in a text field, textarea, select or contenteditable (Ctrl+K belongs to the field there) except the palette's own input, so the callback may toggle. It calls preventDefault() on a match and skips an event already prevented, so two listeners on one page fire once. key defaults to "k"; false turns it off. Removed on unmount. |
|
|
14063
14120
|
| CommandMenuPanel | { items?: CommandItem[]; query?: string; onQueryChange?: (query: string) => void; filter?: CommandFilter; listClassName?: string; placeholder?: string; emptyMessage?: string; title?: string; scope?: ReactNode; inputProps?: ComponentProps<'input'>; className?: string; children?: ReactNode } | The palette rendered in place: the same search header, filtered and grouped listbox, keyboard model and result-count live region, on a static card (border-border, bg-card, no shadow) instead of in a Dialog. Takes every CommandMenu prop except open, onOpenChange, finalFocus and description; className lands on the card, title names the listbox. Focus is not trapped and the input does not take focus on mount (inputProps.autoFocus opts in). Esc clears the query, and passes through once it is empty, so a Popover around the panel still closes. A chosen row runs its onSelect and the panel stays as it was. The page-wide shortcut does not fire from its input. |
|
|
14064
14121
|
| CommandMenuGroup | { label: string; children } | A labelled group of command rows. |
|
|
@@ -14076,6 +14133,7 @@ Use CommandMenu for global or contextual keyboard-driven actions: navigation, cr
|
|
|
14076
14133
|
| query | string | — | Controlled query. Leave undefined to let the palette own it; either way it resets on close (a controlled caller gets onQueryChange('')). |
|
|
14077
14134
|
| onQueryChange | (query: string) => void | — | Called on every query change. Required to control query, optional otherwise. |
|
|
14078
14135
|
| filter | ((items: CommandItem[], query: string) => CommandItem[]) \| null | case-insensitive substring match on label | Replaces or disables the built-in match. A function ranks and filters items itself and its order is kept verbatim; null renders items exactly as given. Either also stops declarative CommandMenuItem children self-filtering. The query arrives raw — trimming and case are policy, and this is the prop that hands policy over. |
|
|
14136
|
+
| highlightMatches | boolean | true | Paints the query wherever it appears in a row's label (not its detail or hint), on the `highlight` / `highlight-foreground` pair, through the CSS Custom Highlight API — no `<mark>` in the row, and the accessible name is unchanged. A literal, case-insensitive match, so under a fuzzy `filter` a row that matched as a subsequence keeps an unpainted label. `false` turns it off. Applies to CommandMenuPanel too. |
|
|
14079
14137
|
| listClassName | string | — | Classes for the scrolling list. className lands on the dialog surface, so this is the seam for the list's own height cap — listClassName="max-h-[60vh]". The list is a Scroller, so the cap lands on the element that scrolls and the edge fades come free. |
|
|
14080
14138
|
| placeholder | string | "Type a command or search…" | Placeholder for the search input. |
|
|
14081
14139
|
| emptyMessage | string | "No results found." | Copy shown when the query matches nothing. |
|
|
@@ -14086,7 +14144,7 @@ Use CommandMenu for global or contextual keyboard-driven actions: navigation, cr
|
|
|
14086
14144
|
|
|
14087
14145
|
## Tokens used
|
|
14088
14146
|
|
|
14089
|
-
`card`, `card-foreground`, `border`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`
|
|
14147
|
+
`highlight`, `highlight-foreground`, `card`, `card-foreground`, `border`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`
|
|
14090
14148
|
|
|
14091
14149
|
## Motion
|
|
14092
14150
|
|
|
@@ -14622,7 +14680,7 @@ Use DatePicker when users need to choose one date — or, with granularity="minu
|
|
|
14622
14680
|
|
|
14623
14681
|
## Tokens used
|
|
14624
14682
|
|
|
14625
|
-
`primary`, `primary-foreground`, `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `popover`, `popover-foreground`, `border`, `
|
|
14683
|
+
`primary`, `primary-foreground`, `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `popover`, `popover-foreground`, `border`, `field-border`, `field`, `ring`
|
|
14626
14684
|
|
|
14627
14685
|
## Accessibility
|
|
14628
14686
|
|
|
@@ -15374,7 +15432,7 @@ Use Feedback to collect lightweight sentiment, usefulness, or quality signals fr
|
|
|
15374
15432
|
|
|
15375
15433
|
## Tokens used
|
|
15376
15434
|
|
|
15377
|
-
`popover`, `popover-foreground`, `border`, `input`, `card`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`, `ring`, `success`
|
|
15435
|
+
`popover`, `popover-foreground`, `border`, `input`, `card`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`, `ring`, `success`, `success-mark`
|
|
15378
15436
|
|
|
15379
15437
|
## Motion
|
|
15380
15438
|
|
|
@@ -16005,13 +16063,15 @@ Placeholder artwork painted by a static WebGL shader from the preset's own art d
|
|
|
16005
16063
|
|
|
16006
16064
|
## Usage
|
|
16007
16065
|
|
|
16008
|
-
Use GradientArt where a real image belongs but none exists yet, or where the image is decoration: a product cover before upload, a profile banner, an album tile, an avatar with no photo. It reads the theme's own tokens, so it follows every preset, mode and live edit; `seed` fixes each item's composition so a list of covers or avatars stays individually recognisable.
|
|
16066
|
+
Use GradientArt where a real image belongs but none exists yet, or where the image is decoration: a product cover before upload, a profile banner, an album tile, an avatar with no photo. It reads the theme's own tokens, so it follows every preset, mode and live edit; `seed` fixes each item's composition so a list of covers or avatars stays individually recognisable. `intensity="soft"` paints the same art as a pastel wash — the iridescent aura AI products use as a card or modal header band, and as a stage for floating chips and logo tiles.
|
|
16009
16067
|
|
|
16010
16068
|
### Best practices
|
|
16011
16069
|
|
|
16012
16070
|
- Do: Pass a stable `seed` per item — the name for an avatar, the title for a cover. Each item then keeps the same art across renders, reloads and themes, which is what lets someone recognise it.
|
|
16013
16071
|
- Do: Size it with layout classes (`aspect-[4/3]`, `h-20`, `size-10 rounded-full`) and put any glyph or initials inside as children; they render above the art.
|
|
16014
16072
|
- Do: Keep the default `mesh` for covers, tiles and avatars. Reach for `grain` where a printed, tactile surface suits — a banner, a hero card — and keep one variant per surface type so art reads as a system.
|
|
16073
|
+
- Do: Use `intensity="soft"` as a stage: a card or modal header band, the art on an integration or pricing card, a field behind floating prompt chips or rounded logo tiles. Make the tiles and chips floating surfaces (`bg-popover` with `border-border-floating` and a shadow, or a `floating` Button) so they lift off the wash; soft stays light in dark mode too, so the same composition holds in both.
|
|
16074
|
+
- Don't: Do not put text straight on soft art with `text-overlay-foreground` ink. Soft is a light ground in every mode, so overlay ink washes out on it; put the content on a Card or a chip over the art, or use the default vivid intensity for a short overlay label.
|
|
16015
16075
|
- Don't: Do not put body text over it. The art spans the palette's lightest and most saturated tones, so no single ink is guaranteed to clear contrast across it; reserve overlays for a short label on a scrim or an avatar's initials.
|
|
16016
16076
|
- Don't: Do not pass colours, and reach for `tones` rarely. The art follows the preset's --art-* tokens on purpose; to change how a product's art looks, set the --theme-art-* hooks in its theme or preset, so every cover, banner and avatar changes together.
|
|
16017
16077
|
- Don't: Do not use it as a page or hero background. It paints at the element's pixel size, so a full-viewport instance costs a large frame on every theme change; it is sized for covers, banners and tiles.
|
|
@@ -16023,12 +16083,14 @@ Use GradientArt where a real image belongs but none exists yet, or where the ima
|
|
|
16023
16083
|
| variant | "mesh" \| "grain" | "mesh" | mesh: soft spots of colour pulled together by gentle waves. grain: glowing light over deep corners with a printed texture. |
|
|
16024
16084
|
| tones | ("info" \| "3" \| "2" \| "1" \| "warning")[] | — | Paint these `--tone-*` colours instead of the preset's art palette. Leave unset to follow the preset's art direction. |
|
|
16025
16085
|
| seed | string \| number | — | Fixes the composition: the same seed paints the same art in every theme. Pass a stable id. |
|
|
16086
|
+
| intensity | "vivid" \| "soft" | "vivid" | vivid: the preset's art at full strength. soft: every colour and the ground lifted to a light pastel of itself (high lightness, half chroma) — a wash for header bands and for staging chips and logo tiles, light in both modes. |
|
|
16026
16087
|
| children | ReactNode | — | Rendered above the art — initials, a glyph, a short label. |
|
|
16027
16088
|
| className | string | — | Size and shape: aspect, height, rounding. |
|
|
16028
16089
|
|
|
16029
16090
|
## Variants
|
|
16030
16091
|
|
|
16031
16092
|
- **variant**: mesh, grain
|
|
16093
|
+
- **intensity**: vivid, soft
|
|
16032
16094
|
|
|
16033
16095
|
## Tokens used
|
|
16034
16096
|
|
|
@@ -16076,6 +16138,29 @@ import { GradientArt } from "@sableui/react"
|
|
|
16076
16138
|
<GradientArt seed="Mara Okafor" className="grid size-10 place-items-center rounded-full text-label-md text-overlay-foreground">MO</GradientArt>
|
|
16077
16139
|
```
|
|
16078
16140
|
|
|
16141
|
+
### Soft header band with logo tiles
|
|
16142
|
+
|
|
16143
|
+
```tsx
|
|
16144
|
+
import { Button, Card, CardDescription, CardFooter, CardHeader, CardTitle, GradientArt, Icon } from "@sableui/react"
|
|
16145
|
+
|
|
16146
|
+
<Card className="w-full max-w-sm">
|
|
16147
|
+
<GradientArt intensity="soft" seed="Integrations" className="flex h-32 items-center justify-center gap-3 rounded-t-lg">
|
|
16148
|
+
{["mail", "calendar", "chart"].map((name) => (
|
|
16149
|
+
<span key={name} className="grid size-12 place-items-center rounded-lg border border-border-floating bg-popover text-popover-foreground shadow-md">
|
|
16150
|
+
<Icon name={name} className="text-xl" />
|
|
16151
|
+
</span>
|
|
16152
|
+
))}
|
|
16153
|
+
</GradientArt>
|
|
16154
|
+
<CardHeader>
|
|
16155
|
+
<CardTitle>Connect your tools</CardTitle>
|
|
16156
|
+
<CardDescription>Bring mail, calendar and analytics into one place.</CardDescription>
|
|
16157
|
+
</CardHeader>
|
|
16158
|
+
<CardFooter>
|
|
16159
|
+
<Button>Connect</Button>
|
|
16160
|
+
</CardFooter>
|
|
16161
|
+
</Card>
|
|
16162
|
+
```
|
|
16163
|
+
|
|
16079
16164
|
---
|
|
16080
16165
|
|
|
16081
16166
|
# InlineStatus
|
|
@@ -16147,7 +16232,7 @@ import { InlineStatus, Spinner } from "@sableui/react"
|
|
|
16147
16232
|
|
|
16148
16233
|
**Status:** stable
|
|
16149
16234
|
|
|
16150
|
-
Styled native text input. Soft-square (rounded-sm),
|
|
16235
|
+
Styled native text input. Soft-square (rounded-sm), the theme's field surface (outlined or filled), 2px focus ring; aria-invalid raises the destructive error treatment.
|
|
16151
16236
|
|
|
16152
16237
|
**Keywords:** input, text input, field, form, email, search, file, invalid, validation
|
|
16153
16238
|
|
|
@@ -16161,6 +16246,8 @@ Use Input for short freeform text, search, email, password, file, and native inp
|
|
|
16161
16246
|
- Do: Use type, inputMode, autoComplete, and placeholder as progressive hints, not as the only label.
|
|
16162
16247
|
- Do: Set aria-invalid or use Field error copy when validation fails.
|
|
16163
16248
|
- Do: Keep a focusable field at 16px or larger on mobile. iOS Safari zooms the whole page when a focused input computes under 16px and never zooms back out. Input handles this itself — below the sm breakpoint it renders text-body-lg (17px) and returns to text-body-md at sm and up. Build a custom field the same way: a second paired tier under a max-sm: variant, never a hand-set font-size.
|
|
16249
|
+
- Do: Choose outlined or filled fields once, in the theme: `--theme-field: var(--muted)` with `--theme-field-border: transparent` turns every Input, Textarea, Select, Combobox, SearchInput, InputGroup and ChatComposer into a borderless filled well; unset, they are outlined on the card. Every field reads the `field` / `field-border` pair, so they change together.
|
|
16250
|
+
- Don't: Restyle one Input as a filled chip with `bg-muted border-transparent`. It drifts from every other field on the page, and the theme can no longer move them together; set the field tokens instead.
|
|
16164
16251
|
- Don't: Override an Input's type with a page tier below 16px (text-body-md, text-body-sm, text-label-*) without a >=16px mobile arm — that reintroduces the iOS focus zoom.
|
|
16165
16252
|
- Don't: Use placeholder text as the label; it disappears once the user types and is not enough context.
|
|
16166
16253
|
- Don't: Use Input for long body copy. Use Textarea for multi-line content.
|
|
@@ -16190,7 +16277,7 @@ Use Input for short freeform text, search, email, password, file, and native inp
|
|
|
16190
16277
|
|
|
16191
16278
|
## Tokens used
|
|
16192
16279
|
|
|
16193
|
-
`
|
|
16280
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
16194
16281
|
|
|
16195
16282
|
## Accessibility
|
|
16196
16283
|
|
|
@@ -16305,7 +16392,7 @@ Use InputGroup when something belongs inside the field with the text: a unit, a
|
|
|
16305
16392
|
|
|
16306
16393
|
## Tokens used
|
|
16307
16394
|
|
|
16308
|
-
`
|
|
16395
|
+
`field-border`, `field`, `ring`, `destructive`, `muted-foreground`, `foreground`
|
|
16309
16396
|
|
|
16310
16397
|
## Motion
|
|
16311
16398
|
|
|
@@ -16651,7 +16738,7 @@ Use LineChart to compare how two or more measures change over the same sequence,
|
|
|
16651
16738
|
|
|
16652
16739
|
## Tokens used
|
|
16653
16740
|
|
|
16654
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
16741
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
16655
16742
|
|
|
16656
16743
|
## Motion
|
|
16657
16744
|
|
|
@@ -17648,7 +17735,7 @@ import { Bubble, Button, Message, MessageScroller } from "@sableui/react"
|
|
|
17648
17735
|
|
|
17649
17736
|
A role=meter reading over Base UI Meter, drawn as a bar or a ring — how full something is within a known range (storage, quota, strength), with a tone for good/caution/bad. Not task progress.
|
|
17650
17737
|
|
|
17651
|
-
**Keywords:** meter, usage bar, storage, quota, capacity, limit, password strength, level
|
|
17738
|
+
**Keywords:** meter, usage bar, storage, quota, capacity, limit, password strength, level, segmented, tick bar, score, credits, spend limit
|
|
17652
17739
|
|
|
17653
17740
|
## Usage
|
|
17654
17741
|
|
|
@@ -17659,15 +17746,17 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
17659
17746
|
- Do: Label it and show the reading in units people use ("7.2 of 10 GB"), with MeterLabel and MeterValue. The bar shows proportion; the number is what gets acted on.
|
|
17660
17747
|
- Do: Pick `tone` from thresholds you own and apply them consistently across the page — warning near the limit, destructive at or over it. The component does not guess where good becomes bad.
|
|
17661
17748
|
- Do: Use shape="ring" when the reading is a score that belongs in a small, glanceable spot on a card. It is the same measurement drawn as a circular gauge, not a different component.
|
|
17749
|
+
- Do: Use `segments` (a tick bar, ▮▮▮▮▮▮▯▯▯▯) for a discrete budget, score or quota read at a glance — a spend limit, credits left, an ICP lead score, months of data coverage. Pick a count that matches the unit when there is one (10 for a score out of 10, 12 for months) and otherwise size it to the track: a tick reads as a tick when it is taller than it is wide, about one per 8px of track (40 across a card row, 20 in a narrow table cell). Any reading above min shows at least one tick.
|
|
17662
17750
|
- Don't: Do not use Meter for a task in flight — an upload, an install, a step count. That is Progress; Meter tells assistive tech nothing is happening. → use **Progress**
|
|
17663
17751
|
- Don't: Do not use it for a value without a meaningful maximum (followers, total spend). A bar implies a full scale; an unbounded count is a stat.
|
|
17752
|
+
- Don't: Do not segment a fine-grained continuous value where the exact proportion matters (disk at 73.4%) — ticks round it to the nearest mark; use the continuous bar. And do not use a tick bar for a task in flight or steps completed: that is Progress, or a Stepper for named steps. → use **Progress**
|
|
17664
17753
|
|
|
17665
17754
|
### Anatomy
|
|
17666
17755
|
|
|
17667
17756
|
| Part | Required | Description |
|
|
17668
17757
|
| --- | --- | --- |
|
|
17669
17758
|
| MeterTrack | yes | The rail that represents the full range. |
|
|
17670
|
-
| MeterIndicator | yes | The fill, sized from value and coloured by tone. |
|
|
17759
|
+
| MeterIndicator | yes | The fill, sized from value and coloured by tone. Not used with `segments`: the track draws its ticks instead. |
|
|
17671
17760
|
| MeterLabel | no | The visible name; also names the meter for assistive tech. |
|
|
17672
17761
|
| MeterValue | no | The formatted reading. |
|
|
17673
17762
|
|
|
@@ -17675,7 +17764,7 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
17675
17764
|
|
|
17676
17765
|
| Name | Type | Description |
|
|
17677
17766
|
| --- | --- | --- |
|
|
17678
|
-
| MeterTrack | Base UI Track props + className + size | The rail. Rendered automatically when Meter has no children; renders a MeterIndicator by default. |
|
|
17767
|
+
| MeterTrack | Base UI Track props + className + size | The rail. Rendered automatically when Meter has no children; renders a MeterIndicator by default — or, when the Meter has `segments`, its row of ticks (children are not rendered then). |
|
|
17679
17768
|
| MeterIndicator | Base UI Indicator props + className + tone | The fill. Inherits the Meter's tone unless given its own. |
|
|
17680
17769
|
| MeterLabel | Base UI Label props + className | Visible label; wires aria-labelledby on the root. |
|
|
17681
17770
|
| MeterValue | Base UI Value props + className | Formatted reading. Accepts a `(formatted, value) => ReactNode` child. |
|
|
@@ -17691,6 +17780,7 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
17691
17780
|
| shape | "bar" \| "ring" | "bar" | bar is a horizontal track; ring is a circular gauge for a small, glanceable reading — the same value, min, max and tones. |
|
|
17692
17781
|
| size | "sm" \| "md" \| "lg" | "md" | Ring diameter (40 / 64 / 96px at the default spacing; the centre value steps with it), or bar thickness on the same sm / md / lg rails as Progress. |
|
|
17693
17782
|
| showValue | boolean | — | Print the value in the ring's centre (formatted like MeterValue). |
|
|
17783
|
+
| segments | number | — | Draw the bar as this many separate ticks instead of a continuous fill (a quota, score or spend limit). Filled ticks = round(share × N), at least one for any reading above min, none at min. Ticks take the tone; empty ones sit on the rail colour. The segmented track has its own, taller sm / md / lg ladder. Bar only: ignored for shape="ring". |
|
|
17694
17784
|
| strokeWidth | number | 6 | Ring stroke width, in viewBox units of 100. |
|
|
17695
17785
|
| format | Intl.NumberFormatOptions | — | Formatting for MeterValue and aria-valuetext (percent, unit, currency). |
|
|
17696
17786
|
| getAriaValueText | (formatted: string, value: number) => string | — | Human-readable reading for assistive tech ("7.2 of 10 gigabytes used"). |
|
|
@@ -17704,13 +17794,14 @@ Use Meter to show a static measurement against a known maximum — storage used,
|
|
|
17704
17794
|
|
|
17705
17795
|
## Tokens used
|
|
17706
17796
|
|
|
17707
|
-
`secondary`, `primary`, `success`, `tone-warning`, `destructive`, `foreground`, `muted-foreground`
|
|
17797
|
+
`secondary`, `primary`, `tone-3`, `success`, `tone-warning`, `destructive`, `foreground`, `muted-foreground`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-3-mark`
|
|
17708
17798
|
|
|
17709
17799
|
## Motion
|
|
17710
17800
|
|
|
17711
17801
|
- **arrival** · Meter · _no motion_ — A meter is a reading, present with the page that reports it. Animating the fill up from zero on load would act out a change that did not happen.
|
|
17712
17802
|
- **response** · Meter · _no motion_ — It reports and accepts no input. A bar that reacted to the pointer would read as a Slider the user could drag.
|
|
17713
17803
|
- **transition** · MeterIndicator · _custom (outside the recipe layer)_ — Width and colour ease on the motion tokens (no recipe animates width) when the reading changes — freeing space after a delete visibly shrinks the bar, so the user sees the cause and effect rather than a different bar.
|
|
17804
|
+
- **transition** · MeterTrack (segments) · _custom (outside the recipe layer)_ — With segments, the fill runs through the ticks from the edge it is leaving — left to right as it grows, right to left as it shrinks — each tick easing its colour on the fast token after a stagger that is a share of the slow token (a full sweep is one slow duration) × --motion-expressive. Every tick switching at once would read as a different bar, not the same budget moving; `subtle` lands them together and off is instant.
|
|
17714
17805
|
- **activity** · Meter · _no motion_ — A meter has no indeterminate state; a reading is either known or not shown. Unknown-but-working is Progress with no value.
|
|
17715
17806
|
|
|
17716
17807
|
## Accessibility
|
|
@@ -17755,6 +17846,31 @@ import { Meter, MeterLabel, MeterTrack, MeterValue } from "@sableui/react"
|
|
|
17755
17846
|
</Meter>
|
|
17756
17847
|
```
|
|
17757
17848
|
|
|
17849
|
+
### Segmented spend limit
|
|
17850
|
+
|
|
17851
|
+
```tsx
|
|
17852
|
+
import { Meter, MeterLabel, MeterTrack, MeterValue } from "@sableui/react"
|
|
17853
|
+
|
|
17854
|
+
<Meter value={1240} max={2000} segments={40} getAriaValueText={() => "€1,240 of €2,000 spent this month"}>
|
|
17855
|
+
<div className="mb-2 flex flex-wrap items-center justify-between gap-x-3 gap-y-1">
|
|
17856
|
+
<MeterLabel>Monthly spend limit</MeterLabel>
|
|
17857
|
+
<MeterValue>{() => "€1,240 / €2,000"}</MeterValue>
|
|
17858
|
+
</div>
|
|
17859
|
+
<MeterTrack />
|
|
17860
|
+
</Meter>
|
|
17861
|
+
```
|
|
17862
|
+
|
|
17863
|
+
### Segmented score cell
|
|
17864
|
+
|
|
17865
|
+
```tsx
|
|
17866
|
+
import { Meter } from "@sableui/react"
|
|
17867
|
+
|
|
17868
|
+
<div className="flex items-center gap-3">
|
|
17869
|
+
<Meter value={8} max={10} segments={10} size="sm" tone="success" className="w-24" aria-label="ICP score" getAriaValueText={(_, v) => `${v} out of 10`} />
|
|
17870
|
+
<span className="text-mono-sm text-foreground">8/10</span>
|
|
17871
|
+
</div>
|
|
17872
|
+
```
|
|
17873
|
+
|
|
17758
17874
|
### Tones
|
|
17759
17875
|
|
|
17760
17876
|
```tsx
|
|
@@ -17987,7 +18103,7 @@ Use Note for helpful supporting information that should sit near the relevant co
|
|
|
17987
18103
|
|
|
17988
18104
|
## Tokens used
|
|
17989
18105
|
|
|
17990
|
-
`foreground`, `border`, `muted`, `muted-foreground`, `tone-info`, `tone-warning`, `success`, `destructive`
|
|
18106
|
+
`foreground`, `border`, `muted`, `muted-foreground`, `tone-info`, `tone-warning`, `success`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`
|
|
17991
18107
|
|
|
17992
18108
|
## Motion
|
|
17993
18109
|
|
|
@@ -18154,7 +18270,7 @@ Use NumberField for numeric values that benefit from validation, min/max bounds,
|
|
|
18154
18270
|
|
|
18155
18271
|
## Tokens used
|
|
18156
18272
|
|
|
18157
|
-
`
|
|
18273
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`, `ring`
|
|
18158
18274
|
|
|
18159
18275
|
## Accessibility
|
|
18160
18276
|
|
|
@@ -18332,7 +18448,7 @@ Use OtpField for a short, fixed-length code the user copies from somewhere else
|
|
|
18332
18448
|
|
|
18333
18449
|
## Tokens used
|
|
18334
18450
|
|
|
18335
|
-
`
|
|
18451
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `border-strong`, `primary`, `ring`, `destructive`
|
|
18336
18452
|
|
|
18337
18453
|
## Motion
|
|
18338
18454
|
|
|
@@ -19159,7 +19275,7 @@ Use PieChart for a share of one total across two to six categories — traffic s
|
|
|
19159
19275
|
|
|
19160
19276
|
## Tokens used
|
|
19161
19277
|
|
|
19162
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
19278
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
19163
19279
|
|
|
19164
19280
|
## Motion
|
|
19165
19281
|
|
|
@@ -19543,7 +19659,7 @@ Use RadarChart to compare the shape of a profile across five to eight dimensions
|
|
|
19543
19659
|
|
|
19544
19660
|
## Tokens used
|
|
19545
19661
|
|
|
19546
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
19662
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
19547
19663
|
|
|
19548
19664
|
## Motion
|
|
19549
19665
|
|
|
@@ -19655,7 +19771,7 @@ Use RadialChart for progress toward goals (several rings), a single score or usa
|
|
|
19655
19771
|
|
|
19656
19772
|
## Tokens used
|
|
19657
19773
|
|
|
19658
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
|
|
19774
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
19659
19775
|
|
|
19660
19776
|
## Motion
|
|
19661
19777
|
|
|
@@ -20315,7 +20431,7 @@ import { PropertyRow, ScrubField, Slider } from "@sableui/react"
|
|
|
20315
20431
|
|
|
20316
20432
|
**Status:** stable
|
|
20317
20433
|
|
|
20318
|
-
Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm),
|
|
20434
|
+
Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), the theme's field surface, 2px focus ring; the clear button appears only when there is a value.
|
|
20319
20435
|
|
|
20320
20436
|
**Keywords:** search, input, filter, query
|
|
20321
20437
|
|
|
@@ -20328,6 +20444,7 @@ Use SearchInput for text queries that filter or search a known content set.
|
|
|
20328
20444
|
- Do: Use a clear placeholder that names the content being searched.
|
|
20329
20445
|
- Do: Provide a visible clear affordance when query state persists.
|
|
20330
20446
|
- Do: When a button stands in for search (a header trigger that opens CommandMenu with ⌘K), use CommandMenuTrigger. It wears this field's shell from the same source, so the two cannot drift; a hand-styled button already did once, into a borderless chip that matched neither the field nor a button.
|
|
20447
|
+
- Do: Light the query in the results it filters with `useMatchHighlight(query)` — a callback ref for the results container, with `selector` to keep it to titles. It paints on the `highlight` pair without wrapping matches in `<mark>`, the same paint Combobox and CommandMenu use for their rows.
|
|
20331
20448
|
- Don't: Do not use SearchInput as a command palette; use CommandMenu for global commands, opened from the header by CommandMenuTrigger. A SearchInput that opens a dialog on focus is a text box you cannot type in.
|
|
20332
20449
|
- Don't: Do not search on every keystroke against an expensive backend without debouncing. The user sees results flicker and reorder under them, and you send a request per character.
|
|
20333
20450
|
|
|
@@ -20347,7 +20464,7 @@ Use SearchInput for text queries that filter or search a known content set.
|
|
|
20347
20464
|
|
|
20348
20465
|
## Tokens used
|
|
20349
20466
|
|
|
20350
|
-
`
|
|
20467
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
20351
20468
|
|
|
20352
20469
|
## Motion
|
|
20353
20470
|
|
|
@@ -20395,6 +20512,23 @@ import { Field, SearchInput } from "@sableui/react"
|
|
|
20395
20512
|
<Field label="Search posts"><SearchInput defaultValue="roadmap" className="max-w-80" /></Field>
|
|
20396
20513
|
```
|
|
20397
20514
|
|
|
20515
|
+
### Highlighting matches in the results
|
|
20516
|
+
|
|
20517
|
+
```tsx
|
|
20518
|
+
import { SearchInput, useMatchHighlight } from "@sableui/react"
|
|
20519
|
+
|
|
20520
|
+
const [q, setQ] = useState("")
|
|
20521
|
+
const results = useMatchHighlight<HTMLUListElement>(q, { selector: "[data-title]" })
|
|
20522
|
+
const shown = posts.filter((post) => post.title.toLowerCase().includes(q.trim().toLowerCase()))
|
|
20523
|
+
|
|
20524
|
+
<SearchInput value={q} onValueChange={setQ} placeholder="Search posts" />
|
|
20525
|
+
<ul ref={results}>
|
|
20526
|
+
{shown.map((post) => (
|
|
20527
|
+
<li key={post.id}><span data-title>{post.title}</span></li>
|
|
20528
|
+
))}
|
|
20529
|
+
</ul>
|
|
20530
|
+
```
|
|
20531
|
+
|
|
20398
20532
|
### Disabled
|
|
20399
20533
|
|
|
20400
20534
|
```tsx
|
|
@@ -20552,6 +20686,110 @@ import { SectionHeader } from "@sableui/react"
|
|
|
20552
20686
|
|
|
20553
20687
|
---
|
|
20554
20688
|
|
|
20689
|
+
# SectionRail
|
|
20690
|
+
|
|
20691
|
+
**Status:** stable
|
|
20692
|
+
|
|
20693
|
+
A slim vertical rail of dots, one per section of a long page, that shows where the reader is and jumps to any section. The current section's bar glides as the page scrolls; a shared tooltip names each dot.
|
|
20694
|
+
|
|
20695
|
+
**Keywords:** section rail, scroll spy, scrollspy, dot navigation, page navigation, table of contents, on this page, chapter nav, scroll progress, case study nav
|
|
20696
|
+
|
|
20697
|
+
## Usage
|
|
20698
|
+
|
|
20699
|
+
Use SectionRail beside a long, single-column read — a case study, an essay, a long landing page — so the reader can see which section they are in and jump to another without leaving the text. It follows the scroll by itself (useActiveSection); pass value when the parent already knows the current section.
|
|
20700
|
+
|
|
20701
|
+
### Best practices
|
|
20702
|
+
|
|
20703
|
+
- Do: Give every section an id that matches its entry, and keep the list to the page's top-level sections — about ten at most, so the rail stays shorter than the viewport.
|
|
20704
|
+
- Do: Place it with layout classes — usually `fixed top-1/2 left-6 z-(--z-nav) -translate-y-1/2` — and hide it below the width where it would overlap the text (`max-lg:hidden`). Offer the same links another way on narrow screens, such as a Collapsible list at the top of the page.
|
|
20705
|
+
- Do: Pass `root` when the content scrolls inside an element (a Scroller, an inset AppShell main) rather than the window, and give sections `scroll-mt-*` when a sticky header would cover their tops after a jump.
|
|
20706
|
+
- Do: Set `side="right"` when the rail sits on the right edge, so labels open toward the content.
|
|
20707
|
+
- Don't: Use it for app navigation between pages; that is SideNav or Sidebar. A SectionRail only moves within one page. → use **SideNav**, **Sidebar**
|
|
20708
|
+
- Don't: Use it to show the steps of a task or a flow; that is Stepper, which shows progress through steps rather than position in a read. → use **Stepper**
|
|
20709
|
+
- Don't: Use it for a handful of switchable views in one place; that is Tabs. → use **Tabs**
|
|
20710
|
+
|
|
20711
|
+
### Anatomy
|
|
20712
|
+
|
|
20713
|
+
| Part | Required | Description |
|
|
20714
|
+
| --- | --- | --- |
|
|
20715
|
+
| Rail | yes | The `<nav>` landmark: a rounded muted track holding one link per section. |
|
|
20716
|
+
| Dot | yes | A link to `#id`, drawn as a small dot that grows under the pointer or focus. |
|
|
20717
|
+
| Marker | yes | One primary bar that sits on the current section's dot and glides to the next. |
|
|
20718
|
+
| Label | yes | A shared tooltip that names the hovered or focused dot and travels between dots. |
|
|
20719
|
+
|
|
20720
|
+
## Props
|
|
20721
|
+
|
|
20722
|
+
| Name | Type | Default | Description |
|
|
20723
|
+
| --- | --- | --- | --- |
|
|
20724
|
+
| sections | readonly { id: string; label: string }[] | — | The sections, in document order. `id` is the section element's id (the link goes to `#id`); `label` is the tooltip and the link's accessible name. |
|
|
20725
|
+
| value | string | — | The current section's id, when the parent decides it. Leave it unset and the rail follows the scroll itself. |
|
|
20726
|
+
| onValueChange | (id: string) => void | — | Called with the new id when the reader scrolls into a section or picks one. |
|
|
20727
|
+
| offset | number | 0.35 | Where the reading line sits, as a fraction of the viewport from its top. A section becomes current once its top crosses it. |
|
|
20728
|
+
| root | RefObject<HTMLElement \| null> | — | The element that scrolls, when it is not the window. |
|
|
20729
|
+
| side | "left" \| "right" | "left" | Which edge of the page the rail sits on. The labels open on the other side, toward the content. |
|
|
20730
|
+
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` inherits the app's policy, `subtle` keeps feedback and drops the marker's glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
20731
|
+
| aria-label | string | "Sections" | The nav landmark's name. Name it for the page ("Case study sections") when it has other navs. |
|
|
20732
|
+
|
|
20733
|
+
## Tokens used
|
|
20734
|
+
|
|
20735
|
+
`muted`, `muted-foreground`, `foreground`, `primary`, `ring`, `popover`, `popover-foreground`, `border-floating`
|
|
20736
|
+
|
|
20737
|
+
## Motion
|
|
20738
|
+
|
|
20739
|
+
- **arrival** · Root · _no motion_ — Page chrome arrives with the page; the marker lands on the current section with no transition, because a bar sliding in on load narrates a move nobody made.
|
|
20740
|
+
- **response** · Dot · `motion-interactive` — The dot darkens and grows under the pointer or focus, and the label travels between dots as one tooltip (TooltipGroup) rather than closing and reopening at each.
|
|
20741
|
+
- **transition** · Marker · `motion-glide` — One bar travels to the new section as the page scrolls, so the change reads as movement down the page. A picked section holds the bar until the reader scrolls by hand, so it goes straight there instead of stepping through every section in between, and stays on the pick even when the page ends before that section can reach the reading line. Expressive: `subtle` moves it at once.
|
|
20742
|
+
- **activity** · Root · _no motion_ — Navigation chrome has no working state.
|
|
20743
|
+
|
|
20744
|
+
## Accessibility
|
|
20745
|
+
|
|
20746
|
+
- Renders a <nav> landmark (default name "Sections") of real links to `#id`, so it works with no script, middle-click and copy-link.
|
|
20747
|
+
- Each link is named by its section's label; the current one carries aria-current="location".
|
|
20748
|
+
- Activating a link from the keyboard moves focus into the section, as following an anchor does, so the next Tab continues from there.
|
|
20749
|
+
- The tooltip opens on focus as well as hover; the dots are 44×32 targets.
|
|
20750
|
+
- Under reduced motion the jump is instant and the marker moves without travel.
|
|
20751
|
+
|
|
20752
|
+
## Related
|
|
20753
|
+
|
|
20754
|
+
- Components: SideNav, Tooltip, Stepper, Tabs, GlidingHighlight
|
|
20755
|
+
|
|
20756
|
+
## Examples
|
|
20757
|
+
|
|
20758
|
+
### Fixed beside a long read
|
|
20759
|
+
|
|
20760
|
+
```tsx
|
|
20761
|
+
import { SectionRail } from "@sableui/react"
|
|
20762
|
+
|
|
20763
|
+
<SectionRail
|
|
20764
|
+
aria-label="Case study sections"
|
|
20765
|
+
sections={[
|
|
20766
|
+
{ id: "overview", label: "Overview" },
|
|
20767
|
+
{ id: "problem", label: "The problem" },
|
|
20768
|
+
{ id: "process", label: "Process" },
|
|
20769
|
+
{ id: "outcome", label: "Outcome" },
|
|
20770
|
+
]}
|
|
20771
|
+
className="fixed top-1/2 left-6 z-(--z-nav) -translate-y-1/2 max-lg:hidden"
|
|
20772
|
+
/>
|
|
20773
|
+
```
|
|
20774
|
+
|
|
20775
|
+
### On the right edge
|
|
20776
|
+
|
|
20777
|
+
```tsx
|
|
20778
|
+
import { SectionRail } from "@sableui/react"
|
|
20779
|
+
|
|
20780
|
+
<SectionRail
|
|
20781
|
+
side="right"
|
|
20782
|
+
sections={[
|
|
20783
|
+
{ id: "intro", label: "Introduction" },
|
|
20784
|
+
{ id: "usage", label: "Usage" },
|
|
20785
|
+
{ id: "api", label: "API" },
|
|
20786
|
+
]}
|
|
20787
|
+
className="fixed top-1/2 right-6 z-(--z-nav) -translate-y-1/2 max-lg:hidden"
|
|
20788
|
+
/>
|
|
20789
|
+
```
|
|
20790
|
+
|
|
20791
|
+
---
|
|
20792
|
+
|
|
20555
20793
|
# SegmentedBar
|
|
20556
20794
|
|
|
20557
20795
|
**Status:** stable
|
|
@@ -20594,7 +20832,7 @@ Use SegmentedBar when a stat card or row needs to show how a total divides into
|
|
|
20594
20832
|
|
|
20595
20833
|
## Tokens used
|
|
20596
20834
|
|
|
20597
|
-
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `primary`, `muted-foreground`, `success`, `tone-warning`, `destructive`
|
|
20835
|
+
`tone-1`, `tone-2`, `tone-3`, `tone-info`, `primary`, `muted-foreground`, `success`, `tone-warning`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
20598
20836
|
|
|
20599
20837
|
## Motion
|
|
20600
20838
|
|
|
@@ -20709,7 +20947,7 @@ Use Select when users choose one value from a known, finite set. Use Combobox fo
|
|
|
20709
20947
|
|
|
20710
20948
|
## Tokens used
|
|
20711
20949
|
|
|
20712
|
-
`
|
|
20950
|
+
`field-border`, `field`, `foreground`, `ring`, `border`, `popover`, `popover-foreground`, `accent`, `accent-foreground`, `muted-foreground`
|
|
20713
20951
|
|
|
20714
20952
|
## Motion
|
|
20715
20953
|
|
|
@@ -21194,6 +21432,7 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
21194
21432
|
- Do: Use it for stable app destinations that should remain visible while content changes.
|
|
21195
21433
|
- Do: Keep item labels predictable and active state obvious.
|
|
21196
21434
|
- Do: Set surface="page" when a full-width top bar sits above the rail. A theme's tinted or dark rail is for a full-height rail that frames the app.
|
|
21435
|
+
- Do: Keep surface="rail" inside AppShell frame="inset": the rail's tint becomes the ground the main panel floats on. Do not add a border-r class back — the gap is the divider.
|
|
21197
21436
|
- Don't: Do not use Sidebar for page-local settings, filters, or temporary action groups.
|
|
21198
21437
|
- Don't: Do not put page content in it. It is fixed-width chrome OUTSIDE the content grid, so anything in it is exempt from the page's measure and rhythm.
|
|
21199
21438
|
|
|
@@ -21219,7 +21458,7 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
21219
21458
|
| collapsed | boolean | false | Collapse the rail to an icon-only width (--sidebar-width-collapsed). Items hide their label + badge and reveal the label in a hover tooltip; the content area reflows. Controlled — wire a toggle from app state. Read the state in custom header/footer content via useSidebar(). |
|
|
21220
21459
|
| icons | boolean | true | Render the leading icon on each SidebarItem. Set false for a text-only rail. Ignored while collapsed (icon-only by definition). |
|
|
21221
21460
|
| variant | "app" \| "inline" | "app" | Where the rail lives. "app" is the page's chrome column — sticky, full viewport height, hidden below lg where AppHeader + BottomNav take over. "inline" is always visible and fills its parent's height, for a rail inside a bounded frame (a nested shell, an embedded preview). Use it instead of overriding the layout with !important classes. |
|
|
21222
|
-
| surface | "rail" \| "page" | "rail" | Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border — right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border — use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat. |
|
|
21461
|
+
| surface | "rail" \| "page" | "rail" | Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border — right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border — use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat. In AppShell's sidebar slot with frame="inset", the rail's surface is also the shell's ground: from lg a "rail" rail drops its border (the gap around the main panel divides them) and either surface stretches to the shell's body row instead of sizing to the viewport. AppShell signals this to its sidebar slot only, so a Sidebar elsewhere in the page is unaffected. |
|
|
21223
21462
|
| motion | "full" \| "subtle" \| "off" | "full" | How much motion this component uses: `full` inherits the app's policy, `subtle` keeps feedback and drops the collapse ease and the current-page glide, `off` stills it. It can only turn motion down — it never overrides a reader's reduce-motion setting. |
|
|
21224
21463
|
|
|
21225
21464
|
## Variants
|
|
@@ -21228,7 +21467,7 @@ Use Sidebar for persistent desktop navigation or application chrome outside the
|
|
|
21228
21467
|
|
|
21229
21468
|
## Tokens used
|
|
21230
21469
|
|
|
21231
|
-
`sidebar`, `sidebar-foreground`, `sidebar-border`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-ring`
|
|
21470
|
+
`sidebar`, `sidebar-foreground`, `sidebar-border`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-ring`, `shell-ground`
|
|
21232
21471
|
|
|
21233
21472
|
## Motion
|
|
21234
21473
|
|
|
@@ -21636,7 +21875,7 @@ Use Sparkline next to a number to show where it has been — a KPI's last 30 day
|
|
|
21636
21875
|
|
|
21637
21876
|
## Tokens used
|
|
21638
21877
|
|
|
21639
|
-
`primary`, `tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `muted-foreground`, `success`, `destructive`
|
|
21878
|
+
`primary`, `tone-1`, `tone-2`, `tone-3`, `tone-info`, `tone-warning`, `muted-foreground`, `success`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
21640
21879
|
|
|
21641
21880
|
## Motion
|
|
21642
21881
|
|
|
@@ -21907,7 +22146,7 @@ Use StatusDot as a compact visual state cue next to text, avatars, rows, or labe
|
|
|
21907
22146
|
|
|
21908
22147
|
## Tokens used
|
|
21909
22148
|
|
|
21910
|
-
`palette-dot`, `muted-foreground`, `tone-3`, `success`, `tone-warning`, `tone-info`, `tone-1`, `tone-2`, `destructive`
|
|
22149
|
+
`palette-dot`, `muted-foreground`, `tone-3`, `success`, `tone-warning`, `tone-info`, `tone-1`, `tone-2`, `destructive`, `destructive-mark`, `success-mark`, `tone-warning-mark`, `tone-info-mark`, `tone-1-mark`, `tone-2-mark`, `tone-3-mark`
|
|
21911
22150
|
|
|
21912
22151
|
## Accessibility
|
|
21913
22152
|
|
|
@@ -21992,7 +22231,7 @@ Use Stepper above or beside a flow that is split into a known, small number of n
|
|
|
21992
22231
|
|
|
21993
22232
|
## Tokens used
|
|
21994
22233
|
|
|
21995
|
-
`primary`, `primary-foreground`, `background`, `foreground`, `muted-foreground`, `border-strong`, `destructive`, `destructive-foreground`, `ring`
|
|
22234
|
+
`primary`, `primary-foreground`, `background`, `foreground`, `muted-foreground`, `border-strong`, `destructive`, `destructive-foreground`, `ring`, `destructive-mark`
|
|
21996
22235
|
|
|
21997
22236
|
## Motion
|
|
21998
22237
|
|
|
@@ -22285,8 +22524,8 @@ Use Table for structured data with consistent columns and rows. Use DataTable wh
|
|
|
22285
22524
|
| TableBody | ComponentPropsWithRef<"tbody"> | The data row group (tbody). |
|
|
22286
22525
|
| TableFooter | ComponentPropsWithRef<"tfoot"> | Summary row group (tfoot) with a muted fill. |
|
|
22287
22526
|
| TableRow | ComponentPropsWithRef<"tr"> | A row (tr) — hairline bottom border, hover lift, and a selected state via the data-selected attribute. |
|
|
22288
|
-
| TableHead | ComponentPropsWithRef<"th"> | A column header cell (th) in muted label styling. |
|
|
22289
|
-
| TableCell | ComponentPropsWithRef<"td"> | A data cell (td). |
|
|
22527
|
+
| TableHead | ComponentPropsWithRef<"th"> & { numeric?: boolean } | A column header cell (th) in muted label styling. `numeric` marks a number column: end-aligned, tabular figures — set it on the column's cells too. |
|
|
22528
|
+
| TableCell | ComponentPropsWithRef<"td"> & { numeric?: boolean } | A data cell (td). `numeric` end-aligns it with tabular figures, matching its column's header. |
|
|
22290
22529
|
| TableCaption | ComponentPropsWithRef<"caption"> | Accessible description of the table (caption), muted. |
|
|
22291
22530
|
|
|
22292
22531
|
## Props
|
|
@@ -22478,7 +22717,7 @@ import { Tabs, TabsList, TabsPanel, TabsTrigger } from "@sableui/react"
|
|
|
22478
22717
|
|
|
22479
22718
|
**Status:** stable
|
|
22480
22719
|
|
|
22481
|
-
Styled native multi-line text control. Soft-square,
|
|
22720
|
+
Styled native multi-line text control. Soft-square, the theme's field surface, --ring focus, and an aria-invalid destructive error state.
|
|
22482
22721
|
|
|
22483
22722
|
**Keywords:** textarea, multi line input, form, text
|
|
22484
22723
|
|
|
@@ -22507,7 +22746,7 @@ Use Textarea for multi-line freeform text entry such as notes, descriptions, or
|
|
|
22507
22746
|
|
|
22508
22747
|
## Tokens used
|
|
22509
22748
|
|
|
22510
|
-
`
|
|
22749
|
+
`field-border`, `field`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
22511
22750
|
|
|
22512
22751
|
## Accessibility
|
|
22513
22752
|
|
|
@@ -24033,7 +24272,7 @@ Which token to reach for, and what it is most often confused with. Generated fro
|
|
|
24033
24272
|
- **Not** `border-strong`. Reach for `border-strong` when the line carries *structure* (a grid rule, a table divider, a section separator) and for `border` when it merely bounds a control.
|
|
24034
24273
|
- **`border-strong`** — A structural divider. Visibly stronger than `border`. *Use for:* Grid lines, table rules, separators between major sections.
|
|
24035
24274
|
- **Not** `border`. If removing the line would make the layout ambiguous, it is structural and belongs here.
|
|
24036
|
-
- **`input`** — The outline of a form control specifically. *Use for:* Text inputs, textareas and selects. Not the edge of a checkbox, radio or switch — that is `control-border`.
|
|
24275
|
+
- **`input`** — The outline of a form control specifically. *Use for:* Text inputs, textareas and selects (through `field-border`, which derives from it). Not the edge of a checkbox, radio or switch — that is `control-border`.
|
|
24037
24276
|
- **Not** `border`. They are often the same value, but a theme is entitled to make form controls read differently from other chrome, and only this token gives it the hook.
|
|
24038
24277
|
- **`control-border`** — The resting edge of a control whose outline is the whole control. Held to 3:1 (WCAG 1.4.11) on every surface. *Use for:* An unchecked checkbox, an unselected radio, an off switch track, a selection card's check or radio mark — anywhere removing the edge would make the control disappear.
|
|
24039
24278
|
- **Not** `input`. A text field is also found by its fill, caret and placeholder, so its outline may stay quiet; a checkbox has nothing else, and at `input`'s strength it measures about 1.4:1.
|
|
@@ -24323,7 +24562,7 @@ Which token to reach for, and what it is most often confused with. Generated fro
|
|
|
24323
24562
|
- **`type-mono-sm-tracking`** — The monospace family: mono-xl through mono-sm, plus mono-caption. *Use for:* Code, identifiers, hashes, keyboard keys, and any figure where digits must align in a column.
|
|
24324
24563
|
- **Not** the sans tiers for numeric data. Proportional digits make a column of numbers ragged; that is what `text-ui-num` solves in chrome and mono solves on a page.
|
|
24325
24564
|
|
|
24326
|
-
### derived (
|
|
24565
|
+
### derived (20)
|
|
24327
24566
|
|
|
24328
24567
|
- **`canvas`** — The WORK surface — the artboard, the document being edited. *Use for:* Behind the thing the tool operates on. `WorkspaceCanvas` uses it.
|
|
24329
24568
|
- **Not** `background`. The thing being edited is not the page behind the UI, and collapsing the two makes the document indistinguishable from its chrome.
|
|
@@ -24345,6 +24584,26 @@ Which token to reach for, and what it is most often confused with. Generated fro
|
|
|
24345
24584
|
- **Not** `hairline`, when the line would otherwise disappear against what it bounds.
|
|
24346
24585
|
- **`border-floating`** — The edge of a floating surface. Fainter than `border`: the shadow carries the elevation. *Use for:* Every floating surface — popover, menu, select, tooltip, dialog, sheet, toast, a floating panel — as `border-border-floating` or `ring-border-floating`, together with `bg-popover` and a `shadow-md`+ shadow. Derived for every theme (a faint alpha of the foreground); a theme may tune it with `--theme-border-floating`.
|
|
24347
24586
|
- **Not** `border`. A full hairline plus a shadow stacks into a heavy outline around anything that floats. `border` is for resting edges: inputs, cards, badges.
|
|
24587
|
+
- **`field`** — The fill of a text field: Input, Textarea, Select, Combobox, SearchInput, InputGroup, ChatComposer. *Use for:* Read by every field through its shell, so you rarely name it. Derived for every theme (`card`, an outlined field on the card); a theme fills its fields instead with `--theme-field: var(--muted)` and `--theme-field-border: transparent`, and every field changes at once. Name it only for a field-shaped surface of your own that must match.
|
|
24588
|
+
- **Not** `card` or `muted` on a hand-built field. A field that paints `card` keeps its outline when the theme fills the rest, and one that paints `muted` stays filled when the theme outlines them.
|
|
24589
|
+
- **`field-border`** — The outline of a text field. Derived from `input`; `transparent` in a theme with filled fields. *Use for:* With `field`, on the same field-shaped surfaces. Tune it with `--theme-field-border`.
|
|
24590
|
+
- **Not** `input` on a text field. `input` stays the theme's outline colour for form chrome; this is the field's outline, which a filled theme turns off.
|
|
24591
|
+
- **`shell-ground`** — The ground of an inset app frame: the rail and the gap around the raised main panel. *Use for:* Painted by `<AppShell frame="inset">` and the Sidebar in its slot; you rarely name it. Derived for every theme as one step off `sidebar` toward its ink, so the panel reads as lifted even where the rail is the page colour. Tune it with `--theme-shell-ground`.
|
|
24592
|
+
- **Not** `sidebar` or `canvas` behind an inset panel. `sidebar` is the page colour in most themes, so the panel would sit on itself; `canvas` is a workspace's artboard, not app chrome.
|
|
24593
|
+
- **`destructive-mark`** — `destructive` at graphic strength: the same hue lifted and saturated, for a mark that carries no text. *Use for:* A meter's or progress fill, a segmented bar, a status dot, a chart series, and the soft tint behind a badge or note (`bg-destructive-mark/15`). Held to 2:1 on the page — visibility, not a text ratio — because each of these sits beside text that says the same thing. Every bundled theme tunes its own; a preset sets `--theme-destructive-mark` or gets one derived from `destructive`.
|
|
24594
|
+
- **Not** `bg-destructive` on a mark. The fill is pinned dark enough to carry a white label, so a meter or dot painted with it reads brown, forest or plum. And not for text — that is `text-destructive` — nor for a graphic that is the only carrier of its meaning, which owes 3:1 (WCAG 1.4.11): use the fill or add the text.
|
|
24595
|
+
- **`success-mark`** — `success` at graphic strength: the same hue lifted and saturated, for a mark that carries no text. *Use for:* A meter's or progress fill, a segmented bar, a status dot, a chart series, and the soft tint behind a badge or note (`bg-success-mark/15`). Held to 2:1 on the page — visibility, not a text ratio — because each of these sits beside text that says the same thing. Every bundled theme tunes its own; a preset sets `--theme-success-mark` or gets one derived from `success`.
|
|
24596
|
+
- **Not** `bg-success` on a mark. The fill is pinned dark enough to carry a white label, so a meter or dot painted with it reads brown, forest or plum. And not for text — that is `text-success` — nor for a graphic that is the only carrier of its meaning, which owes 3:1 (WCAG 1.4.11): use the fill or add the text.
|
|
24597
|
+
- **`tone-warning-mark`** — `tone-warning` at graphic strength: the same hue lifted and saturated, for a mark that carries no text. *Use for:* A meter's or progress fill, a segmented bar, a status dot, a chart series, and the soft tint behind a badge or note (`bg-tone-warning-mark/15`). Held to 2:1 on the page — visibility, not a text ratio — because each of these sits beside text that says the same thing. Every bundled theme tunes its own; a preset sets `--theme-tone-warning-mark` or gets one derived from `tone-warning`.
|
|
24598
|
+
- **Not** `bg-tone-warning` on a mark. The fill is pinned dark enough to carry a white label, so a meter or dot painted with it reads brown, forest or plum. And not for text — that is `text-tone-warning` — nor for a graphic that is the only carrier of its meaning, which owes 3:1 (WCAG 1.4.11): use the fill or add the text.
|
|
24599
|
+
- **`tone-info-mark`** — `tone-info` at graphic strength: the same hue lifted and saturated, for a mark that carries no text. *Use for:* A meter's or progress fill, a segmented bar, a status dot, a chart series, and the soft tint behind a badge or note (`bg-tone-info-mark/15`). Held to 2:1 on the page — visibility, not a text ratio — because each of these sits beside text that says the same thing. Every bundled theme tunes its own; a preset sets `--theme-tone-info-mark` or gets one derived from `tone-info`.
|
|
24600
|
+
- **Not** `bg-tone-info` on a mark. The fill is pinned dark enough to carry a white label, so a meter or dot painted with it reads brown, forest or plum. And not for text — that is `text-tone-info` — nor for a graphic that is the only carrier of its meaning, which owes 3:1 (WCAG 1.4.11): use the fill or add the text.
|
|
24601
|
+
- **`tone-1-mark`** — `tone-1` at graphic strength: the same hue lifted and saturated, for a mark that carries no text. *Use for:* A meter's or progress fill, a segmented bar, a status dot, a chart series, and the soft tint behind a badge or note (`bg-tone-1-mark/15`). Held to 2:1 on the page — visibility, not a text ratio — because each of these sits beside text that says the same thing. Every bundled theme tunes its own; a preset sets `--theme-tone-1-mark` or gets one derived from `tone-1`.
|
|
24602
|
+
- **Not** `bg-tone-1` on a mark. The fill is pinned dark enough to carry a white label, so a meter or dot painted with it reads brown, forest or plum. And not for text — that is `text-tone-1` — nor for a graphic that is the only carrier of its meaning, which owes 3:1 (WCAG 1.4.11): use the fill or add the text.
|
|
24603
|
+
- **`tone-2-mark`** — `tone-2` at graphic strength: the same hue lifted and saturated, for a mark that carries no text. *Use for:* A meter's or progress fill, a segmented bar, a status dot, a chart series, and the soft tint behind a badge or note (`bg-tone-2-mark/15`). Held to 2:1 on the page — visibility, not a text ratio — because each of these sits beside text that says the same thing. Every bundled theme tunes its own; a preset sets `--theme-tone-2-mark` or gets one derived from `tone-2`.
|
|
24604
|
+
- **Not** `bg-tone-2` on a mark. The fill is pinned dark enough to carry a white label, so a meter or dot painted with it reads brown, forest or plum. And not for text — that is `text-tone-2` — nor for a graphic that is the only carrier of its meaning, which owes 3:1 (WCAG 1.4.11): use the fill or add the text.
|
|
24605
|
+
- **`tone-3-mark`** — `tone-3` at graphic strength: the same hue lifted and saturated, for a mark that carries no text. *Use for:* A meter's or progress fill, a segmented bar, a status dot, a chart series, and the soft tint behind a badge or note (`bg-tone-3-mark/15`). Held to 2:1 on the page — visibility, not a text ratio — because each of these sits beside text that says the same thing. Every bundled theme tunes its own; a preset sets `--theme-tone-3-mark` or gets one derived from `tone-3`.
|
|
24606
|
+
- **Not** `bg-tone-3` on a mark. The fill is pinned dark enough to carry a white label, so a meter or dot painted with it reads brown, forest or plum. And not for text — that is `text-tone-3` — nor for a graphic that is the only carrier of its meaning, which owes 3:1 (WCAG 1.4.11): use the fill or add the text.
|
|
24348
24607
|
|
|
24349
24608
|
### Generated palette
|
|
24350
24609
|
|
|
@@ -24594,7 +24853,7 @@ The `@utility` and `@custom-variant` layer in `styles.css`: what each one is, wh
|
|
|
24594
24853
|
- **Instead of** A hand-written dash-offset animation with literal timing, which keeps animating, or keeps its delay, under reduced motion.
|
|
24595
24854
|
- `<Reveal><svg viewBox="0 0 100 100"><path className="motion-draw" pathLength="1" d="M10 50 L90 50" /></svg></Reveal>`
|
|
24596
24855
|
|
|
24597
|
-
### helper (
|
|
24856
|
+
### helper (6)
|
|
24598
24857
|
|
|
24599
24858
|
- **`hit-target`** — Grows an element's tap target to 44px with a transparent `::after`, without moving its layout by a pixel. `--hit-target-size` overrides the size. *Use for:* A deliberately small mark — a tag's remove ×, a tiny disclosure, a toast close — that must still meet the WCAG touch floor. Shrink it (`[--hit-target-size:32px]`) where targets sit side by side, or the later one steals the taps.
|
|
24600
24859
|
- **Instead of** Padding the glyph up (moves it off the row's grid and pushes siblings) or a bigger icon. Never on an `<input>` or other replaced element — pseudo-elements silently do nothing there; wrap it in a label.
|
|
@@ -24611,3 +24870,6 @@ The `@utility` and `@custom-variant` layer in `styles.css`: what each one is, wh
|
|
|
24611
24870
|
- **`checkerboard`** — The transparency checkerboard, painted from `--panel` and a derived darker step so it reads in both modes. `--checkerboard-size` (default 16px) and `--checkerboard-alt` tune it. *Use for:* Behind anything that can be translucent: a colour swatch with alpha, an image or artboard preview, an alpha slider track.
|
|
24612
24871
|
- **Instead of** A hand-rolled `repeating-conic-gradient` with two hardcoded greys. Deliberately not named `bg-checkerboard`, so `cn()` never merges it away against a real `bg-*` colour — you can layer a colour over it.
|
|
24613
24872
|
- `<div className="checkerboard [--checkerboard-size:8px] size-6 rounded-sm" />`
|
|
24873
|
+
- **`texture-dither`** — A faint pixel / halftone dither field that dissolves: a small pseudo-random tile of squares painted in `--border-strong` and faded out by a gradient mask. Static, pure CSS, follows every theme and mode. Hooks: `--dither-fade` (direction, any linear-gradient side or angle, default `to bottom`), `--dither-size` (tile, default 48px = 2px squares) and `--dither-color` (default `var(--border-strong)`). *Use for:* Decoration at the top of a content area, a panel or a card, or behind an empty-state heading. Put it on its own `aria-hidden` element, `absolute -z-10` as the first child of a `relative isolate` parent, so the content after it paints on top with no z-index of its own. It sets `pointer-events: none` and no position, so it never takes layout or catches clicks.
|
|
24874
|
+
- **Instead of** A hand-written `radial-gradient` dot grid (reads as graph paper, not dither), a PNG noise texture, a canvas, or hardcoded greys. Deliberately not named `bg-dither`, so `cn()` never merges it away against a real `bg-*` colour class.
|
|
24875
|
+
- `<div className="relative isolate"><div aria-hidden className="texture-dither absolute inset-x-0 top-0 -z-10 h-40" /></div>`
|