@nadicodeai/ui 12.1.110 → 12.1.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -9
- package/README.md +3 -2
- package/dist/components/account-menu.d.ts +1 -1
- package/dist/components/account-menu.js +1 -1
- package/dist/components/action-menu.d.ts +2 -2
- package/dist/components/action-menu.js +2 -2
- package/dist/components/activity-feed.d.ts +1 -1
- package/dist/components/activity-feed.js +1 -1
- package/dist/components/agent-card.d.ts +5 -3
- package/dist/components/agent-card.d.ts.map +1 -1
- package/dist/components/agent-card.js +5 -3
- package/dist/components/agent-list-row.d.ts +2 -2
- package/dist/components/agent-list-row.js +2 -2
- package/dist/components/agent-orb.d.ts +31 -7
- package/dist/components/agent-orb.d.ts.map +1 -1
- package/dist/components/agent-orb.js +29 -5
- package/dist/components/agent-profile.d.ts +6 -2
- package/dist/components/agent-profile.d.ts.map +1 -1
- package/dist/components/agent-profile.js +6 -2
- package/dist/components/aspect-ratio.d.ts +7 -3
- package/dist/components/aspect-ratio.d.ts.map +1 -1
- package/dist/components/aspect-ratio.js +7 -3
- package/dist/components/avatar.d.ts +11 -5
- package/dist/components/avatar.d.ts.map +1 -1
- package/dist/components/avatar.js +11 -5
- package/dist/components/breadcrumb.d.ts +1 -1
- package/dist/components/breadcrumb.js +1 -1
- package/dist/components/button.d.ts +23 -10
- package/dist/components/button.d.ts.map +1 -1
- package/dist/components/button.js +23 -10
- package/dist/components/card-row.d.ts +4 -2
- package/dist/components/card-row.d.ts.map +1 -1
- package/dist/components/card-row.js +4 -2
- package/dist/components/card.d.ts +1 -1
- package/dist/components/card.js +1 -1
- package/dist/components/carousel.d.ts +9 -4
- package/dist/components/carousel.d.ts.map +1 -1
- package/dist/components/carousel.js +9 -4
- package/dist/components/catalog-drawer.d.ts +1 -1
- package/dist/components/catalog-drawer.js +1 -1
- package/dist/components/chart-card.d.ts +1 -1
- package/dist/components/chart-card.js +1 -1
- package/dist/components/checkbox.d.ts +2 -1
- package/dist/components/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox.js +2 -1
- package/dist/components/choicebox.d.ts +9 -5
- package/dist/components/choicebox.d.ts.map +1 -1
- package/dist/components/choicebox.js +9 -5
- package/dist/components/clearable-input.d.ts +2 -1
- package/dist/components/clearable-input.d.ts.map +1 -1
- package/dist/components/clearable-input.js +2 -1
- package/dist/components/code-block.d.ts +13 -10
- package/dist/components/code-block.d.ts.map +1 -1
- package/dist/components/code-block.js +13 -10
- package/dist/components/collection-filters.d.ts +1 -1
- package/dist/components/collection-filters.js +1 -1
- package/dist/components/collection-search.d.ts +11 -7
- package/dist/components/collection-search.d.ts.map +1 -1
- package/dist/components/collection-search.js +11 -7
- package/dist/components/combobox.d.ts +4 -1
- package/dist/components/combobox.d.ts.map +1 -1
- package/dist/components/combobox.js +4 -1
- package/dist/components/command.d.ts +1 -1
- package/dist/components/command.js +1 -1
- package/dist/components/company-card.d.ts +28 -5
- package/dist/components/company-card.d.ts.map +1 -1
- package/dist/components/company-card.js +28 -5
- package/dist/components/confirm-dialog.d.ts +3 -2
- package/dist/components/confirm-dialog.d.ts.map +1 -1
- package/dist/components/confirm-dialog.js +2 -2
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/copy-button.js +2 -2
- package/dist/components/data-table.d.ts +2 -2
- package/dist/components/data-table.js +2 -2
- package/dist/components/drawer.d.ts +4 -4
- package/dist/components/drawer.d.ts.map +1 -1
- package/dist/components/drawer.js +3 -3
- package/dist/components/dropdown-menu.d.ts +2 -2
- package/dist/components/dropdown-menu.js +2 -2
- package/dist/components/empty-state.d.ts +2 -2
- package/dist/components/empty-state.js +2 -2
- package/dist/components/field.d.ts +4 -0
- package/dist/components/field.d.ts.map +1 -1
- package/dist/components/field.js +4 -0
- package/dist/components/file-diff.d.ts +5 -2
- package/dist/components/file-diff.d.ts.map +1 -1
- package/dist/components/file-diff.js +5 -2
- package/dist/components/form-dialog.d.ts +1 -1
- package/dist/components/form-dialog.js +1 -1
- package/dist/components/form-drawer.d.ts +1 -1
- package/dist/components/form-drawer.js +1 -1
- package/dist/components/gauge.d.ts +9 -7
- package/dist/components/gauge.d.ts.map +1 -1
- package/dist/components/gauge.js +9 -7
- package/dist/components/hover-card.d.ts +5 -2
- package/dist/components/hover-card.d.ts.map +1 -1
- package/dist/components/hover-card.js +5 -2
- package/dist/components/identity-mark.d.ts +3 -3
- package/dist/components/identity-mark.js +3 -3
- package/dist/components/input-group.d.ts +1 -0
- package/dist/components/input-group.d.ts.map +1 -1
- package/dist/components/input-group.js +1 -0
- package/dist/components/input-otp.d.ts +5 -3
- package/dist/components/input-otp.d.ts.map +1 -1
- package/dist/components/input-otp.js +5 -3
- package/dist/components/input.d.ts +4 -2
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +4 -2
- package/dist/components/item.d.ts +1 -1
- package/dist/components/item.js +1 -1
- package/dist/components/kbd.d.ts +4 -2
- package/dist/components/kbd.d.ts.map +1 -1
- package/dist/components/kbd.js +4 -2
- package/dist/components/label.d.ts +8 -3
- package/dist/components/label.d.ts.map +1 -1
- package/dist/components/label.js +8 -3
- package/dist/components/load-more-button.d.ts +3 -2
- package/dist/components/load-more-button.d.ts.map +1 -1
- package/dist/components/load-more-button.js +3 -2
- package/dist/components/message-notice.d.ts +1 -1
- package/dist/components/message-notice.js +1 -1
- package/dist/components/multi-select.d.ts +6 -4
- package/dist/components/multi-select.d.ts.map +1 -1
- package/dist/components/multi-select.js +6 -4
- package/dist/components/notice-banner.d.ts +2 -2
- package/dist/components/notice-banner.js +2 -2
- package/dist/components/object-workspace.d.ts +3 -3
- package/dist/components/object-workspace.d.ts.map +1 -1
- package/dist/components/object-workspace.js +2 -2
- package/dist/components/pairing-code.d.ts +2 -2
- package/dist/components/pairing-code.js +2 -2
- package/dist/components/picture-label.d.ts +3 -2
- package/dist/components/picture-label.d.ts.map +1 -1
- package/dist/components/picture-label.js +3 -2
- package/dist/components/popover.d.ts +7 -3
- package/dist/components/popover.d.ts.map +1 -1
- package/dist/components/popover.js +7 -3
- package/dist/components/progress-checklist.d.ts +1 -1
- package/dist/components/progress-checklist.js +1 -1
- package/dist/components/progress.d.ts +4 -2
- package/dist/components/progress.d.ts.map +1 -1
- package/dist/components/progress.js +4 -2
- package/dist/components/qr-code.d.ts +5 -2
- package/dist/components/qr-code.d.ts.map +1 -1
- package/dist/components/qr-code.js +5 -2
- package/dist/components/radio-group.d.ts +8 -5
- package/dist/components/radio-group.d.ts.map +1 -1
- package/dist/components/radio-group.js +8 -5
- package/dist/components/record-identity.d.ts +1 -1
- package/dist/components/record-identity.js +1 -1
- package/dist/components/record-list.d.ts +2 -2
- package/dist/components/record-list.js +2 -2
- package/dist/components/record-sheet.d.ts +2 -2
- package/dist/components/record-sheet.js +2 -2
- package/dist/components/record-time.d.ts +1 -1
- package/dist/components/record-time.js +1 -1
- package/dist/components/resizable.d.ts +2 -2
- package/dist/components/resizable.js +2 -2
- package/dist/components/row-edit-form.d.ts +1 -1
- package/dist/components/row-edit-form.js +1 -1
- package/dist/components/run-timeline.d.ts +2 -2
- package/dist/components/run-timeline.js +2 -2
- package/dist/components/scroll-area.d.ts +5 -3
- package/dist/components/scroll-area.d.ts.map +1 -1
- package/dist/components/scroll-area.js +5 -3
- package/dist/components/segmented-control.d.ts +1 -1
- package/dist/components/segmented-control.js +1 -1
- package/dist/components/select.d.ts +5 -3
- package/dist/components/select.d.ts.map +1 -1
- package/dist/components/select.js +5 -3
- package/dist/components/separator.d.ts +4 -2
- package/dist/components/separator.d.ts.map +1 -1
- package/dist/components/separator.js +4 -2
- package/dist/components/settings-facts.d.ts +3 -2
- package/dist/components/settings-facts.d.ts.map +1 -1
- package/dist/components/settings-facts.js +3 -2
- package/dist/components/settings-section.d.ts +1 -1
- package/dist/components/settings-section.js +1 -1
- package/dist/components/sheet.d.ts +6 -3
- package/dist/components/sheet.d.ts.map +1 -1
- package/dist/components/sheet.js +6 -3
- package/dist/components/skeleton.d.ts +1 -1
- package/dist/components/skeleton.js +1 -1
- package/dist/components/slider.d.ts +2 -1
- package/dist/components/slider.d.ts.map +1 -1
- package/dist/components/slider.js +2 -1
- package/dist/components/snippet.d.ts +3 -1
- package/dist/components/snippet.d.ts.map +1 -1
- package/dist/components/snippet.js +3 -1
- package/dist/components/sonner.d.ts +2 -2
- package/dist/components/sonner.js +2 -2
- package/dist/components/status-badge.d.ts +5 -2
- package/dist/components/status-badge.d.ts.map +1 -1
- package/dist/components/status-badge.js +5 -2
- package/dist/components/status-dot.d.ts +2 -1
- package/dist/components/status-dot.d.ts.map +1 -1
- package/dist/components/status-dot.js +2 -1
- package/dist/components/switch.d.ts +7 -6
- package/dist/components/switch.d.ts.map +1 -1
- package/dist/components/switch.js +7 -6
- package/dist/components/table.d.ts +18 -11
- package/dist/components/table.d.ts.map +1 -1
- package/dist/components/table.js +18 -11
- package/dist/components/tabs.d.ts +19 -13
- package/dist/components/tabs.d.ts.map +1 -1
- package/dist/components/tabs.js +19 -13
- package/dist/components/text-link.d.ts +9 -4
- package/dist/components/text-link.d.ts.map +1 -1
- package/dist/components/text-link.js +9 -4
- package/dist/components/text-with-copy-button.d.ts +2 -2
- package/dist/components/text-with-copy-button.js +2 -2
- package/dist/components/textarea.d.ts +4 -3
- package/dist/components/textarea.d.ts.map +1 -1
- package/dist/components/textarea.js +4 -3
- package/dist/components/tooltip.d.ts +5 -3
- package/dist/components/tooltip.d.ts.map +1 -1
- package/dist/components/tooltip.js +5 -3
- package/dist/components/usage-meter.d.ts +2 -2
- package/dist/components/usage-meter.js +2 -2
- package/dist/components/waiting-line.d.ts +1 -1
- package/dist/components/waiting-line.js +1 -1
- package/dist/showcase/usage.generated.d.ts.map +1 -1
- package/dist/showcase/usage.generated.js +147 -147
- package/package.json +2 -2
|
@@ -21,7 +21,7 @@ export const USAGE = [
|
|
|
21
21
|
summary: "What happened to a record, and when: one row per event under its day, newest first.",
|
|
22
22
|
when: "A record's history: a Customer's, a Partner's or an Installation's Activity tab in the Portal, the company wiki's updates in Nadia. Each answers \"what happened, and when?\".",
|
|
23
23
|
instead: 'The steps of one Agent run, read in order, are a RunTimeline; a list of records a person finds and acts on is a CollectionPage; a single "last changed" fact is a RecordTime.',
|
|
24
|
-
behaviour: "The search, filters and window sit right above the rows, kept in the URL by the product. Days stand newest first, each a title and a muted date over one card of rows; a row is the time of day, the actor's 24 px mark, the sentence with a status badge only for an exception, and one muted line of detail. \"Show older events\" follows the last day as a full-width `LoadMoreButton`, or as a `ButtonLink` when it navigates; there is never a page count. States, first that holds: failed, loading (still rows in the row's shape), no match with Clear filters, nothing yet in one line, the days. Under 900 px the time moves under the sentence. It draws no card header: the tab is the title.",
|
|
24
|
+
behaviour: "The search, filters and window sit right above the rows, kept in the URL by the product. Days stand newest first, each a title and a muted date over one card of rows; a row is the time of day, the actor's 24 px mark, the sentence with a status badge only for an exception, and one muted line of detail. \"Show older events\" follows the last day as a full-width `LoadMoreButton`, or as a `ButtonLink` when it navigates; there is never a page count. States, first that holds: failed, loading (still rows in the row's shape), no match with Clear filters, nothing yet in one line, the days. Nothing yet is one quiet line, never a picture; a failed read is the warning mark with Retry, never \"no events\". The time is the time of day in the viewer's own zone in tabular figures (`RecordTime`), with UTC and the viewer's zone in its hover card; one rule for every feed, never a time printed in UTC as the viewer's: a server-rendered page passes the viewer's `timeZone`, since without it the server and the first client render print UTC. The actor's mark is a person's initials, an Agent's real orb or an Installation's tile; the system's own work has no mark and never takes an orb. Under 900 px the time moves under the sentence. It draws no card header: the tab is the title.",
|
|
25
25
|
words: 'The sentence names the actor in `<strong>`, the action in the past tense and the object as a link that keeps the text\'s colour: "Marco Rinaldi created the Agent Acme Quotes". Day titles are "Today", "Yesterday", then the weekday; dates are the product\'s own ("14 October"). Nothing yet: "No events yet." and what will appear. No match: "No events match these filters.".',
|
|
26
26
|
access: "The toolbar is named by `label`; each day's title is a level-3 heading over a list of its events; while loading, one status named by `loadingLabel` stands for the rows; each time is a `<time>` with its exact moment for assistive technology. A system event has no mark, so nothing empty is announced.",
|
|
27
27
|
},
|
|
@@ -32,7 +32,7 @@ export const USAGE = [
|
|
|
32
32
|
summary: "The card an Agent's Profile opens with in every product.",
|
|
33
33
|
when: "The head of an Agent's Profile: who she is, what she does, her state, and her model and reasoning beside them.",
|
|
34
34
|
instead: "An Agent in a gallery is `AgentCard`; in a list `AgentListRow`. Another object's page is `ObjectPage`.",
|
|
35
|
-
behaviour: "Her orb at 64, her name with the pencil, her description and her facts on the left; her description is read whole, wrapping and never cut; her model and reasoning on the right; the card's footer saves both sides. The pencil turns her name and
|
|
35
|
+
behaviour: "Her orb at 64, her name with the pencil, her description and her facts on the left; her description is read whole, wrapping and never cut; her model and reasoning on the right; the card's footer saves both sides. The pencil turns her name, description and, where the product holds it, her look into fields in place, saved by the card's own Save, with no drawer or overlay. Her facts are her state, her last activity and the template she was made from; her model is drawn with its maker's mark, and her permissions join the right column where the product holds them. The page's commands sit in the window's three dots where the product has one, and otherwise at the end of her name's row (`actions`). A fact being read draws its place; a missing one keeps its label and a dash.",
|
|
36
36
|
words: "Each product brings its own data, fields and words; the shape, sizes and order are this component's. Her state is a word: *Working*, *Waiting for you*.",
|
|
37
37
|
access: "Her name is the page's `h1`, kept as the heading while she is edited; the pencil is named (*Edit Nadia*); the orb is decorative beside her name.",
|
|
38
38
|
},
|
|
@@ -43,7 +43,7 @@ export const USAGE = [
|
|
|
43
43
|
summary: "A catalogue to choose from in the right-side sheet, each entry added from its own row.",
|
|
44
44
|
when: "Ours: a person picks one or more things from a list too long for a dialog, the page still behind it: add a skill, add an app to a new Agent.",
|
|
45
45
|
instead: '"Don’t use Sheet to confirm destructive actions. The default `modal=false` keeps the page interactive, which weakens severity for a delete or revoke." (https://vercel.com/geist/sheet) Ours: the shelf of everything the company holds, on its own page, is a `LibraryShelf`; a setup in steps is a `FormDrawer` with `job="steps"`; a new record from a few fields is a `FormDialog`.',
|
|
46
|
-
behaviour: '"Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal sheet from the right, as `FormDrawer`. The header holds the title, the description and Close;
|
|
46
|
+
behaviour: '"Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal sheet from the right, as `FormDrawer`. The header holds the title, the description and Close; below it sits the search, which stays in place, and then the body, which scrolls and holds the entries in one column under their section titles. It draws one state at a time: the failure with its retry, three still entries while the catalogue loads, no match with a way to clear the search, nothing at all, or the entries. An entry already held shows a muted check and its word in place of Add. The footer\'s one button closes the drawer.',
|
|
47
47
|
words: '"Title is Title Case and names the entity (`Deployment Details`, `Member Profile`), not the page action." (https://vercel.com/geist/sheet) "Cancel literal stays `Cancel`. Acknowledgment-only Modals (after a key reveal, after a one-time-show) use `Done`, never `OK` or `Close`." (https://vercel.com/geist/modal) Ours: the title is sentence case and names the job: "Add a skill". The search names what it finds: "Search the hub". Each entry\'s button is "Add"; the held word is "Added"; the footer\'s is "Done".',
|
|
48
48
|
access: '"Trap focus inside the sheet while it’s open and return focus to the trigger row on close so keyboard users keep their place in the list." (https://vercel.com/geist/sheet) "Provide a visible close button labeled `Close` (or an icon button with `aria-label="Close"`) since clicking outside doesn’t dismiss." (https://vercel.com/geist/sheet) Ours: a modal dialog named by its title; focus starts in the search. The loading entries are one status named by `loadingLabel`.',
|
|
49
49
|
},
|
|
@@ -52,7 +52,7 @@ export const USAGE = [
|
|
|
52
52
|
module: "collection-filters",
|
|
53
53
|
family: "Compositions",
|
|
54
54
|
summary: "The filters in a collection's toolbar: one Add filter button, a chip for each facet in force, and the facets most often narrowed to waiting as dashed chips.",
|
|
55
|
-
when: "A collection or a feed a person narrows by a property its records share: a status, an owner, a kind. It stands in the toolbar after the search and before the sort.",
|
|
55
|
+
when: "A collection or a feed a person narrows by a property its records share (its query facets): a status, an owner, a kind. It stands in the toolbar after the search and before the sort.",
|
|
56
56
|
instead: "Finding one record by its name is the search. Changing the order is the sort. Switching between sibling collections is the page's tabs.",
|
|
57
57
|
behaviour: "Add filter opens a picker of the facets, then of a facet's values, each searchable; Backspace in an empty search goes back to the facets. A facet in force is a chip that reopens its values, with a button that removes it. A suggested value waits as a dashed chip in the muted grey, led by a + and never by the value's own mark, so it never reads as a filter in force, while its facet is unset, and applies in one click. Focus returns to Add filter when a facet is removed. The product keeps each facet's value, in the URL where it has one.",
|
|
58
58
|
words: 'Every string is the product\'s: `copy` names Add filter, the two searches, the empty result, Back and Remove; each facet names itself, its values and its "All" choice.',
|
|
@@ -85,8 +85,8 @@ export const USAGE = [
|
|
|
85
85
|
module: "confirm-dialog",
|
|
86
86
|
family: "Compositions",
|
|
87
87
|
summary: "A consequence confirmed before it happens, in a blocking alert dialog.",
|
|
88
|
-
when: '"Reach for this over `Modal` when the action is destructive and warrants friction: delete, rotate, revoke, disconnect, downgrade, disable a security setting. The typed gate forces deliberate intent." (https://vercel.com/geist/destructive-action-modal) "Use it for reversible destructive actions too, when the consequence is serious enough to warrant a pause — disabling deployment protection, revoking a shared token. Keep the typed gate; drop `irreversibleDescription`." (https://vercel.com/geist/destructive-action-modal) "For high-stakes destructive actions (delete production resource, rotate signing key, downgrade plan), gate the primary button on a typed match of the resource name." (https://vercel.com/geist/modal) Ours: `ConfirmDialog` is Geist\'s DestructiveActionModal: remove a mailbox, delete an Agent, with `verify` as the typed gate.',
|
|
89
|
-
instead: '"Don\'t use for routine confirmations (save draft, discard changes, close without saving). The typed gate reads as melodrama when the stakes are low — use a plain `Modal` instead." (https://vercel.com/geist/destructive-action-modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: an action that can be undone runs at once and offers its undo in a toast; a form of a few fields is a `FormDialog`; a choice from a catalogue is a `CatalogDrawer`.',
|
|
88
|
+
when: '"Reach for this over `Modal` when the action is destructive and warrants friction: delete, rotate, revoke, disconnect, downgrade, disable a security setting. The typed gate forces deliberate intent." (https://vercel.com/geist/destructive-action-modal) "Use it for reversible destructive actions too, when the consequence is serious enough to warrant a pause — disabling deployment protection, revoking a shared token. Keep the typed gate; drop `irreversibleDescription`." (https://vercel.com/geist/destructive-action-modal) "For high-stakes destructive actions (delete production resource, rotate signing key, downgrade plan), gate the primary button on a typed match of the resource name." (https://vercel.com/geist/modal) Ours: `ConfirmDialog` is Geist\'s DestructiveActionModal: remove a mailbox, delete an Agent, with `verify` as the typed gate. It asks for the record\'s name before the confirm enables on a high-stakes action (delete, rotate, revoke, disconnect, downgrade, a protection switched off), even a reversible one; `irreversible` names what cannot be undone and is left out when the action can be reversed.',
|
|
89
|
+
instead: '"Don\'t use for routine confirmations (save draft, discard changes, close without saving). The typed gate reads as melodrama when the stakes are low — use a plain `Modal` instead." (https://vercel.com/geist/destructive-action-modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a routine confirmation (discard a draft) takes neither `verify` nor `irreversible`; an action that can be undone runs at once and offers its undo in a toast; a form of a few fields is a `FormDialog`; a choice from a catalogue is a `CatalogDrawer`.',
|
|
90
90
|
behaviour: '"The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches `verificationPhrase` exactly." (https://vercel.com/geist/destructive-action-modal) "Enter submits only when the gate is open; it\'s a no-op otherwise. Cancel, outside-click, and Escape all dismiss." (https://vercel.com/geist/destructive-action-modal) "`loading` disables both buttons. The caller owns `open` — do not self-dismiss from `onConfirm`. Close the modal after the API settles (success or error), or keep it open on error so the user can retry." (https://vercel.com/geist/destructive-action-modal) "Default focus to `Cancel` on any destructive Modal. Enter must never trigger the destructive action without a typed confirmation." (https://vercel.com/geist/modal) "Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: `title`, `description`, `confirmLabel` and `cancelLabel` are required; `irreversible` and `verify` are optional, and the absence of `irreversible` is what marks the action reversible. Geist\'s `irreversibleDescription` is `irreversible`, `verificationPhrase` and `verificationLabel` are `verify.phrase` and `verify.prompt`, and `error` is the catalogue\'s message. 480 px wide over a scrim. The title and the description, then the body in a 24 px column: the `irreversible` note, the `verify` field, the failure under it. `irreversible` is a flat red note with a red mark and red words, not a band over the footer; besides it the only red is the failure and the enabled destructive confirm. While `loading` the field turns read-only and keeps focus, Escape does not close it, and the confirm says `confirmPendingLabel`, which falls back to the confirm\'s label. A press outside does not close it, so a stray click never discards a typed phrase or an action in flight; Escape and Cancel do. The footer is a bar that stays at the bottom while a tall confirmation scrolls, and a row on a phone too.',
|
|
91
91
|
words: '"`title` is Title Case, `Verb + Noun`, a statement — never a question. `Delete Project`, not `Delete this project?`." (https://vercel.com/geist/destructive-action-modal) "`description` is sentence case, names the consequence, and interpolates the specific resource when relevant. `<b>my-project</b> and all its deployments will be permanently deleted.` reads stronger than `This project and all its deployments will be deleted.`." (https://vercel.com/geist/destructive-action-modal) "`confirmLabel` matches the title 1:1. Never generic (`Confirm`, `OK`, `Continue`), never a bare verb (`Delete`)." (https://vercel.com/geist/destructive-action-modal) "`cancelLabel` defaults to `Cancel`. Override when Cancel is ambiguous, such as `Keep Turn Running` when the primary action is itself a cancellation." (https://vercel.com/geist/destructive-action-modal) "`verificationPhrase`: for entity deletes, type the **resource name itself** (`my-project`) and pair with `verificationLabel="project name"` so the prompt reads `To confirm, type the project name "my-project"`. That\'s the canonical signal that the user knows which thing they\'re acting on. Fall back to a lowercase verb phrase (`disable vercel authentication`) only when there\'s no entity to name." (https://vercel.com/geist/destructive-action-modal) "`irreversibleDescription` names the specific action and resource and ends with `cannot be undone.` — `Deleting my-project cannot be undone.` rather than the generic `This cannot be undone.`. Omit the prop entirely for reversible actions; the prop\'s absence is the signal, not a falsy value." (https://vercel.com/geist/destructive-action-modal) "`error` surfaces the API failure verbatim in Vercel voice: `Couldn\'t save settings. Try again.`. Never dump a raw error object." (https://vercel.com/geist/destructive-action-modal) "Pair the post-confirm success toast verb 1:1 with the primary button: `Delete Project` button, `Project deleted` toast. Never `Project removed`." (https://vercel.com/geist/destructive-action-modal) "Close irreversible bodies with `This cannot be undone.`; close cascade-only bodies with `Some effects cannot be undone.`. Don’t claim full irreversibility for a partial cascade." (https://vercel.com/geist/modal) Ours: the title is sentence case and names the record, "Remove pec@acme.it", and the confirm repeats the verb and the noun, "Remove mailbox", so the two share a verb and not every word; its success toast uses the same verb ("Mailbox removed"). The description names the record in medium weight.',
|
|
92
92
|
access: '"The verification input is associated with its prompt via `aria-labelledby` + `htmlFor`. Screen readers announce the full prompt (`To confirm, type "delete my-project"`) on focus." (https://vercel.com/geist/destructive-action-modal) "The Warning icon in the irreversibility band is `aria-hidden`; the sentence carries the meaning so nothing is announced twice." (https://vercel.com/geist/destructive-action-modal) "Focus stays inside the modal across an `error` transition so the user can retry without losing context. After success, return focus to the trigger." (https://vercel.com/geist/destructive-action-modal) "Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Keep the Cancel button literally `Cancel` so screen-reader users hear a stable dismissal label across destructive flows." (https://vercel.com/geist/modal) Ours: `role="alertdialog"` named by its title and described by its consequence; the note is `role="note"` with its icon hidden. Focus starts on Cancel, or in the field with `verify`.',
|
|
@@ -109,7 +109,7 @@ export const USAGE = [
|
|
|
109
109
|
summary: "A new record, or one or two values from a command, in a modal dialog.",
|
|
110
110
|
when: '"Use Modal when a decision must block the rest of the page. For persistent associated context where the underlying page stays readable, use `Sheet` on desktop or `Drawer` on mobile." (https://vercel.com/geist/modal) Ours: a person adds a record through a few fields, or changes one or two values a command asks for: new customer, add a mailbox, add an MCP server.',
|
|
111
111
|
instead: '"Skip Modal for routine create flows that have a dedicated page; route to the page instead." (https://vercel.com/geist/modal) "Confirm destructive actions in a Modal. `Drawer` and `Sheet` don’t fully dim the page, so they read as too soft for a delete or revoke." (https://vercel.com/geist/modal) Ours: a record already in a list edits in its row with `RowEditForm`; a choice from a catalogue is a `CatalogDrawer`; a setup in steps with a wait is a `FormDrawer` with `job="steps"`; a consequence is confirmed in a `ConfirmDialog`.',
|
|
112
|
-
behaviour: '"Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close.',
|
|
112
|
+
behaviour: '"Allow Escape and outside-click to dismiss non-destructive Modals; gate dismissal on destructive ones with unsaved input." (https://vercel.com/geist/modal) Ours: centred over a scrim without blur, 540 px wide, a bottom sheet on a phone: the title and its help, the fields in a 12 px column, then the footer, which stays at the bottom while a tall form scrolls, with Cancel at the leading edge and the primary at the trailing edge, in one row also on a phone. `FormDialogForm` puts the fields in the dialog\'s own scroll body and the actions in its fixed footer; no other overlay is nested inside. A refusal or a failed save sits above the footer and the fields keep what was typed. While its save runs it holds open, Cancel and the primary hold, and the waiting line runs under them. With `unsaved.dirty`, Escape and a press outside are held back and `unsaved.onDismissBlocked` runs so the caller can ask; Close and Cancel always close.',
|
|
113
113
|
words: '"`Modal.Title` is a Title Case statement, never a question. `Transfer Project` is correct; `Transfer Project?` is wrong." (https://vercel.com/geist/modal) "Primary button is `Verb + Noun` and matches the title verb (`Transfer Project` title pairs with `Transfer Project` button). Never `Confirm`, `OK`, or a bare verb on a destructive primary." (https://vercel.com/geist/modal) "Cancel literal stays `Cancel`. Acknowledgment-only Modals (after a key reveal, after a one-time-show) use `Done`, never `OK` or `Close`." (https://vercel.com/geist/modal) Ours: the title and the primary are sentence case, where Geist writes Title Case: the title names the job and its object, "New customer", "Add a mailbox"; the primary repeats it, "Create customer", "Add mailbox".',
|
|
114
114
|
access: '"Set `aria-labelledby` to the `Modal.Title` id so screen readers announce the title on open." (https://vercel.com/geist/modal) "Trap focus inside the Modal while it’s open and return focus to the trigger after close. Restore body scroll on the same tick the Modal unmounts." (https://vercel.com/geist/modal) "After an error inside the Modal, keep focus inside so the user can retry; after success, return focus to the trigger." (https://vercel.com/geist/modal) Ours: a modal dialog with a Close named by `closeLabel`; focus starts in its first field or `initialFocus` and returns to the opener or `finalFocus`. Escape and Close close it unless the save runs.',
|
|
115
115
|
},
|
|
@@ -120,7 +120,7 @@ export const USAGE = [
|
|
|
120
120
|
summary: "The right-side sheet for a catalogue or a setup in steps, at one width.",
|
|
121
121
|
when: "Ours: choosing from a catalogue too long for a dialog (through `CatalogDrawer`), or setting something up in several steps with a wait: connect a messaging channel, Telegram's code.",
|
|
122
122
|
instead: "\"Don’t use Sheet to confirm destructive actions. The default `modal=false` keeps the page interactive, which weakens severity for a delete or revoke.\" (https://vercel.com/geist/sheet) Ours: a new record from a few fields is a `FormDialog`; a record already in a list edits in its row with `RowEditForm`; a fact on the record's own page changes in place in its section; a consequence is confirmed in a `ConfirmDialog`; a phone's short job is a `Drawer`.",
|
|
123
|
-
behaviour: '"Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet\'s one width that no caller sets. The header holds the step ("Step 2 of 3") over the title, the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs.',
|
|
123
|
+
behaviour: '"Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: a modal `Sheet` from the right edge over the scrim, because the drawer owns the screen, at the Sheet\'s one width that no caller sets. The header holds the step ("Step 2 of 3", in mono) over the title, which stays the job\'s name, then the description and Close; the body scrolls between the header and a footer that stays, with no fill or rule of its own, Cancel then the primary at its trailing edge. A press outside does not close it; Escape does, unless `unsaved.dirty` holds it back. Cancel holds while a save runs.',
|
|
124
124
|
words: '"Title is Title Case and names the entity (`Deployment Details`, `Member Profile`), not the page action." (https://vercel.com/geist/sheet) "Body is read-mostly: sentence case prose with Title Case sub-headings. Action buttons are optional; when present, follow `Verb + Noun`." (https://vercel.com/geist/sheet) Ours: the title is sentence case and names the job and its object: "Connect Telegram", "Add a skill". The step reads "Step N of M". The primary says what it does: "Connect", "Continue", never "OK".',
|
|
125
125
|
access: '"Trap focus inside the sheet while it’s open and return focus to the trigger row on close so keyboard users keep their place in the list." (https://vercel.com/geist/sheet) "Provide a visible close button labeled `Close` (or an icon button with `aria-label="Close"`) since clicking outside doesn’t dismiss." (https://vercel.com/geist/sheet) "Announce the sheet with `aria-labelledby` pointing at the title; pair it with `aria-describedby` only when the body is short and load-bearing." (https://vercel.com/geist/sheet) Ours: a modal dialog named by its title; focus returns to the opener on close, or to `finalFocus` when the opener is gone. The step is read before the title.',
|
|
126
126
|
},
|
|
@@ -153,7 +153,7 @@ export const USAGE = [
|
|
|
153
153
|
summary: "A section of an object's page: a card that owns one decision about the object.",
|
|
154
154
|
when: "A part of an ObjectPage a person reads or changes on its own: a customer's Company details, an Agent's Instructions, a Library entry's source.",
|
|
155
155
|
instead: "Values a person edits and saves together are a SettingsSection; a list of the object's records is a RecordList with its heading; a passing condition is a MessageNotice.",
|
|
156
|
-
behaviour: "A resting card: its title and the one-sentence description directly under it, both always given, at the trailing edge `meta` (muted) then `action` as an outline button, then its body. No icon in the header. Neutral throughout.",
|
|
156
|
+
behaviour: "A resting card: its title and the one-sentence description directly under it, both always given, at the trailing edge `meta` (muted) then `action` as an outline button, then its body. No icon in the header. Neutral throughout. A section that edits in place swaps to the same section as a `SettingsSection`, with Cancel (`save.cancel`) before Save.",
|
|
157
157
|
words: 'The title names what the section holds ("Company", "Instructions"); the description says in one sentence what it holds or lets a person change; the action is a verb on it ("Edit"); `meta` is a short count or time ("2 of 5 Agents").',
|
|
158
158
|
access: "The title is a heading one level under the page (h2), deeper only under another heading through `headingLevel`.",
|
|
159
159
|
},
|
|
@@ -185,8 +185,8 @@ export const USAGE = [
|
|
|
185
185
|
family: "Compositions",
|
|
186
186
|
summary: "Entity rows: objects a person opens or acts on, one line each on one resting card: the identity mark and name over what it is, a few facts, a status only for the exception, when it happened, at most two row commands, and the three dots last.",
|
|
187
187
|
when: "“Use `<Entity>` for a row of descriptive content paired with one or two controls (member rows, integration rows, domain rows).” (https://vercel.com/geist/entity) Ours: the records are things a person opens or acts on and that name themselves: projects, deployments, domains, members, integrations, Agents, Skills, mailboxes. A list inside another page: an Overview's exceptions, an object page's \"Needs attention\". A page whose whole job is the list is a `CollectionPage`, which draws this. Values edited in place (a domain, an environment variable) are the same rows with an `editor`: the row expands into its form. Of the three kinds of list, entity rows and rows edited in place are this; records compared across columns are a `DataTable`.",
|
|
188
|
-
instead: "“For tabular data with sortable columns and shared row shape, use `Table` instead.” (https://vercel.com/geist/entity) “For a static key/value metadata block on a detail page, use `Description`.” (https://vercel.com/geist/entity) Ours: Geist's `Table` is a `DataTable`: this list has no visible headings and at most three facts, so a fourth fact is a table. Its `Description` is `ObjectFacts`. Events in time are an `ActivityFeed`. Reusable things a person adds from a catalogue are a `LibraryShelf`. A descriptive row with prose is an `Item`.",
|
|
189
|
-
behaviour: "One bordered card, a 1 px rule between records and none after the last. A `checklist` puts the record's `ProgressChecklist` ring just before its commands, where Vercel ends a project's row with its Production Checklist; the ring opens its own drawer, never the row. Every row has its 32 px mark, its name and, under it, one muted line; `name` is required and so are the mark and the line, and a record with nothing to say passes the dash. Give every column a `size` in pixels; the name takes the rest. With `layout=\"line\"`, as on Vercel's Projects page, the facts and the status share one muted line, joined by a middle dot, in a column as wide as the name's, and the row stays one line (mark, name over its line, that summary, the time, the ring, the three dots) on a card from 448 px, and on a narrower card the summary and the time move under the name as one muted line joined by a middle dot, in the same row, their labels read by assistive technology only. Rows of `columns` follow the same rule once the card is narrower than their columns: the facts, the status and the time leave the row for that one line, ending in an ellipsis, never a labelled list. `facts` takes at most three. A menu of one command that is not destructive, with no row command, is that command's button, except in a `line` row, which keeps it in the three dots as Vercel's project row does; without a `commandsSize` the column is as wide as the widest such button, so its label is never cut. Status is only for the exception, empty for the usual record, and its column stands only while a record in the list shows one, so the name and its line keep the room; the commands column likewise stands only while a record has a command, a menu or a message, or the rows open. A `line` row's summary joins only the facts the record has, so it never ends in a dot. A failed or refused action of the record
|
|
188
|
+
instead: "“For tabular data with sortable columns and shared row shape, use `Table` instead.” (https://vercel.com/geist/entity) “For a static key/value metadata block on a detail page, use `Description`.” (https://vercel.com/geist/entity) Ours: Geist's `Table` is a `DataTable`: this list has no visible headings and at most three facts, so a fourth fact is a table, and anything more than a few facts lives on the record's page. A screen gives `RecordList` the records and what each one says, never its own toolbar, table, rows or menus, and nothing else draws a record row. Its `Description` is `ObjectFacts`. Events in time are an `ActivityFeed`. Reusable things a person adds from a catalogue are a `LibraryShelf`. A descriptive row with prose is an `Item`.",
|
|
189
|
+
behaviour: "One bordered card, a 1 px rule between records and none after the last. A row is 72 px, with 16 px above and below, and its columns stand left to right at the widths the list declares, so a person reads every list the same way: the `select` box (40 px, only when the list is picked from, an optional column), the identity (the rest: the one column without a size), the facts (each its `size`), the status (`status.size`), the time (`time.size`, tabular), the `checklist` ring (52 px, only when the record has steps to be ready) and the commands (`commandsSize`; left out, 64 px for the three dots alone). A `checklist` puts the record's `ProgressChecklist` ring just before its commands, where Vercel ends a project's row with its Production Checklist; the ring opens its own drawer, never the row. Every row has its 32 px mark, its name (600) and, under it, one muted 14 px line; `name` is required and so are the mark and the line, and a record with nothing to say passes the dash. Give every column a `size` in pixels; the name takes the rest. With `layout=\"line\"`, as on Vercel's Projects page, the facts and the status share one muted line, joined by a middle dot, in a column as wide as the name's, and the row stays one line (mark, name over its line, that summary, the time, the ring, the three dots) on a card from 448 px, and on a narrower card the summary and the time move under the name as one muted line joined by a middle dot, in the same row, their labels read by assistive technology only. Rows of `columns` follow the same rule once the card is narrower than their columns: the facts, the status and the time leave the row for that one line, ending in an ellipsis, never a labelled list. `facts` takes at most three. A menu of one command that is not destructive, with no row command, is that command's button, except in a `line` row, which keeps it in the three dots as Vercel's project row does; without a `commandsSize` the column is as wide as the widest such button, so its label is never cut. Status is only for the exception, empty for the usual record, and its column stands only while a record in the list shows one, so the name and its line keep the room; the commands column likewise stands only while a record has a command, a menu or a message, or the rows open, and it widens for a failed action's next steps only while a row shows one. Never the three dots before the button, a decorative icon for the record's mark, a status on every row, or a destructive button on every row: a destructive command stays in the three dots. A `line` row's summary joins only the facts the record has, so it never ends in a dot. A failed or refused action of the record shows as its catalogue message inside its own row, never a notice under the row: it replaces the detail line, with its next steps beside its commands, or under the message on a card too narrow for the row's columns. With `open` the whole row is one target and takes the `accent` hover, and a row that only opens ends in a muted chevron; a row that opens nothing never reacts to hover. With `editor` the record's row opens its form under its line. `empty` is required: `null` is the choice to draw nothing for a list with no records, and any other value is the state it draws. A note about the whole list is `footer.note`: one muted line, an optional *Learn more* after it at the left and one secondary action at the right, in a strip of its own under the count and the paging, as Vercel ends its deployments list (\"Deployment retention is enabled. Learn more\", *View recently deleted*). Colour is neutral, the name in ink and every other cell in the body grey, except the status badge and a failed action's message. “Pick the variant by what the user needs: no-results for a filtered list that returned zero rows, blank slate or informational for a resource the user hasn’t created, cleared for completed work, permission for role or tier denials, error for a failed load.” (https://vercel.com/geist/empty-state) Ours: `failed`, `noMatch` and `empty` are those variants, drawn inside the card. “The right column holds at most one or two controls. If the row needs more, move secondary actions into a `Dots Menu`.” (https://vercel.com/geist/entity) Ours: one or two row commands (outline buttons of 32 px) stand before the three dots (36 px), the Dots Menu. “Render the Skeleton variant (`entity-with-skeleton`) during load instead of an empty row, and swap to real content once data resolves.” (https://vercel.com/geist/entity) Ours: three still skeleton rows hold the place, in a region that announces what replaces them; then the first that holds: the failed read, no match, the empty state, the rows.",
|
|
190
190
|
words: '“Lead the left column with a scannable identifier: an `Avatar` or icon, a Title Case label, then sentence-case secondary metadata (`Member since Mar 14, 2026`).” (https://vercel.com/geist/entity) Ours: the name is the record\'s own and the labels around it are sentence case, not Title Case. “Keep right-column buttons Verb + Noun (`Remove Member`, `Resend Invite`). Bare verbs like `Remove` or `Confirm` lose context once the row scrolls offscreen.” (https://vercel.com/geist/entity) Ours: every string is the product\'s: `label` names the list, a time\'s `phrase` writes its sentence ("Updated {when}"), `missing` is what a screen reader hears for a record with no time, the menu\'s `label` reads "More for {record}". A row command names its result in sentence case ("Edit domain"). A status is the exception\'s word ("Needs attention"), never "Active" on every row.',
|
|
191
191
|
access: '“Wrap the loading region in `aria-busy="true"` and announce completion with `aria-live="polite"` on the destination container, not the skeleton itself.” (https://vercel.com/geist/skeleton) Ours: the loading rows sit in one `SkeletonRegion` that is busy while they stand and announces what replaces them. A table named by `label`, its column headings read by assistive technology only. The row link carries `open.label`; the three dots carry the menu\'s `label`, and the menu\'s first line names the record. The loading rows are a status named by `loadingLabel`, inside the one live region that announces what replaces them. “For multi-select rows, the leading `Checkbox` carries `aria-label="Select {entity name}"` so the row is selectable without relying on the visual label.” (https://vercel.com/geist/entity) Ours: each box is named by `select.label`, which reads "Select {record}".',
|
|
192
192
|
},
|
|
@@ -197,9 +197,9 @@ export const USAGE = [
|
|
|
197
197
|
summary: "One record from a list, looked at in a sheet at the right edge, then back to the list.",
|
|
198
198
|
when: "A list's record holds more than its row shows and is only read, not changed: a Stripe event in Billing, a usage row.",
|
|
199
199
|
instead: "A record changed from its list edits in its row with `RowEditForm`; a record with sections and commands of its own has its own page, an `ObjectPage`; a choice from a catalogue is a `CatalogDrawer`.",
|
|
200
|
-
behaviour: "Opens from the right
|
|
200
|
+
behaviour: "Opens from the right as an inset card 12 px from the window's edges with 16 px corners, the list still behind it and usable. It is 420 px wide from 640 px, 512 px from 1024 px (the sheet's own width), and the window less its margins below 640 px. The header holds the record's name, one muted line and Close; the body scrolls through its facts as labelled values, then the read-only payload with its copy button; the footer holds one command to the record's owner. A press outside does not close it. It has no states of its own: it opens only on a record.",
|
|
201
201
|
words: "The title is the record's own name, as its row shows it. The footer's command names where it goes: \"Open Beta Studio\".",
|
|
202
|
-
access: "A
|
|
202
|
+
access: "A dialog named by its title, not `modal`: no scrim, and focus stays inside it. Escape and Close close it. Focus returns to the row it opened from (`finalFocus`) after every close but a mouse, touch or pen one, as `Sheet` does by default; a caller that passes its own `finalFocus` decides. The payload is a read-only text area named by its label.",
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
name: "RowEditForm",
|
|
@@ -208,7 +208,7 @@ export const USAGE = [
|
|
|
208
208
|
summary: "A record's form opened inside its own row, the rows above and below staying where they are.",
|
|
209
209
|
when: "A person changes a record they see in a list: edit a mailbox, a task, an MCP server, a channel. It is a `RecordList`'s `editor` or a `DataTable`'s `rowEditor`.",
|
|
210
210
|
instead: "A new record from a few fields is a `FormDialog`; a fact on the record's own page changes in place in its section; a choice from a catalogue is a `CatalogDrawer`.",
|
|
211
|
-
behaviour: "The fields open under the record's line; a refusal or a failed save sits above the footer. The footer, one tone below, holds an optional hint at its leading edge, then Cancel and the save rightmost. Save stays off until a field changes: the caller sets `submit.disabled` from its own fields. Cancel holds while the save runs, and the waiting line runs under them.",
|
|
211
|
+
behaviour: "The fields open under the record's line, inside its row, and the other rows stay where they are; a refusal or a failed save sits above the footer. The footer, one tone below, holds an optional hint at its leading edge, then Cancel and the save rightmost. Save stays off until a field changes: the caller sets `submit.disabled` from its own fields. Cancel holds while the save runs, and the waiting line runs under them.",
|
|
212
212
|
words: 'The save says what it does, "Save"; Cancel is "Cancel". The hint is a way on, such as where to find a token, never a restatement of a field\'s label.',
|
|
213
213
|
access: "A form in the row's place, which the caller moves focus into. The running save is marked busy, and the line saying why the save cannot run yet is its description (`submit.describedBy`).",
|
|
214
214
|
},
|
|
@@ -230,7 +230,7 @@ export const USAGE = [
|
|
|
230
230
|
summary: "A card of one setting that says what changing it does and ends in a footer.",
|
|
231
231
|
when: "One setting a person changes on a SettingsPage or as a section of an ObjectPage: Personal details, Company identity, Notifications. A typed value takes `save`; a switch or a short reversible choice applies at once through its own control's change, with no Save. Also a plan-gated feature, a delete card, one read-only value (an ID in a read-only mono InputGroup with its copy button, no Save), and a paid quantity (its stepper field, the price as the description, a two-fact summary under it).",
|
|
232
232
|
instead: "Read-only facts of an object are a SettingsFactsSection; a table or a list of records is a TableCard; Theme and Language live in the account menu; a record's edit in a list is a RowEditForm.",
|
|
233
|
-
behaviour: '"Groups related form controls inside a bordered card with optional footer actions." (https://vercel.com/geist/fieldset) Ours: one setting per card: a single SettingsField, the description its only help. One more shape exists, declared by a part and never by classes at the call site. A card of several settings holds SettingsRows, label left and control right, only when every row is a switch or every row is a select. "Use Toggle for a single boolean setting where ON takes effect immediately, like `Password Protection` or `Auto-Cancel Builds`." (https://vercel.com/geist/toggle) "Pair with a form footer only when the setting needs an explicit `Save` step." (https://vercel.com/geist/toggle) "Disable Toggle only when the action is impossible (missing plan, locked policy); pair with helper text or a `Tooltip` that names the resolver." (https://vercel.com/geist/toggle) Ours: that switch is `headerSwitch`: at the title\'s right, named by the title, the footer holding only its line, no Save; a switch blocked from above is shown off and disabled, the footer saying why and never a tooltip. Required: the title and the description. The header\'s actions stand at the title\'s right; on a card narrower than 28 rem they keep the title\'s row and the description runs under both, as Vercel\'s cards read on a phone. The footer\'s left line (`footerHint`, `save.hint`) is optional and specific to the card, never a generic line repeated on every card: a limit, what a value is for, a plan it needs, a link; absent, it is nothing, and a card with neither a line nor a button has no footer. Ours: `save`: a typed value, one card with Save. Save stays disabled until a field differs from what is saved and says `pendingLabel` while saving with Cancel beside it; a completed save calls `saved`, and the product raises one toast from its catalogue ("Completion toasts follow `{Noun} {past-participle}`: `Blob deleted`, `Domain added`, `Environment variable saved`. Never include `successfully`; the action name implies it." https://vercel.com/geist/toast), and Save returns to disabled. Ours: the section never raises a toast itself. A failed save keeps every value and shows under its field, or above the footer when no field owns it. `tone` marks a state with a command (a failed payment, a trial ending): a 1px border and a footer tinted in the hue with its line in it; title, line, fields and buttons stay neutral, an outline secondary then a default primary. A button takes the hue of its command, not of the card: `destructive` for a permanent loss, `warning` for a disruptive command that can be undone. A delete card is `tone="error"` with the object\'s summary as its body; an archive card is neutral with a `warning` button in the footer. "Use a Note for inline contextual feedback next to the field, card, or section it describes: a region change warning above a region picker, a rate-limit notice next to a usage gauge." (https://vercel.com/geist/note) "One Note per concept." (https://vercel.com/geist/note) Ours: a message about the fields inside is a MessageNotice in the body, the card staying neutral. "Disabled checkboxes still need a Tooltip naming the reason; a greyed box with no explanation reads as a bug." (https://vercel.com/geist/checkbox) Ours: `lockedReason` is the reason,
|
|
233
|
+
behaviour: '"Groups related form controls inside a bordered card with optional footer actions." (https://vercel.com/geist/fieldset) Ours: one setting per card: a single SettingsField, the description its only help. A typed value (a name, a URL, a server, a port, a key) gets its own card. A semantic subgroup of related controls inside a field, such as radios or checkboxes, is a `FieldSet` with a `FieldLegend`. One more shape exists, declared by a part and never by classes at the call site. A card of several settings holds SettingsRows, label left and control right, only when every row is a switch or every row is a select. "Use Toggle for a single boolean setting where ON takes effect immediately, like `Password Protection` or `Auto-Cancel Builds`." (https://vercel.com/geist/toggle) "Pair with a form footer only when the setting needs an explicit `Save` step." (https://vercel.com/geist/toggle) "Disable Toggle only when the action is impossible (missing plan, locked policy); pair with helper text or a `Tooltip` that names the resolver." (https://vercel.com/geist/toggle) Ours: that switch is `headerSwitch`: at the title\'s right, named by the title, the footer holding only its line, no Save; a switch blocked from above is shown off and disabled, the footer saying why and never a tooltip. Required: the title and the description. The header\'s actions stand at the title\'s right; on a card narrower than 28 rem they keep the title\'s row and the description runs under both, as Vercel\'s cards read on a phone. The footer\'s left line (`footerHint`, `save.hint`) is optional and specific to the card, never a generic line repeated on every card: a limit, what a value is for, a plan it needs, a link; absent, it is nothing, and a card with neither a line nor a button has no footer. Ours: `save`: a typed value, one card with its own Save in the footer, always visible. The section owns the changed state: Save stays disabled until a field differs from what is saved and says `pendingLabel` while saving with Cancel beside it; a completed save calls `saved`, and the product raises one toast from its catalogue ("Completion toasts follow `{Noun} {past-participle}`: `Blob deleted`, `Domain added`, `Environment variable saved`. Never include `successfully`; the action name implies it." https://vercel.com/geist/toast), and Save returns to disabled, with no saved state in the footer. Ours: the section never raises a toast itself. A failed save keeps every value and shows under its field, or above the footer when no field owns it. `tone` marks a state with a command (a failed payment, a trial ending): a 1px border and a footer tinted in the hue with its line in it, the line saying the condition in words; title, line, fields and buttons stay neutral, an outline secondary then a default primary. A button takes the hue of its command, not of the card: `destructive` (red) for a permanent loss (Delete), `warning` (amber) for a disruptive command that can be undone (Pause, Archive). A delete card is `tone="error"` with the object\'s summary as its body; an archive card is neutral with a `warning` button in the footer. "Use a Note for inline contextual feedback next to the field, card, or section it describes: a region change warning above a region picker, a rate-limit notice next to a usage gauge." (https://vercel.com/geist/note) "One Note per concept." (https://vercel.com/geist/note) Ours: a message about the fields inside is a MessageNotice in the body, the card staying neutral. "Disabled checkboxes still need a Tooltip naming the reason; a greyed box with no explanation reads as a bug." (https://vercel.com/geist/checkbox) Ours: `lockedReason` (Geist\'s disabled wall) is the reason, written as the footer\'s line, not in a tooltip; it keeps the fields in view, inert with muted text, under a not-allowed cursor, the title and line unchanged; `unlock` is the way to get access, left out when nothing the person does unlocks it. A locked section has no `save` and no header `actions`.',
|
|
234
234
|
words: '"Helper text is sentence case, one sentence with a trailing period, on a sibling element wired through `aria-describedby`." (https://vercel.com/geist/input) Ours: the description is that helper text for the whole card, and no field has its own. "Render an optional one-sentence description as a sibling under the label that explains ON only. Don’t describe OFF; it’s the negation." (https://vercel.com/geist/toggle) Ours: a header-switch card\'s description says what turning it on does. The title names the values ("Personal details"); the description says what changing them does, in one line; the footer line is a Learn more link, a limit ("Use 32 characters at most."), what stays unaffected, or what is blocked and why ("Ask an owner to change this."); Save and Saving come from the product\'s catalogue, and so does the toast a `saved` callback raises; a delete card\'s description opens "Permanently delete".',
|
|
235
235
|
access: "\"Provide an accessible name through `children`, `aria-label`, or `aria-labelledby`.\" (https://vercel.com/geist/toggle) Ours: the title is the section's h2 and names the form that saves it, and the header switch. The footer's line is one status, announced politely; a failure under a field marks the control invalid and describes it. Locked fields are inert, so the section's title, description and reason are what a screen reader meets.",
|
|
236
236
|
},
|
|
@@ -240,8 +240,8 @@ export const USAGE = [
|
|
|
240
240
|
family: "Actions",
|
|
241
241
|
summary: "The package's one button: a command, its label naming the result.",
|
|
242
242
|
when: '"Use `Button` for actions that mutate state (deploy, save, delete); use `ButtonLink` for navigation that changes the URL." (https://vercel.com/geist/button) Ours: one `default` per surface, the thing this page or dialog is for.',
|
|
243
|
-
instead: '"Switch to a `Menu` or `Split Button` when more than one related action shares a row." (https://vercel.com/geist/button) Ours: a link inside a sentence is `TextLink`. A record\'s secondary commands go in `ActionMenu`. Appending to a list is a `LoadMoreButton`. An on/off value is a `Switch`.',
|
|
244
|
-
behaviour: '"Default `Button` is primary." (https://vercel.com/geist/button) "Pass `type="secondary"` for the supporting action and `type="error"` for destructive confirmations." (https://vercel.com/geist/button) "`primary`, `success`, `ghost`, and `violet` are not valid `type` values." (https://vercel.com/geist/button) "For form submits, use `typeName="submit"`." (https://vercel.com/geist/button) "The HTML `type` attribute lives on `typeName`, not on `type`, which controls the visual variant." (https://vercel.com/geist/button) Ours: the look is `variant` (`default`, `secondary`, `outline`, `ghost`, `link`, `destructive` for Geist\'s error, `warning`) and `type` is the HTML type, `button` unless it submits. "The default size is medium." (https://vercel.com/geist/button) Ours: `xs
|
|
243
|
+
instead: '"Switch to a `Menu` or `Split Button` when more than one related action shares a row." (https://vercel.com/geist/button) Ours: a link inside a sentence is `TextLink`. A record\'s secondary commands go in `ActionMenu`. Appending to a list is a `LoadMoreButton`. An on/off value is a `Switch`. An element that is neither a `Button` nor a `ButtonLink` takes `buttonVariants`.',
|
|
244
|
+
behaviour: '"Default `Button` is primary." (https://vercel.com/geist/button) "Pass `type="secondary"` for the supporting action and `type="error"` for destructive confirmations." (https://vercel.com/geist/button) "`primary`, `success`, `ghost`, and `violet` are not valid `type` values." (https://vercel.com/geist/button) "For form submits, use `typeName="submit"`." (https://vercel.com/geist/button) "The HTML `type` attribute lives on `typeName`, not on `type`, which controls the visual variant." (https://vercel.com/geist/button) Ours: the look is `variant` (`default`, `secondary`, `outline`, `ghost`, `link`, `destructive` for Geist\'s error, `warning`, and `brandSolid` and `brandGhost` for a colour or black ground) and `type` is the HTML type, `button` unless it submits. There is no `CustomButton`: colour is ours, chosen by `variant`, never at the call site. "The default size is medium." (https://vercel.com/geist/button) Ours: `xs` (24 px), `sm` (32 px), `default` (36 px, medium) and `lg` (40 px); `sm` and `xs` are for a control nested in dense content, such as a composer or an input group, never for chrome. The corner is 6 px, 8 px at `lg` and 4 px on the 24 px icon button. The label is the button type at `sm` and `default`, the large one at `lg` and the 12 px one at `xs`, and the inline padding is 6 px at `sm`, 10 px at `default` and 14 px at `lg` (8 px at `xs`). "Pass `loading` instead of swapping in a spinner so the button stays focusable and announces the busy state to assistive tech." (https://vercel.com/geist/button) Ours: `loading` is the only busy state: the button keeps its place and focus, says `loadingLabel` in the label\'s place, ignores a second press and shows no spinner. "Disable a button only when the action is impossible right now (missing input, insufficient permission); pair with a `Tooltip` that explains why." (https://vercel.com/geist/button) Ours: the reason is written beside the button. "Icon-only buttons should include the `svgOnly` prop and an `aria-label`." (https://vercel.com/geist/button) Ours: an icon-only button takes an `icon` size and its icon as the child; `shape="circle"` is only for it. An icon with text passes as `prefix` or `suffix`. "Combination of `shape="rounded"` and the `shadow` prop, often used on marketing pages." (https://vercel.com/geist/button) Ours: `shape="rounded"` is a pill for a call to action that stands apart (a banner\'s or a scroller\'s), with no `shadow` prop. Ours: text children sit in a label box that adds 6 px each side to the padding, except at `xs` and the icon sizes, which have none; an element child (a `render` target, a row with a trailing `Kbd`) lays itself out and gets no box. Colour sits on the whole button for three looks: `default` is ink with a white label (in dark, the light action fill with a near-black label, never pure white), `destructive` is the error fill for permanent loss (Delete project), `warning` is the amber fill for a disruptive command that can be undone (Pause project). `link` is the link colour as text, with the arrow as `suffix` when the destination is outside the product. `outline` and `secondary` are neutral with a hairline edge, `ghost` has no chrome and serves a tertiary text action or an icon-only command (the three dots, close, collapse), and no look puts a hue on its edge. Held (disabled or `loading`), a filled look (`default`, `outline`, `secondary`, `destructive` and `warning`, so `outline` and `secondary` too) drops its hue to the muted fill, the muted text and the hairline at full opacity, so it reads as unavailable; `ghost`, `link` and the two brand looks dim instead. Pressed looks like hover, with no movement. On a colour or black ground a control takes a look whose label clears AA there: `brandSolid` (white fill, ink label) or `brandGhost` (white label, no fill) on cobalto, rosso and black, and the `default` ink button on verde, arancio and giallo.',
|
|
245
245
|
words: '"Title Case the label and name what happens: `Deploy Project`, `Invite Member`, `Rotate Key`." (https://vercel.com/geist/button) "Avoid bare verbs (`Submit`) and generic confirms (`OK`, `Confirm`)." (https://vercel.com/geist/button) "Destructive buttons follow `Verb + Noun` and pair 1:1 with their toast: `Delete Project` then `Project deleted`." (https://vercel.com/geist/button) "Mode-switch buttons append `Instead`: `Use a Recovery Code Instead`." (https://vercel.com/geist/button) Ours: sentence case, not Title Case: *Invite member*, *Remove mailbox* then *Mailbox removed*, *Use a recovery code instead*. `loadingLabel` is the same words in the -ing form: *Inviting member*. Italian runs about a third longer (*Rimuovi casella*), so a footer leaves room and a label never wraps.',
|
|
246
246
|
access: '"Icon-only buttons require both `svgOnly` and `aria-label`; the validator throws without them." (https://vercel.com/geist/button) "The `aria-label` names the action and the target (`Copy deployment URL`), not the icon (`Copy`)." (https://vercel.com/geist/button) "Don’t set `aria-label` on a button that already has visible text; it overrides the label and creates a screen-reader mismatch." (https://vercel.com/geist/button) Ours: the type requires `aria-label` or `aria-labelledby` on an icon-only button. A native `button`, `type="button"` unless it submits. `loading` sets `aria-busy`. Focus shows the base outline in one colour for every look; nothing repaints it.',
|
|
247
247
|
},
|
|
@@ -273,8 +273,8 @@ export const USAGE = [
|
|
|
273
273
|
family: "Actions",
|
|
274
274
|
summary: "Copies an exact value someone will paste elsewhere.",
|
|
275
275
|
when: "An exact value someone will paste elsewhere: an Agent ID, a server address, a command. A shell prompt shown before a command stays outside the copied value.",
|
|
276
|
-
instead: "Several lines of code are a `CodeBlock`. Ordinary prose never gets copy chrome. A secret needs its reveal policy first.",
|
|
277
|
-
behaviour: '"A button that copies a given string to the clipboard and provides feedback when copied." (https://vercel.com/geist/copy-button) Ours: copies the full underlying value even when the shown one is shortened. The copy mark fades into the check for about a second, and only after the clipboard confirms; no colour changes. A failure shows the alert mark and the button stays for a retry. The standalone `icon` button is outlined; `icon-sm` sits after a value and `icon-xs` inside a field, both without chrome. `copied` shows the check from outside for a parent that copies other text, and `onCopy` runs once the clipboard confirms. While the clipboard works a second press is ignored and the button looks as it did, with no held look.',
|
|
276
|
+
instead: "Several lines of code are a `CodeBlock`. Ordinary prose never gets copy chrome. A secret needs its reveal policy first. A shown value that is copied is a `TextWithCopyButton`, which binds the value to this button.",
|
|
277
|
+
behaviour: '"A button that copies a given string to the clipboard and provides feedback when copied." (https://vercel.com/geist/copy-button) Ours: it is a `Button` over the existing clipboard behaviour, with the pending state and the announced result below, and adds no clipboard code of its own. It copies the full underlying value even when the shown one is shortened. The copy mark fades into the check for about a second, and only after the clipboard confirms; no colour changes. A failure shows the alert mark and the button stays for a retry. The standalone `icon` button is outlined; `icon-sm` sits after a value and `icon-xs` inside a field, both without chrome. `copied` shows the check from outside for a parent that copies other text, and `onCopy` runs once the clipboard confirms. While the clipboard works a second press is ignored and the button looks as it did, with no held look.',
|
|
278
278
|
words: 'Three catalogue strings: `copyLabel` names the target ("Copy Agent ID"), `copiedLabel` ("Copied"), `copyFailedLabel` ("Couldn\'t copy").',
|
|
279
279
|
access: '`aria-label` is the copy label and does not change. The result is read once from a hidden `role="status"`.',
|
|
280
280
|
},
|
|
@@ -283,8 +283,8 @@ export const USAGE = [
|
|
|
283
283
|
module: "load-more-button",
|
|
284
284
|
family: "Actions",
|
|
285
285
|
summary: "The button at the end of a list that appends its next items in place.",
|
|
286
|
-
when: '"A full-width button used to append more items to a paginated list, with loading and styling variants." (https://vercel.com/geist/load-more-button) Ours: a list that grows in place by a page of items at a time: older events, more results. The button follows the last item.',
|
|
287
|
-
instead: "A list that is whole stays whole, with no button. Going to another page of the product is a `ButtonLink
|
|
286
|
+
when: '"A full-width button used to append more items to a paginated list, with loading and styling variants." (https://vercel.com/geist/load-more-button) Ours: a list that grows in place by a page of items at a time: older events, more results. The button follows the last item; `ActivityFeed`\'s `older` is one.',
|
|
287
|
+
instead: "A list that is whole stays whole, with no button. Going to another page of the product is a `ButtonLink`, which `ActivityFeed`'s `older` is when it navigates.",
|
|
288
288
|
behaviour: "Takes the list's full width, 36 px high, below the last item, always `outline`: neutral, no hue in any state. The label and the `loadingLabel` are both required. While the next items load it is `loading`, held in the muted look, and the rows already shown stay. It sits one `md` step (16 px) below the list; `noGap` removes that space and `noBorderRadius` squares its corners, for a button that closes a list inside a card. The caller drops the button when no items are left.",
|
|
289
289
|
words: "Ours: sentence case, naming what comes: *Load more*, *Show older events*. The loading word is the same in the -ing form: *Loading more*, *Showing older events*; Italian *Carica altro*, *Caricamento*.",
|
|
290
290
|
access: "A native `button` that is `aria-busy` while it loads. New items appear after the last shown one, so focus stays on the button's place in the list.",
|
|
@@ -317,7 +317,7 @@ export const USAGE = [
|
|
|
317
317
|
family: "Inputs",
|
|
318
318
|
summary: "A text box that empties itself: a clear button once it has text, and Escape.",
|
|
319
319
|
when: '"Text input with a clear button that resets the value on Escape." (https://vercel.com/geist/clearable-input) Ours: a single-line value a person may want to empty in one move: a filter, a search, a reference they paste and replace.',
|
|
320
|
-
instead: "A collection's search is `CollectionSearch`. A value that is never emptied is `Input`. A box with a fixed prefix or suffix is `Input`; one with a copy button is `InputGroup
|
|
320
|
+
instead: "A collection's search is `CollectionSearch`. A value that is never emptied is `Input`. A box with a fixed prefix or suffix is `Input`; one with a copy button is `InputGroup`, and any other fixed part beside a box is an `InputGroup` composition.",
|
|
321
321
|
behaviour: 'Same ring, halo, hover, disabled and error as `Input`; colour sits where it does there and nowhere else. The clear button shows only while the box has text and is enabled, as an Esc key cap flush against the end of the box that darkens on hover and keeps a transparent fill (a close mark on a touch screen, which has no Escape). Pressing it, or Escape inside the box, empties the value, calls `onClear` and puts focus back in the box. Escape on an empty box does nothing here, so it closes the dialog or drawer around it. "Automatically clears the input if escape is pressed." (https://vercel.com/geist/input) "Displays the <kbd>⌘</kbd> <kbd>K</kbd> shortcut to indicate that the input supports a command palette." (https://vercel.com/geist/input) "Transitions to showing <kbd>Esc</kbd> when the field is dirty." (https://vercel.com/geist/input) Ours: the `shortcut` keys are one cap per key (*⌘* then *K*, 4 px apart, 10 px from the edge), hidden on a touch screen. `loading` marks the box busy and, after a second, draws the waiting line along its bottom edge; nothing spins and the results stay. `label` and `error` draw the `Field` parts around the box as on `Input`, and a string `label` requires `id`; a box with no name does not compile. `className` lands on the box.',
|
|
322
322
|
words: 'The clear button is named for what it empties (*Clear search*, *Clear filter*). "Placeholders show an example value (`my-awesome-project`, `example.com`), never instructions like `Enter your project name`." (https://vercel.com/geist/input) Ours: the placeholder may also be the scope.',
|
|
323
323
|
access: "A visible `label` or `aria-label` names the box. The clear button is a named button, `aria-keyshortcuts` Escape; the prefix and the keys are hidden from assistive technology. `aria-busy` while `loading`.",
|
|
@@ -327,11 +327,11 @@ export const USAGE = [
|
|
|
327
327
|
module: "collection-search",
|
|
328
328
|
family: "Inputs",
|
|
329
329
|
summary: "The search a collection toolbar fills its row with.",
|
|
330
|
-
when: '"Pre-configured search input with a magnifying glass icon and clear button." (https://vercel.com/geist/search-input) Ours: finding a record inside one collection, at the start of its
|
|
330
|
+
when: '"Pre-configured search input with a magnifying glass icon and clear button." (https://vercel.com/geist/search-input) Ours: finding a record inside one collection, at the start of its `CollectionToolbar`.',
|
|
331
331
|
instead: 'Finding anything across the product is `CommandDialog`. Narrowing by a facet is `CollectionFilters`. A free value in a form is `Input`. "For an inline search box, set the `search` variant with a scoped placeholder like `Search projects`; don’t embed a `<SearchInput>` inside an unrelated form." (https://vercel.com/geist/input) Ours: this component is the search; `Input` has no `search` variant.',
|
|
332
|
-
behaviour: '
|
|
332
|
+
behaviour: '"Automatically clears the input if escape is pressed." (https://vercel.com/geist/input) "Displays the <kbd>⌘</kbd> <kbd>K</kbd> shortcut to indicate that the input supports a command palette." (https://vercel.com/geist/input) "Transitions to showing <kbd>Esc</kbd> when the field is dirty." (https://vercel.com/geist/input) Ours: a `ClearableInput` with the search mark in a 14 px slot, or a custom `prefix` in its place. Once the box has text and is enabled, an Esc key cap (a close mark on a touch screen) stands flush against its end; pressing it, or Escape inside the box, empties the value, calls `onClear` and puts focus back in the box. Ring, halo, hover and disabled are `Input`\'s. Results stay on screen while a search updates; `loading` marks the box busy and draws the waiting line after a second. Optional `shortcut` keys stand at the end while the box is empty and give way to the Esc key cap once it has text.',
|
|
333
333
|
words: '"A `SearchInput` placeholder names the scope (`Search projects`) so the role is clear without sighted context." (https://vercel.com/geist/input) Ours: the search\'s label is what it finds, also shown as its placeholder: *Find a skill*, *Search the hub*.',
|
|
334
|
-
access: 'The input carries `aria-label` from `label` and `type="search"`; the clear button is named by `clearLabel
|
|
334
|
+
access: 'The input carries `aria-label` from `label` and `type="search"`; the clear button is named by `clearLabel` and appears only while the box has text and is enabled; the search mark is decorative. A search always has both names, so neither can be left out.',
|
|
335
335
|
},
|
|
336
336
|
{
|
|
337
337
|
name: "Field",
|
|
@@ -340,7 +340,7 @@ export const USAGE = [
|
|
|
340
340
|
summary: "A control with its label and its error, tied together; a checkbox, a radio or a switch with the sentence beside it.",
|
|
341
341
|
when: "A checkbox, a radio or a switch with its sentence, or a group of such rows. Inside a settings section `SettingsField` does it and hands the control its `controlProps`.",
|
|
342
342
|
instead: 'A text box, a textarea, a select, a combobox or a multi-select takes `label` and `error` as props and draws these same parts: wrapping one in `Field` with a `FieldLabel` is the same job done twice. A bordered tile that holds a radio or checkbox and a sentence is `ChoiceboxGroup`. A whole save group is `SettingsSection`. A group of radios or checkboxes is a `FieldSet` with a `FieldLegend`: "Wrap related radios in `<fieldset>` + `<legend>` so screen readers announce the group name before each option." (https://vercel.com/geist/radio) "Wrap related checkboxes in `<fieldset>` with a `<legend>` so screen readers announce the group name before each option." (https://vercel.com/geist/checkbox)',
|
|
343
|
-
behaviour: '"Accessible text label for form controls." (https://vercel.com/geist/label) Label, then the control, then the error under it, 8 px apart, at regular weight; the fields of a `FieldGroup` stand 16 px apart. The label stays neutral when the control is invalid: red sits on the control\'s edge and on the message, the message with an alert mark before it at 13 px (16 px beside a large control). A control fills its field. "Helper text is sentence case, one sentence with a trailing period, on a sibling element wired through `aria-describedby`." (https://vercel.com/geist/input) Ours: help is the one-line description of the card or the form that holds the field, not a line under each field; a `FieldDescription` is for the sentence beside a checkbox, a radio or a switch. "Validate on blur, not on every keystroke; surface the message by passing a string to `error`." (https://vercel.com/geist/input) "Validate on blur and pass a string to `error` to render the inline message; the validation replaces helper text on failure." (https://vercel.com/geist/textarea) Ours: the message updates as the value is corrected. "Keep the field focusable while saving; pair `disabled` with a spinner only when input is impossible." (https://vercel.com/geist/input) Ours: no spinner, the loading state stays still. "Trim leading and trailing whitespace before submit so ` example.com` and `example.com` resolve to one value." (https://vercel.com/geist/input) Ours: the save of `FormDialog`, `FormDrawer`, `RowEditForm` and `SettingsSection` trims the ends of every text value; a bare `<form>` does not. "The click target already extends to the label. Don’t override the `<label>`/`htmlFor` association with a custom wrapper that breaks the click region." (https://vercel.com/geist/checkbox) "Disabled checkboxes still need a Tooltip naming the reason; a greyed box with no explanation reads as a bug." (https://vercel.com/geist/checkbox) Ours: a disabled field says why in the card\'s footer, never in a tooltip around the control.',
|
|
343
|
+
behaviour: '"Accessible text label for form controls." (https://vercel.com/geist/label) Label, then the control, then the error under it, 8 px apart, at regular weight; the fields of a `FieldGroup` stand 16 px apart. The label stays neutral when the control is invalid: red sits on the control\'s edge and on the message, the message with an alert mark before it at 13 px (16 px beside a large control). A control fills its field. "Helper text is sentence case, one sentence with a trailing period, on a sibling element wired through `aria-describedby`." (https://vercel.com/geist/input) Ours: help is the one-line description of the card or the form that holds the field, not a line under each field; a `FieldDescription` is for the sentence beside a checkbox, a radio or a switch. "Validate on blur, not on every keystroke; surface the message by passing a string to `error`." (https://vercel.com/geist/input) "Validate on blur and pass a string to `error` to render the inline message; the validation replaces helper text on failure." (https://vercel.com/geist/textarea) Ours: the message updates as the value is corrected. "Keep the field focusable while saving; pair `disabled` with a spinner only when input is impossible." (https://vercel.com/geist/input) Ours: no spinner, the loading state stays still. "Trim leading and trailing whitespace before submit so ` example.com` and `example.com` resolve to one value." (https://vercel.com/geist/input) Ours: the save of `FormDialog`, `FormDrawer`, `RowEditForm` and `SettingsSection` trims the ends of every text value; a bare `<form>` does not. "The click target already extends to the label. Don’t override the `<label>`/`htmlFor` association with a custom wrapper that breaks the click region." (https://vercel.com/geist/checkbox) "Disabled checkboxes still need a Tooltip naming the reason; a greyed box with no explanation reads as a bug." (https://vercel.com/geist/checkbox) Ours: a disabled field says why in the card\'s footer, never in a tooltip around the control. `Field` only lays the parts out; the caller connects the real validation to the control, the help and the error, with the field owner that matches the product\'s form engine (`SettingsField` inside a settings section), and the values survive a recoverable failure. Visual styling never changes the form engine.',
|
|
344
344
|
words: '"Labels are short Title Case nouns: `Project Name`, `Domain`, `Environment Variable Name`." (https://vercel.com/geist/input) Ours: sentence case, a short noun (*Display name*); a field that may stay empty says so in its label (*Address line 2 (optional)*), never in its placeholder. "Placeholders show an example value (`my-awesome-project`, `example.com`), never instructions like `Enter your project name`." (https://vercel.com/geist/input) "Validation names the field and the constraint, ends in a period, and skips `please` (`Project name is required.`, `Code must be 6 digits.`)." (https://vercel.com/geist/input) Ours: *Enter a full address, like name@company.it.*',
|
|
345
345
|
access: '`FieldError` is `role="alert"`. The control carries `id`, `aria-invalid`, `aria-describedby` (the error), `disabled` and `required`. "Don’t wrap a labelled `<Input>` in a `Tooltip`; put the explainer on a sibling icon button so the label stays announced." (https://vercel.com/geist/input)',
|
|
346
346
|
},
|
|
@@ -350,7 +350,7 @@ export const USAGE = [
|
|
|
350
350
|
family: "Inputs",
|
|
351
351
|
summary: "One line of free text.",
|
|
352
352
|
when: '"Retrieve text input from a user." (https://vercel.com/geist/input) "Pick `<Input>` for a single line of free-form text such as names, domains, or tokens." (https://vercel.com/geist/input)',
|
|
353
|
-
instead: '"Switch to `Textarea` the moment content can wrap to multiple lines." (https://vercel.com/geist/input) "Use `Combobox` when the value comes from a known list the user filters by typing." (https://vercel.com/geist/input) "For an inline search box, set the `search` variant with a scoped placeholder like `Search projects`; don’t embed a `<SearchInput>` inside an unrelated form." (https://vercel.com/geist/input) Ours: there is no `search` variant. A collection\'s search is `CollectionSearch`, and a value people empty often is `ClearableInput`. A short known set is radios or a select. A button inside the box, or a server-given value with its copy button, is an `InputGroup`.',
|
|
353
|
+
instead: '"Switch to `Textarea` the moment content can wrap to multiple lines." (https://vercel.com/geist/input) "Use `Combobox` when the value comes from a known list the user filters by typing." (https://vercel.com/geist/input) "For an inline search box, set the `search` variant with a scoped placeholder like `Search projects`; don’t embed a `<SearchInput>` inside an unrelated form." (https://vercel.com/geist/input) Ours: there is no `search` variant. A collection\'s search is `CollectionSearch`, and a value people empty often is `ClearableInput`. A short known set is radios or a select. A button inside the box, a block addon, a textarea toolbar, or a server-given value with its copy button, is an `InputGroup`; Geist\'s `prefixContainer` and `suffixContainer` are compositions of `InputGroup`. The card around a field is a `SettingsField` or a `Field`.',
|
|
354
354
|
behaviour: 'Sizes `sm` (32 px), default (36 px) and `lg` (40 px, larger type) are shared by every field, so fields in a row line up. The edge is a 1 px ring outside the box that darkens on hover; focus makes it full strength with a soft 4 px halo around it. Colour sits in one place: an error turns the ring red and fills the halo, always and also while focused, and colours the message; the label, fill, text and placeholder stay neutral. Disabled steps down in fill and text, never in opacity, and ignores hover. `label` and `error` draw the `Field` parts around the box: the label above it, the message under it with an alert mark. `prefix` and `suffix` are filled cells inside the ring, divided from the text, with 14 px marks (8 px of padding at `sm`); the suffix reads in the text colour. `look="plain"` sets both on the box\'s own fill with no divider, flush against the text. `rounded` makes the box a pill. `className` lands on the box. "Validate on blur, not on every keystroke; surface the message by passing a string to `error`." (https://vercel.com/geist/input) "Trim leading and trailing whitespace before submit so ` example.com` and `example.com` resolve to one value." (https://vercel.com/geist/input) "Keep the field focusable while saving; pair `disabled` with a spinner only when input is impossible." (https://vercel.com/geist/input) "Don’t wrap a labelled `<Input>` in a `Tooltip`; put the explainer on a sibling icon button so the label stays announced." (https://vercel.com/geist/input) "Helper text is sentence case, one sentence with a trailing period, on a sibling element wired through `aria-describedby`." (https://vercel.com/geist/input) Ours: help is the one-line description of the card or form around the field, not a line under it. Nothing spins while a field saves. Only the forms `Field` names trim.',
|
|
355
355
|
words: '"Labels are short Title Case nouns: `Project Name`, `Domain`, `Environment Variable Name`." (https://vercel.com/geist/input) "Placeholders show an example value (`my-awesome-project`, `example.com`), never instructions like `Enter your project name`." (https://vercel.com/geist/input) "Validation names the field and the constraint, ends in a period, and skips `please` (`Project name is required.`, `Code must be 6 digits.`)." (https://vercel.com/geist/input) Ours: the label is a short noun in sentence case with no colon, not Title Case, and the example comes from our catalogue (*Bizeta Arredamenti*).',
|
|
356
356
|
access: '"Passing a string to `label` requires `id`; the `InputPropsWithStringLabelAndId` union won’t compile without it and screen readers lose the association." (https://vercel.com/geist/input) Ours: a field whose name is drawn elsewhere (a toolbar\'s search, a table cell) takes `aria-label` or `aria-labelledby` instead, and one with no name does not compile. The error is joined through `aria-describedby`. Use the right `type`, `autocomplete` and `inputMode`. "For icon-only affordances inside a row of inputs, use `<Button shape="circle" svgOnly aria-label="…">` rather than an unlabeled icon." (https://vercel.com/geist/input)',
|
|
@@ -362,7 +362,7 @@ export const USAGE = [
|
|
|
362
362
|
summary: "One input box with something fixed inside it: a mark, a prefix, a button.",
|
|
363
363
|
when: "A single-line value with a button inside the box: a copy button, a block addon, a textarea with a toolbar, a server-given value (a project ID) shown read-only in mono beside its copy button, with no Save.",
|
|
364
364
|
instead: "A prefix or suffix of text or an icon (*https://*, *.com*), in a cell or plain, is the `prefix`, `suffix` and `look` props of `Input`. A value that clears itself is `ClearableInput`; a collection's search is `CollectionSearch`, never a hand-built group. Many known values to filter is `Combobox`.",
|
|
365
|
-
behaviour: "The group draws the edge and the focus halo for the input inside it, so the ring wraps the addons too, and takes the sizes of `Input`: the input follows the group's. An addon is a `segment` (a filled cell with a divider, the look of `Input`'s prefix and suffix) or `plain` (a mark on the box's own fill, flush against the text). An icon button in an addon fills the row, sits flush against the edge, is muted until hovered and never fills on hover or press. A disabled input steps the whole group down in fill and text, never in opacity, an icon button included; a button in an addon that is held, such as a send button waiting for text, leaves the box as it is. Colour sits where it does on `Input`: only an error reddens the ring; a suffix reads in the text colour in both looks, a prefix in the muted grey. `rounded` makes the group a pill. The group's own `size` is the only place a size is set.",
|
|
365
|
+
behaviour: "The group draws the edge and the focus halo for the input inside it, so the ring wraps the addons too, and takes the sizes of `Input`: the input follows the group's. An addon is a `segment` (a filled cell with a divider, the look of `Input`'s prefix and suffix) or `plain` (a mark on the box's own fill, flush against the text). An icon button in an addon fills the row, sits flush against the edge, is muted until hovered and never fills on hover or press. A text button in an addon is `InputGroupButton` at `xs`, 24 px high. A disabled input steps the whole group down in fill and text, never in opacity, an icon button included; a button in an addon that is held, such as a send button waiting for text, leaves the box as it is. Colour sits where it does on `Input`: only an error reddens the ring; a suffix reads in the text colour in both looks, a prefix in the muted grey. `rounded` makes the group a pill. The group's own `size` is the only place a size is set.",
|
|
366
366
|
words: "Prefix and suffix are literal text, never the label. An inline button names what it does: *Copy address*.",
|
|
367
367
|
access: '`role="group"` around the box. The input inside is named by `aria-label` or `aria-labelledby`. "For icon-only affordances inside a row of inputs, use `<Button shape="circle" svgOnly aria-label="…">` rather than an unlabeled icon." (https://vercel.com/geist/input) Ours: an inline button carries its `aria-label`.',
|
|
368
368
|
},
|
|
@@ -373,18 +373,18 @@ export const USAGE = [
|
|
|
373
373
|
summary: "A fixed-length code typed into one slot per character.",
|
|
374
374
|
when: "A fixed-length code a person types: the two-factor code at the Portal's sign-in.",
|
|
375
375
|
instead: "A code a person only compares between two screens is `PairingCode`. A long token is `Input` with a copy or paste action.",
|
|
376
|
-
behaviour: "One real input behind the slots: paste fills them all, Backspace walks back. The active slot carries the focus outline and a still caret. A wrong code turns the group red and keeps the digits so the person can correct them. Resend follows the product's real policy.",
|
|
377
|
-
words: "
|
|
378
|
-
access: 'One labelled input with `autocomplete="one-time-code"` and `inputMode="numeric"`. The separator
|
|
376
|
+
behaviour: "One real input behind the slots: paste fills them all, Backspace walks back. The active slot carries the focus outline and a still caret. A wrong code turns the group red and keeps the digits so the person can correct them. Resend follows the product's real policy. The component owns none of the length (`maxLength`), expiry or submission: the product sets and handles them.",
|
|
377
|
+
words: "A visible label says where the code comes from. The error says what to do: *That code didn't match. Check the newest code in your app.*",
|
|
378
|
+
access: 'One labelled input with `autocomplete="one-time-code"` and `inputMode="numeric"`. The separator has `role="separator"`.',
|
|
379
379
|
},
|
|
380
380
|
{
|
|
381
381
|
name: "Label",
|
|
382
382
|
module: "label",
|
|
383
383
|
family: "Inputs",
|
|
384
384
|
summary: "The visible name of one control.",
|
|
385
|
-
when: "\"Accessible text label for form controls.\" (https://vercel.com/geist/label) Ours: a control standing on its own that needs a visible name: a checkbox's sentence, a switch's setting, a bare control in a toolbar or a table cell.",
|
|
386
|
-
instead: "A text box, a textarea or a select names itself with its own `label`, so it never needs this. A group of controls takes `FieldLegend
|
|
387
|
-
behaviour: "
|
|
385
|
+
when: "\"Accessible text label for form controls.\" (https://vercel.com/geist/label) Ours: a control standing on its own that needs a visible name: a checkbox's sentence, a switch's setting, a bare control in a toolbar or a table cell. `FieldLabel` is the same label inside a `Field`.",
|
|
386
|
+
instead: "A text box, a textarea or a select names itself with its own `label`, so it never needs this. A group of controls takes `FieldLegend`, a heading at 16 px semibold (its `label` variant is 13 px regular, like this label). A short tag of metadata is a `Tag`. Geist's `value` and `withInput` are not needed: the label is its children, and the `Field` places it, 8 px above a control in a vertical `Field`, at 13 px regular.",
|
|
387
|
+
behaviour: "In a vertical `Field` it stands above its control, 8 px clear of it (the `Field`'s gap; the label has no margin), in 13 px type at regular weight in the secondary grey, one step quieter than the control's own text. Beside a checkbox or a radio it is the sentence at 13 px in the text colour; beside a switch it is 12 px at medium weight. It stays neutral when the control is invalid. When its control is disabled it steps down to the placeholder grey, never in opacity. Clicking it moves to its control.",
|
|
388
388
|
words: '"Labels are short Title Case nouns: `Project Name`, `Domain`, `Environment Variable Name`." (https://vercel.com/geist/input) Ours: a noun or a positive statement in sentence case, no colon, kept as written (the label never recases its text, which is what Geist\'s `bypassCasing` asks for): *Approvals*, *Send a copy to the person who asked*. A placeholder never stands in for it.',
|
|
389
389
|
access: "A `label` tied to its control by `htmlFor` and the control's `id`, or wrapping it. Never overridden by another `aria-label`.",
|
|
390
390
|
},
|
|
@@ -428,7 +428,7 @@ export const USAGE = [
|
|
|
428
428
|
summary: "A bounded number chosen by a thumb's position on a track, with its name and its live value.",
|
|
429
429
|
when: '"Use a slider for ranged numeric input where shape and proximity matter more than precision: bandwidth caps, opacity, audio volume, color channels." (https://vercel.com/geist/slider) Ours: a limit, a share.',
|
|
430
430
|
instead: '"For exact values like a port number or memory limit, pair the slider with a numeric `Input`; keyboard users default to typing." (https://vercel.com/geist/slider) Ours: `showEndInput`, and `showStartInput` on a range, add that box. A few named levels are radios.',
|
|
431
|
-
behaviour: '"Snap to a sensible step (`1`, `5`, `10%`) so dragging never produces values like `47.83291`." (https://vercel.com/geist/slider) "Clamp `min` and `max` to real product limits." (https://vercel.com/geist/slider) "Always render the live value next to the track in tabular nums, and label what the number represents (`Sample Rate · 44 kHz`)." (https://vercel.com/geist/slider) "The track alone is not self-describing." (https://vercel.com/geist/slider) "Threshold colors (warning, error tints past a limit) should match the same numeric breakpoint surfaced elsewhere in the UI; don’t invent a slider-only threshold." (https://vercel.com/geist/slider) Ours: `label`, `min`, `max` and `step` are required. The label sits above the track at 13 px in the secondary grey, with the live value beside it. The track is 8 px high and grey, the filled part is blue, and the thumb is a 6 by 14 px white bar; a disabled slider turns the filled part grey and leaves the rest. Colour: blue is the one hue, the filled part and nothing else. The thumb grows by a fifth while it is dragged (only the dragged one) or has keyboard focus, and hovering changes nothing. A range has two thumbs and cannot cross. A typed value is snapped to the step and kept inside the bounds when the box loses focus or Enter is pressed. `onValueCommitted` fires once when the change settles.',
|
|
431
|
+
behaviour: '"Snap to a sensible step (`1`, `5`, `10%`) so dragging never produces values like `47.83291`." (https://vercel.com/geist/slider) "Clamp `min` and `max` to real product limits." (https://vercel.com/geist/slider) "Always render the live value next to the track in tabular nums, and label what the number represents (`Sample Rate · 44 kHz`)." (https://vercel.com/geist/slider) "The track alone is not self-describing." (https://vercel.com/geist/slider) "Threshold colors (warning, error tints past a limit) should match the same numeric breakpoint surfaced elsewhere in the UI; don’t invent a slider-only threshold." (https://vercel.com/geist/slider) Ours: `label`, `min`, `max` and `step` are required. Values are arrays, one number per thumb, and the start and end boxes are `Input` `sm`. The label sits above the track at 13 px in the secondary grey, with the live value beside it. The track is 8 px high and grey, the filled part is blue, and the thumb is a 6 by 14 px white bar; a disabled slider turns the filled part grey and leaves the rest. Colour: blue is the one hue, the filled part and nothing else. The thumb grows by a fifth while it is dragged (only the dragged one) or has keyboard focus, and hovering changes nothing. A range has two thumbs and cannot cross. A typed value is snapped to the step and kept inside the bounds when the box loses focus or Enter is pressed. `onValueCommitted` fires once when the change settles.',
|
|
432
432
|
words: "Ours: the label names the quantity: *Monthly limit*. The unit comes from `format`, so the value reads *€400*, not 400. The help under the slider states the bounds and the step.",
|
|
433
433
|
access: '"Give the slider an accessible name through a sibling `<label htmlFor>` or `aria-label`, and let the native arrow-key, Page Up/Down, Home/End behavior do the keyboard work." (https://vercel.com/geist/slider) "Don’t intercept those keys." (https://vercel.com/geist/slider) Ours: each thumb is `role="slider"` named by the label, with `aria-valuetext` carrying the unit from `format`. A single thumb and its box are named by the label; the two thumbs and the two boxes of a range by the label and their position, *Price 1* and *Price 2*.',
|
|
434
434
|
},
|
|
@@ -439,7 +439,7 @@ export const USAGE = [
|
|
|
439
439
|
summary: "Several lines of free text that grow with their content.",
|
|
440
440
|
when: '"Retrieve multi-line user input." (https://vercel.com/geist/textarea) "Pick `<Textarea>` for content that wraps to multiple lines (commit messages, descriptions, notes); use `Input` for a single value like a name or domain." (https://vercel.com/geist/textarea)',
|
|
441
441
|
instead: "Ours: code or a value to copy is `CodeBlock`, read-only.",
|
|
442
|
-
behaviour: 'Same ring, halo, hover, disabled and error as `Input`; the red of an error sits on the ring, the halo and the message only, and holds while the box has focus. It grows with its content (`field-sizing: content`) from a roomy 100 px minimum with a 10 px by 12 px inset; `lg` takes
|
|
442
|
+
behaviour: 'Same ring, halo, hover, disabled and error as `Input`; the red of an error sits on the ring, the halo and the message only, and holds while the box has focus. It grows with its content (`field-sizing: content`) from a roomy 100 px minimum with a 10 px by 12 px inset; `lg` takes 16 px type at every width (the others drop to 14 px from `md` up) and the larger corner, `sm` is the default\'s size. Read-only keeps the plain fill: it does not look disabled. `label` and `error` draw the `Field` parts around the box (in a settings section, `SettingsField` does it), and a string `label` requires `id`, as on `Input`; a box with no name does not compile. "Set a generous default `rows` and grow only when the surface has vertical room; don’t let the field push primary actions below the fold." (https://vercel.com/geist/textarea) Ours: there is no resize handle and `rows` does not set the height. Nothing caps the growth, so a form keeps its footer\'s primary action in view and lets the body scroll. "Validate on blur and pass a string to `error` to render the inline message; the validation replaces helper text on failure." (https://vercel.com/geist/textarea) "Trim leading and trailing whitespace on submit so empty newlines don’t pass a `required` check." (https://vercel.com/geist/textarea) Ours: typing keeps every space and newline; the save of `FormDialog`, `FormDrawer`, `RowEditForm` and `SettingsSection` trims the ends (see `Field`). A character count shows only when a real limit exists.',
|
|
443
443
|
words: '"Labels are short Title Case nouns (`Description`, `Release Notes`); placeholders show an example value, not instructions like `Enter a description`." (https://vercel.com/geist/textarea) "Validation names the field and constraint, ends in a period, and skips `please` (`Description is required.`, `Release notes can’t exceed 500 characters.`)." (https://vercel.com/geist/textarea) "Render helper text as a sibling `<p>` linked through `aria-describedby`; sentence case, one sentence, with a period." (https://vercel.com/geist/textarea) Ours: the label names the content (*Instructions*) in sentence case. Help is the one-line description of the card or form around the box, not a `<p>` under it. The placeholder is a short example, not a paragraph.',
|
|
444
444
|
access: "A visible label tied by `id`, or `aria-label`; the error through `aria-describedby`. In a dialog, the footer's Save stays reachable while the box grows.",
|
|
445
445
|
},
|
|
@@ -450,7 +450,7 @@ export const USAGE = [
|
|
|
450
450
|
summary: "A box that is checked, unchecked or mixed.",
|
|
451
451
|
when: '"Multi-select inside a list, like table-row pickers, multi-pick filters, and opt-in preference groups." (https://vercel.com/geist/checkbox) "Acknowledgments where the user must affirm a specific statement (terms of service, irreversible export)." (https://vercel.com/geist/checkbox)',
|
|
452
452
|
instead: '"For a single boolean setting like dark mode or password protection, use `Toggle`." (https://vercel.com/geist/checkbox) "The on/off mechanic is clearer there than a lone checkbox." (https://vercel.com/geist/checkbox) Ours: Geist\'s `Toggle` is our `Switch`. One of several exclusive options is a `RadioGroup`, and one that needs a description is a `ChoiceboxGroup`. Several values from a long list is a multi-select.',
|
|
453
|
-
behaviour: '"Indeterminate is a visual state, not a third value." (https://vercel.com/geist/checkbox) "Drive it from a parent that knows partial selection, and clear it as soon as every child is fully checked or unchecked." (https://vercel.com/geist/checkbox) "Validation on a required acknowledgment fires on submit, not on blur, so checking and unchecking shouldn’t flash an error." (https://vercel.com/geist/checkbox) "Disabled checkboxes still need a Tooltip naming the reason; a greyed box with no explanation reads as a bug." (https://vercel.com/geist/checkbox) Ours: named by `aria-label`, `aria-labelledby` or an `id` that a `FieldLabel` points at; a box with none does not compile. It is 16 px with a 4 px corner, the label 8 px away at 13 px regular weight on a 20 px row, and the label is part of the target. Colour: only a checked box fills, in
|
|
453
|
+
behaviour: '"Indeterminate is a visual state, not a third value." (https://vercel.com/geist/checkbox) "Drive it from a parent that knows partial selection, and clear it as soon as every child is fully checked or unchecked." (https://vercel.com/geist/checkbox) "Validation on a required acknowledgment fires on submit, not on blur, so checking and unchecking shouldn’t flash an error." (https://vercel.com/geist/checkbox) "Disabled checkboxes still need a Tooltip naming the reason; a greyed box with no explanation reads as a bug." (https://vercel.com/geist/checkbox) Ours: named by `aria-label`, `aria-labelledby` or an `id` that a `FieldLabel` points at; a box with none does not compile. It is 16 px with a 4 px corner, the label 8 px away at 13 px regular weight on a 20 px row, and the label is part of the target. Colour: only a checked box fills, with the action pair (ink and a white mark, the light action fill and a near-black mark in dark); an unchecked box shows the surface and greys on hover. Mixed is an unfilled box with a grey dash. A disabled box turns grey, an unchecked or mixed one with a lighter edge and a checked one mid grey, and ignores hover.',
|
|
454
454
|
words: '"Group label above a `<fieldset>` is a Title Case noun like `Notifications` or `Required Permissions`. No trailing colon." (https://vercel.com/geist/checkbox) "Acknowledgment label is a full sentence ending in a period: `I agree to the Terms of Service.`" (https://vercel.com/geist/checkbox) "Indeterminate copy names the partial count next to the group label (`3 of 5 selected`). Never leave the dash state unlabeled." (https://vercel.com/geist/checkbox) Ours: sentence case, not Title Case. The label says what is true when checked, positively: *Send a copy to the person who asked*, not *Don\'t send*.',
|
|
455
455
|
access: '"Wrap related checkboxes in `<fieldset>` with a `<legend>` so screen readers announce the group name before each option." (https://vercel.com/geist/checkbox) "Row-select checkbox in a table has no visible label. Set `aria-label="Select {row name}"` so the row stays identifiable out of context." (https://vercel.com/geist/checkbox) "The click target already extends to the label. Don’t override the `<label>`/`htmlFor` association with a custom wrapper that breaks the click region." (https://vercel.com/geist/checkbox) Ours: `role="checkbox"` with `aria-checked` true, false or mixed. A row\'s box is named for its record: *Select pec@acme.it*. A set sits in a `FieldSet` with its legend.',
|
|
456
456
|
},
|
|
@@ -459,9 +459,9 @@ export const USAGE = [
|
|
|
459
459
|
module: "choicebox",
|
|
460
460
|
family: "Choices",
|
|
461
461
|
summary: "A group of larger tiles, each a radio or a checkbox with a title and a description.",
|
|
462
|
-
when: '"A choice that benefits from a larger tap target plus a description or icon, like a framework picker, plan comparison, or deployment region with latency." (https://vercel.com/geist/choicebox) "Single-select for mutually exclusive choices, multi-select for additive ones." (https://vercel.com/geist/choicebox) "Don’t mix the two within one group." (https://vercel.com/geist/choicebox) "Cap at 4–6 tiles." (https://vercel.com/geist/choicebox) Ours: where a Library entry applies, an API key\'s access level.',
|
|
462
|
+
when: '"A choice that benefits from a larger tap target plus a description or icon, like a framework picker, plan comparison, or deployment region with latency." (https://vercel.com/geist/choicebox) "Single-select for mutually exclusive choices, multi-select for additive ones." (https://vercel.com/geist/choicebox) "Don’t mix the two within one group." (https://vercel.com/geist/choicebox) "Cap at 4–6 tiles." (https://vercel.com/geist/choicebox) Ours: where a Library entry applies, an API key\'s access level. The options are known in advance and number no more than six.',
|
|
463
463
|
instead: '"Past that, switch to `Select` or `Combobox` so the page doesn’t scroll for a single field." (https://vercel.com/geist/choicebox) "For plain text labels with no description, use `Radio`." (https://vercel.com/geist/choicebox) Ours: Geist\'s `Radio` is our `RadioGroup`, or a `Checkbox` for several. Two or three views of one surface are a `SegmentedControl`.',
|
|
464
|
-
behaviour: '"The whole tile is the click and focus target; tapping anywhere inside selects it." (https://vercel.com/geist/choicebox) "Don’t place nested buttons or links inside a tile that would steal the click." (https://vercel.com/geist/choicebox) "Selected state shows a check or filled dot in the corner." (https://vercel.com/geist/choicebox) "The border highlight alone isn’t enough on low-contrast screens." (https://vercel.com/geist/choicebox) "Disabled tiles need a Tooltip naming why (`Available on Pro`)." (https://vercel.com/geist/choicebox) "A faded tile with no reason reads as broken." (https://vercel.com/geist/choicebox) Ours:
|
|
464
|
+
behaviour: '"The whole tile is the click and focus target; tapping anywhere inside selects it." (https://vercel.com/geist/choicebox) "Don’t place nested buttons or links inside a tile that would steal the click." (https://vercel.com/geist/choicebox) "Selected state shows a check or filled dot in the corner." (https://vercel.com/geist/choicebox) "The border highlight alone isn’t enough on low-contrast screens." (https://vercel.com/geist/choicebox) "Disabled tiles need a Tooltip naming why (`Available on Pro`)." (https://vercel.com/geist/choicebox) "A faded tile with no reason reads as broken." (https://vercel.com/geist/choicebox) Ours: each tile is a `ChoiceboxGroupItem`, which throws outside a `ChoiceboxGroup`. The group has a `label` and a `type`, and every tile a `title` and a `description`; a group or a tile without them does not compile. Tiles share one row and wrap to a column on a narrow screen; `orientation="vertical"` stacks them. They are 12 px apart, each with 12 px inside and 24 px between its words and its corner mark, the mark centred against the words. Colour: a selected, available tile is the one place blue sits on a choice, as a blue border, a blue tint behind the title and description, blue words, and a blue corner mark; a selected tile that is unavailable stays grey, never a faded blue. An unselected tile stays neutral and takes a grey fill on hover. The corner always draws the dot or the check, empty until the tile is selected. The whole tinted row (title, specs, description, reason and corner mark) is the label that selects; `children` of a tile appear below that row while it is selected, outside the label, so a click there does not select, and they are for reading, never a link or a button. A tile, or the whole group, is unavailable only with a `disabledReason`, written under the tile\'s description or the legend, and only its title greys. A tile may carry `specs`, grey chips under its title, and one `badge` beside it for the option to prefer (*Recommended*); both are grey `secondary` badges, because blue marks only the selected tile.',
|
|
465
465
|
words: '"Titles are parallel: one Title Case title plus one sentence-case description per tile, ending in a period." (https://vercel.com/geist/choicebox) "Don’t restate the title in the description." (https://vercel.com/geist/choicebox) "The description adds the differentiator (`$20/mo · 100 GB bandwidth`), not a synonym." (https://vercel.com/geist/choicebox) "Icons are decorative when paired with a title; if the icon is the only label, give the tile an `aria-label` naming the choice." (https://vercel.com/geist/choicebox) Ours: the title is a short noun phrase in sentence case: *Pro trial*; the description is *Free for two weeks.* The label names the choice: *Plan*.',
|
|
466
466
|
access: '"Tiles render as radios or checkboxes under the hood, so keep them inside a `<fieldset>` with a `<legend>` so screen readers announce the group." (https://vercel.com/geist/choicebox) "Arrow keys move within a single-select group, Space toggles in multi-select." (https://vercel.com/geist/choicebox) "Don’t override those keys with custom handlers." (https://vercel.com/geist/choicebox) "Color is not the selection signal." (https://vercel.com/geist/choicebox) "Pair the highlight border with the corner check so colorblind users still see what’s active." (https://vercel.com/geist/choicebox) Ours: a `FieldSet` with the label as its legend, which names a group of checkboxes; a `role="radiogroup"` is labelled by the same legend. Each tile is named by its title and described by its description and reason. Keyboard focus outlines the whole tile, and the box in its corner draws no outline of its own.',
|
|
467
467
|
},
|
|
@@ -483,7 +483,7 @@ export const USAGE = [
|
|
|
483
483
|
summary: "Type to find one value, or several, in a known set.",
|
|
484
484
|
when: '"Filters large lists to selectable options based on the matching query." (https://vercel.com/geist/combobox) "Pick `<Combobox>` when users type to filter a known list of values (regions, frameworks, env-var names)." (https://vercel.com/geist/combobox) Ours: assign an Agent, choose a time zone, add several Agents at once with `multiple`.',
|
|
485
485
|
instead: '"For short, fixed lists where typing adds nothing, switch to `Select`." (https://vercel.com/geist/combobox) "When more than one value can be chosen at once, use `MultiSelect`." (https://vercel.com/geist/combobox) "For free-form filter strings that don’t resolve to a single option, drop in `Input` with the `search` variant." (https://vercel.com/geist/combobox) Ours: several values chosen from a list you scan rather than type into is a `MultiSelect`; `Combobox` takes several with `multiple`. `Input` has no `search` variant, so free text is an `Input` and narrowing a collection is `CollectionFilters`.',
|
|
486
|
-
behaviour: 'The box is `Input`\'s: its three sizes (`size` on `ComboboxInput`), its ring, hover, focus halo and disabled, with the search mark at the start, a `placeholder` that names the scope, and the open-list or clear button flush at the end, in the muted grey until hovered (the open-list button is 30 px high and centred at the default size, full height at `sm` and `lg`). Colour sits in one place: an error turns the ring red, fills the halo and colours the typed text; the placeholder, marks and list stay neutral. `label` and `error` go on `ComboboxInput`, and a string `label` requires `id`; a box with no name does not compile. The list, a solid floating surface, opens 8 px under it at its width (a narrower `className` width centres under the box), in a 6 px inset, up to five and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap. A row is one line that ends in an ellipsis, with a 16 px mark 12 px before the name and a 14 px check at the chosen row\'s end, after the name. A row that cannot be chosen turns its text muted, never dimmed. Typing filters the list. "Options can opt out of the fixed row height with `ignoreDefaultHeight` to render multi-line content (e.g. a shared environment variable name plus its target). The list sizes each row to its content and stays scrollable." (https://vercel.com/geist/combobox) Ours: there is no `ignoreDefaultHeight`; a two-line row is an explicit column child. "Show a loading state for async results; don’t collapse the list while the request is in flight." (https://vercel.com/geist/combobox) Ours: while results load the list stays and `ComboboxLoading` says so, and the chosen value stays; `ComboboxContent` requires `empty`, the sentence for no match, or `null` while a search is still running. "Render a custom empty state of the form `No {items} match "{query}"` rather than a bare `No results`." (https://vercel.com/geist/combobox) "Keep arrow-key navigation through `<Combobox.Option>` items; don’t hijack Enter to submit the surrounding form while the list is open." (https://vercel.com/geist/combobox) "Set `clearable` to show a clear button when a value is selected." (https://vercel.com/geist/combobox) Ours: `showClear` with a `clearLabel` does it; clear empties the box and keeps focus there. With `multiple`, each choice is a chip with its own remove; Backspace in an empty box removes the last. "It is common to use Combobox inside a Modal. On mobile, the Modal automatically renders a Dialog instead." (https://vercel.com/geist/combobox) "Inside a `Modal`, Geist switches to a Dialog on mobile automatically; don’t layer a second portal." (https://vercel.com/geist/combobox) Ours: our overlay is `Dialog`, and `ComboboxContent` is the list\'s one portal.',
|
|
486
|
+
behaviour: 'The parts are `ComboboxInput`, `ComboboxContent`, `ComboboxList`, `ComboboxItem`, `ComboboxGroup` with its `ComboboxLabel`, `ComboboxLoading`, and `ComboboxChips` with its `ComboboxChip` and `ComboboxChipsInput` for `multiple`; the line for no match is the `empty` of `ComboboxContent`. The box fills its container and is `Input`\'s: its three sizes (`size` on `ComboboxInput`), its ring, hover, focus halo and disabled, with the search mark at the start, a `placeholder` that names the scope, and the open-list or clear button flush at the end, in the muted grey until hovered (the open-list button is 30 px high and centred at the default size, full height at `sm` and `lg`). Colour sits in one place: an error turns the ring red, fills the halo and colours the typed text; the placeholder, marks and list stay neutral. `label` and `error` go on `ComboboxInput`, and a string `label` requires `id`; a box with no name does not compile. The list, a solid floating surface, opens 8 px under it at its width (a narrower `className` width centres under the box), in a 6 px inset, up to five and a half rows tall (210 px) so a cut row shows there is more, with rows 36 px tall and no gap. A row is one line that ends in an ellipsis, with a 16 px mark 12 px before the name and a 14 px check at the chosen row\'s end, after the name. A row that cannot be chosen turns its text muted, never dimmed. Typing filters the list. "Options can opt out of the fixed row height with `ignoreDefaultHeight` to render multi-line content (e.g. a shared environment variable name plus its target). The list sizes each row to its content and stays scrollable." (https://vercel.com/geist/combobox) Ours: there is no `ignoreDefaultHeight`; a two-line row is an explicit column child. "Show a loading state for async results; don’t collapse the list while the request is in flight." (https://vercel.com/geist/combobox) Ours: while results load the list stays and `ComboboxLoading` says so, and the chosen value stays; `ComboboxContent` requires `empty`, the sentence for no match, or `null` while a search is still running. "Render a custom empty state of the form `No {items} match "{query}"` rather than a bare `No results`." (https://vercel.com/geist/combobox) "Keep arrow-key navigation through `<Combobox.Option>` items; don’t hijack Enter to submit the surrounding form while the list is open." (https://vercel.com/geist/combobox) "Set `clearable` to show a clear button when a value is selected." (https://vercel.com/geist/combobox) Ours: `showClear` with a `clearLabel` does it; clear empties the box and keeps focus there. With `multiple`, each choice is a chip with its own remove; Backspace in an empty box removes the last. "It is common to use Combobox inside a Modal. On mobile, the Modal automatically renders a Dialog instead." (https://vercel.com/geist/combobox) "Inside a `Modal`, Geist switches to a Dialog on mobile automatically; don’t layer a second portal." (https://vercel.com/geist/combobox) Ours: our overlay is `Dialog`, and `ComboboxContent` is the list\'s one portal.',
|
|
487
487
|
words: '"Visible label is a short Title Case noun (`Region`, `Environment Variable Name`)." (https://vercel.com/geist/combobox) "Placeholder is the inline hint: `Search regions`, `DATABASE_URL`. Never bare `Search…` and never the label restated." (https://vercel.com/geist/combobox) "Option text is Title Case for short values and matches canonical branding (`Next.js`, not `NextJS`); same register across the list." (https://vercel.com/geist/combobox) "Validation names the field and constraint, sentence case with a period (`Select a region.`)." (https://vercel.com/geist/combobox) Ours: the label and the rows are sentence case. The hint is scoped: *Find an Agent*. No match quotes the query: *No Agent matches “Xy”.* A chip reads as the record\'s name.',
|
|
488
488
|
access: '"`<Combobox>` has no `label` prop; pair a sibling `<Label htmlFor>` with the root `id`, or set `aria-label` on the root for icon-only triggers." (https://vercel.com/geist/combobox) "The root accepts `aria-label` only; there is no `aria-labelledby` prop, so the sibling-label path is the way to reference visible text." (https://vercel.com/geist/combobox) "Trap focus inside the popover when nested in a `Modal` so Tab cycles options instead of the page behind it." (https://vercel.com/geist/combobox) Ours: `ComboboxInput` takes a visible `label` tied to the box by `id`, or `aria-label` or `aria-labelledby`; combobox and listbox roles; the open-list and clear buttons are named by `triggerLabel` and `clearLabel`; each chip\'s remove is named: *Remove Acme Quotes*. An Agent\'s orb in a row is a decorative mark beside her name.',
|
|
489
489
|
},
|
|
@@ -503,9 +503,9 @@ export const USAGE = [
|
|
|
503
503
|
module: "multi-select",
|
|
504
504
|
family: "Choices",
|
|
505
505
|
summary: "A menu of checkbox rows that chooses several values from a known list, with the count in its trigger.",
|
|
506
|
-
when: '"A keyboard-navigable dropdown for selecting multiple items with advanced focus management." (https://vercel.com/geist/multi-select) "Pick `<MultiSelect>` when users pick more than one value from a known list (regions, scopes, tags)." (https://vercel.com/geist/multi-select)
|
|
507
|
-
instead: '"For a single value from a short list, use `Select`." (https://vercel.com/geist/multi-select) "When typing to filter is more important than seeing every option at once, use `Combobox`." (https://vercel.com/geist/multi-select) "Skip `<MultiSelect>` for boolean settings; `Toggle` handles those." (https://vercel.com/geist/multi-select) Ours: `Combobox` takes several values with `multiple`; a yes-or-no setting is a `Switch`, never a one-row multi-select
|
|
508
|
-
behaviour: '"Use controlled mode when state lives in the URL or syncs to the server so the trigger label and stored value stay in lockstep." (https://vercel.com/geist/multi-select) "Use controlled state to manage selections programmatically." (https://vercel.com/geist/multi-select) Ours: the caller always owns the selection and passes it back through each row, so the trigger and the stored value (the URL, the server) never disagree; the root takes the same `selectedCount` and `totalCount` the trigger\'s summary is built from. "Show the selected count in the trigger (`3 regions selected`); show the single name when only one is picked." (https://vercel.com/geist/multi-select) The trigger is `Input`\'s box at its default size, in regular weight, with the chevron at the end (it turns up while the list is open); it keeps the hover edge while the list is open and takes the page\'s outline for keyboard focus, as a button does. Everything is neutral: a checked box is ink, never blue, and an error turns only the trigger\'s ring red. `label` and `error` go on `MultiSelectTrigger`, and a string `label` requires `id`; a trigger with no name does not compile. The list opens 8 px under the trigger at least as wide as it, in an 8 px inset, with rows 28 px tall and no gap, and stops 16 px short of the screen\'s edge (at most 24rem tall, then it scrolls). A row is the checkbox in a 28 px square that fills on hover, then a button holding the row\'s name; pressing the name runs the button, pressing the box toggles the row. "**Checkbox focus + Enter/Space**: Toggle individual item" (https://vercel.com/geist/multi-select) "**Button focus + Enter/Space**: Smart selection (Select Only, Select All, or Toggle based on context)" (https://vercel.com/geist/multi-select) "**Hidden action labels**: Appear on hover/focus to show available actions" (https://vercel.com/geist/multi-select) Ours: the button\'s label reads *Select all* on a chosen row while some rows are unchosen, and *Select only* on every other row;
|
|
506
|
+
when: '"A keyboard-navigable dropdown for selecting multiple items with advanced focus management." (https://vercel.com/geist/multi-select) "Pick `<MultiSelect>` when users pick more than one value from a known list (regions, scopes, tags)." (https://vercel.com/geist/multi-select)',
|
|
507
|
+
instead: '"For a single value from a short list, use `Select`." (https://vercel.com/geist/multi-select) "When typing to filter is more important than seeing every option at once, use `Combobox`." (https://vercel.com/geist/multi-select) "Skip `<MultiSelect>` for boolean settings; `Toggle` handles those." (https://vercel.com/geist/multi-select) Ours: `Combobox` takes several values with `multiple`; a yes-or-no setting is a `Switch`, never a one-row multi-select; the query facets of a collection, where a suggested value waits as a dashed chip, are `CollectionFilters`.',
|
|
508
|
+
behaviour: '"Use controlled mode when state lives in the URL or syncs to the server so the trigger label and stored value stay in lockstep." (https://vercel.com/geist/multi-select) "Use controlled state to manage selections programmatically." (https://vercel.com/geist/multi-select) Ours: the caller always owns the selection and passes it back through each row, so the trigger and the stored value (the URL, the server) never disagree; the root takes the same `selectedCount` and `totalCount` the trigger\'s summary is built from. "Show the selected count in the trigger (`3 regions selected`); show the single name when only one is picked." (https://vercel.com/geist/multi-select) The trigger is `Input`\'s box at its default size, in regular weight, with the chevron at the end (it turns up while the list is open); it keeps the hover edge while the list is open and takes the page\'s outline for keyboard focus, as a button does. Everything is neutral: a checked box is ink, never blue, and an error turns only the trigger\'s ring red. `label` and `error` go on `MultiSelectTrigger`, and a string `label` requires `id`; a trigger with no name does not compile. The list opens 8 px under the trigger at least as wide as it, in an 8 px inset, with rows 28 px tall and no gap, and stops 16 px short of the screen\'s edge (at most 24rem tall, then it scrolls). A row is the checkbox in a 28 px square that fills on hover, then a button holding the row\'s name; pressing the name runs the button, pressing the box toggles the row. "**Checkbox focus + Enter/Space**: Toggle individual item" (https://vercel.com/geist/multi-select) "**Button focus + Enter/Space**: Smart selection (Select Only, Select All, or Toggle based on context)" (https://vercel.com/geist/multi-select) "**Hidden action labels**: Appear on hover/focus to show available actions" (https://vercel.com/geist/multi-select) Ours: the button\'s label reads *Select all* on a chosen row while some rows are unchosen, and *Select only* on every other row, and a list of one row has no button, only the label; the button shows on hover and focus of the row, and always on touch. "Keep checkbox focus and button focus distinct so Up/Down navigates rows and Left/Right toggles between the row’s checkbox and action button." (https://vercel.com/geist/multi-select) "**Up/Down arrows**: Navigate between rows while maintaining checkbox/button focus state" (https://vercel.com/geist/multi-select) "**Left/Right arrows**: Switch between checkbox and button focus within the current row" (https://vercel.com/geist/multi-select) "**Tab**: Focus away from the menu (natural tab behavior)" (https://vercel.com/geist/multi-select) "**Enter/Space**: Execute actions based on current focus (checkbox toggle or button click)" (https://vercel.com/geist/multi-select) "Trap focus inside the menu while it’s open and return focus to the trigger on close." (https://vercel.com/geist/multi-select) Ours: focus moves into the list when it opens; Tab leaves it in order. "For empty filters, render `No {items} match "{query}"` rather than `No results`." (https://vercel.com/geist/multi-select) Ours: there is no filter box, so a list that needs one is a `Combobox` with `multiple`. Its list is solid, as every menu is, and a row that cannot be chosen turns its text muted, never dimmed.',
|
|
509
509
|
words: 'The trigger says how many are chosen in the product\'s words: the name when exactly one is chosen, *No regions selected* when none. It is never empty. Rows are sentence case names. "Announce bulk actions (`Select All`, `Select Only`) through the visible button label so the action matches what the screen reader speaks." (https://vercel.com/geist/multi-select) Ours: the two button labels come from the caller, in sentence case.',
|
|
510
510
|
access: '"Each row checkbox needs an `aria-label` that names the item (`Select us-east-1`); a bare `Select` is unanchored for screen readers." (https://vercel.com/geist/multi-select) Ours: each row\'s checkbox is named by the row\'s name; each button is named by its label and the row (*Select only us-east-1*). "The trigger button needs a stable accessible name even when zero items are selected; don’t rely on the placeholder alone." (https://vercel.com/geist/multi-select) Ours: the trigger is a button with `aria-expanded`, named by its visible label tied by `id`, or by `aria-label` or `aria-labelledby`; the summary describes it.',
|
|
511
511
|
},
|
|
@@ -516,7 +516,7 @@ export const USAGE = [
|
|
|
516
516
|
summary: "One option of a few, all shown at once.",
|
|
517
517
|
when: '"A single choice from 2–6 mutually exclusive options where seeing every option matters (deploy regions, plan tiers, billing cycle)." (https://vercel.com/geist/radio) Ours: a plan\'s billing cycle, who can see an Agent.',
|
|
518
518
|
instead: '"Past 6 options, switch to `Select` or `Combobox` so the list doesn’t dominate the form." (https://vercel.com/geist/radio) "For binary on/off, use `Toggle`." (https://vercel.com/geist/radio) "For richer per-option content (icon, description, badge), use `Choicebox`." (https://vercel.com/geist/radio) Ours: Geist\'s `Toggle` is our `Switch` and its `Choicebox` our `ChoiceboxGroup`. Two or three views of one surface are a `SegmentedControl`. Another view of the same thing is `Tabs`.',
|
|
519
|
-
behaviour: '"Pre-select the safest default so the field reads as configured, never as required-but-empty." (https://vercel.com/geist/radio) "Skip the default only when the choice has real consequences and you want a deliberate pick." (https://vercel.com/geist/radio) "Required state goes on the `RadioGroup`, not on individual options." (https://vercel.com/geist/radio) "Required-against-a-single-radio is meaningless." (https://vercel.com/geist/radio) "Arrow keys move selection within the group and skip disabled options." (https://vercel.com/geist/radio) "Tab moves to the next field, not the next radio." (https://vercel.com/geist/radio) "Disabled options need a Tooltip naming why (`Available on Pro and Enterprise`)." (https://vercel.com/geist/radio) "A greyed-out radio with no reason reads as broken." (https://vercel.com/geist/radio) Ours: the group is named by `aria-label` or `aria-labelledby`, and each `RadioGroupItem` by `aria-label`, `aria-labelledby` or an `id` that a `FieldLabel` points at; one without a name does not compile. Options are 16 px circles 24 px apart (a dense call site passes its own `gap`)
|
|
519
|
+
behaviour: '"Pre-select the safest default so the field reads as configured, never as required-but-empty." (https://vercel.com/geist/radio) "Skip the default only when the choice has real consequences and you want a deliberate pick." (https://vercel.com/geist/radio) "Required state goes on the `RadioGroup`, not on individual options." (https://vercel.com/geist/radio) "Required-against-a-single-radio is meaningless." (https://vercel.com/geist/radio) "Arrow keys move selection within the group and skip disabled options." (https://vercel.com/geist/radio) "Tab moves to the next field, not the next radio." (https://vercel.com/geist/radio) "Disabled options need a Tooltip naming why (`Available on Pro and Enterprise`)." (https://vercel.com/geist/radio) "A greyed-out radio with no reason reads as broken." (https://vercel.com/geist/radio) Ours: the group is named by `aria-label` or `aria-labelledby`, and each `RadioGroupItem` by `aria-label`, `aria-labelledby` or an `id` that a `FieldLabel` points at; one without a name does not compile. Options are 16 px circles 24 px apart (a dense call site passes its own `gap`); in a `Field`, each label stands 8 px from its circle at 13 px regular weight on a 20 px line. Colour: the circle is unfilled and shows the surface it sits on, an unchecked enabled one greys its edge and fill on hover, and the chosen one darkens its edge to the ink of the primary action and shows an 8 px ink dot; nothing takes a hue, and a disabled option greys edge, dot and label together, with no hover change. On coarse or non-hover input the target grows to the touch target while the mark stays 16 px. The arrows also choose. The reason for a disabled option is written in its own help line.',
|
|
520
520
|
words: '"Group label is a Title Case noun like `Deployment Region` or `Billing Cycle`." (https://vercel.com/geist/radio) "Render it via `<legend>` or a sibling label tied with `aria-labelledby`." (https://vercel.com/geist/radio) "Option labels are parallel: same part of speech, same length range, same register." (https://vercel.com/geist/radio) "`Monthly` / `Yearly`, not `Monthly` / `Pay yearly`." (https://vercel.com/geist/radio) Ours: sentence case with no end punctuation: *Only me*, *Everyone in the company*. The legend names the setting; it does not ask a question.',
|
|
521
521
|
access: '"Wrap related radios in `<fieldset>` + `<legend>` so screen readers announce the group name before each option." (https://vercel.com/geist/radio) "The standalone unlabeled radio (custom UI) needs an `aria-label` describing the choice." (https://vercel.com/geist/radio) "Never ship a radio with no accessible name." (https://vercel.com/geist/radio) "Don’t replace the native focus ring with a CSS hack that drops outline-offset; keyboard users lose track of which option is focused." (https://vercel.com/geist/radio) Ours: `role="radiogroup"` inside a `FieldSet`. The legend names the fieldset, not the group, so point `aria-labelledby` at the `FieldLegend`\'s id.',
|
|
522
522
|
},
|
|
@@ -538,7 +538,7 @@ export const USAGE = [
|
|
|
538
538
|
summary: "Two or three options that show different views of one surface, the chosen one filled.",
|
|
539
539
|
when: '"Use Switch as a segmented selector for 2–3 mutually exclusive options that show different views of the same surface, like `Source` vs `Output`." (https://vercel.com/geist/switch) Ours: Geist\'s `Switch` is our `SegmentedControl`.',
|
|
540
540
|
instead: '"For a boolean on/off setting, use `Toggle`." (https://vercel.com/geist/switch) "Switch implements radio semantics, so two options stay mutually exclusive rather than reading as a checkbox." (https://vercel.com/geist/switch) "Past 3 options or when labels grow beyond a couple of words, switch to `Tabs` or a `Select`." (https://vercel.com/geist/switch) Ours: Geist\'s `Toggle` is our `Switch`. The theme is `ThemeModeSwitcher`, never a second picker. Four to six that must be compared are a `RadioGroup`. Pressed buttons that can be several or none are a `ToggleGroup`. Panels of one object are `Tabs`.',
|
|
541
|
-
behaviour: '"Pass a `name` so the underlying radios are grouped; without it, more than one option can appear selected at once." (https://vercel.com/geist/switch) "Set `defaultChecked` (or controlled `checked`) on exactly one `Switch.Control` so the group has a defined initial state." (https://vercel.com/geist/switch) "Pad each `Switch.Control` so the widest label fits without the active pill resizing on selection." (https://vercel.com/geist/switch) "Test with the longest copy in the set." (https://vercel.com/geist/switch) Ours: the group\'s name (`aria-label` or `aria-labelledby`), a label on every option and a starting choice (`defaultValue` or `value`) are required; without them it does not compile. The options are one radio group, so they stay exclusive without a form `name`. The options share the width of the widest, so the fill never resizes as the choice moves; where the row is narrower than that, each option keeps its own word\'s width rather than cutting it. The track is white with a hairline and a 4 px inset, so it is 32, 36 or 40 px high around options of 24, 28 or 32 px. Colour: neutral throughout, the chosen option filled grey with ink words and the others grey words that turn ink on hover; nothing takes a hue. Icons are 16 px, 20 px at `lg`. A disabled option keeps the chosen fill, lightens only its words and has no hover change. The options are radios in one group: arrows move and choose, and pressing the chosen one changes nothing. It applies at once; it is not a field that waits for a Save. `fullWidth` when it heads a form or a dialog, as the two-factor form\'s method switch does.',
|
|
541
|
+
behaviour: '"Pass a `name` so the underlying radios are grouped; without it, more than one option can appear selected at once." (https://vercel.com/geist/switch) "Set `defaultChecked` (or controlled `checked`) on exactly one `Switch.Control` so the group has a defined initial state." (https://vercel.com/geist/switch) "Pad each `Switch.Control` so the widest label fits without the active pill resizing on selection." (https://vercel.com/geist/switch) "Test with the longest copy in the set." (https://vercel.com/geist/switch) Ours: the group\'s name (`aria-label` or `aria-labelledby`), a label on every option and a starting choice (`defaultValue` or `value`) are required; without them it does not compile. The options are one radio group, so they stay exclusive without a form `name`. The options share the width of the widest, so the fill never resizes as the choice moves; where the row is narrower than that, each option keeps its own word\'s width rather than cutting it. The track is white with a hairline and a 4 px inset, so it is 32, 36 or 40 px high around options of 24, 28 or 32 px. `size` `sm`, `default` and `lg` give options 24, 28 and 32 px high. Colour: neutral throughout, the chosen option filled grey with ink words and the others grey words that turn ink on hover; nothing takes a hue. Icons are 16 px, 20 px at `lg`. A disabled option keeps the chosen fill, lightens only its words and has no hover change. The options are radios in one group: arrows move and choose, and pressing the chosen one changes nothing. It applies at once; it is not a field that waits for a Save. `fullWidth` when it heads a form or a dialog, as the two-factor form\'s method switch does.',
|
|
542
542
|
words: '"Title Case each `Switch.Control` label." (https://vercel.com/geist/switch) "Keep labels to one or two words and parallel: `Source` / `Output`, not `Source` / `Show output`." (https://vercel.com/geist/switch) Ours: sentence case: *Source*, *Output*.',
|
|
543
543
|
access: '"Always provide a `label` on every `Switch.Control`, even when an `icon` carries the meaning, so screen readers announce the option." (https://vercel.com/geist/switch) "The component renders it as `geist-sr-only` for icon-only controls." (https://vercel.com/geist/switch) "Pair an icon-only Switch with a `Tooltip` on each control so sighted users get the same label assistive tech receives." (https://vercel.com/geist/switch) Ours: `role="radiogroup"` named by `aria-label` or `aria-labelledby`; each option a radio named by its label, shown as a tooltip on an icon-only option.',
|
|
544
544
|
},
|
|
@@ -547,9 +547,9 @@ export const USAGE = [
|
|
|
547
547
|
module: "select",
|
|
548
548
|
family: "Choices",
|
|
549
549
|
summary: "A short fixed list in our style, opened from a trigger that shows the choice.",
|
|
550
|
-
when: '"Display a dropdown list of items." (https://vercel.com/geist/select) "Pick `<Select>` for short, fixed lists (under ~10 items) where typing adds nothing; switch to `Combobox` once filtering helps." (https://vercel.com/geist/select) Ours: four or more options, each short enough to need no sentence: how often a task runs.',
|
|
550
|
+
when: '"Display a dropdown list of items." (https://vercel.com/geist/select) "Pick `<Select>` for short, fixed lists (under ~10 items) where typing adds nothing; switch to `Combobox` once filtering helps." (https://vercel.com/geist/select) Ours: four or more options, each short enough to need no sentence: how often a task runs. A list of two or three static options is a `RadioGroup`, as Geist says.',
|
|
551
551
|
instead: '"Use `MultiSelect` when more than one value can be chosen at once; use `Switch` for a 2–3 option segmented choice." (https://vercel.com/geist/select) Ours: two or three options are radios or a segmented control. Inside a native form a long list is `NativeSelect`.',
|
|
552
|
-
behaviour: 'The trigger is `Input`\'s box: its sizes, its ring, hover, focus halo and disabled, the label above it and the message under it. Colour sits in one place: an error turns the ring red and fills the halo, and colours the message; the fill, the value, the chevron and the label stay neutral. It shows the chosen value, after an optional `prefix` mark, with the chevron at the end in the muted grey (a `suffix` mark takes the chevron\'s place), and fills its container; it keeps the hover edge while its list is open. The list, a solid floating surface, opens under it, 8 px away, never covering the trigger (`alignItemWithTrigger` is off), in a 6 px inset, with a 14 px check at the chosen row\'s end, rows 36 px tall and no gap between them; a row that cannot be chosen turns its text muted, never dimmed. Arrows, typing, Enter, Escape. A placeholder reads in the muted grey and is never an option. `label` and `error` go on `SelectTrigger`, and a string `label` requires `id`; a trigger with no name does not compile. "Group options past ~10 items with native `<optgroup>`; Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours: group the options with `SelectGroup`. "Validate on blur and pass a string to `error`; messages name the field and end in a period (`Select a framework.`)." (https://vercel.com/geist/select) "Don’t wrap a labelled `<Select>` in a `Tooltip`; put the hint on a sibling icon button so the label stays announced." (https://vercel.com/geist/select) Other form rules are `Field`\'s.',
|
|
552
|
+
behaviour: 'The trigger is `Input`\'s box: its sizes, its ring, hover, focus halo and disabled, the label above it and the message under it, in the three sizes `sm`, default and `lg` that `Input` has. Colour sits in one place: an error turns the ring red and fills the halo, and colours the message; the fill, the value, the chevron and the label stay neutral. It shows the chosen value, after an optional `prefix` mark, with the chevron at the end in the muted grey (a `suffix` mark takes the chevron\'s place), and fills its container; it keeps the hover edge while its list is open. The list, a solid floating surface with a 12 px corner, opens under it, 8 px away, never covering the trigger (`alignItemWithTrigger` is off), in a 6 px inset, with a 14 px check at the chosen row\'s end, rows 36 px tall and no gap between them; a row that cannot be chosen turns its text muted, never dimmed. Arrows, typing, Enter, Escape. A placeholder reads in the muted grey and is never an option. `label` and `error` go on `SelectTrigger`, and a string `label` requires `id`; a trigger with no name does not compile. "Group options past ~10 items with native `<optgroup>`; Geist `<Select>` has no `.Group` static." (https://vercel.com/geist/select) Ours: group the options with `SelectGroup`. "Validate on blur and pass a string to `error`; messages name the field and end in a period (`Select a framework.`)." (https://vercel.com/geist/select) "Don’t wrap a labelled `<Select>` in a `Tooltip`; put the hint on a sibling icon button so the label stays announced." (https://vercel.com/geist/select) Other form rules are `Field`\'s.',
|
|
553
553
|
words: '"Options are Title Case when short and match canonical branding (`Next.js`, not `NextJS`); keep the same register across the list." (https://vercel.com/geist/select) "Label is a short Title Case noun (`Framework`, `Region`); accept the prop directly on `<Select>`." (https://vercel.com/geist/select) "Placeholder is action-oriented (`Select a framework`), never `Choose one…`, `Pick`, or the label restated." (https://vercel.com/geist/select) Ours: options are parallel phrases in sentence case (*Every hour*, *Every weekday*), the label is a short noun in sentence case, and the trigger never says *Select…* or *Choose one*.',
|
|
554
554
|
access: "A visible label tied to the trigger by `id`, or `aria-label` or `aria-labelledby`. Listbox and option roles from Base UI; focus returns to the trigger on close. The prefix and suffix are decorative and not part of the trigger's name.",
|
|
555
555
|
},
|
|
@@ -559,8 +559,8 @@ export const USAGE = [
|
|
|
559
559
|
family: "Choices",
|
|
560
560
|
summary: "A setting that is on or off.",
|
|
561
561
|
when: '"Use Toggle for a single boolean setting where ON takes effect immediately, like `Password Protection` or `Auto-Cancel Builds`." (https://vercel.com/geist/toggle) Ours: Geist\'s `Toggle` is our `Switch`, inside a settings section.',
|
|
562
|
-
instead: '"Switch to `Checkbox` for a multi-select list, or to `Switch` for 2–3 mutually exclusive views." (https://vercel.com/geist/toggle) Ours: Geist\'s `Switch` is our `SegmentedControl`.
|
|
563
|
-
behaviour: '"`checked` is required and the component is controlled. Own the state and update it from `onChange`." (https://vercel.com/geist/toggle) "Persist on change and surface the result in a `useToasts().success(…)` toast (`Password protection enabled`); don’t leave the user wondering whether the flip stuck." (https://vercel.com/geist/toggle) "Pair with a form footer only when the setting needs an explicit `Save` step." (https://vercel.com/geist/toggle) "Disable Toggle only when the action is impossible (missing plan, locked policy); pair with helper text or a `Tooltip` that names the resolver." (https://vercel.com/geist/toggle) Ours: `checked` is optional, since a switch also runs uncontrolled with `defaultChecked`; the toast is raised with `showMessageToast`; the reason for a disabled switch is written in the card\'s footer, and it is left off. Always named: `aria-label` or `aria-labelledby`, or an `id` that a `FieldLabel`\'s `htmlFor` points at, and a switch with none of them does not compile. A bare `Switch` is the 28 by 14 px track with an 11 px thumb; `md` is 36 by 20 px and `lg` 40 by 24 px. Colour: off is the input grey, on is ink, and a disabled switch turns grey, on or off, with a greyer thumb and no shadow. Space flips it. On a settings page it applies at once and the product\'s toast confirms it, in one of
|
|
562
|
+
instead: '"Switch to `Checkbox` for a multi-select list, or to `Switch` for 2–3 mutually exclusive views." (https://vercel.com/geist/toggle) Ours: Geist\'s `Switch` is our `SegmentedControl`. Our `Toggle` is a button that stays pressed, and several of them are a `ToggleGroup`. Choosing between named values is a `RadioGroup`.',
|
|
563
|
+
behaviour: '"`checked` is required and the component is controlled. Own the state and update it from `onChange`." (https://vercel.com/geist/toggle) "Persist on change and surface the result in a `useToasts().success(…)` toast (`Password protection enabled`); don’t leave the user wondering whether the flip stuck." (https://vercel.com/geist/toggle) "Pair with a form footer only when the setting needs an explicit `Save` step." (https://vercel.com/geist/toggle) "Disable Toggle only when the action is impossible (missing plan, locked policy); pair with helper text or a `Tooltip` that names the resolver." (https://vercel.com/geist/toggle) Ours: `checked` is optional, since a switch also runs uncontrolled with `defaultChecked`; the toast is raised with `showMessageToast`; the reason for a disabled switch is written in the card\'s footer, and it is left off. Always named: `aria-label` or `aria-labelledby`, or an `id` that a `FieldLabel`\'s `htmlFor` points at, and a switch with none of them does not compile. A bare `Switch` is the 28 by 14 px track with an 11 px thumb; `md` is 36 by 20 px and `lg` 40 by 24 px. Colour: off is the input grey, on is ink, and a disabled switch turns grey, on or off, with a greyer thumb and no shadow. Space flips it. On a settings page it applies at once and the product\'s toast confirms it, in one of three places: in the card\'s body under its label, its state word after it (`SettingsField`\'s `control="switch"`); in the card\'s header at the right of the title when the switch is the whole card, the footer holding only its line; or at the end of a row of a card of several settings, the label to its left. A failed change leaves the switch showing the true state.',
|
|
564
564
|
words: '"The label is `children`, not a `label` prop. Title Case noun phrase, 1–4 words, naming what is true when ON: `Password Protection`, not `Enable Password Protection`." (https://vercel.com/geist/toggle) "Render an optional one-sentence description as a sibling under the label that explains ON only." (https://vercel.com/geist/toggle) "Don’t describe OFF; it’s the negation." (https://vercel.com/geist/toggle) "Keep `labelCasing="title"` (the default) so labels match other Title Case surfaces." (https://vercel.com/geist/toggle) "Use `labelCasing="normal"` only when sentence-case content sits inline next to the toggle." (https://vercel.com/geist/toggle) Ours: the label is 12 px at medium weight, in sentence case: *Approvals*, with the card\'s description in one sentence. The state word is a separate word beside the switch, not its name.',
|
|
565
565
|
access: '"Provide an accessible name through `children`, `aria-label`, or `aria-labelledby`." (https://vercel.com/geist/toggle) "Geist warns in development if all three are missing." (https://vercel.com/geist/toggle) "Set `aria-label` only when the visible label sits elsewhere on the row; otherwise let `children` carry it so sighted and screen-reader copy stay aligned." (https://vercel.com/geist/toggle) Ours: `role="switch"` with `aria-checked`, the name the same in both states; a missing name does not compile.',
|
|
566
566
|
},
|
|
@@ -624,9 +624,9 @@ export const USAGE = [
|
|
|
624
624
|
module: "agent-orb",
|
|
625
625
|
family: "Status",
|
|
626
626
|
summary: "An Agent, anywhere: her orb, the only thing that moves on its own.",
|
|
627
|
-
when:
|
|
628
|
-
instead: 'A person is an `Avatar`. The system\'s own wait is the waiting line, never an orb. An orb in artwork takes `motion="ambient"` and claims no activity.',
|
|
629
|
-
behaviour: "The only thing that moves on its own. Idle turns and breathes; working and thinking
|
|
627
|
+
when: 'An Agent, anywhere: 20 in the sidebar, 32 in a row, 64 on her Profile card, 96 in an Agent card, 160 on entry. There is no other Agent picture. The sizes are 20, 24, 32, 40, 64, 96 and 160 px, and no other. On a photograph the orb takes the stronger `presence="over-picture"` weight; everywhere else it is `plain`.',
|
|
628
|
+
instead: 'A person is an `Avatar`. The system\'s own wait is the waiting line, never an orb. An orb in artwork, such as a sign-in picture, takes `motion="ambient"` and claims no activity. An orb that stands for an Agent shows her real state.',
|
|
629
|
+
behaviour: "The only thing that moves on its own. Idle turns at its form's own pace and breathes; working and thinking move with their cue, the ribbon around working and three dots above thinking; waiting holds; blocked makes one interrupted effort and stops; done celebrates once and settles back into idle. A surface that only knows a machine checked in draws idle. No state draws a circle around the orb. Ambient motion moves the form at its own pace with no state pose or cue. Reduced motion: movement stops, a thinking or working orb breathes between full ink and 0.45 opacity so busy still reads as busy, and an ambient orb holds still. An orb is one of seven forms, `ribbon`, `lattice`, `orbit`, `globe`, `network`, `braid` and `morph`, in one colour a person chose: cobalto, verde or arancio, or none, which draws it in the text colour. Giallo is not offered because it fails 3:1 on white; rosso is not offered because it is the error red. nadicode's own two Agents are the only ones with a figure nobody chooses, the same at every size and in every state. Nadia is her striped ribbon, the form `nadia`: azzurro, verde, the text colour and rosso along its whole length. Sofia, the Agent that looks up a new company, is her sister, the form `sofia`: the same ribbon turned to face Nadia's, striped cobalto, giallo, the text colour and arancio, the identity hues Nadia does not wear. Neither figure is ever stored as an Agent's look. An Agent's form is stored as `orb:<form>` and its colour as the identity hex or nothing; a template declares its starting look the same way. The stored-appearance resolver reads it: the root Agent always draws as Nadia, an unknown form draws `orbit`, an unknown colour the text colour, and a stored rosso draws arancio. The retired `forma:*` shapes each draw one of the seven forms, so an Agent that chose one before the orbs keeps a distinct look. An Agent's look is a person's choice and is never derived from a name, id or role. A template that names no look is the one exception: `templateAgentOrb` gives it a form and colour fixed by its slug, so the templates in a gallery differ and the Portal and Nadia draw the same one; Nadia and Sofia keep their figures.",
|
|
630
630
|
words: "Text sits beside the orb, never on it. Where the exact state matters a word names it: *Working*, *Waiting for you*.",
|
|
631
631
|
access: '`role="img"` labelled with the Agent\'s name, and her state where it matters; `decorative` when her name is already beside it.',
|
|
632
632
|
},
|
|
@@ -657,9 +657,9 @@ export const USAGE = [
|
|
|
657
657
|
module: "avatar",
|
|
658
658
|
family: "Status",
|
|
659
659
|
summary: "A person or a team: their picture, else their initials.",
|
|
660
|
-
when: '"Use a single `<Avatar>` for one person, team, or organization." (https://vercel.com/geist/avatar) "For two or more stacked avatars, use `<AvatarGroup>` so the cluster gets correct overlap, sizing, and a single accessible label." (https://vercel.com/geist/avatar) Ours: a person or a team: the account at the foot of the sidebar, a member list, who changed something. It always says who it shows: `label` names the entity (*Jane Doe*), and `decorative` stands for a name beside it that already does. The type asks for one of the two. "Pass `src` first and fall back to `letter` (1–2 uppercase chars) when the image is missing." (https://vercel.com/geist/avatar) Ours: the children are an `AvatarImage` and an `AvatarFallback` with the initials. "Pick a size that matches adjacent type: 20–24px next to `text-label-14`, 32px next to `text-label-16`, 48–64px in headers and onboarding states." (https://vercel.com/geist/avatar) Ours: `size` follows the type beside it: `xs` (16 px) and `sm` (24 px) next to small labels, `default` (32 px) next to body text, `lg` (48 px) in a header or an onboarding step. `AvatarWithIcon` puts a status glyph at the corner of a person\'s avatar
|
|
661
|
-
instead: "Ours: an Agent is always its `AgentOrb`, never an avatar. A company is its initials in a 6 px tile inside `RecordIdentity`.",
|
|
662
|
-
behaviour: '"By default the first member sits on top of the stack, so the first credited author stays the most prominent." (https://vercel.com/geist/avatar) "Set `reverse` to flip the order so the last member sits on top instead." (https://vercel.com/geist/avatar) "The visual left-to-right order is unchanged." (https://vercel.com/geist/avatar) "By default `overlap="auto"` scales the spacing with `size`, keeping a generous, evenly-spaced cluster at any size." (https://vercel.com/geist/avatar) "Pass a number to set the overlap in pixels instead." (https://vercel.com/geist/avatar) "Lower values give more generous spacing; higher values pack tighter for dense, space-constrained UI." (https://vercel.com/geist/avatar) Ours: `auto` is about 30 % of the size (5, 7, 10 and 14 px), and each member is cut out with a 1 px ring in the page colour. When `limit` cuts the group, the last place is a dark `+N` count that covers the member it hides. "Reserve `placeholder` for the loading shell, never as a permanent fallback." (https://vercel.com/geist/avatar) Ours: there is no `placeholder
|
|
660
|
+
when: '"Use a single `<Avatar>` for one person, team, or organization." (https://vercel.com/geist/avatar) "For two or more stacked avatars, use `<AvatarGroup>` so the cluster gets correct overlap, sizing, and a single accessible label." (https://vercel.com/geist/avatar) Ours: a person or a team: the account at the foot of the sidebar, a member list, who changed something. It always says who it shows: `label` names the entity (*Jane Doe*), and `decorative` stands for a name beside it that already does. The type asks for one of the two. "Pass `src` first and fall back to `letter` (1–2 uppercase chars) when the image is missing." (https://vercel.com/geist/avatar) Ours: the children are an `AvatarImage` and an `AvatarFallback` with the initials. "Pick a size that matches adjacent type: 20–24px next to `text-label-14`, 32px next to `text-label-16`, 48–64px in headers and onboarding states." (https://vercel.com/geist/avatar) Ours: `size` follows the type beside it: `xs` (16 px) and `sm` (24 px) next to small labels, `default` (32 px) next to body text, `lg` (48 px) in a header or an onboarding step. `AvatarWithIcon` puts a status glyph at the corner of a person\'s avatar; its `name` is required and gives the initials, and, like `Avatar`, the type asks for `label` (naming the person and the state) or `decorative`.',
|
|
661
|
+
instead: "Ours: an Agent is always its `AgentOrb`, never an avatar. A company is its initials in a 6 px tile inside `RecordIdentity`. A record's mark, a person's included, is `IdentityMark`, which draws an `Avatar` for a person. There are no git-provider avatars and no fetch of a picture by username.",
|
|
662
|
+
behaviour: '"By default the first member sits on top of the stack, so the first credited author stays the most prominent." (https://vercel.com/geist/avatar) "Set `reverse` to flip the order so the last member sits on top instead." (https://vercel.com/geist/avatar) "The visual left-to-right order is unchanged." (https://vercel.com/geist/avatar) "By default `overlap="auto"` scales the spacing with `size`, keeping a generous, evenly-spaced cluster at any size." (https://vercel.com/geist/avatar) "Pass a number to set the overlap in pixels instead." (https://vercel.com/geist/avatar) "Lower values give more generous spacing; higher values pack tighter for dense, space-constrained UI." (https://vercel.com/geist/avatar) Ours: `auto` is about 30 % of the size (5, 7, 10 and 14 px), and each member is cut out with a 1 px ring in the page colour. When `limit` cuts the group, the last place is a dark `+N` count that covers the member it hides; `AvatarGroup` draws that count itself, so the caller passes `members` and one `label`, never a count of its own. "Reserve `placeholder` for the loading shell, never as a permanent fallback." (https://vercel.com/geist/avatar) Ours: there is no `placeholder`, since a still skeleton is ours; a picture that fails falls back to initials from `avatarInitials`, set in the neutral accent fill with the text colour on it, in both themes. For a name with no readable letter the caller passes a glyph, so it is never blank. The picture is held by a 1 px hairline. The corner glyph of `AvatarWithIcon` is the person\'s state, in the muted foreground colour, so its `label` names the person and the state. The profile\'s upload button is `AvatarUploadButton`.',
|
|
663
663
|
words: '"Keep `letter` uppercase and derived from the entity name. No emoji, no punctuation, no `?`." (https://vercel.com/geist/avatar) Ours: the initials come from the name, else from the email; one initial at `xs`. The overflow is a count: *+2*.',
|
|
664
664
|
access: '"`title` is the literal entity name (`Acme Inc.`, `Jane Doe`)." (https://vercel.com/geist/avatar) "Geist already prefixes letter avatars with `Avatar with initials:` for screen readers, so don’t hand-write `Avatar of …`." (https://vercel.com/geist/avatar) Ours: the name is `label`, as it is written (*Jane Doe*, not *Avatar of Jane Doe*), and we add no prefix. Beside a visible name it is `decorative`. A group carries one label: *5 members*. A corner glyph is `aria-hidden`; `label` says the state: *Jane Doe, paused*.',
|
|
665
665
|
},
|
|
@@ -712,10 +712,10 @@ export const USAGE = [
|
|
|
712
712
|
module: "identity-mark",
|
|
713
713
|
family: "Status",
|
|
714
714
|
summary: "What says which thing a record is, at a fixed size at the start of its row, its object's head or its feed line: an Agent's orb, an app's logo, a company's initials, a person's picture, or a record kind's icon on its tile.",
|
|
715
|
-
when: "The first thing in a record's row (32), at the head of its object page (64), in a feed or a sidebar identity line (24), on a Library entry (40): wherever it tells a person which thing they are looking at.",
|
|
715
|
+
when: "The first thing in a record's row (32), at the head of its object page (64), in a feed or a sidebar identity line (24), on a Library entry (40), for the company at the top of Nadia's list (28): wherever it tells a person which thing they are looking at.",
|
|
716
716
|
instead: "An icon beside a card's title, a KPI's label or a section heading only repeats the words and is left out. A picture for a first use or a whole-page moment is the empty state's or the page's own. A model is its `ModelIdentity`.",
|
|
717
|
-
behaviour: "Static, except an Agent's orb, which moves with her state. An Agent is always her orb from the orb engine, never an avatar or a painted circle, and `frame` lets the product wrap that orb, such as in the transition that carries her to her page; an app keeps its own logo on the
|
|
718
|
-
words: "None of its own: the record's name stands beside it. Initials
|
|
717
|
+
behaviour: "Static, except an Agent's orb, which moves with her state. An Agent is always her real orb from the orb engine, at the largest orb size not above the mark's, with no tile and no ring, never an avatar or a painted circle, and `frame` lets the product wrap that orb, such as in the transition that carries her to her page; an app keeps its own logo in its bordered tile on the page ground, or its first letter when it has no logo; a company is its logo: the one white ink Sofia kept from its site on a black tile (`{colors.identity-ink}`) in both themes, as the company's card stamps it, or a square SVG icon in its own colours filling the tile, as Vercel shows a project's favicon; its two initials on the ringed card tile until a logo is found, or when the stored logo does not load; a person is their picture, then their initials, in a round mark; a record with its own object picture, such as a Library skill or plugin, draws that picture on its kind's ground; a record with no picture of its own takes its kind's own icon, the same icon its section has in the navigation (Mail for a mailbox, a server for an MCP server, a key for a password), in a ringed tile on the card ground; a Library skill takes the purple category ground with the Skills icon and a plugin the `info-soft` ground with the Plugins icon, both without a ring, so the ground says which kind it is, and an entry that has its own object picture draws it on that ground in the icon's place. A company or record tile takes `{rounded.sm}` (6 px) corners, `{rounded.md}` (8 px) at 40 and 64; an app's tile takes `{rounded.md}` at every size.",
|
|
718
|
+
words: "None of its own: the record's name stands beside it. Initials stand for a company or a person: the first letters of the name's first two words. An app with no logo takes the first letter of its name as a monogram, and no other kind takes letters.",
|
|
719
719
|
access: "Decorative beside the record's name. Alone, as in a narrow rail, `label` names it.",
|
|
720
720
|
},
|
|
721
721
|
{
|
|
@@ -758,7 +758,7 @@ export const USAGE = [
|
|
|
758
758
|
summary: "How far one record is through the steps that make it ready: a blue ring at the end of its row that opens the steps in a drawer at the right edge.",
|
|
759
759
|
when: "“Production Checklist”, the ring at the end of a project's row on Vercel's Projects page, which opens the steps that make a project ready for production. (https://vercel.com/dashboard) Ours: the steps a Customer or a Partner takes before nadicode's work runs for them, each one a fact the Portal can read (company details confirmed, a plan, Nadia connected, an Agent at work), at the end of the record's row.",
|
|
760
760
|
instead: 'Steps a person follows on a page, one after the other, are `NumberedSteps`; a setup with a wait is `FormDrawer job="steps"`. A ratio with no steps behind it is a `Gauge`. One state of a record is a `StatusBadge`.',
|
|
761
|
-
behaviour:
|
|
761
|
+
behaviour: "The ring is a 24 px `Gauge` in blue with the double check inside in ink, so the glyph reads at a glance, on a 36 px ghost button before the row's commands, whose tooltip names the checklist and how far it is (\"Customer setup 3/4\"). It opens a drawer from the right: the ring again, the title, one line, then one card per step: its icon on a tile of its tone, its title, one line, and the outline button that goes where the step is done; every card is the same hairline card, and a done step differs only by its blue Done chip after its title, with no button, so the steps still to do, with their buttons, draw the eye. The arc is the share of steps done (done steps over all steps, none drawn for no steps) and the track is the neutral remainder, so the ring is whole when every step is done. The footer's button closes it. A step is done because the thing it asks for exists, so the checklist has no Skip and remembers nothing. A step's tone says which kind of step it is, never whether it is done.",
|
|
762
762
|
words: 'Every string is the product\'s. The title names the checklist ("Customer setup"); a step\'s title reads as the thing done ("Plan chosen"); its button names where it goes ("Open billing").',
|
|
763
763
|
access: "The ring's button is named by `label`, which says whose checklist and how many steps are done; the ring itself is hidden from assistive technology. The drawer is a dialog named by its title; Escape, × and the footer's button close it and give focus back to the ring. The steps are a list.",
|
|
764
764
|
},
|
|
@@ -769,7 +769,7 @@ export const USAGE = [
|
|
|
769
769
|
summary: 'A record\'s identity in a row (DESIGN.md "Tables and missing values"): its mark at 32, its name, and under it one muted `detail` line saying what the record is or where it comes from, so the row reads without a column heading.',
|
|
770
770
|
when: "The first cell of every collection row: the mark, the name, and one muted line saying what the record is or where it comes from, so the row reads without a heading.",
|
|
771
771
|
instead: "An Agent in a gallery is `AgentListRow`; a model is `ModelIdentity`. A descriptive item with prose and actions is `Item`.",
|
|
772
|
-
behaviour: "The mark stands 16 px before the name. The name is semibold ink and the detail muted, two lines with no gap between them, so the row is an entity row; `mark` and `detail` are both required, and a record with nothing to say in the detail passes the dash of `TableValue`. The name holds one line and ends in an ellipsis, whole in its title, with a `hint` such as an ID read after it. The detail is one line too and ends in an ellipsis, whole in its title when it is text; in a row on a card narrower than its columns, the facts that leave the row join it after a middle dot, so the record stays the name over one line. `wraps` only when the name is the whole record, such as a memory, and then the detail wraps too. The table owns the link.",
|
|
772
|
+
behaviour: "The mark stands 16 px before the name. The name is semibold (600) ink and the detail a muted 14 px line, two lines with no gap between them, so the row is an entity row; `mark` and `detail` are both required, and a record with nothing to say in the detail passes the dash of `TableValue`. The name holds one line and ends in an ellipsis, whole in its title, with a `hint` such as an ID read after it. The detail is one line too and ends in an ellipsis, whole in its title when it is text; in a row on a card narrower than its columns, the facts that leave the row join it after a middle dot, so the record stays the name over one line. `wraps` only when the name is the whole record, such as a memory, and then the detail wraps too. The table owns the link.",
|
|
773
773
|
words: '“Lead the left column with a scannable identifier: an `Avatar` or icon, a Title Case label, then sentence-case secondary metadata (`Member since Mar 14, 2026`).” (https://vercel.com/geist/entity) Ours: the name as the record calls itself, and the detail in sentence case: kind, then relation, three to six words, "Company skill · every Agent has it".',
|
|
774
774
|
access: "The name is the row link's text; the mark is decorative; the title carries the full name.",
|
|
775
775
|
},
|
|
@@ -778,9 +778,9 @@ export const USAGE = [
|
|
|
778
778
|
module: "status-badge",
|
|
779
779
|
family: "Status",
|
|
780
780
|
summary: "A record's state: a mark and a word in one of five tones.",
|
|
781
|
-
when: '"Use Badge for short, scannable metadata that sits next to the thing it describes: status, plan tier, environment, or role." (https://vercel.com/geist/badge) Ours: a record\'s state, when it sets the row apart: failed, waiting on a person, a step in motion, paused, connected. Gray is every terminal state that needs no action. "Map meaning to color: `green` for healthy, `red` for error, `amber` for warning, `blue` for informational or production, `gray` for neutral." (https://vercel.com/geist/badge) Ours: success is green, danger red, warning amber, info blue, neutral gray, whatever the state\'s name, in our colour values. The tone and the word are required, and the type asks for both.',
|
|
781
|
+
when: '"Use Badge for short, scannable metadata that sits next to the thing it describes: status, plan tier, environment, or role." (https://vercel.com/geist/badge) Ours: a record\'s state, when it sets the row apart: failed, waiting on a person, a step in motion, paused, connected. Gray is the default and every terminal state that needs no action; green is healthy or complete; red is unusable or failed; amber is waiting on a person\'s decision; blue is a step in motion. "Map meaning to color: `green` for healthy, `red` for error, `amber` for warning, `blue` for informational or production, `gray` for neutral." (https://vercel.com/geist/badge) Ours: success is green, danger red, warning amber, info blue, neutral gray, whatever the state\'s name, in our colour values. The tone and the word are required, and the type asks for both.',
|
|
782
782
|
instead: '"For non-deployment statuses (Workflow runs, Queue messages, Sandbox, Cron jobs), use a `Badge` with the canonical state vocabulary instead of repurposing the dot." (https://vercel.com/geist/status-dot) Ours: that `Badge` is `StatusBadge`. For a category (an installation, a partner) use `Tag`. For plain metadata (a role, a plan) use `Badge`. For a control reporting its own action use `ActionStatus`. For a mark beside a word that already names the state, `StatusDot`.',
|
|
783
|
-
behaviour: '"Badges are static labels." (https://vercel.com/geist/badge) "Don’t wire `onClick` onto them; promote to a Button or link if the user can act on the value." (https://vercel.com/geist/badge) Ours: a status never moves and is never a button. "The `-subtle` suffix tones any of them down on dense surfaces." (https://vercel.com/geist/badge) Ours: `subtle`, the default, is for rows, tables, lists and menus: it fills the pill with the tone\'s soft ground and sets the mark and the word in its strong shade; gray takes the foreground colour, never a muted one. `solid` is for a status that stands on its own, such as an object\'s page header or a card title: it fills the pill with the tone\'s strong colour and sets the mark and the word on it. No border and no ring either way. "Don’t add a checkmark icon for success states or an X for errors; the variant carries that signal." (https://vercel.com/geist/badge) Ours: it always draws its tone\'s mark, even for success and error: the mark repeats the tone as a shape so a status reads without colour. Each app keeps one value-to-tone map in one module; nothing else picks a status colour. A healthy record shows no badge.',
|
|
783
|
+
behaviour: '"Badges are static labels." (https://vercel.com/geist/badge) "Don’t wire `onClick` onto them; promote to a Button or link if the user can act on the value." (https://vercel.com/geist/badge) Ours: a status never moves and is never a button. "The `-subtle` suffix tones any of them down on dense surfaces." (https://vercel.com/geist/badge) Ours: `subtle`, the default, is for rows, tables, lists and menus: it fills the pill with the tone\'s soft ground and sets the mark and the word in its strong shade; gray takes the foreground colour, never a muted one. `solid` is for a status that stands on its own, such as an object\'s page header or a card title: it fills the pill with the tone\'s strong colour and sets the mark and the word on it. No border and no ring either way. The `subtle` pairs are the tone\'s soft ground with its deep shade (gray: `canvas-soft-2` with the foreground colour). "Don’t add a checkmark icon for success states or an X for errors; the variant carries that signal." (https://vercel.com/geist/badge) Ours: it always draws its tone\'s mark, even for success and error: the mark repeats the tone as a shape so a status reads without colour. Each app keeps one value-to-tone map in one module; nothing else picks a status colour. A healthy record shows no badge.',
|
|
784
784
|
words: '"Title Case, one word when possible, two max: `Active`, `Pending`, `Pro`, `Enterprise Trial`." (https://vercel.com/geist/badge) Ours: a state, not a verb (*Failed*, *Waiting for you*), in sentence case, one to three words, no trailing ellipsis. Italian runs a third longer: *In attesa di te*. "Match the canonical API or log term: `Production` not `Prod`, `Deployed` not `Live`, `Canceled` not `Cancelled` (the Vercel API uses one L)." (https://vercel.com/geist/badge) "Skip stuffing sentences inside (`Currently Active`, `You are on Pro`); the surrounding row supplies the context." (https://vercel.com/geist/badge)',
|
|
785
785
|
access: '"Don’t rely on color alone; the text has to be readable without it." (https://vercel.com/geist/badge) Ours: the word is the name; the mark is `aria-hidden`, so colour never carries the state alone. Not focusable.',
|
|
786
786
|
},
|
|
@@ -791,7 +791,7 @@ export const USAGE = [
|
|
|
791
791
|
summary: "A small still dot for one state, named by its label or by the word beside it.",
|
|
792
792
|
when: '"Deployment lifecycle only. `state` accepts `QUEUED | BUILDING | READY | ERROR | CANCELED | DELETED`." (https://vercel.com/geist/status-dot) Ours: any one state in dense chrome (a channel, a machine in a list), set as `tone`. It always says which state it shows: `label` names it (*Ready*), and `decorative` stands for a word beside it that already does. The type asks for one of the two. "Don’t flash the color through every transitional state on a polling tick; only update when the readyState changes." (https://vercel.com/geist/status-dot) Ours: pick the tone from the state and keep it until the state changes.',
|
|
793
793
|
instead: '"For non-deployment statuses (Workflow runs, Queue messages, Sandbox, Cron jobs), use a `Badge` with the canonical state vocabulary instead of repurposing the dot." (https://vercel.com/geist/status-dot) Ours: that `Badge` is `StatusBadge`; so is a status that stands alone, or one that needs a mark and a word. "For health summaries that need a number (uptime, hit rate), use `Gauge`; for in-flight work with a knowable total, use `Progress`." (https://vercel.com/geist/status-dot) Ours: something in motion is the waiting line or an Agent\'s orb; the dot has no info tone.',
|
|
794
|
-
behaviour: '"The dot animates while `BUILDING` or `QUEUED` and goes static once the deployment reaches a terminal state." (https://vercel.com/geist/status-dot) "Don’t add a separate spinner alongside it." (https://vercel.com/geist/status-dot) Ours: still, even while a state is in progress: it never pulses. A 10 px dot; tones are `good`, `muted`, `warn`, `bad`, at full strength. Colour sits on the dot alone: with `showLabel` the word beside it is 14 px and neutral, never in the tone\'s colour. `label` alone draws the dot with the word as its name and tooltip; `showLabel` draws the word 8 px after it, for a dot with no other text around it.',
|
|
794
|
+
behaviour: '"The dot animates while `BUILDING` or `QUEUED` and goes static once the deployment reaches a terminal state." (https://vercel.com/geist/status-dot) "Don’t add a separate spinner alongside it." (https://vercel.com/geist/status-dot) Ours: still, even while a state is in progress: it never pulses. It has no deployment states and no `state` prop, only four generic tones. A 10 px dot; tones are `good`, `muted`, `warn`, `bad`, at full strength. Colour sits on the dot alone: with `showLabel` the word beside it is 14 px and neutral, never in the tone\'s colour. `label` alone draws the dot with the word as its name and tooltip; `showLabel` draws the word 8 px after it, for a dot with no other text around it.',
|
|
795
795
|
words: '"Use `label` only when the dot stands alone without surrounding text; Geist sentence-cases the state for you (`Building`, `Ready`, `Error`)." (https://vercel.com/geist/status-dot) Ours: `label` is the required name unless the dot is `decorative`, and we write it in sentence case ourselves: *Ready*, *Error*, *Canceled*. "Don’t wrap the dot in extra prose like `Status: Ready`. The label already names the state." (https://vercel.com/geist/status-dot)',
|
|
796
796
|
access: '"The component composes its own `aria-label` from `titlePrefix` plus the state message; don’t override it with a generic `aria-label="status"`." (https://vercel.com/geist/status-dot) Ours: there is no `titlePrefix` and no `aria-label` prop; `label` is the dot\'s name, an image role with that name. "When the dot sits inline with text that already names the state, mark the dot decorative with `aria-hidden` so screen readers don’t announce it twice." (https://vercel.com/geist/status-dot) Ours: `decorative` does it; the word beside the dot must name the state, and if that word is missing, the state is missing. "Color is not the only signal: every state ships with a distinct title and label so colorblind users get the same information." (https://vercel.com/geist/status-dot)',
|
|
797
797
|
},
|
|
@@ -813,8 +813,8 @@ export const USAGE = [
|
|
|
813
813
|
summary: "One Agent in a list of them: the orb, the name, and the line that says what the Agent is doing now or what it is for.",
|
|
814
814
|
when: "Nadia's Agents in her window's sidebar, and a website scene that shows a list of Agents.",
|
|
815
815
|
instead: "An Agent as a record among others in a page's list is a RecordList row with an agent IdentityMark; an Agent's own page heads with ObjectPage.",
|
|
816
|
-
behaviour: "It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500. A state tone puts a
|
|
817
|
-
words: 'The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests"). The tone never says more than the line does.',
|
|
816
|
+
behaviour: "It is the row's presentation alone: the consumer owns the control around it (Nadia's button that opens the Agent, a scene's list item that opens nothing). The current row's name is set in 500. A state tone puts a 10 px dot before the state line, 6 px from its words. Narrow, the orb stands alone and the words stay for a screen reader; the consumer names it with its state line in a tooltip, as Nadia's list does. Only the orb moves in the list. In Nadia's list the row is at least 52 px high, her orb at 32 px in her real state, in the narrow column from 640 to 899 px as in the wide one.",
|
|
817
|
+
words: 'The name, then one state line: what she is doing now ("Answering on Telegram", "Working") or what she is for ("Sorts requests"). The tone never says more than the line does. Nadia\'s list says "Working", "Idle" or "Needs you"; the dot marks a state that needs the person ("Needs you", a failed set-up), and the orb shows working by itself.',
|
|
818
818
|
access: "The orb is decorative; the name and the state line are the row's text, read by its control. The dot is hidden from assistive technology, so the line carries its meaning.",
|
|
819
819
|
},
|
|
820
820
|
{
|
|
@@ -824,7 +824,7 @@ export const USAGE = [
|
|
|
824
824
|
summary: "The top bar's path to the page.",
|
|
825
825
|
when: "\"Navigation aid that shows the user's location within a site's hierarchy, with text and menu variants.\" (https://vercel.com/geist/breadcrumbs) Ours: the top bar of every product page: the sidebar group or the parents, ending at the destination or the object. An object opened from search still shows its parent.",
|
|
826
826
|
instead: "Never a back button in its place. A return link may remember a visit, beside the path, not instead of it. Sibling views are tabs.",
|
|
827
|
-
behaviour: "Neutral only, one step of grey carries every state. `text` (the default) is a 20 px line of 14 px text with a 16 px chevron between steps and 6 px around each; `menu` is a row of 22 px chips with a rule and no chevrons, each chip cut with an ellipsis past 10 rem and carrying its full name in a tooltip, scrolling sideways when the path is long; a chip's rule darkens a step on hover. Ancestors are links in the body colour that turn ink on hover; the current item is ink and not a link. An ancestor the person may not open is a `BreadcrumbLink` with `disabled`: the placeholder grey, which sits close to the muted grey, so `aria-disabled`, the not-allowed cursor and no hover carry the state, and the type refuses an `href` or a `render` on it, so it cannot navigate. A long path folds its middle into `BreadcrumbEllipsis` and keeps the parent and the current item.
|
|
827
|
+
behaviour: 'Compose `Breadcrumb` (the labelled `nav`) around one `BreadcrumbList`, whose `BreadcrumbItem`s each hold a `BreadcrumbLink` for an ancestor, or a `BreadcrumbPage` for the current item, last; between two items goes a `BreadcrumbSeparator` (a chevron by default), and a folded middle is a `BreadcrumbEllipsis` in an item of its own. `type="menu"` draws no `BreadcrumbSeparator`, so the same composition works for both types. Neutral only, one step of grey carries every state. `text` (the default) is a 20 px line of 14 px text with a 16 px chevron between steps and 6 px around each; `menu` is a row of 22 px chips with a rule and no chevrons, each chip cut with an ellipsis past 10 rem and carrying its full name in a tooltip, scrolling sideways when the path is long; a chip\'s rule darkens a step on hover. Ancestors are links in the body colour that turn ink on hover; the current item is ink and not a link. An ancestor the person may not open is a `BreadcrumbLink` with `disabled`: the placeholder grey, which sits close to the muted grey, so `aria-disabled`, the not-allowed cursor and no hover carry the state, and the type refuses an `href` or a `render` on it, so it cannot navigate. A long path folds its middle into `BreadcrumbEllipsis` and keeps the parent and the current item. `menu` is only the chip look: a crumb does not open a menu of its siblings.',
|
|
828
828
|
words: "The groups' and records' own names, exactly as the sidebar and the record write them. The `aria-label` and the ellipsis's `label` come from the product's catalogue: \"Path\", \"More\".",
|
|
829
829
|
access: 'A labelled `nav` with an ordered list; the current item has `aria-current="page"`; a disabled ancestor has `aria-disabled`; the chevrons and the ⋯ glyph are hidden, and the folded middle reads its `label`.',
|
|
830
830
|
},
|
|
@@ -835,7 +835,7 @@ export const USAGE = [
|
|
|
835
835
|
summary: "Find anything across the product and go there.",
|
|
836
836
|
when: '"Use CommandMenu for a global, keyboard-first command palette that finds resources and runs actions across the app." (https://vercel.com/geist/command-menu) Ours: finding anything across the product and going there: an Agent, a customer, a page, a creation page (*New Agent*). Opened by ⌘K or the sidebar\'s Find. It goes to places and never runs a command.',
|
|
837
837
|
instead: '"For a menu opened from a visible trigger on a single resource, use `Menu`; for right-click on a row, use `ContextMenu`." (https://vercel.com/geist/command-menu) "Group items into pages (`Projects`, `Team Settings`) when a single flat list would exceed roughly 30 items or span multiple resource types." (https://vercel.com/geist/command-menu) Ours: those are `DropdownMenu` and `ContextMenu`. One record\'s commands are its `ActionMenu`. Search inside one collection is `CollectionSearch`. Choosing the page\'s scope is `ScopeSwitcher`. `CommandDialog` has no pages: its list is groups.',
|
|
838
|
-
behaviour: '"Bind `⌘K` on macOS and `Ctrl+K` elsewhere. Don’t reuse the binding for any in-page filter input; it’s a global shortcut." (https://vercel.com/geist/command-menu) "Open into the root page; preserve the search query when navigating back from a sub-page so the user’s typing isn’t lost." (https://vercel.com/geist/command-menu) "Trap focus inside the overlay while it’s open and return focus to the previously active element on close." (https://vercel.com/geist/command-menu) "Show a recent or default item set when the input is empty so the menu is useful before typing." (https://vercel.com/geist/command-menu) Ours: a solid surface, never glass; opens instantly, 640 px wide and 15% down the window; on a phone it is a bottom sheet, 640 px high with 8 px top corners and the list filling it. The input row stands above the list with a line under it, the field in large type with no icon and an *Esc* key at its end that closes the dialog and is the next stop after the field on Tab; the list scrolls past 436 px. The first row is highlighted on open. No match says so. Every row is 36 px with the pointer cursor, a group\'s heading is a muted 36 px row, a divider bleeds across the list. A row\'s shortcut is a `CommandShortcut` and a word or icon at its end is a `CommandSuffix`. Colour carries nothing: the highlighted row is a faint neutral wash with its text unchanged, group headings, suffixes and the empty line are muted, and no row, key or divider takes a hue.',
|
|
838
|
+
behaviour: '"Bind `⌘K` on macOS and `Ctrl+K` elsewhere. Don’t reuse the binding for any in-page filter input; it’s a global shortcut." (https://vercel.com/geist/command-menu) "Open into the root page; preserve the search query when navigating back from a sub-page so the user’s typing isn’t lost." (https://vercel.com/geist/command-menu) "Trap focus inside the overlay while it’s open and return focus to the previously active element on close." (https://vercel.com/geist/command-menu) "Show a recent or default item set when the input is empty so the menu is useful before typing." (https://vercel.com/geist/command-menu) Ours: a solid surface, never glass; opens instantly, 640 px wide and 15% down the window; on a phone it is a bottom sheet, 640 px high with 8 px top corners and the list filling it. The input row stands above the list with a line under it, the field in large type with no icon and an *Esc* key at its end that closes the dialog and is the next stop after the field on Tab; the list scrolls past 436 px. The first row is highlighted on open. No match says so. Every row is 36 px with the pointer cursor, a group\'s heading is a muted 36 px row, a divider bleeds across the list. A row\'s shortcut is a `CommandShortcut` and a word or icon at its end is a `CommandSuffix`. Colour carries nothing: the highlighted row is a faint neutral wash with its text unchanged, group headings, suffixes and the empty line are muted, and no row, key or divider takes a hue. `CommandDialog` draws `Command` itself, so a product never mounts one; its parts are `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem` and `CommandShortcut` (a `Kbd`).',
|
|
839
839
|
words: '"`<CommandMenu.Item>` children are Title Case verb phrases (`Deploy Project`, `Invite Team Member`). Avoid navigation phrasing like `Go to project page`; CommandMenu commands act, not browse." (https://vercel.com/geist/command-menu) "`CommandMenuPage.label` is Title Case and names the scope (`Projects`, `Team Settings`)." (https://vercel.com/geist/command-menu) "`CommandMenuPage.placeholder` is sentence case, action-oriented, and ends with `…` (`Search projects…`, `Type a command or search…`). Bare `Search…` is wrong because it doesn’t name the scope." (https://vercel.com/geist/command-menu) "`<CommandMenu.Group heading>` is Title Case, 1–2 words (`Actions`, `Recent`)." (https://vercel.com/geist/command-menu) Ours: groups are plural nouns in sentence case (*Agents*, *Go to*), one or two words. Items are names, or verb and noun for a creation page (*New Agent*), because the dialog goes to places. The placeholder names what is searched, never a bare "Search". No match quotes the query.',
|
|
840
840
|
access: '"Use `aria-live="polite"` on the result count so screen readers hear how the list narrows as the user types." (https://vercel.com/geist/command-menu) "Up/Down arrows move highlight, Enter activates, Escape closes. Backspace at an empty input pops the page stack." (https://vercel.com/geist/command-menu) "Display each item’s `keybind` as a `Kbd` slot so the shortcut is discoverable to sighted users and announced as a label to screen readers." (https://vercel.com/geist/command-menu) Ours: a dialog with a screen-reader `title` and `description`, both required; the *Esc* key is a button named by `closeLabel`, which is required; the field is a combobox over a listbox; the highlighted item is `aria-selected`. A polite live region reads `resultCount` as the list narrows, and it is required. `CommandInput` requires its `placeholder`, `CommandGroup` its `heading` and `CommandItem` its `onSelect`, so no field, group or row is left unnamed or inert; a row with no group stands outside a `CommandGroup`. A shortcut is a `CommandShortcut`, drawn as `kbd` elements. With no pages there is no stack for Backspace to pop.',
|
|
841
841
|
},
|
|
@@ -954,10 +954,10 @@ export const USAGE = [
|
|
|
954
954
|
module: "tabs",
|
|
955
955
|
family: "Navigation",
|
|
956
956
|
summary: "Panels of one surface that switch in place.",
|
|
957
|
-
when: '"Use Tabs to switch between sibling views inside a single page (`Overview`, `Logs`, `Settings`)." (https://vercel.com/geist/tabs) "Cap a Tabs row at 5–7 entries on desktop and 3–4 on mobile. Past that, consolidate or move secondary views behind a `Menu`." (https://vercel.com/geist/tabs) Ours: the cap is 7 on a desktop and 4 on a phone.',
|
|
957
|
+
when: '"Use Tabs to switch between sibling views inside a single page (`Overview`, `Logs`, `Settings`)." (https://vercel.com/geist/tabs) "Cap a Tabs row at 5–7 entries on desktop and 3–4 on mobile. Past that, consolidate or move secondary views behind a `Menu`." (https://vercel.com/geist/tabs) Ours: the cap is 7 on a desktop and 4 on a phone, for a row of line tabs.',
|
|
958
958
|
instead: '"For navigation between unrelated pages, use a sub-menu, not Tabs." (https://vercel.com/geist/tabs) "Tabs imply the views share scope, URL parent, and data model." (https://vercel.com/geist/tabs) Ours: views with their own URL are `TabsNav`. A choice that changes a value is a `RadioGroup` or `ToggleGroup`. A stable destination belongs in the `Sidebar`.',
|
|
959
|
-
behaviour: '"Selecting a tab is instant; don’t trigger network confirmation or toast on tab change." (https://vercel.com/geist/tabs) "Reflect the active tab in the URL (query param or path) so deep-links and refresh restore state." (https://vercel.com/geist/tabs) "Disable individual tabs only for permission or empty-state reasons; pair the disabled tab with a `tooltip` that names the constraint." (https://vercel.com/geist/tabs) Ours: the `tooltip` is `disabledReason`, shown on hover and focus and kept as the tab\'s description
|
|
960
|
-
words: '"`tabs[].title` is Title Case, 1–2 words, and names the destination noun (`Overview`, `Logs`, `Settings`). Verbs belong on buttons; `View Logs` is wrong on a tab." (https://vercel.com/geist/tabs) "`tabs[].tooltip` is sentence case and explains the constraint (`Only visible to project owners.`), not the tab’s purpose." (https://vercel.com/geist/tabs) "Don’t append a count to the title (`Logs (12)`); use a badge slot instead and drop the badge at zero." (https://vercel.com/geist/tabs) Ours: the title is sentence case (*Overview*, *Installations*), not Title Case. There is no badge slot: a count belongs to the list\'s footer. A reason is *Only owners see billing*. Italian (*Installazioni*) scrolls, never wraps or truncates.',
|
|
959
|
+
behaviour: '"Selecting a tab is instant; don’t trigger network confirmation or toast on tab change." (https://vercel.com/geist/tabs) "Reflect the active tab in the URL (query param or path) so deep-links and refresh restore state." (https://vercel.com/geist/tabs) "Disable individual tabs only for permission or empty-state reasons; pair the disabled tab with a `tooltip` that names the constraint." (https://vercel.com/geist/tabs) Ours: the `tooltip` is `disabledReason`, shown on hover and focus and kept as the tab\'s description; a tab is disabled only for a permission or an empty state, and `disabled` requires its `disabledReason`. One treatment among siblings, chosen through `variant`, never a page border: `line` (50 px high with 2 px sides, `body-sm` at 400, 24 px between tabs, a 2 px ink underline over the full-width row\'s 1 px rule) for a page\'s collections, `default` (a 32 px pill with 12 px sides, `label-sm` at 400, no gap between tabs, on the selection fill) for an object\'s sections. On a coarse or non-hover input a tab is at least 44 px high and wide, as a touch target. The page owns the chosen tab: `value` and `onValueChange` are required. There is no sliding underline. Colour is neutral only: the chosen tab is ink, the rest are muted until hover, and a disabled tab is never dimmed: in `line` it reads as an unchosen one, in `default` it sits on the selection fill with muted text, both with a not-allowed cursor.',
|
|
960
|
+
words: '"`tabs[].title` is Title Case, 1–2 words, and names the destination noun (`Overview`, `Logs`, `Settings`). Verbs belong on buttons; `View Logs` is wrong on a tab." (https://vercel.com/geist/tabs) "`tabs[].tooltip` is sentence case and explains the constraint (`Only visible to project owners.`), not the tab’s purpose." (https://vercel.com/geist/tabs) "Don’t append a count to the title (`Logs (12)`); use a badge slot instead and drop the badge at zero." (https://vercel.com/geist/tabs) Ours: the title is a noun with no icon, in sentence case (*Overview*, *Installations*), not Title Case. There is no badge slot: a count belongs to the list\'s footer. A reason is *Only owners see billing*. Italian (*Installazioni*) scrolls, never wraps or truncates.',
|
|
961
961
|
access: '"Keep Left/Right arrows moving focus across tabs and Enter/Space activating; don’t hijack with global shortcuts." (https://vercel.com/geist/tabs) "Label the tablist with `aria-label` when no visible heading sits above it (`aria-label="Sections"`)." (https://vercel.com/geist/tabs) "Maintain a visible focus ring on the active tab; don’t suppress focus styles for visual polish." (https://vercel.com/geist/tabs) Ours: the arrow keys move with one tab stop. `tablist`, `tab`, `tabpanel` with `aria-selected`. The row is named by `aria-label` or `aria-labelledby`, which the type requires; a disabled tab is `aria-disabled`.',
|
|
962
962
|
},
|
|
963
963
|
{
|
|
@@ -967,8 +967,8 @@ export const USAGE = [
|
|
|
967
967
|
summary: "Sibling views of one object, each with its own URL, under the top bar.",
|
|
968
968
|
when: "Sibling views of one object under the top bar, when each view has its own URL (an object's sections), with `TabsLink` for each.",
|
|
969
969
|
instead: "Panels that switch in place are `Tabs`. A stable destination belongs in the `Sidebar`. An object's ancestry is the `Breadcrumb`.",
|
|
970
|
-
behaviour: "\"Reflect the active tab in the URL (query param or path) so deep-links and refresh restore state.\" (https://vercel.com/geist/tabs) Ours: each view is its own URL, so a link carries it. Overflow scrolls sideways and brings the current link into view, and the row fades out over 32 px at each end where links stand beyond it. The current link takes the selection fill, or in `line`
|
|
971
|
-
words: '"`tabs[].title` is Title Case, 1–2 words, and names the destination noun (`Overview`, `Logs`, `Settings`). Verbs belong on buttons; `View Logs` is wrong on a tab." (https://vercel.com/geist/tabs) "Don’t append a count to the title (`Logs (12)`); use a badge slot instead and drop the badge at zero." (https://vercel.com/geist/tabs) Ours: the title is sentence case (*Overview*, *Installations*), not Title Case. There is no badge slot: a count belongs to the list\'s footer. Italian scrolls, never wraps or truncates.',
|
|
970
|
+
behaviour: "\"Reflect the active tab in the URL (query param or path) so deep-links and refresh restore state.\" (https://vercel.com/geist/tabs) Ours: each view is its own URL, so a link carries it. Overflow scrolls sideways and brings the current link into view, and the row fades out over 32 px at each end where links stand beyond it. The current link takes the selection fill, or in `line` a 2 px ink underline over the row's 1 px rule; the other links are muted until hover, with no hue. Sizes and spacing follow `Tabs`. The row holds at most 7 links; in a `band`, which a phone scrolls sideways instead of holding fewer, that is 7 at every width, as an Agent's six views do, and a `line` row follows the 7 and 4 cap of `Tabs`. The sub-pages of one object stand as the filled-pill row in a `band` under the top bar, as Vercel draws a deployment's tabs: the band is this component's, with `above` for the object's identity, so no page draws the border, the fill or the gutters around the row. The band keeps the page's own column and gutter, and the first link's words start at the page title's edge, its fill reaching into the gutter as Vercel's tabs do. The `line` row has its own rule and takes no band.",
|
|
971
|
+
words: '"`tabs[].title` is Title Case, 1–2 words, and names the destination noun (`Overview`, `Logs`, `Settings`). Verbs belong on buttons; `View Logs` is wrong on a tab." (https://vercel.com/geist/tabs) "Don’t append a count to the title (`Logs (12)`); use a badge slot instead and drop the badge at zero." (https://vercel.com/geist/tabs) Ours: the title is sentence case (*Overview*, *Installations*), not Title Case. There is no badge slot: a count belongs to the list\'s footer. A link has no icon. Italian scrolls, never wraps or truncates.',
|
|
972
972
|
access: '"Label the tablist with `aria-label` when no visible heading sits above it (`aria-label="Sections"`)." (https://vercel.com/geist/tabs) "Maintain a visible focus ring on the active tab; don’t suppress focus styles for visual polish." (https://vercel.com/geist/tabs) Ours: a `nav` of links named by `aria-label` or `aria-labelledby`, which the type requires, with `aria-current="page"` on the current one.',
|
|
973
973
|
},
|
|
974
974
|
{
|
|
@@ -976,9 +976,9 @@ export const USAGE = [
|
|
|
976
976
|
module: "text-link",
|
|
977
977
|
family: "Navigation",
|
|
978
978
|
summary: "The classes of a link that reads as text: `inline` inside a sentence, `plain` on its own, `quiet` for a muted way out.",
|
|
979
|
-
when: "A link inside a sentence (`inline`, blue and underlined)
|
|
980
|
-
instead: "A command is a `Button`; a destination drawn as a button is a link with `buttonVariants`. Going to a record from its row is the record's name in `RecordIdentity`.",
|
|
981
|
-
behaviour: "Classes only: the caller renders the real anchor or router link. `inline` thickens its underline on hover; `plain` and `quiet` underline on hover. Paint changes ride the shared paint transition. Colour: only `inline` takes the link blue, as Vercel puts blue on links and nowhere else in running text; `plain` keeps the colour of the text around it, and `quiet` is muted and turns ink on hover, which is how a card footer's or a list note's *Learn more* reads. A link that leaves the product ends in the arrow icon after its text, which `quiet` sizes.",
|
|
979
|
+
when: "A link inside a sentence, or offered alone as a section's way on (`inline`, blue and underlined at rest); a link that is itself the value, such as a record's name or a fact (`plain`); a secondary way out beside the content, such as a card header's *See everything*, which takes the same blue treatment as `inline`; or a muted way to somewhere such as a footer's *Learn more* (`quiet`).",
|
|
980
|
+
instead: "A command is a `Button`; a destination drawn as a button is a link with `buttonVariants`. Going to a record from its row is the record's name in `RecordIdentity`. Breadcrumb and back navigation are not text links: they keep their own quieter navigation treatment.",
|
|
981
|
+
behaviour: "Classes only: the caller renders the real anchor or router link. `inline` thickens its underline on hover; `plain` and `quiet` underline on hover. The underline sits 4 px under the text in every kind. Paint changes ride the shared paint transition. Colour: only `inline` takes the link blue, as Vercel puts blue on links and nowhere else in running text; `plain` keeps the colour of the text around it, and `quiet` is muted and turns ink on hover, which is how a card footer's or a list note's *Learn more* reads. A link that leaves the product ends in the arrow icon after its text, which `quiet` sizes.",
|
|
982
982
|
words: "The link's text names where it goes: *Open Billing*, *Read the privacy notice*; never *Click here* or a bare URL.",
|
|
983
983
|
access: "A real `a` with an `href`. A link that leaves the product says so in its text or carries the arrow with a name. Focus is the base layer's 2 px outline.",
|
|
984
984
|
},
|
|
@@ -987,9 +987,9 @@ export const USAGE = [
|
|
|
987
987
|
module: "agent-card",
|
|
988
988
|
family: "Data",
|
|
989
989
|
summary: "One Agent, or another pictured object, as a card in a gallery, as Vercel's Templates gallery draws a template.",
|
|
990
|
-
when: "A gallery of Agents or templates where each one is chosen by its picture and its job: the Agents to create from, the templates of a hub.",
|
|
991
|
-
instead: "A list of Agents to open is `AgentListRow`. An Agent's own page opens with `AgentProfileCard
|
|
992
|
-
behaviour: "Vercel's template card: the name over its job, cut after two lines, then the small meta and an optional status, and under them the preview tile, inset 32 px from the card's leading edge and running off its trailing and bottom edges, the picture (her orb at 96) in its middle on the recessed ground. The one action stands at the name's right, as a card's one header action does. A long name wraps inside the card. A text object, with no picture, ends after its words.",
|
|
990
|
+
when: "A gallery of Agents or templates where each one is chosen by its picture and its job: the Agents to create from, the templates of a hub. An Agent shown as an object is this one card shape wherever it appears, in the Portal's Library and in Nadia's gallery alike.",
|
|
991
|
+
instead: "A list of Agents to open is `AgentListRow`. An Agent's own page opens with `AgentProfileCard`, where the name is the page heading. A Library skill or plugin is a row of its collection (`CollectionPage`).",
|
|
992
|
+
behaviour: "Vercel's template card: the name over its job, cut after two lines, then the small meta and an optional status row, and under them the preview tile, inset 32 px from the card's leading edge and running off its trailing and bottom edges, the picture (her orb at 96) in its middle on the recessed ground. The one action stands at the name's right, as a card's one header action does. A long name wraps inside the card. A text object, with no picture, ends after its words.",
|
|
993
993
|
words: "The name as the Agent calls herself; the job in one line (*Writes quotes from the price list*); the meta as short facts joined by a middle dot (*6 skills · v1.2.0*); the action verb and noun (*Create Agent*).",
|
|
994
994
|
access: "The name is the card's heading text; the orb is decorative beside it; the action is a real button or link.",
|
|
995
995
|
},
|
|
@@ -1000,7 +1000,7 @@ export const USAGE = [
|
|
|
1000
1000
|
summary: "The surface of every card; on its own only a container inside a composition.",
|
|
1001
1001
|
when: '"For in-product cards and dashboard tiles, use `Card`; Book is too decorative for repeated UI rows." (https://vercel.com/geist/book) Ours: only inside a composition that owns the job: the object card of an `ObjectPage`, the setting card of a `SettingsSection`, a KPI, a chart, a table. A new kind of card is a new composition, never a `Card` assembled at a call site.',
|
|
1002
1002
|
instead: "\"Groups related form controls inside a bordered card with optional footer actions.\" (https://vercel.com/geist/fieldset) Ours: that card, a title and description, one control and a footer with help at the start and one action at the end, is `SettingsSection`. An object's card (a header bar with its name and commands, a body of facts, rows that open, a footer hint with one link elsewhere) is `ObjectPage`. The page's title row is never in a card. A row of a list is `Item`. A plane of brand colour is `BrandField`.",
|
|
1003
|
-
behaviour:
|
|
1003
|
+
behaviour: "\"One Note per concept.\" (https://vercel.com/geist/note) \"Stacking three Notes on a card means the page architecture, not the Note copy, is wrong.\" (https://vercel.com/geist/note) \"Use the Error component as a block surface when a section or page-level resource failed to load: a panel, a dashboard card, a route boundary.\" (https://vercel.com/geist/error) Ours: the corner and shadow come with `surface`: a raised card takes the larger corner. Never set a corner at the call site. `surface` defaults to `resting`, the resting height of \"Elevation & Depth\" in DESIGN.md. `CardHeader` lays out title, description and one `CardAction`, which stays at the title's right at every width when it is one control (a button, a badge, a sparkline, the three dots), as Vercel keeps a card's Add on its title row on a phone, and moves under the description in a header narrower than 28rem when it holds several (the header's own width, not the viewport's); `CardFooter` is a strip in the `recessed` role, one tone off the card body inside the card's edge, behind one seam in the border colour: `canvas-soft` below the white card in light, and `dark-canvas` below the resting body in dark; on the left one short muted line, only where it says something specific to that card (a limit such as *Please use 32 characters at maximum.*, what a value is for, the plan a feature needs or a link to learn more) and never one sentence every card repeats, on the right at most one button or link, the section's action. It is 56 px high whether or not it holds a button, and as high as its lines in a `size=\"sm\"` card, the compact card of a side column or a figure. The two kinds differ in what their header and footer hold: an object card's header holds the object's commands and its footer only a link, so it never saves; a setting card's header holds a title, a description and at most a switch that applies on change, and its footer holds the Save. A card is neutral; colour enters only through a status inside it or a section's `tone`.",
|
|
1004
1004
|
words: "The title is a noun phrase naming what the card holds; the description one sentence directly under it, only when it adds something.",
|
|
1005
1005
|
access: "A plain container; give it `render={<section />}` with a heading when it is a region of the page.",
|
|
1006
1006
|
},
|
|
@@ -1011,7 +1011,7 @@ export const USAGE = [
|
|
|
1011
1011
|
summary: "The row sibling cards sit in.",
|
|
1012
1012
|
when: "Two to five sibling cards side by side: the KPIs of an overview, a pair of charts.",
|
|
1013
1013
|
instead: 'A list of records is `Item` or a table. A single card needs no row. Geist\'s ruled `Grid`, with visible guide lines, crosses and cell padding, is not built: "Use Grid for two-dimensional cell-and-guide layouts in marketing pages, docs landing pages, and feature breakdowns where the rule lines and cell borders are part of the design." (https://vercel.com/geist/grid) "For plain n-column responsive content (cards, lists), use Tailwind `grid grid-cols-*` utilities directly; Grid is overkill when guides aren’t visible." (https://vercel.com/geist/grid) Ours: this is that plain n-column case.',
|
|
1014
|
-
behaviour: '"Set `columns` and `rows` at all three breakpoints so cells reflow predictably between mobile, tablet, and desktop." (https://vercel.com/geist/grid) Ours: one column below `from`, then `columns` columns, 16 px apart, with no `rows`; `auto` gives each card an equal share, and `5` sets two by two with the fifth across, then three over two from `lg
|
|
1014
|
+
behaviour: '"Set `columns` and `rows` at all three breakpoints so cells reflow predictably between mobile, tablet, and desktop." (https://vercel.com/geist/grid) Ours: one column below `from`, then `columns` columns, 16 px apart, with no `rows`; `auto` gives each card an equal share, and `5` sets two by two with the fifth across, then three over two from `lg` (the pair sharing the width of the three), then all five in one row from `xl` (1280 px), as OpenRouter\'s Activity sets its five tiles, never leaving one card alone beside a hole. Below `from`, a phone included, the five are one column; with `from="lg"` there is no two by two step. Each card fills its cell, so the bottom edge is even. The row is neutral; it tints nothing.',
|
|
1015
1015
|
words: "None of its own.",
|
|
1016
1016
|
access: "A layout container; reading order is source order.",
|
|
1017
1017
|
},
|
|
@@ -1020,10 +1020,10 @@ export const USAGE = [
|
|
|
1020
1020
|
module: "code-block",
|
|
1021
1021
|
family: "Data",
|
|
1022
1022
|
summary: "Several lines of exact text to read, copy or point at line by line.",
|
|
1023
|
-
when: "“Use `<CodeBlock>` for multi-line, syntax-highlighted source the reader will scan or paste.” (https://vercel.com/geist/code-block) Ours: several lines a person reads or copies exactly: a configuration, static JSON, a command sequence, a file with the lines under discussion marked. There is no syntax colour yet.",
|
|
1023
|
+
when: "“Use `<CodeBlock>` for multi-line, syntax-highlighted source the reader will scan or paste.” (https://vercel.com/geist/code-block) Ours: several lines a person reads or copies exactly: a configuration, static JSON, a command sequence, a file with the lines under discussion marked. There is no syntax colour yet. Static JSON is serialized by the caller or the adapter before it reaches `code`; the block shows the text as given.",
|
|
1024
1024
|
instead: "“For a single inline token (env var, function name, file path), use `Inline Code`.” (https://vercel.com/geist/code-block) “Pick `Snippet` for a copy-to-clipboard shell command or one-line key reveal; it ships with the prompt glyph and copy affordance built in.” (https://vercel.com/geist/code-block) Ours: a token inside a sentence is inline code. One short value to copy is `TextWithCopyButton`. A before and after to compute is `FileDiff`. Code in a conversation is Nadia's `MarkdownText`.",
|
|
1025
|
-
behaviour: "“Highlight only the lines under discussion. A block where every line is highlighted reads the same as no highlight at all.” (https://vercel.com/geist/code-block) “Mark added or removed lines with the `diff` syntax or the dedicated added/removed props. Faking them with `// added` comments breaks copy-paste.” (https://vercel.com/geist/code-block) “Show the filename header when the snippet has a paste destination (`app/page.tsx`, `vercel.json`). Omit it for ephemeral examples.” (https://vercel.com/geist/code-block) “You can link to lines. Just press on any line number.” (https://vercel.com/geist/code-block) “Use the `tabs` prop for a tabbed language switcher instead of the default select.” (https://vercel.com/geist/code-block) “Always pass a `language`/`syntax` (`tsx`, `bash`, `json`, `diff`). Highlighting is the primary reason to choose `<CodeBlock>` over a plain `<pre>`.” (https://vercel.com/geist/code-block) Ours: read-only, no wrapping, 20 px rows, each line numbered with a number that links to it. Colour sits on the row and its 2 px bar at the start, never on the text or the numbers: `highlightedLinesNumbers` (info), `addedLinesNumbers` (success) and `removedLinesNumbers` (destructive); a line carries one mark, and the line a link points at takes the warning fill. A mark is fill and bar with no glyph, announced by the line's name. `hideLineNumbers` is for a few lines where numbers add nothing. The header (40 px, muted fill, the file's icon and name) exists only with a `filename`, a `switcher` or `tabs`. `tabs` draw as a row above the header, a `switcher` (a select) sits in the header before the copy button; never both. In the header the filename and the copy mark are muted; without a header the copy button floats 15 px from the top right corner in the full text colour and shows while the block is hovered or something in it has focus (always on a touch screen). A line number links to its line for a pointer and is out of the tab order. The file icon follows the filename's extension. There is no syntax colour and no `language` until a highlighter is adopted, and no Open in v0 action.",
|
|
1026
|
-
words: "“Snippets stay runnable. Don’t paraphrase real code into pseudo-syntax, and don’t prepend `$` to shell commands. `<Snippet>` already renders the prompt, so `text=\"$ vercel deploy\"` doubles to `$ $ vercel deploy`.” (https://vercel.com/geist/code-block) “Keep prose around the block in sentence case and put backticks around CLI flags inline (`` `--prebuilt` ``).” (https://vercel.com/geist/code-block) Ours: the real filename,
|
|
1025
|
+
behaviour: "“Highlight only the lines under discussion. A block where every line is highlighted reads the same as no highlight at all.” (https://vercel.com/geist/code-block) “Mark added or removed lines with the `diff` syntax or the dedicated added/removed props. Faking them with `// added` comments breaks copy-paste.” (https://vercel.com/geist/code-block) “Show the filename header when the snippet has a paste destination (`app/page.tsx`, `vercel.json`). Omit it for ephemeral examples.” (https://vercel.com/geist/code-block) “You can link to lines. Just press on any line number.” (https://vercel.com/geist/code-block) “Use the `tabs` prop for a tabbed language switcher instead of the default select.” (https://vercel.com/geist/code-block) “Always pass a `language`/`syntax` (`tsx`, `bash`, `json`, `diff`). Highlighting is the primary reason to choose `<CodeBlock>` over a plain `<pre>`.” (https://vercel.com/geist/code-block) Ours: read-only, no wrapping, 20 px rows, each line numbered with a number that links to it. Colour sits on the row and its 2 px bar at the start, never on the text or the numbers: `highlightedLinesNumbers` (info), `addedLinesNumbers` (success) and `removedLinesNumbers` (destructive); a line carries one mark, and the line a link points at takes the warning fill. A mark is fill and bar with no glyph, announced by the line's name. `hideLineNumbers` is for a few lines where numbers add nothing; the marks still show and are announced. The header (40 px, muted fill, the file's icon and name) exists only with a `filename`, a `switcher` or `tabs`. `tabs` draw as a row above the header, a `switcher` (a select) sits in the header before the copy button; never both. In the header the filename and the copy mark are muted; without a header the copy button floats 15 px from the top right corner in the full text colour and shows while the block is hovered or something in it has focus (always on a touch screen). The copy button is `CopyButton` at `icon-sm`, 32 px, not Geist's 28 px: `CopyButton` has no 28 px size. A line number links to its line for a pointer and is out of the tab order. The file icon follows the filename's extension. There is no syntax colour and no `language` until a highlighter is adopted, and no Open in v0 action.",
|
|
1026
|
+
words: "“Snippets stay runnable. Don’t paraphrase real code into pseudo-syntax, and don’t prepend `$` to shell commands. `<Snippet>` already renders the prompt, so `text=\"$ vercel deploy\"` doubles to `$ $ vercel deploy`.” (https://vercel.com/geist/code-block) “Keep prose around the block in sentence case and put backticks around CLI flags inline (`` `--prebuilt` ``).” (https://vercel.com/geist/code-block) Ours: the real filename, and only when the code has a place to be pasted into, otherwise none. A switcher's `label` names what it switches (*Language*), and its options are the languages by their common names (*JSON*). `label` names the code (*MCP configuration*). `lineLabel` says a marked line and its mark in the catalogue's words (*Line 3, added*, *Riga 3, aggiunta*); it is only called for a highlighted, added or removed line. `copyLabel` names what is copied (*Copy mcp.json*); `copiedLabel` (*Copied*); `copyFailedLabel` (*Couldn't copy*). `label` and the three copy words are required, and `lineLabel` with any marked line.",
|
|
1027
1027
|
access: "Ours: the code is a focusable, scrollable region named by `label`. Line numbers are hidden from assistive technology and are not tab stops; a highlighted, added or removed line carries `lineLabel`'s words as hidden text, with the numbers shown or not. The copy button names its target and announces the result.",
|
|
1028
1028
|
},
|
|
1029
1029
|
{
|
|
@@ -1033,8 +1033,8 @@ export const USAGE = [
|
|
|
1033
1033
|
summary: "Records compared across columns: the same facts for every record, read down a column and across a row, inside their `TableCard`.",
|
|
1034
1034
|
when: "“Use `<Table>` for tabular data where rows share the same shape and at least one column is sortable or comparable across rows.” (https://vercel.com/geist/table) Ours: the person compares: usage per Agent, statements, invoices, customers with their partner and count. The columns are fixed, each has a heading, and at least one is a figure or can be sorted. There are three kinds of list. Objects a person opens (a project, a deployment, a member, an integration) are entity rows: a `RecordList`. Values edited in place (a domain, an environment variable) are entity rows that expand into their form: a `RecordList`'s `editor`. Records compared across columns are this table.",
|
|
1035
1035
|
instead: "“For a row of descriptive content paired with a single action (membership row, integration row), use `Entity` instead.” (https://vercel.com/geist/table) “For a key/value metadata block on a detail page, use `Description`, not a two-column table.” (https://vercel.com/geist/table) Ours: Geist's `Entity` is a `RecordList` row. A registry of entities whose columns `RecordList` cannot hold, more than three facts beside a filter bar the product owns, as Vercel's Deployments list, is this table with `rows=\"entities\"`, which draws the same rows: headings for assistive technology only, 72 px rows, a `RecordIdentity` in the identity column. Its `Description` is `ObjectFacts`. A list on a page of its own is a `CollectionPage`. A descriptive list is `ItemGroup`.",
|
|
1036
|
-
behaviour: "Every column has a `header`; a column without one does not compile. The headings are always visible, medium weight in the cells' body grey,
|
|
1037
|
-
words: '“Column headers (`<Table.Head>`) are Title Case nouns or noun phrases: `Last Used`, `Requests (7d)`, `Created`, `Status`. Never sentences.” (https://vercel.com/geist/table) “Render `—` in cells where a value is unknown or not applicable. Don’t substitute `N/A`, `null`, or an empty string.” (https://vercel.com/geist/table) “Use the canonical short relative-time form in cells (`2m ago`, `5h ago`); switch to `Mar 14, 2026` past 7 days. See `Relative Time Card`.” (https://vercel.com/geist/table) “Pagination labels are `Previous` and `Next`. Page-count copy reads `Page 2 of 7` or `21–40 of 142` with an en-dash inside the range.” (https://vercel.com/geist/table) Ours: headings are sentence case nouns with their unit, "Cost (€)", "Tokens", where Geist writes Title Case. The words of a time and of Previous and Next are the product\'s catalogue, in both languages. The footer is a count or the paging; the summary row is a labelled total. Italian headings run a third longer and never wrap.',
|
|
1036
|
+
behaviour: "Every column has a `header`; a column without one does not compile. The headings are always visible, medium weight in the cells' body grey, 48 px high, with 12 px of room under the labels and then the rule, so the first row starts at the rule like every other; rows are 40 px. Each column is as wide as its definition's `size`, in pixels, and the columns without one share the rest, so a table gives a size to every column but the one that takes the spare width; a value too long for its column ends in an ellipsis, whole in its title (a record's name carries its own), unless the column `wraps`; a column marked `identifier` holds ids or paths and is cut in the middle instead. The last column trails unless it `wraps` or is `centered`; a `centered` column's heading and values sit in the middle of it. Records are divided by a 1 px rule, none after the last. The first and last columns stand in from the card's edges by the card's spacing, so the first lines up with the title. Records of several kinds stand in one table under `group` headings (Vercel's Usage page: *Delivery network*, *Functions*): a heading row that spans the columns, in ink, medium text, 36 px high with the same rule under it, one per group in the order the groups first appear, never a second table; a heading is the group's name and carries no figure of its own. A row that opens a record takes `accent` on hover, and any row on focus within and while its menu is open; a row that opens nothing never reacts to hover. A selected row takes `muted`; headers and footers never highlight and carry no fill. A row never scrolls sideways: on a card narrower than its columns (the sized ones and 160 px for each without a size) the row keeps its name and its `primaryColumns`, and every other column leaves it, its value joining the one muted line under the name, joined by a middle dot and ending in an ellipsis (a `Tag` reads as its text there, and a `wraps` column's prose wraps the line whole), or `dropped` when its `compact` says so, as Vercel's lists read on a phone: no labels, no second line; an `optional` column's empty value is left out of the line. A table whose every column is in `primaryColumns`, a settings matrix, lays out by its content there instead, so the name wraps beside the columns. A document's table, such as a legal page's, is read whole instead: its columns take the `wraps` option, so their words wrap and the row grows, and it keeps its header row; in the compact layout every cell but the identity wraps between words. A bounded page's paging stands in the card's footer strip at its trailing edge, opposite `footer`, which stands at the leading edge: its `status` (\"Page 2 of 7\") before Previous and Next, which are the product's router links (`previousLink`, `nextLink`) when each page is its own address, and buttons otherwise; the one that leads nowhere stays, disabled. A note about the whole table is `footerNote`, in a strip of its own under that one: one muted line, an optional *Learn more* after it at the left and one secondary action at the right (Vercel's deployments list ends \"Deployment retention is enabled. Learn more\" with *View recently deleted*); it is the one place a table says a sentence. Colour is neutral: headings and cells in the body grey, no hue, except a status badge or a tone the cell itself carries. “When the underlying list is empty (filter cleared, never created), render `Empty State` outside the table rather than an empty `<Table.Body>`.” (https://vercel.com/geist/table) “After an async filter change, wrap the region in `aria-live=\"polite\"` so screen readers announce the new state.” (https://vercel.com/geist/empty-state) Ours: empty sits outside the body, in a polite live region that stays in place so a filter that empties the table is announced. “Sortable column headers are buttons. The visible label stays Title Case; the sort-direction arrow is decorative and the button announces the next sort state to assistive tech.” (https://vercel.com/geist/table) Ours: the order has one owner, the app's `sorting`, which the URL holds, so no heading is a button and every page of a paged list follows one order. “Apply `tabular-nums` (or Geist Mono) to numeric columns so digits align across rows for comparison.” (https://vercel.com/geist/table) Ours: a numeric column trails in tabular figures.",
|
|
1037
|
+
words: '“Column headers (`<Table.Head>`) are Title Case nouns or noun phrases: `Last Used`, `Requests (7d)`, `Created`, `Status`. Never sentences.” (https://vercel.com/geist/table) “Render `—` in cells where a value is unknown or not applicable. Don’t substitute `N/A`, `null`, or an empty string.” (https://vercel.com/geist/table) “Use the canonical short relative-time form in cells (`2m ago`, `5h ago`); switch to `Mar 14, 2026` past 7 days. See `Relative Time Card`.” (https://vercel.com/geist/table) “Pagination labels are `Previous` and `Next`. Page-count copy reads `Page 2 of 7` or `21–40 of 142` with an en-dash inside the range.” (https://vercel.com/geist/table) Ours: headings are sentence case nouns with their unit, "Cost (€)", "Tokens", where Geist writes Title Case. The words of a time and of Previous and Next are the product\'s catalogue, in both languages. The footer is a count or the paging; totals and paging each stand in a footer zone of their own, the paging at the trailing edge; the summary row is a labelled total. Italian headings run a third longer and never wrap.',
|
|
1038
1038
|
access: 'A native table named by `tableLabel`, which is required; `th scope="col"`; the record\'s name is the row\'s one link. A sorted column\'s heading carries `aria-sort`. Ours: a missing value is a hidden "—" with screen-reader text, never blank.',
|
|
1039
1039
|
},
|
|
1040
1040
|
{
|
|
@@ -1065,8 +1065,8 @@ export const USAGE = [
|
|
|
1065
1065
|
family: "Data",
|
|
1066
1066
|
summary: "A file's before and after, line by line.",
|
|
1067
1067
|
when: "Showing what changed in a file an Agent edited, or in a configuration before it is saved.",
|
|
1068
|
-
instead: "A file as it stands is `CodeBlock`. A count of changed lines is `DiffCount`.",
|
|
1069
|
-
behaviour: "Added lines on the success fill with a plus, removed on the destructive fill with a true minus; unchanged lines beyond `context` fold into one marker.",
|
|
1068
|
+
instead: "A file as it stands is `CodeBlock`. A count of changed lines is `DiffCount`. It is not a general code editor or a JSON viewer.",
|
|
1069
|
+
behaviour: "Added lines on the success fill with a plus, removed on the destructive fill with a true minus; unchanged lines beyond `context` fold into one marker. The source is shown exactly as given, whitespace kept. It names no file: the conversation or tool that owns the change stays the owner and names it.",
|
|
1070
1070
|
words: "The added and removed labels and the folded-lines count come from the product's catalogue.",
|
|
1071
1071
|
access: "Each changed line carries its added or removed label in screen-reader text; the plus and minus marks are hidden.",
|
|
1072
1072
|
},
|
|
@@ -1075,9 +1075,9 @@ export const USAGE = [
|
|
|
1075
1075
|
module: "gauge",
|
|
1076
1076
|
family: "Data",
|
|
1077
1077
|
summary: "A ring that shows one ratio out of 100.",
|
|
1078
|
-
when: '"A 0–100 ratio against a fixed maximum where the comparison is the point, like quota usage, build cache hit rate, uptime, or billing-period consumption." (https://vercel.com/geist/gauge) Ours: `size` follows the weight of the figure beside it.',
|
|
1079
|
-
instead: '"For determinate task progress with a known total (uploads, multi-step setup), use `Progress`." (https://vercel.com/geist/gauge) "For binary or enumerated state, use `Status Dot` for deployments or `Badge` for everything else." (https://vercel.com/geist/gauge) Ours: those are `StatusDot` and `StatusBadge`. A single figure on its own is a `KpiCard`. A wait of unknown length is the waiting line; the gauge has no indeterminate form. Two shares of one whole are a chart.',
|
|
1080
|
-
behaviour: '"Use `arc="equal"` for true ratios so 50% reads as exactly half." (https://vercel.com/geist/gauge) "Use the default `primary` arc for single-percentage usage where the filled portion is the story." (https://vercel.com/geist/gauge) "When using the gauge to display a ratio, use the `equal` arc priority to make both arcs are equally sized." (https://vercel.com/geist/gauge) Ours: there is no `arc` prop; the gauge always draws the primary arc, from the top, clockwise, with round ends and a small gap before the neutral track; at 0 or 100 there is no gap. Colour sits on the arc alone: the track stays the neutral line colour, and the number and icon stay neutral. "Threshold colors should match the same numeric breakpoints used elsewhere in the product (`>=80%` warning, `>=95%` error). Don’t invent gauge-only thresholds." (https://vercel.com/geist/gauge) Ours: `tone` is that threshold, never derived from the value. "Pair `indeterminate` with explanatory copy nearby (`Calculating usage…`) so the user knows the value is loading, not zero." (https://vercel.com/geist/gauge) Ours: there is no `indeterminate`; a value still loading is the waiting line. The smallest size has room only for an icon. The arc moves on `open`, still under reduced motion.',
|
|
1078
|
+
when: '"A 0–100 ratio against a fixed maximum where the comparison is the point, like quota usage, build cache hit rate, uptime, or billing-period consumption." (https://vercel.com/geist/gauge) Ours: one ratio out of 100 on Base UI `Meter`. `size` follows the weight of the figure beside it: `xs` (20 px), `sm` (32), `md` (64) and `lg` (128). The centre holds the number or an icon.',
|
|
1079
|
+
instead: '"For determinate task progress with a known total (uploads, multi-step setup), use `Progress`." (https://vercel.com/geist/gauge) "For binary or enumerated state, use `Status Dot` for deployments or `Badge` for everything else." (https://vercel.com/geist/gauge) Ours: those are `StatusDot` and `StatusBadge`. A single figure on its own is a `KpiCard`. A wait of unknown length is the waiting line; the gauge has no indeterminate form. Two shares of one whole are a chart: the gauge is a control for one ratio, never a chart.',
|
|
1080
|
+
behaviour: '"Use `arc="equal"` for true ratios so 50% reads as exactly half." (https://vercel.com/geist/gauge) "Use the default `primary` arc for single-percentage usage where the filled portion is the story." (https://vercel.com/geist/gauge) "When using the gauge to display a ratio, use the `equal` arc priority to make both arcs are equally sized." (https://vercel.com/geist/gauge) Ours: there is no `arc` prop and so no two-arc `equal` ratio; the gauge always draws the primary arc, from the top, clockwise, with round ends and a small gap before the neutral track; at 0 or 100 there is no gap. Colour sits on the arc alone: the track stays the neutral line colour, and the number and icon stay neutral. "Threshold colors should match the same numeric breakpoints used elsewhere in the product (`>=80%` warning, `>=95%` error). Don’t invent gauge-only thresholds." (https://vercel.com/geist/gauge) Ours: `tone` is that threshold, never derived from the value, and takes `Progress`\'s tones; there is no `colors` map. "Pair `indeterminate` with explanatory copy nearby (`Calculating usage…`) so the user knows the value is loading, not zero." (https://vercel.com/geist/gauge) Ours: there is no `indeterminate`; a value still loading is the waiting line. The smallest size has room only for an icon. The arc moves on `open`, still under reduced motion.',
|
|
1081
1081
|
words: '"Always pair the gauge with an adjacent label or `Tooltip` naming what the number represents (`Build Cache Hit Rate`). The gauge alone is not self-describing." (https://vercel.com/geist/gauge) "Don’t put units inside `children`; the label carries the unit (`Uptime · 99.97%`). `children` is reserved for an icon overlay." (https://vercel.com/geist/gauge) "When `showValue` is on, the rendered number is the value only. Never inject a `%` or unit string into the prop." (https://vercel.com/geist/gauge) Ours: sentence case, *Cache hit rate · 99.97%*. `showValue` prints the number; `children` is only an icon.',
|
|
1082
1082
|
access: '"The component sets `role="progressbar"` with `aria-valuemin`, `aria-valuemax`, and `aria-valuenow`. Don’t override those." (https://vercel.com/geist/gauge) Ours: the role is `meter`, with the same three values. "The adjacent label is the gauge’s accessible name. Tie it with `aria-labelledby` on the gauge wrapper so screen readers read “Uptime, 99 percent.”" (https://vercel.com/geist/gauge) Ours: it always has a name, `aria-label` or `aria-labelledby` pointing at the adjacent label, and the type asks for one. "Don’t use color to encode the threshold without redundant text; pair the warning tint with copy below or in the Tooltip." (https://vercel.com/geist/gauge)',
|
|
1083
1083
|
},
|
|
@@ -1088,7 +1088,7 @@ export const USAGE = [
|
|
|
1088
1088
|
summary: "A row said in a sentence: media, a title, one line of description, trailing actions.",
|
|
1089
1089
|
when: "“Use `<Entity>` for a row of descriptive content paired with one or two controls (member rows, integration rows, domain rows).” (https://vercel.com/geist/entity) Ours: things best said in a sentence, with one or two controls and no columns: a connected account, a session, a notification preference, a Library shelf row, an activity row. Media, a title over its description, trailing controls.",
|
|
1090
1090
|
instead: "“For tabular data with sortable columns and shared row shape, use `Table` instead.” (https://vercel.com/geist/entity) “For a static key/value metadata block on a detail page, use `Description`.” (https://vercel.com/geist/entity) Ours: Geist's `Table` is a `DataTable` and its `Description` is `ObjectFacts`. An object a person opens, with a mark, facts and a status, is a `RecordList` row; the same row edited in place is a `RecordList` with an `editor`. A page whose job is one collection is `CollectionPage`. Agents in a gallery are `AgentListRow`; Library entries `CatalogEntry`.",
|
|
1091
|
-
behaviour: 'Every row has a title over its one muted description line; only a message row read whole, or a second content beside the first, may be description only. Media, content and actions stand 16 px apart, centred on one another, in a 72 px row of 16 px padding;
|
|
1091
|
+
behaviour: 'A list of descriptive rows is an `ItemGroup` of `Item`s, never table semantics. Every row has a title over its one muted description line; only a message row read whole, or a second content beside the first, may be description only. Media, content and actions stand 16 px apart, centred on one another, in a 72 px row of 16 px padding; a 600 title stands over one 20 px description with no gap, and each holds one line and ends in an ellipsis (`whole` lets a message wrap). Inside a card the group is `divided`: no gaps, one hairline between items. An item that is an `a`, a `button` or a `label` takes the `accent` hover and is one hit target; a plain item never reacts to hover; nested controls stay independent. A button in the row is `size="sm"` (32 px). A row a person picks starts with `ItemSelect` and is drawn `render={<label />}`, so the whole row ticks its box and has no other control. A second content beside the first takes only the room its words need. Colour is neutral: the title in ink, the description and any trailing words muted, no hue. “The right column holds at most one or two controls. If the row needs more, move secondary actions into a `Dots Menu`.” (https://vercel.com/geist/entity) Ours: the third control goes in the row\'s three dots. “Render the Skeleton variant (`entity-with-skeleton`) during load instead of an empty row, and swap to real content once data resolves.” (https://vercel.com/geist/entity) Ours: `ItemSkeleton` stands in a `SkeletonRegion`, still, with no shimmer.',
|
|
1092
1092
|
words: "“Lead the left column with a scannable identifier: an `Avatar` or icon, a Title Case label, then sentence-case secondary metadata (`Member since Mar 14, 2026`).” (https://vercel.com/geist/entity) “Keep right-column buttons Verb + Noun (`Remove Member`, `Resend Invite`). Bare verbs like `Remove` or `Confirm` lose context once the row scrolls offscreen.” (https://vercel.com/geist/entity) Ours: the title is the name, or what happened as a short sentence (*Nadia filed 12 invoices*), and the description one sentence, in sentence case: *Remove member*, never *Remove*.",
|
|
1093
1093
|
access: "Draw each row of a group `render={<li />}`, so the group is a list of list items. A linked item's text is its name; image media is decorative beside the title. “For multi-select rows, the leading `Checkbox` carries `aria-label=\"Select {entity name}\"` so the row is selectable without relying on the visual label.” (https://vercel.com/geist/entity) Ours: the box of `ItemSelect` is named *Select {name}* in the product's words.",
|
|
1094
1094
|
},
|
|
@@ -1099,7 +1099,7 @@ export const USAGE = [
|
|
|
1099
1099
|
summary: "One key, or a modifier with its key, of a shortcut beside the command it triggers.",
|
|
1100
1100
|
when: '"Use `<Kbd>` for shortcut hints inside prose, menu items, and button suffixes." "Use `small` inside dense surfaces like menu rows, command-bar items, or table cells where the default size crowds adjacent text." (https://vercel.com/geist/keyboard-input) Ours: `small` is `size="sm"`. Find with ⌘K, the sidebar with ⌘B. Only shortcuts actually bound are shown.',
|
|
1101
1101
|
instead: '"In long-form docs that narrate a shortcut, write `the ⌘ K shortcut` directly so the page renders the same when copied to plain text." (https://vercel.com/geist/keyboard-input) Ours: a key a person types into is an `Input`; a code a person types is `InputOTP`.',
|
|
1102
|
-
behaviour: '"Pass modifiers via boolean props (`meta`, `shift`, `alt`, `ctrl`)." "The component swaps `⌘` for `Ctrl` on Windows and Linux, so hard-coding `<Kbd>Cmd+K</Kbd>` ships the wrong glyph to half your users." (https://vercel.com/geist/keyboard-input) Ours: not interactive. ⌘ ⇧ ⌥ ⌃ on Apple systems, *Ctrl* *Shift* *Alt* elsewhere, with `meta` becoming *Ctrl*. The server draws the Apple form and the browser corrects it after hydration. Several flags make one key: `meta shift`. It is 24 px high (`size="sm"`: 20 px) in the interface\'s sans type, 14 px (12 px), with no gap between Apple glyphs. Colour: one neutral key in the primary ink on the page surface with a hairline ring, no hue; inside a tooltip it drops the ring and takes a fill one step off the tooltip\'s inverted surface,
|
|
1102
|
+
behaviour: '"Pass modifiers via boolean props (`meta`, `shift`, `alt`, `ctrl`)." "The component swaps `⌘` for `Ctrl` on Windows and Linux, so hard-coding `<Kbd>Cmd+K</Kbd>` ships the wrong glyph to half your users." (https://vercel.com/geist/keyboard-input) Ours: not interactive. ⌘ ⇧ ⌥ ⌃ on Apple systems, *Ctrl* *Shift* *Alt* elsewhere, with `meta` becoming *Ctrl*. The server draws the Apple form and the browser corrects it after hydration. Several flags make one key: `meta shift`; there is no `KbdGroup`. `CommandShortcut` draws this same `Kbd`. It is 24 px high (`size="sm"`: 20 px) in the interface\'s sans type, 14 px (12 px), with no gap between Apple glyphs. Colour: one neutral key in the primary ink on the page surface with a hairline ring, no hue; inside a tooltip it drops the ring and takes a fill one step off the tooltip\'s inverted surface, and the tooltip draws it 20 px high whatever its `size`.',
|
|
1103
1103
|
words: '"`children` is one key, digit, or named key (`K`, `7`, `Enter`, `Esc`)." "Don’t lowercase it, don’t spell out modifiers inside, and don’t pack a sentence into the element." "Punctuation lives outside the element: `Press <Kbd meta>K</Kbd> to open the command menu.`" "Period, comma, and `or` separators stay in surrounding prose so screen readers don’t announce them as keys." (https://vercel.com/geist/keyboard-input) Ours: children is a capital letter, a digit, a mark or a named key from `KBD_KEYS`.',
|
|
1104
1104
|
access: "Ours: a `kbd` element, read in order; the command's own name carries the meaning.",
|
|
1105
1105
|
},
|
|
@@ -1121,7 +1121,7 @@ export const USAGE = [
|
|
|
1121
1121
|
summary: "An object's facts as labelled values in a grid, inside one of its sections.",
|
|
1122
1122
|
when: "“Use `<Description>` for definition-list metadata: a short Title Case key paired with a single value (`Last Deployed`, `Region`, `Plan`). For inline help under a form field, use the input’s helper-text slot instead.” (https://vercel.com/geist/description) Ours: read-only values that describe an object, inside an ObjectSection: a company's legal name, VAT number and address. One fact alone is a description: a short label over its value.",
|
|
1123
1123
|
instead: "Facts under the object's name belong to ObjectPage's `facts`; values a person changes are SettingsFields in a SettingsSection; figures compared across records are a DataTable.",
|
|
1124
|
-
behaviour: "Two columns from `sm`, one below; `compact` tightens the gaps for a dense card; a `block` fact takes the full width and keeps its line breaks. A fact is its label (14/14 px, muted) over its value (14/16 px, medium, ink) 8 px apart in a 16 px label line, 40 px in all; every fact has both. `right` aligns a fact to the trailing edge, and `ellipsis` cuts the label and the value to one line,
|
|
1124
|
+
behaviour: "Two columns from `sm`, one below; `compact` tightens the gaps for a dense card; a `block` fact takes the full width and keeps its line breaks. A fact is its label (14/14 px, muted) over its value (14/16 px, medium, ink) 8 px apart in a 16 px label line, 40 px in all; every fact has both. `right` aligns a fact to the trailing edge, and `ellipsis` cuts the label and the value to one line, and a string value is whole in its title; a `block` fact cannot take it. Neutral throughout. “Pass `tooltip` only when the title alone is ambiguous and a one-sentence definition resolves it. The tooltip text is sentence case and ends with a period.” (https://vercel.com/geist/description) Ours: the `tooltip` stands behind an info icon after the label that a keyboard reaches; the value never carries it.",
|
|
1125
1125
|
words: '“Title slot is Title Case noun (`Last Deployed`, `Build Duration`); content is sentence case unless the value is a literal identifier, ID, or timestamp that should be preserved verbatim.” (https://vercel.com/geist/description) Ours: a label is a short sentence case noun ("Billing email"), not Geist\'s Title Case; a value carries no unit the label already says.',
|
|
1126
1126
|
access: "“Geist renders `<dl>`/`<dt>`/`<dd>` so screen readers announce each key/value pair as a definition. Don’t wrap the component in extra paragraphs that break the list semantics.” (https://vercel.com/geist/description) “Don’t put interactive controls in the title slot. Buttons, menus, and links belong in the content (`<dd>`) or in the parent layout, not the label (`<dt>`).” (https://vercel.com/geist/description) Ours: each label is its value's term and names it; only the tooltip's own trigger sits in a label.",
|
|
1127
1127
|
},
|
|
@@ -1132,7 +1132,7 @@ export const USAGE = [
|
|
|
1132
1132
|
summary: "The short code two screens show at once so a person can tell they are talking to each other, such as the four digits Nadia and Alveare Server both show while a Nadia asks to join.",
|
|
1133
1133
|
when: "Pairing two devices or apps a person can see side by side: the four digits Nadia and Alveare Server both show while a Nadia asks to join.",
|
|
1134
1134
|
instead: "A code a person types is `InputOTP`. A link another device scans is `QrCode`.",
|
|
1135
|
-
behaviour: "Each digit in its own cell, large enough to compare from across a desk. A leading zero is a digit.",
|
|
1135
|
+
behaviour: "Each digit in its own cell at `heading-lg` with tabular figures, large enough to compare from across a desk. A leading zero is a digit. It only shows a code and takes no input.",
|
|
1136
1136
|
words: "The label says what the code is: *Pairing code*.",
|
|
1137
1137
|
access: '`role="img"` named by the label and the digits read one by one.',
|
|
1138
1138
|
},
|
|
@@ -1141,9 +1141,9 @@ export const USAGE = [
|
|
|
1141
1141
|
module: "progress",
|
|
1142
1142
|
family: "Data",
|
|
1143
1143
|
summary: "A known amount against a real maximum, with its label and value.",
|
|
1144
|
-
when: '"Determinate work whose total is knowable, like file uploads, multi-step setup, build steps, or batch deletions." (https://vercel.com/geist/progress) "Use `max` for the real ceiling (`max={files.length}`), not a hardcoded `100`. The component computes the percentage from `value / max`." (https://vercel.com/geist/progress) Ours: it always has a `value` and a name, as `aria-label` or as `aria-labelledby` pointing at its `ProgressLabel`, and the type asks for both. `stops` mark genuine stages, each named. `size` is `md` (10 px), or `sm` (4 px) inside a table cell or a row.',
|
|
1144
|
+
when: '"Determinate work whose total is knowable, like file uploads, multi-step setup, build steps, or batch deletions." (https://vercel.com/geist/progress) "Use `max` for the real ceiling (`max={files.length}`), not a hardcoded `100`. The component computes the percentage from `value / max`." (https://vercel.com/geist/progress) Ours: it always has a `value` and a name, as `aria-label` or as `aria-labelledby` pointing at its `ProgressLabel`, and the type asks for both. `stops` mark genuine stages, each named. `size` is `md` (10 px), or `sm` (4 px) inside a table cell or a row. `ProgressLabel` and `ProgressValue` go inside it as children; it draws `ProgressTrack` and `ProgressIndicator` itself.',
|
|
1145
1145
|
instead: '"For short indeterminate waits (~1–3s), use `Spinner`; for inline copy like `Saving`, use `Loading Dots`." (https://vercel.com/geist/progress) Ours: neither exists; an unknown wait is the waiting line, never a bar with an invented percentage. "For usage against a quota or ratio, use `Gauge`; the circle reads as health, the bar reads as progress." (https://vercel.com/geist/progress) Ours: a run\'s consumption in a dense spot is a `UsageMeter`. A single figure is a `KpiCard`.',
|
|
1146
|
-
behaviour: '"Threshold colors via `dynamic colors` should mirror the same breakpoints used elsewhere (warning at the same threshold a quota note fires)." (https://vercel.com/geist/progress) Ours: colour sits on the fill alone: the track is always the neutral line colour and the words beside the bar stay neutral. The fill is neutral by default; `tone` is that threshold (amber where the note fires, red past the cap), never derived from the value
|
|
1146
|
+
behaviour: '"Threshold colors via `dynamic colors` should mirror the same breakpoints used elsewhere (warning at the same threshold a quota note fires)." (https://vercel.com/geist/progress) Ours: colour sits on the fill alone: the track is always the neutral line colour and the words beside the bar stay neutral. The fill is neutral by default; `tone` is that threshold (amber where the note fires, red past the cap), never derived from the value; it is `default`, `secondary`, `success`, `warning`, `critical` or `info`. Past the maximum the bar fills and turns red, whatever the tone. The fill\'s end is rounded, and square when `stops` cut the bar. The fill moves only when the reading changes, on `open`; still under reduced motion. The printed value is always the live reading. "Use stops for genuine multi-stage work and label the stage next to the bar (`Step 2 of 4 · Building`); a stop with no label is decorative noise." (https://vercel.com/geist/progress)',
|
|
1147
1147
|
words: '"Pair the bar with text naming the work and units: `Uploading 12 of 30 files`, `Building · 1.2 GB / 4 GB`. The bar alone doesn’t say what’s progressing." (https://vercel.com/geist/progress) "Don’t append `successfully` or `complete` once the bar fills; swap to a completion state (toast, success row, redirect)." (https://vercel.com/geist/progress) "For long operations, name the work in the surrounding copy (`Building deployment…`) instead of leaving a bare percentage." (https://vercel.com/geist/progress) Ours: print the work and its unit in `ProgressValue`.',
|
|
1148
1148
|
access: '"Component sets `role="progressbar"` with `aria-valuemin`, `aria-valuemax`, and `aria-valuenow`; pass an accessible name through `aria-label` on the wrapper or a sibling `<label>` tied with `aria-labelledby`." (https://vercel.com/geist/progress) "Throttle `aria-valuenow` updates to roughly once a second so screen readers don’t announce every increment of a fast upload." (https://vercel.com/geist/progress) Ours: the printed value is live, and the value it announces follows it at most once a second. "Stops accept their own `ariaLabel`; name each one (`Build complete`, `Tests complete`) so the bar is navigable without sight." (https://vercel.com/geist/progress) Ours: a stop\'s name is its `label`. With `stops`, the stage reached leads the spoken value; a stop is not focusable, because a progressbar\'s children are presentational.',
|
|
1149
1149
|
},
|
|
@@ -1154,9 +1154,9 @@ export const USAGE = [
|
|
|
1154
1154
|
summary: "A link drawn as a QR code another device can scan.",
|
|
1155
1155
|
when: "Opening something on a phone from a screen: a mobile sign-in, a download link.",
|
|
1156
1156
|
instead: "Two screens compared by eye is `PairingCode`. A link a person clicks is a link.",
|
|
1157
|
-
behaviour: "Square dark modules on a white tile in every theme, with the quiet zone a reader needs.",
|
|
1157
|
+
behaviour: "Square dark modules on a white tile in every theme, with the quiet zone a reader needs. It draws a real, scannable `value` and nothing more: the caller shows a readable fallback beside it (the link as text) and an alternative action for when scanning is unavailable, and shows the code's expiry or invalidity from the actual state of the value, not from the drawing.",
|
|
1158
1158
|
words: "The label names what the code opens: *Open Nadia on your phone*.",
|
|
1159
|
-
access: '`role="img"` named by `label`.',
|
|
1159
|
+
access: '`role="img"` named by `label`. Keep secrets out of the label; it names what the code opens, never the value.',
|
|
1160
1160
|
},
|
|
1161
1161
|
{
|
|
1162
1162
|
name: "RecordTime",
|
|
@@ -1165,7 +1165,7 @@ export const USAGE = [
|
|
|
1165
1165
|
summary: "When something happened to a record: a relative time in the sentence that says what, or the time of day in a feed's time column.",
|
|
1166
1166
|
when: '"Use `<RelativeTimeCard>` anywhere a recent timestamp appears in scannable surfaces: `Table` cells, `Entity` rows, deploy lists, activity feeds." (https://vercel.com/geist/relative-time-card) Ours: `RelativeTimeCard` is `RecordTime`: the moment a record was made, changed or last ran, in its row or its facts, and the time of each event in an ActivityFeed or a RunTimeline step (`mode="clock"`).',
|
|
1167
1167
|
instead: '"For a static date in body prose past 7 days, render `Mar 14, 2026` directly." (https://vercel.com/geist/relative-time-card) Ours: a date a person picks is a Calendar; a span of days a page reads is a DateRangeControl; a run\'s duration is a fact written by the product (*Took 4 min*).',
|
|
1168
|
-
behaviour: "\"The hover card already shows absolute UTC and local time, so don’t duplicate that copy elsewhere on the row.\" (https://vercel.com/geist/relative-time-card) Ours: relative within a week, then the day and month, with the year only when it is another year; `mode=\"clock\"` prints the time of day in the viewer's zone in tabular figures. Hovering either shows a card that opens above after 150 ms, 13 px from the time, with a stem: the relative reading first, then the exact date and time in UTC and in the viewer's zone, each behind a zone chip, the viewer's by its offset (*GMT+2*). A page rendered on a server passes the viewer's `timeZone` when it knows it, so the server and the browser write the same time; otherwise the server and the hydrating page write UTC and the page then rewrites it in the browser's zone. A time that names no moment draws `children`, a state such as *Pending*, or nothing.",
|
|
1168
|
+
behaviour: "\"The hover card already shows absolute UTC and local time, so don’t duplicate that copy elsewhere on the row.\" (https://vercel.com/geist/relative-time-card) Ours: relative within a week, then the day and month, with the year only when it is another year; `mode=\"clock\"` prints the time of day in the viewer's zone in tabular figures. Hovering either shows a card that opens above after 150 ms, 13 px from the time, with a stem: the relative reading first, then the exact date and time in UTC and in the viewer's zone, each behind a zone chip, the viewer's by its offset (*GMT+2*). A page rendered on a server passes the viewer's `timeZone` when it knows it, so the server and the browser write the same time; otherwise the server and the hydrating page write UTC and the page then rewrites it in the browser's zone. A time that names no moment draws `children`, a state such as *Pending*, or nothing. While `at` does name a moment, `children` (*Queued*) replaces the sentence and the card stays.",
|
|
1169
1169
|
words: '"Pass `date` as a number (Unix ms or epoch)." "Don’t pre-format the value, and don’t replace `children` with a formatted string." "The component’s short formatter is the canonical form (`2m`, `5h`, `Yesterday`)." "Don’t append `ago` after the component." "The formatter already produces `2m ago` / `5h ago`, so `<RelativeTimeCard /> ago` reads as `2m ago ago`." "Use `children` only for non-time labels (`Just now`, `Pending`, `Queued`) where the formatter can’t describe the state." "Pair with a leading label when the row is ambiguous on its own: `Last deploy <RelativeTimeCard date={ts} />`." (https://vercel.com/geist/relative-time-card) Ours: `at` takes a Date, epoch milliseconds or an ISO string, never a display string such as *3 hours ago*. The reading is the locale\'s long form (*3 hours ago*, *12 Sep*), not `2m`. The product writes the sentence through `phrase` (*Updated {when}*, *Ran {when}*), so *ago* is never added after it; the clock needs no words. The package formats every date with the person\'s `locale`.',
|
|
1170
1170
|
access: "Ours: a `<time>` element with the machine-readable moment; assistive technology reads the exact local moment after the visible text.",
|
|
1171
1171
|
},
|
|
@@ -1174,9 +1174,9 @@ export const USAGE = [
|
|
|
1174
1174
|
module: "run-timeline",
|
|
1175
1175
|
family: "Data",
|
|
1176
1176
|
summary: "One run of an Agent, read step by step on a neutral rail.",
|
|
1177
|
-
when: "The steps of one run, in order: a scheduled task's run, an Agent's handoff. Each step is something the run did or will do.",
|
|
1177
|
+
when: "The steps of one run, in order, one step per row: a scheduled task's run, an Agent's handoff, a workflow. Each step is something the run did or will do.",
|
|
1178
1178
|
instead: "What happened to a record over time is an ActivityFeed; steps a person follows are NumberedSteps; the stages a system runs on an entry screen are the EntryStageList.",
|
|
1179
|
-
behaviour:
|
|
1179
|
+
behaviour: "Each step is a node on one rail, its label and title, one line of detail, and its time at the trailing edge (\"—\" until it happens). The node marks the step and never grades it: a done step's node is the body-grey dot, the current step's node is ink, a step to come is drawn in the line colour with its title muted. An outcome that needs a person is the StatusBadge inside its step (\"Needs your decision\"). Each step's time stands in the feed's column, in the feed's time style. Nothing moves. It is also a framework-neutral CSS component: the `nc-run-timeline` class on the list with the parts `nc-run-timeline-step` (carrying `data-progress` of `done`, `current` or `upcoming`), `-node`, `-content`, `-label`, `-title` and `-detail`, which another renderer can use without this component.",
|
|
1180
1180
|
words: 'The label numbers the step ("Step 01"); a done step\'s title is in the past tense ("Filed the invoices"), a step to come in the imperative ("Send the summary to Marco"); a waiting step\'s detail says what it waits for ("Waits for your decision on step 03").',
|
|
1181
1181
|
access: 'An ordered list; the current step carries `aria-current="step"`; the rail and nodes are hidden from assistive technology, and so is the dash of a step that has not happened.',
|
|
1182
1182
|
},
|
|
@@ -1187,7 +1187,7 @@ export const USAGE = [
|
|
|
1187
1187
|
summary: "Read-only facts about one object, framed as a card with a title, a line, one action and a footer.",
|
|
1188
1188
|
when: "Read-only facts about one object: who it is, where it belongs, its identifiers. `SettingsFactsSection` frames them as a card with a title and one action; `ObjectFacts` is the list alone.",
|
|
1189
1189
|
instead: "Facts a person changes are a `SettingsSection` with its own Save. One server-given value, an ID, is a `SettingsSection` with the value in a read-only mono `InputGroup` and its copy button, no Save. Facts compared across records are a table. A relation is its identity or a `Tag`, never its own card.",
|
|
1190
|
-
behaviour: '"Use `<Description>` for definition-list metadata: a short Title Case key paired with a single value (`Last Deployed`, `Region`, `Plan`)." (https://vercel.com/geist/description) Ours: required: the description and the footer\'s left line (`footerHint`), as on a `SettingsSection`; `footer` holds actions, never a Save.
|
|
1190
|
+
behaviour: '"Use `<Description>` for definition-list metadata: a short Title Case key paired with a single value (`Last Deployed`, `Region`, `Plan`)." (https://vercel.com/geist/description) Ours: required: the description and the footer\'s left line (`footerHint`), as on a `SettingsSection`; `footer` holds actions, never a Save. Facts are plain pairs, the label above its value, in two equal columns from 640 px (`sm`) and one column below it. Every label stays when its value is missing, as *—*. A long value spans both columns. No fact gets a box of its own. In a side column, `presentation="summary"` drops the card, the description and the footer line.',
|
|
1191
1191
|
words: '"Title slot is Title Case noun (`Last Deployed`, `Build Duration`); content is sentence case unless the value is a literal identifier, ID, or timestamp that should be preserved verbatim." (https://vercel.com/geist/description) Ours: labels are nouns in sentence case, no colon: *VAT number*. Identifiers in mono with a copy button, dates in the person\'s locale. The footer line says what the facts are for or links to the docs.',
|
|
1192
1192
|
access: '"Geist renders `<dl>`/`<dt>`/`<dd>` so screen readers announce each key/value pair as a definition." (https://vercel.com/geist/description) "Don’t put interactive controls in the title slot." (https://vercel.com/geist/description) Ours: a `dl` of `dt` and `dd`, each value named by its label. The section is labelled by its heading, which also takes focus when an editor closes. A dash carries screen-reader text.',
|
|
1193
1193
|
},
|
|
@@ -1198,7 +1198,7 @@ export const USAGE = [
|
|
|
1198
1198
|
summary: "One command a person copies and runs in a terminal.",
|
|
1199
1199
|
when: "“Use Snippet for a runnable shell command the user is meant to copy. For inline tokens (env var names, paths) use `InlineCode`, and for multi-line source use `CodeBlock`.” (https://vercel.com/geist/snippet) Ours: a command or a short sequence of commands to paste into a terminal: an install, a sign-in, a pairing step.",
|
|
1200
1200
|
instead: "Ours: a value that is not a command (an identifier, a URL, a checksum) is `TextWithCopyButton`. A script someone reads before running is a `CodeBlock`. A token inside a sentence is inline code.",
|
|
1201
|
-
behaviour: '“Pass the command as `text`. Authors never type a leading `$`; the component renders the prompt, so `text="$ vercel deploy"` displays as `$ $ vercel deploy`.” (https://vercel.com/geist/snippet) “Set `prompt={false}` for non-shell content (URLs, JSON, output you want copied verbatim) so the rendered string matches what gets copied.” (https://vercel.com/geist/snippet) “Keep one command per Snippet. Pass an array to `text` for a short multi-line block; for longer scripts, switch to `CodeBlock` so users can read before they copy.” (https://vercel.com/geist/snippet) “Use `copyText` only when `text` contains rich nodes (`<span>` highlights, conditional fragments) and the clipboard payload should be plain text. For a string `text`, `copyText` is redundant.” (https://vercel.com/geist/snippet) “Use the `copied` prop to control the copy button animation externally. This lets you copy different text (e.g. from a context card) while reusing the snippet\'s checkmark feedback.” (https://vercel.com/geist/snippet) “Use `copied` plus an `onCopy` callback when a parent surface (a card, a tooltip) needs to show the same checkmark feedback while copying different text.” (https://vercel.com/geist/snippet) Ours:
|
|
1201
|
+
behaviour: '“Pass the command as `text`. Authors never type a leading `$`; the component renders the prompt, so `text="$ vercel deploy"` displays as `$ $ vercel deploy`.” (https://vercel.com/geist/snippet) “Set `prompt={false}` for non-shell content (URLs, JSON, output you want copied verbatim) so the rendered string matches what gets copied.” (https://vercel.com/geist/snippet) “Keep one command per Snippet. Pass an array to `text` for a short multi-line block; for longer scripts, switch to `CodeBlock` so users can read before they copy.” (https://vercel.com/geist/snippet) “Use `copyText` only when `text` contains rich nodes (`<span>` highlights, conditional fragments) and the clipboard payload should be plain text. For a string `text`, `copyText` is redundant.” (https://vercel.com/geist/snippet) “Use the `copied` prop to control the copy button animation externally. This lets you copy different text (e.g. from a context card) while reusing the snippet\'s checkmark feedback.” (https://vercel.com/geist/snippet) “Use `copied` plus an `onCopy` callback when a parent surface (a card, a tooltip) needs to show the same checkmark feedback while copying different text.” (https://vercel.com/geist/snippet) Ours: Geist\'s `dark`, `type` and `width` props are not ours: `variant` sets the colour and the shell sizes itself to its text. The copy mark is a `CopyButton`; `copied` and `onCopy` let a parent show its checkmark. An array of strings is copied joined by line breaks, one row each after its own prompt; the prompt is never copied. The shell is as wide as its text, up to its row (`className="w-full"` fills the row); a longer line scrolls sideways with no scrollbar. `text=""` with a `placeholder` shows the placeholder, copies nothing and has no button. `text` that is an element needs `copyText`; a string needs none. `onCopy` reports a copy once the clipboard confirms. `variant` puts one colour on the whole shell, its text and the copy mark together: `info`, `warning` or `destructive` for a command inside a note of that kind, `inverted` for a dark block on a light page; the default is neutral, which is what almost every snippet is. Hovering the copy mark dims it and draws a 1 px ring; it never takes a fill or leaves the shell\'s text colour.',
|
|
1202
1202
|
words: '“Pair `placeholder` with `text=""` for an empty state. Sentence case, no trailing period, no `Please`: `Run vercel link to fetch env vars`. The placeholder is informational, not copied.” (https://vercel.com/geist/snippet) Ours: the command as typed. The placeholder example is *Run nadia login to connect this computer*. `copyLabel` names the command (*Copy install command*); `copiedLabel` and `copyFailedLabel` are required with it.',
|
|
1203
1203
|
access: "Ours: the text is read as text; the prompt is hidden from assistive technology. The button is named by `copyLabel`, and the result is read once from its status.",
|
|
1204
1204
|
},
|
|
@@ -1207,9 +1207,9 @@ export const USAGE = [
|
|
|
1207
1207
|
module: "table",
|
|
1208
1208
|
family: "Data",
|
|
1209
1209
|
summary: "The native table every comparison is drawn with: a head row, figure rows of one line, and cells padded the same at every width.",
|
|
1210
|
-
when: "“Use `<Table>` for tabular data where rows share the same shape and at least one column is sortable or comparable across rows.” (https://vercel.com/geist/table) Ours: a fixed table a composition draws itself: a summary inside a card, a list of plan limits, figures compared down a column. A table of records is `DataTable`, which draws this one.",
|
|
1210
|
+
when: "“Use `<Table>` for tabular data where rows share the same shape and at least one column is sortable or comparable across rows.” (https://vercel.com/geist/table) Ours: a fixed table a composition draws itself: a summary inside a card, a list of plan limits, figures compared down a column. A table of records is `DataTable`, which draws this one, inside a `TableCard`. It does not virtualize its rows; that is a separate capability.",
|
|
1211
1211
|
instead: "“For a row of descriptive content paired with a single action (membership row, integration row), use `Entity` instead.” (https://vercel.com/geist/table) “For a key/value metadata block on a detail page, use `Description`, not a two-column table.” (https://vercel.com/geist/table) “When the underlying list is empty (filter cleared, never created), render `Empty State` outside the table rather than an empty `<Table.Body>`.” (https://vercel.com/geist/table) Ours: Geist's `Entity` is `Item`, or `RecordList` when the row names a record; its `Description` is `ObjectFacts`; its `Empty State` is `EmptyState`. Records with sorting, paging and an empty state are `DataTable`.",
|
|
1212
|
-
behaviour:
|
|
1212
|
+
behaviour: 'A head row 48 px high, with 12 px of room under its labels and then a 1 px rule, so the first row starts at the rule like every other; body rows 40 px (a cell that holds a control, such as the three dots, is padded just enough to keep the row at 40), every cell padded 8 px sideways and 10 px above and below in the body, and headings at 14 px / 20 px, weight 500, visible because people compare by them. Body rows have no rule between them unless the body is `bordered`, which draws it straight across the row. In a body that is not `bordered`, the first and last cell of a row take a 6 px corner, so a stripe or a highlight reads as a rounded bar; a `bordered` row keeps square corners, so its rule meets the edge. The last column, its heading and its cells, aligns to the trailing edge; any other numeric column takes `text-right` and `tabular-nums` to match. `striped`, `bordered` and `interactive` belong to `TableBody`: stripes fill the odd rows with `muted` (a selected row there takes `accent`), `bordered` draws a rule between rows (not after the last) and no frame, and an interactive row takes `accent` on hover, on focus within and while its menu is open, over a stripe; a row that opens nothing is not `interactive`. Headers and footers never highlight and carry no fill, and a selected row (`data-state="selected"`) takes `muted`; a footer has a rule above it and ink, medium text. A column\'s width is a `TableCol` in a `TableColgroup`. The table has no fill or edge of its own: it takes its enclosing surface, whose `Card` owns the outer edge, and a visible header and every record but the last end in a quiet 1 px `border` rule. A table that opens nothing implies no action on hover. `density="comfortable"` is the entity row of a record that names itself. On a card narrower than its `compactDetails` layout a row never scrolls sideways: its secondary columns leave the row and their values stand in the `TableDetailRow` under it, one muted line joined by a middle dot that ends in an ellipsis, their labels read by assistive technology only, as Vercel\'s lists read on a phone; a `Tag` there reads as its plain text, and a fact that `wraps` lets the line wrap whole instead of ending in an ellipsis. An entity row (`density="comfortable"`) keeps its record on one 72 px row instead: its facts join the line under the name that `RecordIdentity` draws (`TableCompactLine`), so the row is the name over one line. Colour is neutral throughout: headings and cells share the body grey and a heading differs by weight, the footer is ink, and the hue of a state lives in the `StatusBadge` of a cell, never in the row. “Sortable column headers are buttons. The visible label stays Title Case; the sort-direction arrow is decorative and the button announces the next sort state to assistive tech.” (https://vercel.com/geist/table) Ours: no heading sorts the rows, so no heading is a button; the order is the page\'s, through a `CollectionSort` above the list, and only `aria-sort` marks the column it follows. “Apply `tabular-nums` (or Geist Mono) to numeric columns so digits align across rows for comparison.” (https://vercel.com/geist/table) Ours: `DataTable` sets it on a numeric column; in a `Table` a figure\'s cell takes `tabular-nums`.',
|
|
1213
1213
|
words: "“Column headers (`<Table.Head>`) are Title Case nouns or noun phrases: `Last Used`, `Requests (7d)`, `Created`, `Status`. Never sentences.” (https://vercel.com/geist/table) “Render `—` in cells where a value is unknown or not applicable. Don’t substitute `N/A`, `null`, or an empty string.” (https://vercel.com/geist/table) “Use the canonical short relative-time form in cells (`2m ago`, `5h ago`); switch to `Mar 14, 2026` past 7 days. See `Relative Time Card`.” (https://vercel.com/geist/table) Ours: headings are sentence case nouns with their unit, *Cost (€)*, *Tokens*, where Geist writes Title Case. Italian headings run a third longer and never wrap. A missing value is `TableValue`'s dash, read as its `missingLabel`. The table is always named: its `aria-label` says what it lists (*Usage by Agent*).",
|
|
1214
1214
|
access: 'A native `table` named by `aria-label` or `aria-labelledby`, which the types require, or `aria-hidden` when it is only the placeholder of a loading view; `th scope="col"`, with `aria-sort` on the column the rows are ordered by.',
|
|
1215
1215
|
},
|
|
@@ -1242,7 +1242,7 @@ export const USAGE = [
|
|
|
1242
1242
|
summary: "A short exact value with the button that copies it.",
|
|
1243
1243
|
when: "A short value someone reads and pastes elsewhere: an identifier, a checksum, a slug, a server address.",
|
|
1244
1244
|
instead: "Several lines are a `CodeBlock`; a command is a `Snippet`. A value that needs no copying is plain text. The button alone, beside text that is not the value, is `CopyButton`.",
|
|
1245
|
-
behaviour: "Shows the value in the body type, with a 24 px `CopyButton` whose 16 px mark sits 6 px after it. The value is slightly softer than the full text colour and the mark softer still; hovering either takes both to the full text colour, with no fill, border or shadow. Only the button copies: the value stays selectable, so a copy the clipboard refuses can be done by hand. A value too long for its row wraps at any character; `ellipsis` instead cuts it in the middle in one line (`MiddleTruncate`) and the whole value is still what copies. The result shows as the button's check mark, with no toast.",
|
|
1245
|
+
behaviour: "Shows the exact `value`, which is also what is copied, in the body type, with a 24 px `CopyButton` whose 16 px mark sits 6 px after it. The value is slightly softer than the full text colour and the mark softer still; hovering either takes both to the full text colour, with no fill, border or shadow. Only the button copies: the value stays selectable, so a copy the clipboard refuses can be done by hand. A value too long for its row wraps at any character; `ellipsis` instead cuts it in the middle in one line (`MiddleTruncate`) and the whole value is still what copies. The result shows as the button's check mark, with no toast.",
|
|
1246
1246
|
words: "`copyLabel` names what is copied (*Copy config digest*), never a bare *Copy*; `copiedLabel` says what was copied (*Config digest copied*); `copyFailedLabel` says it failed (*Couldn't copy*). All three are required.",
|
|
1247
1247
|
access: "The value is text, read in full. The button is named by `copyLabel`; the result is read once from its status.",
|
|
1248
1248
|
},
|
|
@@ -1251,7 +1251,7 @@ export const USAGE = [
|
|
|
1251
1251
|
module: "usage-meter",
|
|
1252
1252
|
family: "Data",
|
|
1253
1253
|
summary: "Consumption against an allowance in a dense spot.",
|
|
1254
|
-
when: "`UsageMeter`: consumption against an allowance in a dense spot, such as
|
|
1254
|
+
when: "`UsageMeter`: consumption against an allowance in a dense spot, such as tokens, cost, latency or run count, with the figure printed.",
|
|
1255
1255
|
instead: "A known amount against a real maximum with its label is `Progress`. An unknown wait is the waiting line, never a bar with an invented percentage. A single figure is a `KpiCard`.",
|
|
1256
1256
|
behaviour: "The fill moves only when the reading changes, on `transition`; still under reduced motion. The tone is the product's threshold, never derived from the value. Past the maximum the bar fills and turns red.",
|
|
1257
1257
|
words: "Print the work and its unit: *620 of 1,000*. The meter's fields are short and set in uppercase mono by the stylesheet, so they are written in sentence case.",
|
|
@@ -1275,9 +1275,9 @@ export const USAGE = [
|
|
|
1275
1275
|
summary: "What a place with nothing in it says, by its cause: never created, empty at the moment, nothing matched, finished, not allowed, or failed to load.",
|
|
1276
1276
|
when: '"Fill spaces when no content has been added yet, or is temporarily empty due to the nature of the feature and should be designed to prevent confusion." (https://vercel.com/geist/empty-state) "A card used to communicate an error state with a title and message." (https://vercel.com/geist/error-card) "Pick the variant by what the user needs: no-results for a filtered list that returned zero rows, blank slate or informational for a resource the user hasn’t created, cleared for completed work, permission for role or tier denials, error for a failed load." (https://vercel.com/geist/empty-state) "Render the permission and tier-denial variants full-page when the user lands on a route they can’t view. Use a `Note` only when one tile inside an otherwise-accessible page is gated." (https://vercel.com/geist/empty-state) "Use the Error component as a block surface when a section or page-level resource failed to load: a panel, a dashboard card, a route boundary." (https://vercel.com/geist/error) Ours: pick the `kind` by the cause. `firstUse` is the blank slate and the informational variant: no record was ever made. `nothingYet`: empty now and it fills by itself. `noResults`: the search or filter found nothing. `cleared`: the work is done. `denied`: this viewer\'s role or plan does not open it. `failed`: the read failed; it is the Error and the Error Card, a failure inside a card with `scope="card"` and one that stands alone as a block with `page` or `region`. There is no educational kind.',
|
|
1277
1277
|
instead: '"Don’t put critical persistent warnings here. Empty states vanish when the list populates; persistent warnings belong in `Note` or the page header." (https://vercel.com/geist/empty-state) "Pick `toasts.error()` for transient action failures (`Couldn’t save settings. Try again.`) and the `error` prop on `Input` for field-level validation. Don’t replace either with this block." (https://vercel.com/geist/error) Ours: `Note` is `MessageNotice` or `NoticeBanner`, `toasts.error()` is `showMessageToast` with the tone `error`, and the `error` prop on `Input` is `FieldError`. Rows still loading are skeleton rows. A failure of one record\'s action is that record\'s row message. One gated tile on an open page is a `MessageNotice`, so `denied` takes `page` or `region`, never `card`.',
|
|
1278
|
-
behaviour: '"The CTA must be a real `Button` or `Link`, not an `onClick` div, so it joins the tab order and exposes a role." (https://vercel.com/geist/empty-state) "Cap at one primary CTA, plus one secondary when the first action could legitimately be one of two paths (`Import Repository` and `Deploy Template`). Three CTAs is a smell." (https://vercel.com/geist/empty-state) "Don’t auto-launch a tour from the educational variant; pair `Start Tour` with `Skip`." (https://vercel.com/geist/empty-state) "The recovery action must do something concrete: a `Try Again` button when the operation is retry-safe, a named verb (`Reconnect GitHub`, `Update Payment Method`) when it isn’t." (https://vercel.com/geist/error) "Don’t auto-retry in the background; the user came to this surface to decide." (https://vercel.com/geist/error) "For full-page route errors (`error.tsx`), return focus to the `Try Again` button on appearance so a keyboard user can retry without hunting for it." (https://vercel.com/geist/error) "Always pair platform or system errors with a stable identifier (request ID `x-vercel-id`, deployment ID `dpl_…`, run ID, trace ID). Validation and permission denials are user-state, not system, and don’t need an ID." (https://vercel.com/geist/error) "Render the stable ID on a monospace sub-line under a collapsed `<details>` so the user can copy-paste it into a support thread." (https://vercel.com/geist/error) Ours: `firstUse` has the one picture, one action and at most one docs link under it; there is no educational kind, so no tour. A failure shows at most two of its message\'s actions, Retry first, and moves focus to the first one when it fills a page (`scope="page"`); its `reference` is the collapsed Disclosure holding the monospace identifier and a copy button. A page or a region stands on a resting card, inside a card it takes none, and a collection always passes `card`; a card that lists a few items, as a collection\'s rail does, passes `line`, where `nothingYet` and `cleared` are one 44 px row like an item: the message at the left, after the cleared check at 16 px. Every kind that carries a title, `failed` excepted, draws it the same way: a mark or picture, a 16 px medium title, one or two muted 14 px lines, 24 px apart, centred, the lines no wider than 340 px. `cleared` and `denied` carry a bordered 54 px tile with a muted icon, never a hue; the picture is `firstUse`\'s alone. The action is the filled primary button, as Vercel\'s Connect draws Create Connector in its empty card; the docs link is muted. A page or a region is padded 48 px above and below and 70 px at the sides (a first use narrows to 24 px under 640 px for its picture). `failed` is the one hued kind: on a page or a region its ground, line, mark and text are all in the error colour, with a 16 px regular title, 16 px padding and 8 px between its parts, and the buttons stay neutral; inside a card it takes the text and mark colour alone, because the card owns the edge and a second one would double it. Required: `firstUse` and `denied` take a title and a line, `firstUse` its picture, `failed` the catalogue message whose title is always drawn.',
|
|
1278
|
+
behaviour: '"The CTA must be a real `Button` or `Link`, not an `onClick` div, so it joins the tab order and exposes a role." (https://vercel.com/geist/empty-state) "Cap at one primary CTA, plus one secondary when the first action could legitimately be one of two paths (`Import Repository` and `Deploy Template`). Three CTAs is a smell." (https://vercel.com/geist/empty-state) "Don’t auto-launch a tour from the educational variant; pair `Start Tour` with `Skip`." (https://vercel.com/geist/empty-state) "The recovery action must do something concrete: a `Try Again` button when the operation is retry-safe, a named verb (`Reconnect GitHub`, `Update Payment Method`) when it isn’t." (https://vercel.com/geist/error) "Don’t auto-retry in the background; the user came to this surface to decide." (https://vercel.com/geist/error) "For full-page route errors (`error.tsx`), return focus to the `Try Again` button on appearance so a keyboard user can retry without hunting for it." (https://vercel.com/geist/error) "Always pair platform or system errors with a stable identifier (request ID `x-vercel-id`, deployment ID `dpl_…`, run ID, trace ID). Validation and permission denials are user-state, not system, and don’t need an ID." (https://vercel.com/geist/error) "Render the stable ID on a monospace sub-line under a collapsed `<details>` so the user can copy-paste it into a support thread." (https://vercel.com/geist/error) Ours: `firstUse` has the one picture, one action and at most one docs link under it; there is no educational kind, so no tour. A failure shows at most two of its message\'s actions, Retry first, and moves focus to the first one when it fills a page (`scope="page"`); its `reference` is the collapsed Disclosure holding the monospace identifier and a copy button. A page or a region stands on a resting card, inside a card it takes none, and a collection always passes `card`; a card that lists a few items, as a collection\'s rail does, passes `line`, where `nothingYet` and `cleared` are one 44 px row like an item: the message at the left, after the cleared check at 16 px. Every kind that carries a title, `failed` excepted, draws it the same way: a mark or picture, a 16 px medium title, one or two muted 14 px lines, 24 px apart, centred, the lines no wider than 340 px. `cleared` and `denied` carry a bordered 54 px tile with a muted icon, never a hue; the picture is `firstUse`\'s alone. The action is the filled primary button, as Vercel\'s Connect draws Create Connector in its empty card; the docs link is muted. A page or a region is padded 48 px above and below and 70 px at the sides (a first use narrows to 24 px under 640 px for its picture). `failed` is the one hued kind: on a page or a region its ground, line, mark and text are all in the error colour, with a 16 px regular title, 16 px padding and 8 px between its parts, and the buttons stay neutral; inside a card it takes the text and mark colour alone, because the card owns the edge and a second one would double it. A first use\'s picture is 240 × 176 px on a page and 200 × 144 px in a region or a card. Required: `firstUse` and `denied` take a title and a line, `firstUse` its picture, `failed` the catalogue message whose title is always drawn.',
|
|
1279
1279
|
words: '"`title` is Title Case (`No Logs Match Your Filter`); `description` is sentence case and adds new information instead of restating the title." (https://vercel.com/geist/empty-state) Ours: a title is sentence case and names what is missing ("No mailboxes yet"), not Title Case; a quiet line is one sentence. "Quote a single typed query verbatim with curly quotes: `No logs match “${query}”. Clear the filter to see all logs.` For multi-facet filters use the plural template `No {Items} Match Your Filters` and suggest widening or clearing." (https://vercel.com/geist/empty-state) "Onboarding bodies name the next action that creates the first item: `Push to your Git repository to create your first one.` Tier-gated bodies follow `{Feature value} with the {Plan} plan.`" (https://vercel.com/geist/empty-state) "Error variant pairs the body with a copyable request ID and a `Try Again` button." (https://vercel.com/geist/empty-state) "CTA labels are Title Case `Verb + Noun`. Never `Get Started`, `Continue`, or `OK`." (https://vercel.com/geist/empty-state) Ours: an action label is sentence case, verb and noun ("Clear filters", "New Agent"), never "Get started" or "OK". "State what happened and what to do next, in that order. Cut apologetic preambles (`Unfortunately`, `Oops`, `We’re sorry`)." (https://vercel.com/geist/error) "Use `Couldn’t` or `Can’t` for user-state errors (`Couldn’t verify your passkey. Try again.`); use `Failed to` for system or infra errors that mirror CLI output (`Build failed. Bundle exceeds 50 MB.`). `Unable to` is banned." (https://vercel.com/geist/error) "Don’t fall back to `Something Went Wrong` as a title; name the resource that failed (`Couldn’t Load Page`, `Couldn’t Load Deployments`)." (https://vercel.com/geist/error) "Never humor an error. Users hitting an error are frustrated; insincere copy makes it worse." (https://vercel.com/geist/error) Ours: a failure is the catalogue\'s message, never a string of the component\'s own, and its title names what failed to load.',
|
|
1280
|
-
access: '"After an async filter change, wrap the region in `aria-live="polite"` so screen readers announce the new state." (https://vercel.com/geist/empty-state) "When the error appears asynchronously (after a failed fetch), wrap the region in `aria-live="polite"` so it’s announced. Reserve `aria-live="assertive"` for true blocking errors that interrupt input." (https://vercel.com/geist/error) Ours: a title, a failure\'s included, is a heading, level 2 or, inside a titled section, 3. The picture is decorative. A failure\'s message is a `role="status"` line, polite, never an assertive alert, because it arrives after a read, not in the middle of input. `nothingYet`, `noResults` and `cleared` are plain lines with no live role of their own: the wrapper that stays mounted while rows and absence swap (the `aria-live="polite"` region, such as `SkeletonRegion`) announces them, once.',
|
|
1280
|
+
access: '"After an async filter change, wrap the region in `aria-live="polite"` so screen readers announce the new state." (https://vercel.com/geist/empty-state) "When the error appears asynchronously (after a failed fetch), wrap the region in `aria-live="polite"` so it’s announced. Reserve `aria-live="assertive"` for true blocking errors that interrupt input." (https://vercel.com/geist/error) Ours: a title, a failure\'s included, is a heading, level 2 or, inside a titled section, 3. The picture is decorative. A failure\'s message is a `role="status"` line, polite, never an assertive alert, because it arrives after a read, not in the middle of input. `nothingYet`, `noResults` and `cleared` are plain lines with no live role of their own: the wrapper that stays mounted while rows and absence swap (the `aria-live="polite"` region, such as `SkeletonRegion`) announces them, once. Beside a table, an absence stands outside the semantic table body, never as a row of it, in that same wrapper.',
|
|
1281
1281
|
},
|
|
1282
1282
|
{
|
|
1283
1283
|
name: "EntryProgress",
|
|
@@ -1308,7 +1308,7 @@ export const USAGE = [
|
|
|
1308
1308
|
summary: "A card of a few facts about something named in a dense row, shown on hover or focus.",
|
|
1309
1309
|
when: '"Use Context Card to reveal entity metadata on hover or focus: a user, a deployment, a project, an API key." "The trigger is usually a name link or avatar in dense data." (https://vercel.com/geist/context-card) Ours: `Context Card` is `HoverCard`. The exact moment behind a relative time is also a card. An entity\'s card is a `HoverCardSummary`.',
|
|
1310
1310
|
instead: '"For a one-line "why" without metadata rows, use `Tooltip`." "For long-form content, edit forms, or persistent navigation, route to a `Drawer` or a detail page." "Don’t use Context Card to surface destructive actions; the panel can dismiss on cursor exit before the user commits." "Don’t nest a Context Card inside a Tooltip or another Context Card; the second layer steals focus and traps keyboard users." (https://vercel.com/geist/context-card) Ours: `Drawer` is `Sheet`. A card is never the only way to an action.',
|
|
1311
|
-
behaviour: '"Context Card opens on hover and keyboard focus and closes on cursor exit or blur." "Preserve the ~150ms entry delay so it doesn’t flash on a fast mouse sweep." "Cap interactive content at one primary action (`View Project`, `Open Settings`)." "More than one CTA reads as a menu and belongs in `Menu`." "Use `align` to position the card at the start, center, or end of the trigger." "Use `render` to set the trigger as a custom element, such as a link, button, or badge." (https://vercel.com/geist/context-card) Ours: the
|
|
1311
|
+
behaviour: '"Context Card opens on hover and keyboard focus and closes on cursor exit or blur." "Preserve the ~150ms entry delay so it doesn’t flash on a fast mouse sweep." "Cap interactive content at one primary action (`View Project`, `Open Settings`)." "More than one CTA reads as a menu and belongs in `Menu`." "Use `align` to position the card at the start, center, or end of the trigger." "Use `render` to set the trigger as a custom element, such as a link, button, or badge." (https://vercel.com/geist/context-card) Ours: the parts are `HoverCard`, `HoverCardTrigger`, `HoverCardContent` and `HoverCardSummary`. The card opens above by default, 150 ms after the pointer or focus arrives (`delay` on `HoverCardTrigger`), stays open while the pointer is on it. `HoverCardContent` requires its facts as children. A `HoverCardSummary` is the entity\'s name, one identifying line, two to four label and value facts, and at most one link, which takes the product\'s router link through `render`. Colour: one neutral surface with no hue; the only accent a card carries is its link, in the outline button.',
|
|
1312
1312
|
words: '"Lead with the entity name as a Title Case heading and one identifying line in sentence case underneath (team slug, owner, deployment URL)." "Follow with 2–4 metadata rows of `Label: value`." "Keys are Title Case noun phrases (`Last Active`, `Created`, `Plan`); values follow the same formatting rules as table cells." "Use the em-dash character `—` for unknown values, never `N/A` or `null`." "Don’t restate what the trigger already showed." "If the row already renders the deployment URL, don’t repeat it as the first card line." (https://vercel.com/geist/context-card) Ours: the name and the keys are in sentence case (*Last active*). A time\'s card leads with the relative label, and its trigger keeps its verb sentence: *Updated 3 hours ago*.',
|
|
1313
1313
|
access: '"The trigger keeps its own accessible name; the card is supplementary and shouldn’t replace it." "Card content is reachable by keyboard once the trigger has focus; Escape closes the card and returns focus to the trigger." (https://vercel.com/geist/context-card) Ours: the trigger is a real element (a `time` with its `datetime` for a moment), so the facts never depend on hover. Keyboard opening is tested, not assumed.',
|
|
1314
1314
|
},
|
|
@@ -1320,7 +1320,7 @@ export const USAGE = [
|
|
|
1320
1320
|
when: "A page that cannot show what was asked for: an address or record that is not there (absence), or a request the product stopped (fault). `PictureLabel` is its label.",
|
|
1321
1321
|
instead: "For one region that failed inside a working page use `EmptyState failed`; the rest of the page stays. Never turn the whole page into an error because one card failed.",
|
|
1322
1322
|
behaviour: "Picture 256 px, 320 from sm, 384 from lg. The label stands centred under the picture at every width, on the same centre line as the words below. Still.",
|
|
1323
|
-
words: "Never apologise, never name the mechanism. The label's title says what the picture is doing; the second line gives its materials and year.",
|
|
1323
|
+
words: "Never apologise, never name the mechanism. The label's title says what the figure in the picture is doing, which is the page's one joke; the second line, in mono, gives its materials and year.",
|
|
1324
1324
|
access: "The picture is decorative; the label is a `figcaption`, read once. The page's heading is its `h1`; its actions are real links.",
|
|
1325
1325
|
},
|
|
1326
1326
|
{
|
|
@@ -1330,7 +1330,7 @@ export const USAGE = [
|
|
|
1330
1330
|
summary: "A catalogue message about one thing on the screen.",
|
|
1331
1331
|
when: '"Display text that requires attention or provides additional information." (https://vercel.com/geist/note) "Use a Note for inline contextual feedback next to the field, card, or section it describes: a region change warning above a region picker, a rate-limit notice next to a usage gauge." (https://vercel.com/geist/note) "Choose `variant` by meaning: `error` for a problem the user must fix, `warning` for a consequence to acknowledge, `success` for a passed check, `secondary` for neutral information." (https://vercel.com/geist/note) Ours: a message beside the field, card or section it describes, under the control whose write failed (`action`), or atop the section whose condition lasts (`section`). The `tone` is the `variant`: `destructive` is `error`, `warning` is `warning`, `info` is `success` (a passed check or a state the product manages for the reader, drawn in the accent blue) and `neutral` is `secondary`; there is no violet or cyan.',
|
|
1332
1332
|
instead: '"Pick `Banner` when the message is page-level or system-wide and needs a CTA, `Toast` for transient acknowledgments, `Modal` for destructive confirmations." (https://vercel.com/geist/note) Ours: `Banner` is `NoticeBanner`, `Toast` is `showMessageToast` and `Modal` is `ConfirmDialog`. For a field\'s wrong value use `FieldError`. For a failed read use `EmptyState failed`.',
|
|
1333
|
-
behaviour: '"A Note is persistent until the underlying state changes; don’t add an ad hoc dismiss control because it competes with the message." (https://vercel.com/geist/note) "One Note per concept. Stacking three Notes on a card means the page architecture, not the Note copy, is wrong." (https://vercel.com/geist/note) "The optional `NoteAction` holds a single inline CTA. Don’t pair it with a second button." (https://vercel.com/geist/note) Ours:
|
|
1333
|
+
behaviour: '"A Note is persistent until the underlying state changes; don’t add an ad hoc dismiss control because it competes with the message." (https://vercel.com/geist/note) "One Note per concept. Stacking three Notes on a card means the page architecture, not the Note copy, is wrong." (https://vercel.com/geist/note) "The optional `NoteAction` holds a single inline CTA. Don’t pair it with a second button." (https://vercel.com/geist/note) Ours: `Alert` is its primitive. Tone `destructive` by default, `warning`, `info` or `neutral`; there is no `success` tone. Colour sits on the line, the text and the mark; the ground stays clear, and the action stays neutral. `section` is the framed note, a line 36 px high with its mark and sentence on one row and the action at its trailing edge (under 768 px the action takes its own row, full width), with or without an action; `action` is the unframed passage, its mark and sentence on one row with the action link inside the sentence, after a space. The title and the body run on as one passage, the title closed by a full stop when it has none of its own, so the two never read as one sentence; the title leads in bold (semibold in a note, medium in a passage) only when a body follows it, and stands at regular weight alone. A message worded for a notice has one action, a small default button in a section and an underlined 14 px link in the passage, and at most a second one after it (Nadia\'s *Ask Nadia* after *Try again*), an outline button in a section and a second link in the passage; a third is not shown. Long words wrap, nothing truncates. A successful retry removes it. Required: the catalogue `message`, so the sentence and its mark are always drawn.',
|
|
1334
1334
|
words: '"`NoteLabel` is a 1–2 word Title Case prefix that names the topic: `Region Change`, `Rate Limit`, `Plan Limit`. Cut hedges like `Heads Up`, `FYI`, and `Note`." (https://vercel.com/geist/note) "`NoteContent` is one sentence in active voice that names the impact: `Changing this region restarts all functions.`" (https://vercel.com/geist/note) "No `variant="info"` exists; omit `variant` for the default info icon or use `variant="secondary"` for neutral copy." (https://vercel.com/geist/note) "Single-fragment labels take no period; full descriptive sentences in the body do." (https://vercel.com/geist/note) Ours: words come from the catalogue only. The message\'s title stands in for `NoteLabel` and `NoteContent`, in sentence case, and `info` exists as Geist\'s blue `success`. The action is labelled with its result: *Try again*, *Sign in*. When the message carries an action, its body does not repeat it: the link is the step, so the body ends before "Try again".',
|
|
1335
1335
|
access: '`action`: `role="alert"` (`status` for info and neutral). `section`: `role="status"`, `aria-live="polite"`, atomic. The mark is hidden. Focus stays where the person was.',
|
|
1336
1336
|
},
|
|
@@ -1340,8 +1340,8 @@ export const USAGE = [
|
|
|
1340
1340
|
family: "Feedback",
|
|
1341
1341
|
summary: "A condition of the whole page, the account or the service, with its fix.",
|
|
1342
1342
|
when: '"A prominent message that spans the full width of its container to announce important information." (https://vercel.com/geist/banner) "Used for temporary, project-wide notifications that require resolution" (https://vercel.com/geist/project-banner) "Use Project Banner for project-wide states that need resolution: overdue billing, an active rollback, attack mitigation, an expiring trial blocking deploys." (https://vercel.com/geist/project-banner) "Match `variant` to severity: `error` for critical downtime or payment-blocking states, `warning` for an exceptional state with non-immediate action, `success` for a positive temporary mitigation, `gray` for routine project-wide notices." (https://vercel.com/geist/project-banner) Ours: a condition of the whole page, the account or the service that the page itself can resolve, with that resolving action on the banner. The `tone` is the `variant`: `destructive` is `error`, `warning` is `warning`, `info` is `success` and `neutral` is `gray`. One sits above the page\'s content, under its title.',
|
|
1343
|
-
instead: '"Pick `Note` for inline contextual messages tied to a single field or card, `Toast` for transient acknowledgments, `Modal` for confirmations." (https://vercel.com/geist/project-banner) Ours: `Note` is `MessageNotice` (`placement="section"`), `Toast` is `showMessageToast` and `Modal` is `ConfirmDialog`. For a measured level use a `KpiCard` with its tone; crossing a threshold is a notification in the inbox, not a banner.',
|
|
1344
|
-
behaviour: '"Project Banner is non-dismissible by design. If the message can be dismissed without resolving the underlying state, it isn’t banner-worthy; move it to a `Note`." (https://vercel.com/geist/project-banner) "Show one Project Banner at a time. Stacking competing banners drowns the most urgent state." (https://vercel.com/geist/project-banner) "Always pass a `callToAction` that resolves the state. A banner with no route is a dead end." (https://vercel.com/geist/project-banner) Ours: a strip as wide as its container: a filled ground with a line on its top and bottom edge only, no corners, 40 px high and centred, with the mark, the sentence and the fix on one row; under 768 px the fix drops under the sentence, aligned with its text. Colour sits on the whole strip (ground, both lines, mark and text); the fix is an underlined link in the darkest step of the same hue (neutral: `text-foreground`) and returns to the strip\'s text on hover. Neutral and info take the Info mark, warning and destructive the TriangleAlert. `action` is the `callToAction`: one command, never two, never a ready-made button, and there is no close button. Required: `action`, `title`, `tone` and `liveRegion`; the mark is always drawn.',
|
|
1343
|
+
instead: '"Pick `Note` for inline contextual messages tied to a single field or card, `Toast` for transient acknowledgments, `Modal` for confirmations." (https://vercel.com/geist/project-banner) Ours: there is no announcement strip. `Note` is `MessageNotice` (`placement="section"`), `Toast` is `showMessageToast` and `Modal` is `ConfirmDialog`. For a measured level use a `KpiCard` with its tone; crossing a threshold is a notification in the inbox, not a banner.',
|
|
1344
|
+
behaviour: '"Project Banner is non-dismissible by design. If the message can be dismissed without resolving the underlying state, it isn’t banner-worthy; move it to a `Note`." (https://vercel.com/geist/project-banner) "Show one Project Banner at a time. Stacking competing banners drowns the most urgent state." (https://vercel.com/geist/project-banner) "Always pass a `callToAction` that resolves the state. A banner with no route is a dead end." (https://vercel.com/geist/project-banner) Ours: a strip as wide as its container: a filled ground with a line on its top and bottom edge only, no corners, 40 px high and centred, with the mark, the sentence and the fix on one row; under 768 px the fix drops under the sentence, aligned with its text. Colour sits on the whole strip (ground, both lines, mark and text); the fix is an underlined link in the darkest step of the same hue (neutral: `text-foreground`) and returns to the strip\'s text on hover. Neutral and info take the Info mark, warning and destructive the TriangleAlert. `action` is the `callToAction`: one command, never two, never a ready-made button, and there is no close button. Required: `action`, `title`, `tone` and `liveRegion`; the mark is always drawn, and `icon` swaps the tone\'s mark for another lucide icon.',
|
|
1345
1345
|
words: '"`label` is one sentence in sentence case that names the impact: `Your Pro trial expires in 3 days.` Don’t open with `Heads up` or apologetic preambles." (https://vercel.com/geist/project-banner) "`callToAction.label` is Title Case `Verb + Noun` and points at the resolver: `Update Payment Method`, `Reactivate Project`, `Review Tokens`." (https://vercel.com/geist/project-banner) "Name the affected entity when the project context isn’t obvious from the surrounding chrome (`Production deployments are paused on my-project`)." (https://vercel.com/geist/project-banner) "Don’t encode severity in the copy with emoji or interjections; the variant carries that signal." (https://vercel.com/geist/project-banner) "`NoteLabel` is a 1–2 word Title Case prefix that names the topic: `Region Change`, `Rate Limit`, `Plan Limit`. Cut hedges like `Heads Up`, `FYI`, and `Note`." (https://vercel.com/geist/note) Ours: the `title` is the `label`: the one sentence, naming the condition, its impact and the object. A `description` only adds what the title leaves out, in one more sentence. Our `label` is the `NoteLabel` topic prefix. The action is sentence case, verb and noun, pointing at the resolver: "Open Slack settings", "Update card". No "Please". The words come from the product\'s catalogue code.',
|
|
1346
1346
|
access: '`liveRegion="polite"` gives `role="status"`, announced once when it appears; `"off"` gives `role="note"` for one already there at load. The mark, given or not, is hidden.',
|
|
1347
1347
|
},
|
|
@@ -1363,7 +1363,7 @@ export const USAGE = [
|
|
|
1363
1363
|
summary: "A still placeholder in the shape of content whose layout is known.",
|
|
1364
1364
|
when: '"Show a Skeleton when async data fills a known layout: table rows, card grids, profile blocks, sidebars." (https://vercel.com/geist/skeleton) Ours: a group of placeholders sits in the `SkeletonRegion` that later holds the data.',
|
|
1365
1365
|
instead: '"For a single in-flight action, use `Spinner`; for an indeterminate inline wait, use `LoadingDots`; for known progress, use `Progress`." "Don’t use Skeleton as permanent decoration or as a placeholder for empty states." "When there’s no data to load, render an `EmptyState`." (https://vercel.com/geist/skeleton) Ours: there is no spinner and no loading dots: a wait on something the person pressed is `PendingLabel` and `WaitingLine`. An unknown layout shows nothing until the data arrives.',
|
|
1366
|
-
behaviour: '"Set `width` and `height` to match the final content so the layout doesn’t shift when data resolves." "A 200×20 block becoming an 80×16 string reads as a glitch." "Pick `pill`, `rounded`, or `squared` to mirror the eventual element’s shape (avatars `pill`, buttons and chips `rounded`, image tiles `squared`)." "When the skeleton wraps children, keep dimensions stable so the reveal swap doesn’t reflow surrounding content." "If you do not pass a fixed size, it will be calculated automatically." "The skeleton will hide when children are not null, but the size is retained." (https://vercel.com/geist/skeleton) Ours: `pill` is `circle` and `rounded` is `rectangle`. A skeleton with neither a size nor a child does not compile: `height` with `width`, or `className` for a size a token sets (`h-4 w-full`; the type cannot check that it holds one), or `boxHeight` with `height` for a text line, where the bar sits at the top of the box and the rest is space below it. Given a size, pass the content as children, `null` until it arrives: the bar shows, then the content replaces it in the same box. Without a size, wrap the real control and `show` covers it, `show={false}` reveals it, rather than drawing a twin of it. Still: it never pulses or shimmers. It resolves to data, an absence or a failure.',
|
|
1366
|
+
behaviour: '"Set `width` and `height` to match the final content so the layout doesn’t shift when data resolves." "A 200×20 block becoming an 80×16 string reads as a glitch." "Pick `pill`, `rounded`, or `squared` to mirror the eventual element’s shape (avatars `pill`, buttons and chips `rounded`, image tiles `squared`)." "When the skeleton wraps children, keep dimensions stable so the reveal swap doesn’t reflow surrounding content." "If you do not pass a fixed size, it will be calculated automatically." "The skeleton will hide when children are not null, but the size is retained." (https://vercel.com/geist/skeleton) Ours: `pill` is `circle` and `rounded` is `rectangle`. A skeleton with neither a size nor a child does not compile: `height` with `width`, or `className` for a size a token sets (`h-4 w-full`; the type cannot check that it holds one), or `boxHeight` with `height` for a text line, where the bar sits at the top of the box and the rest is space below it. Given a size, pass the content as children, `null` until it arrives: the bar shows, then the content replaces it in the same box. Without a size, wrap the real control and `show` covers it, `show={false}` reveals it, rather than drawing a twin of it. Still: it never pulses or shimmers, and Geist\'s `animated` option is not taken. It resolves to data, an absence or a failure.',
|
|
1367
1367
|
words: 'Ours: none. No "Loading" text beside it.',
|
|
1368
1368
|
access: '"Skeletons are decorative; avoid placing focusable controls inside them while loading." "Disable the shimmer with the `no animation` variant on low-power surfaces and respect `prefers-reduced-motion`." (https://vercel.com/geist/skeleton) Ours: hidden from assistive technology and never focusable; an unsized wrapper covers its children inert and is `aria-busy` while it covers them. There is no shimmer to disable, so the placeholder is the same under `prefers-reduced-motion`. A group of bare placeholders needs a `SkeletonRegion`.',
|
|
1369
1369
|
},
|
|
@@ -1383,9 +1383,9 @@ export const USAGE = [
|
|
|
1383
1383
|
module: "sonner",
|
|
1384
1384
|
family: "Feedback",
|
|
1385
1385
|
summary: "The one place toasts appear, mounted once per product.",
|
|
1386
|
-
when: '"A succinct message that is displayed temporarily." (https://vercel.com/geist/toast) "Use a toast for non-blocking acknowledgments of user-initiated actions: `Domain added`, `Project archived`, `Deployment canceled`." (https://vercel.com/geist/toast) "Pick the method by how the user experienced the event, not by HTTP status. A user-canceled deploy is `toasts.message(\'Deployment canceled\')`, not `success`. A partial deploy with skipped routes is `toasts.warning(...)`." (https://vercel.com/geist/toast) Ours: mount it once and raise toasts through `showMessageToast`. `toasts.message` is the tone `info`, and `success`, `warning` and `error` keep their names.',
|
|
1386
|
+
when: '"A succinct message that is displayed temporarily." (https://vercel.com/geist/toast) "Use a toast for non-blocking acknowledgments of user-initiated actions: `Domain added`, `Project archived`, `Deployment canceled`." (https://vercel.com/geist/toast) "Pick the method by how the user experienced the event, not by HTTP status. A user-canceled deploy is `toasts.message(\'Deployment canceled\')`, not `success`. A partial deploy with skipped routes is `toasts.warning(...)`." (https://vercel.com/geist/toast) Ours: mount it once and raise toasts through `showMessageToast` from `@nadicodeai/ui/components/toast`. `toasts.message` is the tone `info`, and `success`, `warning` and `error` keep their names.',
|
|
1387
1387
|
instead: '"Don’t use a toast alone for billing failures, permission denials, or build failures the user has to triage. Pair a ≤6-word toast (`Build failed`) with a persistent row carrying the recovery step and a stable identifier." (https://vercel.com/geist/toast) "Field-level validation belongs on the `Input`, not in a toast. Persistent configuration warnings belong in `Note` or `Banner`." (https://vercel.com/geist/toast) Ours: the persistent row is a `MessageNotice`, a field\'s wrong value stays on the field (`FieldError`), and a lasting condition is a `NoticeBanner`.',
|
|
1388
|
-
behaviour: "\"Default toasts auto-dismiss; pass `preserve` only when the user must read or act on the message before it disappears.\" (https://vercel.com/geist/toast) \"Undo snackbars stay on screen for 5–10 seconds and pair the past-tense toast with a single `Undo` action button.\" (https://vercel.com/geist/toast) \"Don’t stack toasts to narrate one async flow; emit the success or error toast at the terminal step.\" (https://vercel.com/geist/toast) Ours: `showMessageToast` takes no `preserve`, so every toast leaves after 6 s, which is also the Undo window; the first action with a handler becomes the toast's one button. Colour sits on the whole toast: `success` fills it with the accent blue (Geist's blue success, the same hue as an `info` note), `warning` and `error` with their solid colour, each with that colour's text; `info` stays the solid floating surface, never glass. The tone's mark stays, because a tone must read without colour and the hidden tone word Geist uses would need a word in the product's language that `showMessageToast` does not take. A toast is 420 px wide at most, 16 px padded, with a close button of 32 px at its trailing edge. A toast that stays up shows the waiting line, not a spinner. Required: `closeLabel` and `regionLabel`, because the close button and the region need a name in the product's language.",
|
|
1388
|
+
behaviour: "\"Default toasts auto-dismiss; pass `preserve` only when the user must read or act on the message before it disappears.\" (https://vercel.com/geist/toast) \"Undo snackbars stay on screen for 5–10 seconds and pair the past-tense toast with a single `Undo` action button.\" (https://vercel.com/geist/toast) \"Don’t stack toasts to narrate one async flow; emit the success or error toast at the terminal step.\" (https://vercel.com/geist/toast) Ours: `showMessageToast` takes no `preserve`, so every toast leaves after 6 s, which is also the Undo window; the first action with a handler becomes the toast's one button, and its label is the catalogue's, so a literal `Undo` is not enforced. A toast always has a tone, so there is no neutral toast without a mark: `info` is the nearest. Colour sits on the whole toast: `success` fills it with the accent blue (Geist's blue success, the same hue as an `info` note), `warning` and `error` with their solid colour, each with that colour's text; `info` stays the solid floating surface, never glass. The tone's mark stays, because a tone must read without colour and the hidden tone word Geist uses would need a word in the product's language that `showMessageToast` does not take. A toast is 420 px wide at most, 16 px padded, with a close button of 32 px at its trailing edge. A toast that stays up shows the waiting line, not a spinner. Required: `closeLabel` and `regionLabel`, because the close button and the region need a name in the product's language.",
|
|
1389
1389
|
words: '"One sentence, sentence case, no trailing period when the toast is a single sentence." (https://vercel.com/geist/toast) "Completion toasts follow `{Noun} {past-participle}`: `Blob deleted`, `Domain added`, `Environment variable saved`. Never include `successfully`; the action name implies it." (https://vercel.com/geist/toast) "Error toasts are two sentences with periods and end with a recovery step: `Couldn’t verify domain. Try again.` Use `Couldn’t` for user-state errors and `Failed to` for system or infra errors; match adjacent shipped copy and don’t flip mid-flow." (https://vercel.com/geist/toast) "Pair the toast verb 1:1 with the destructive button verb (`Delete Project` then `Project deleted`, never `Project removed`)." (https://vercel.com/geist/toast) "Undo snackbars use the literal label `Undo`, never `Restore`, `Bring Back`, or `Cancel`. Only use the pattern when the rollback is safe." (https://vercel.com/geist/toast) Ours: the words come from the catalogue; *Mailbox removed* pairs with its button *Remove mailbox*.',
|
|
1390
1390
|
access: '"The toast region announces with `aria-live="polite"`; reserve `assertive` for blocking errors that interrupt a flow." (https://vercel.com/geist/toast) "Don’t put primary navigation inside a toast; transient surfaces vanish before keyboard users can reach them." (https://vercel.com/geist/toast) Ours: the toaster\'s live region is named by `regionLabel` and the close button by `closeLabel`. Navigation is never the only route to recovery: the record\'s own row keeps its controls. Undo is reachable by keyboard (Alt+T moves focus to the toasts) while it shows.',
|
|
1391
1391
|
},
|
|
@@ -1394,9 +1394,9 @@ export const USAGE = [
|
|
|
1394
1394
|
module: "tooltip",
|
|
1395
1395
|
family: "Feedback",
|
|
1396
1396
|
summary: "A short, optional fact about its trigger.",
|
|
1397
|
-
when: '"Use a Tooltip to explain **why** something exists, not **what** it is." "The visible label names the thing; the tooltip adds the constraint, scope, or limit." (https://vercel.com/geist/tooltip) "Use lifecycle Tooltips (`Alpha`, `Experimental`, `Beta`, `Early Access`) to name the limits that apply: API stability, SLA, support, pricing, retention." (https://vercel.com/geist/tooltip) Ours:
|
|
1397
|
+
when: '"Use a Tooltip to explain **why** something exists, not **what** it is." "The visible label names the thing; the tooltip adds the constraint, scope, or limit." (https://vercel.com/geist/tooltip) "Use lifecycle Tooltips (`Alpha`, `Experimental`, `Beta`, `Early Access`) to name the limits that apply: API stability, SLA, support, pricing, retention." (https://vercel.com/geist/tooltip) Ours: the parts are `TooltipProvider`, `Tooltip`, `TooltipTrigger` and `TooltipContent`. A shortcut is shown in a tooltip only when one is bound.',
|
|
1398
1398
|
instead: '"For an entity preview with metadata rows (avatar, identifier, 2–4 facts, optional action), use `Context Card`." "For long-form content or actions that need persistence, route to a `Drawer` or page navigation." (https://vercel.com/geist/tooltip) Ours: `Context Card` is `HoverCard` and `Drawer` is `Sheet`. A disabled command\'s reason is written under it, never only in a tooltip.',
|
|
1399
|
-
behaviour: '"Tooltips open on hover and on keyboard focus." "Keep the default ~150ms entry delay so the tooltip doesn’t flicker on a sweeping mouse." "Don’t wrap a labelled `Input` in a Tooltip." "The trigger lands on the `<label>`, not the field, and the tooltip text becomes a second invisible label for screen readers." "Put help on a sibling icon button." "Keep primary actions outside the Tooltip; touch users can’t reach a hover-revealed control." (https://vercel.com/geist/tooltip) Ours: the pointer wait is 150 ms and keyboard focus opens it at once; `delay={0}` on the trigger or a `TooltipProvider` removes the wait. Above by default, 10 px from the trigger, at most 250 px wide, 13 px text balanced over its lines. Never interactive. `arrow={false}` drops the stem; `centerText={false}` left-aligns a tooltip that wraps. `TooltipContent` requires its text as children. Colour: one inverted neutral, the page\'s ink as the fill and its surface as the text, with a stem of the same fill and no hue, in both themes; a shortcut is a `Kbd` after the text, drawn 20 px high.',
|
|
1399
|
+
behaviour: '"Tooltips open on hover and on keyboard focus." "Keep the default ~150ms entry delay so the tooltip doesn’t flicker on a sweeping mouse." "Don’t wrap a labelled `Input` in a Tooltip." "The trigger lands on the `<label>`, not the field, and the tooltip text becomes a second invisible label for screen readers." "Put help on a sibling icon button." "Keep primary actions outside the Tooltip; touch users can’t reach a hover-revealed control." (https://vercel.com/geist/tooltip) Ours: the pointer wait is 150 ms and keyboard focus opens it at once; `delay={0}` on the trigger or a `TooltipProvider` removes the wait. Above by default, 10 px from the trigger, at most 250 px wide, 13 px text balanced over its lines. Never interactive. `arrow={false}` drops the stem (Geist\'s `tip={false}`); `centerText={false}` left-aligns a tooltip that wraps (Geist\'s `center={false}`). Geist\'s coloured `type` is not provided. `TooltipContent` requires its text as children. Colour: one inverted neutral, the page\'s ink as the fill and its surface as the text, with a stem of the same fill and no hue, in both themes; a shortcut is a `Kbd` after the text, drawn 20 px high.',
|
|
1400
1400
|
words: '"One sentence or fragment in `text`." "Sentence case, no period for a single fragment." "Skip tooltips that repeat the visible label (`text="Rate Limit"` on a `Rate Limit` button) or describe the interaction (`text="Click to override"`)." "Lifecycle Tooltips follow `{Label}: {one-line meaning}. {Specific limit}.`" "For a paid Beta feature, combine the lifecycle and pricing in one Tooltip rather than stacking two badges." (https://vercel.com/geist/tooltip) Ours: the text is `TooltipContent`\'s children, for example *Resets on 1 November*.',
|
|
1401
1401
|
access: '"An icon-only trigger needs an `aria-label` that names the action; the Tooltip body adds context, it doesn’t replace the label." "Escape closes the tooltip and returns focus to the trigger." (https://vercel.com/geist/tooltip)',
|
|
1402
1402
|
},
|
|
@@ -1418,7 +1418,7 @@ export const USAGE = [
|
|
|
1418
1418
|
summary: "The one moving thing a system wait may show: a 2 px ink line, after a second.",
|
|
1419
1419
|
when: "A system wait the person asked for that runs past a second: a save, a connection, a download without a count.",
|
|
1420
1420
|
instead: "Under a second the `PendingLabel` alone. A known amount is a progress line with a count. A page load is `Skeleton`. An Agent working is her orb and a sentence.",
|
|
1421
|
-
behaviour: "Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing. No track colour; the segment is ink, never info blue, crossing at 32% width. Under reduced motion it breathes.",
|
|
1421
|
+
behaviour: "Takes its 2 px from the start and stays empty until 1 s, so its arrival moves nothing. No track colour; the segment is ink, never info blue, crossing at 32% width. Under reduced motion it breathes in opacity, across the whole track, instead of travelling.",
|
|
1422
1422
|
words: "None of its own. The control beside it carries the pending word; a standalone line takes a `label` naming the work: *Connecting Telegram*.",
|
|
1423
1423
|
access: 'With a label: `role="status"`, announced once after the second. `decorative` when the control beside it already says it. The segment is hidden.',
|
|
1424
1424
|
},
|
|
@@ -1429,7 +1429,7 @@ export const USAGE = [
|
|
|
1429
1429
|
summary: "The account menu at the foot of a shell's navigation: whose account it is, and the menu opening on that same identity with what they can do beneath it.",
|
|
1430
1430
|
when: "The account at the sidebar's foot in the Portal, Nadia and Alveare: the gear to the account's settings, Theme, Language, a product's own settings, its commands, and Sign out.",
|
|
1431
1431
|
instead: "A record's or a page's commands are an ActionMenu; a setting with more than a few options or one that needs a Save belongs on a SettingsPage.",
|
|
1432
|
-
behaviour: "The menu opens 6 px above its trigger, 288 px wide, as Vercel's does: a header of the name and every line under it with the gear at its end, one divider, then 36 px rows, each its label leading and its icon or control trailing: Theme and Language as small round pills, the product's choices as submenus, its commands with any reading before their icon, and Sign out last. Theme and Language apply at once and keep the menu open. While signing out, Sign out says its pending word, the menu stays open and the trigger is locked; a failure shows under it. A Sign out that `confirms` closes the menu instead, for the product's own confirmation.",
|
|
1432
|
+
behaviour: "The menu opens 6 px above its trigger, 288 px wide, as Vercel's does: a header of the name and every line under it with the gear at its end and no picture, one divider, then 36 px rows, each its label leading and its icon or control trailing in muted ink: Theme and Language as small round pills, the product's choices as submenus, its commands with any reading before their icon, and Sign out last in the body colour, since signing out destroys nothing. Ruled by the owner on 16 October 2026. Theme and Language apply at once and keep the menu open. While signing out, Sign out says its pending word, the menu stays open and the trigger is locked; a failure shows under it. A Sign out that `confirms` closes the menu instead, for the product's own confirmation.",
|
|
1433
1433
|
words: 'The menu\'s name ("Account"), the person\'s name and lines, then each row\'s noun or verb from the product\'s catalogue: "Theme", "Language", "Usage", "Sign out" and "Signing out".',
|
|
1434
1434
|
access: 'The trigger and the menu are named "{label}: {name}"; the gear carries its label; the pending sign-out is aria-busy and announced through a status; a failure is an alert.',
|
|
1435
1435
|
},
|
|
@@ -1440,7 +1440,7 @@ export const USAGE = [
|
|
|
1440
1440
|
summary: "A record's menu opened from its row by a right-click: the mirror of its `ActionMenu`.",
|
|
1441
1441
|
when: '"ContextMenu must never be the only entry point for an action. Mirror every item in a visible `Menu` trigger or row button so mouse-only and keyboard-only users have parity." (https://vercel.com/geist/context-menu) Ours: a right-click or long-press convenience on a record that already has a visible menu with the same commands.',
|
|
1442
1442
|
instead: '"For a global command palette, use `CommandMenu`; for a dropdown opened from a visible trigger, use `Menu`." (https://vercel.com/geist/context-menu) Ours: for anything a person must find use the visible three dots or a button. For global search use `CommandDialog`.',
|
|
1443
|
-
behaviour: '"Bind to right-click on desktop and long-press on touch. Suppress the native browser menu only on the trigger area, not the whole page." (https://vercel.com/geist/context-menu) "Position the menu at the pointer; if it would overflow the viewport, flip horizontally then vertically before clipping." (https://vercel.com/geist/context-menu) "Close on activation, Escape, and outside-click. Don’t close on hover-out." (https://vercel.com/geist/context-menu) Ours: the dropdown\'s surface and rows exactly. The same commands in the same order, the same muted rows and reasons, destructive last after a divider. With no command the row renders alone.',
|
|
1443
|
+
behaviour: '"Bind to right-click on desktop and long-press on touch. Suppress the native browser menu only on the trigger area, not the whole page." (https://vercel.com/geist/context-menu) "Position the menu at the pointer; if it would overflow the viewport, flip horizontally then vertically before clipping." (https://vercel.com/geist/context-menu) "Close on activation, Escape, and outside-click. Don’t close on hover-out." (https://vercel.com/geist/context-menu) Ours: it takes the same `RecordMenu` as the row\'s `ActionMenu`, over the `ContextMenu` family, with the dropdown\'s surface and rows exactly. The same commands in the same order, the same muted rows and reasons, destructive last after a divider. With no command the row renders alone.',
|
|
1444
1444
|
words: '"Items follow the same rules as `Menu`: Title Case `Verb + Noun` (`Open in New Tab`, `Copy URL`, `Delete Deployment`). Bare verbs are wrong." (https://vercel.com/geist/context-menu) Ours: sentence case, "Copy address". Identical to the visible menu\'s, in the same order; a command reached two ways reads the same both ways.',
|
|
1445
1445
|
access: '"Trigger the keyboard menu key (`Shift+F10` on Windows/Linux, the menu key, or platform equivalent) to open the same menu without right-click." (https://vercel.com/geist/context-menu) "Up/Down arrows move focus, Enter/Space activates, Escape closes and returns focus to the row." (https://vercel.com/geist/context-menu) Ours: the menu is named by `label`, which is required.',
|
|
1446
1446
|
},
|
|
@@ -1451,7 +1451,7 @@ export const USAGE = [
|
|
|
1451
1451
|
summary: "A record's three dots: the menu of its secondary commands, headed by the record's name.",
|
|
1452
1452
|
when: '"An overflow menu triggered by a three-dot icon that reveals additional actions in a dropdown." (https://vercel.com/geist/dots-menu) "Use Menu for a discoverable trigger that opens a list of actions on a single resource (a dots menu on a row, a dropdown on a primary entity)." (https://vercel.com/geist/menu) Ours: the secondary commands of one record from a visible trigger: the row\'s three dots, or the page\'s, which comes last in the title row. A record\'s one main task is a visible button beside the dots; one non-destructive command that can run, alone, is drawn as a button by `RecordList` and `PageActions`, never as a menu; one that cannot run or is locked stays in the menu so its reason is read.',
|
|
1453
1453
|
instead: '"For right-click or long-press on a row, use `ContextMenu`. For global commands behind `⌘K`, use `CommandMenu`. For two related primary actions, use a split button rather than burying the secondary action." (https://vercel.com/geist/menu) Ours: for a record\'s main task use a visible button (Edit opens the row). For finding across everything use `CommandDialog`. For a value to choose use `Select`. For right-click use the mirror, `ActionContextMenu`.',
|
|
1454
|
-
behaviour: '"Open on click, not hover; hover-open menus collide with screen readers and trackpad scrolls." (https://vercel.com/geist/menu) "Position auto-flips based on window bounds; don’t hardcode a side that clips on narrow viewports." (https://vercel.com/geist/menu) "Close on item activation, Escape, and outside-click. Don’t auto-close on a hover-out." (https://vercel.com/geist/menu) "Use `MenuItemLocked` for permission-gated actions so the lock icon and disabled state explain why the row is inert." (https://vercel.com/geist/menu) Ours: that is `locked` with its `reason`, in the
|
|
1454
|
+
behaviour: '"Open on click, not hover; hover-open menus collide with screen readers and trackpad scrolls." (https://vercel.com/geist/menu) "Position auto-flips based on window bounds; don’t hardcode a side that clips on narrow viewports." (https://vercel.com/geist/menu) "Close on item activation, Escape, and outside-click. Don’t auto-close on a hover-out." (https://vercel.com/geist/menu) "Use `MenuItemLocked` for permission-gated actions so the lock icon and disabled state explain why the row is inert." (https://vercel.com/geist/menu) Ours: that is `locked` with its `reason`, which is required: the row stays in place, inert, with a lock at its end and the reason under its label. The trigger is a ghost icon button at `{spacing.control-height}` (36 px) wherever it stands, named for its record ("More for pec@acme.it"), and comes last, after any button; it keeps its fill while open. The menu opens 10 px under the trigger, aligned to its end: the record\'s name, the commands with their icons first and the value one acts on in mono under it, then the destructive ones after a divider in the error colour. A command that cannot run now takes `disabledReason`: it stays in place, 36 px like the rest, with its text muted, never dimmed, and says why in a line under its label; a destructive one is muted, not red; a running one takes `pending` and `pendingLabel`. Nothing renders with no command.',
|
|
1455
1455
|
words: '"Item children are Title Case `Verb + Noun` (`Rename Project`, `Duplicate Deployment`). Bare verbs like `Rename` or `Edit` are wrong outside obvious single-object context." (https://vercel.com/geist/menu) "End an item with `…` only when activating it opens a follow-up dialog (`Rename…`, `Transfer to Team…`)." (https://vercel.com/geist/menu) "Group destructive items at the bottom, separated by a divider, and keep the destructive copy as `Verb + Noun` (`Delete Project`, never bare `Delete`)." (https://vercel.com/geist/menu) "Cap a Menu around 10 items. Past that, group with `MenuSection` or move secondary actions to a settings page." (https://vercel.com/geist/menu) Ours: sentence case, "Remove mailbox", not Title Case. The trigger reads "More for {record}"; the header is the record\'s name. Rows wrap, never truncate.',
|
|
1456
1456
|
access: '"Typeahead jumps to the next item whose label starts with the typed character; keep the visible label first so typeahead matches what the user sees." (https://vercel.com/geist/menu) "Return focus to the trigger on close so keyboard users keep their place in the row." (https://vercel.com/geist/menu) Ours: the trigger is a ghost icon button named by `label`, which is required, `aria-expanded` while open. `role="menu"`, items `menuitem` in a group named by `record`, which is required; every command requires its `icon`. The value a command acts on and its reason are the item\'s description; icons hidden.',
|
|
1457
1457
|
},
|
|
@@ -1494,8 +1494,8 @@ export const USAGE = [
|
|
|
1494
1494
|
family: "Menus",
|
|
1495
1495
|
summary: "A bottom sheet for a short job on a phone.",
|
|
1496
1496
|
when: '"Only use a Drawer on small viewports." (https://vercel.com/geist/drawer) "Drawer renders as a bottom sheet on small viewports only. On desktop render `Modal` (or `Sheet` for lateral context) directly instead of forcing Drawer." (https://vercel.com/geist/drawer) "A Drawer is good for short focused mobile actions: a single form, a filter sheet, a primary CTA paired with `Cancel`." (https://vercel.com/geist/drawer) Ours: no screen calls it today.',
|
|
1497
|
-
instead: '"Don’t use Drawer to confirm destructive actions. The lack of a fully blocking dim weakens the severity signal that delete and revoke flows need; route to `Modal`." (https://vercel.com/geist/drawer) Ours: on a desktop a blocking job is `Dialog` and context beside a record is `Sheet`; a catalogue
|
|
1498
|
-
behaviour: '"Tap-outside and swipe-down dismiss by default; preserve both unless the form has dirty input." (https://vercel.com/geist/drawer) "Use `verticalScroll` so the drawer body scrolls inside its frame instead of the page behind it." (https://vercel.com/geist/drawer) "Cap content height with `customHeight` only when the default height clips the primary action; the action and `Cancel` must stay above the fold." (https://vercel.com/geist/drawer) Ours: `unsaved.dirty` holds back the swipe, Escape and a press outside, and `unsaved.onDismissBlocked` runs. The body always scrolls inside the frame, so there is no `verticalScroll`; `height` is Geist\'s `customHeight`. It rises from the bottom edge and follows the finger, is at most 80% of the viewport tall, and is all neutral. Nested drawers stack.',
|
|
1497
|
+
instead: '"Don’t use Drawer to confirm destructive actions. The lack of a fully blocking dim weakens the severity signal that delete and revoke flows need; route to `Modal`." (https://vercel.com/geist/drawer) Ours: on a desktop a blocking job is `Dialog` and context beside a record is `Sheet`; a catalogue is `CatalogDrawer` and a setup in steps is `FormDrawer`; both are the right-side sheet, not this one.',
|
|
1498
|
+
behaviour: '"Tap-outside and swipe-down dismiss by default; preserve both unless the form has dirty input." (https://vercel.com/geist/drawer) "Use `verticalScroll` so the drawer body scrolls inside its frame instead of the page behind it." (https://vercel.com/geist/drawer) "Cap content height with `customHeight` only when the default height clips the primary action; the action and `Cancel` must stay above the fold." (https://vercel.com/geist/drawer) Ours: `unsaved.dirty` holds back the swipe, Escape, the system back gesture and a press outside while input is typed, and `unsaved.onDismissBlocked` runs. The body always scrolls inside the frame, so there is no `verticalScroll`; `height` is Geist\'s `customHeight`. It rises from the bottom edge and follows the finger, is at most 80% of the viewport tall, and is all neutral. Nested drawers stack.',
|
|
1499
1499
|
words: '"`DrawerTitle` is a Title Case statement that names the entity (`Deployment Details`, `Filter Logs`)." (https://vercel.com/geist/drawer) "Body is sentence case prose. One primary `Verb + Noun` button and a literal `Cancel`; don’t cram a destructive cascade copy into the smaller frame." (https://vercel.com/geist/drawer) "Don’t restate the page heading as the drawer title; name what this view does." (https://vercel.com/geist/drawer) Ours: the title is sentence case (*Filter logs*), centred at 18/24 and semibold.',
|
|
1500
1500
|
access: '"Trap focus inside the drawer while open and return focus to the trigger on close." (https://vercel.com/geist/drawer) "Escape closes the drawer; honor the system back gesture on mobile so users can dismiss without reaching for the close affordance." (https://vercel.com/geist/drawer) "Lock body scroll on open and restore it on close so iOS rubber-band scroll doesn’t leak through." (https://vercel.com/geist/drawer) Ours: a modal dialog named by its title. It draws no Close of its own; a footer that needs a visible one uses `DrawerClose`.',
|
|
1501
1501
|
},
|
|
@@ -1505,8 +1505,8 @@ export const USAGE = [
|
|
|
1505
1505
|
family: "Menus",
|
|
1506
1506
|
summary: "A menu that floats from a visible trigger, built from its parts: the surface, labelled groups, commands, links, choices and dividers.",
|
|
1507
1507
|
when: '"Use Menu for a discoverable trigger that opens a list of actions on a single resource (a dots menu on a row, a dropdown on a primary entity)." (https://vercel.com/geist/menu) Ours: a menu no composition draws: a column chooser, a sort, a product\'s own choices in a submenu.',
|
|
1508
|
-
instead: "\"For right-click or long-press on a row, use `ContextMenu`. For global commands behind `⌘K`, use `CommandMenu`. For two related primary actions, use a split button rather than burying the secondary action.\" (https://vercel.com/geist/menu) Ours: those are `ContextMenu` and `CommandDialog`; two related primary actions stand side by side as buttons. A record's or a page's commands are `ActionMenu` or `PageActions`' menu, from data. The account's rows are `AccountMenu`. A value to choose in a form is `Select`.",
|
|
1509
|
-
behaviour: '"Dropdown menu opened via button. Supports typeahead and keyboard navigation." (https://vercel.com/geist/menu) "Open on click, not hover; hover-open menus collide with screen readers and trackpad scrolls." (https://vercel.com/geist/menu) "Position auto-flips based on window bounds; don’t hardcode a side that clips on narrow viewports." (https://vercel.com/geist/menu) "Close on item activation, Escape, and outside-click. Don’t auto-close on a hover-out." (https://vercel.com/geist/menu) "Use `MenuItemLocked` for permission-gated actions so the lock icon and disabled state explain why the row is inert." (https://vercel.com/geist/menu) Ours: that is `locked` with its `reason`. A solid floating surface, 200 px wide at least, 10 px from its trigger. A labelled trigger may end in a chevron; an icon trigger has none. Safe commands come first, each with its icon, groups split by a divider, the destructive command last. Colour sits on that command alone: its text is the error colour at rest and its row takes the soft error fill when highlighted; the panel, the other rows, a section title and the divider stay neutral, and a suffix or lock is muted, 12 px after the label. A command that cannot run stays in place with muted text, never dimmed, and says why in a muted line under its label, which a tap on a phone reads as well as a pointer, since a tooltip opens for the mouse only; a destructive one is muted, not red. Every row is 36 px (44 on touch) with the pointer cursor, or the default one when it cannot be chosen, a row with a reason growing by its line, and a section title is a muted row of that height. The highlight is the row\'s fill alone: no text inside a row changes colour with it, and a row that cannot be chosen takes no highlight.',
|
|
1508
|
+
instead: "\"For right-click or long-press on a row, use `ContextMenu`. For global commands behind `⌘K`, use `CommandMenu`. For two related primary actions, use a split button rather than burying the secondary action.\" (https://vercel.com/geist/menu) Ours: those are `ContextMenu` and `CommandDialog`; two related primary actions stand side by side as buttons. A record's or a page's commands are `ActionMenu` or `PageActions`' menu, from data. The account's rows are `AccountMenu`. These four draw their rows with `DropdownMenuCommand`; the `DropdownMenu` family is used directly only for a choice menu such as a sort, with `DropdownMenuLink` for a destination row. A value to choose in a form is `Select`.",
|
|
1509
|
+
behaviour: '"Dropdown menu opened via button. Supports typeahead and keyboard navigation." (https://vercel.com/geist/menu) "Open on click, not hover; hover-open menus collide with screen readers and trackpad scrolls." (https://vercel.com/geist/menu) "Position auto-flips based on window bounds; don’t hardcode a side that clips on narrow viewports." (https://vercel.com/geist/menu) "Close on item activation, Escape, and outside-click. Don’t auto-close on a hover-out." (https://vercel.com/geist/menu) "Use `MenuItemLocked` for permission-gated actions so the lock icon and disabled state explain why the row is inert." (https://vercel.com/geist/menu) Ours: that is `locked` with its `reason`. A solid floating surface, 200 px wide at least, 10 px from its trigger. A labelled trigger may end in a chevron; an icon trigger has none. Safe commands come first, each with its icon, groups split by a divider, the destructive command last. Colour sits on that command alone: its text is the error colour at rest and its row takes the soft error fill when highlighted; the panel, the other rows, a section title and the divider stay neutral, and a suffix or lock is muted, 12 px after the label. A command that cannot run stays in place with muted text, never dimmed, and says why in a muted line under its label, which a tap on a phone reads as well as a pointer, since a tooltip opens for the mouse only; a destructive one is muted, not red. Every row, of any kind, is 36 px in the default comfortable density (44 on touch; the compact density, `density="compact"`, tightens the padding to about 28 px on a fine pointer, and no menu in the product uses it) with `{rounded.sm}` corners and the pointer cursor, or the default one when it cannot be chosen, a row with a reason growing by its line, and a section title is a muted row of that height. The highlight is the row\'s fill alone: no text inside a row changes colour with it, and a row that cannot be chosen takes no highlight. A submenu goes one level deep at most (`one-submenu-level`), and never holds a destructive command.',
|
|
1510
1510
|
words: '"Item children are Title Case `Verb + Noun` (`Rename Project`, `Duplicate Deployment`). Bare verbs like `Rename` or `Edit` are wrong outside obvious single-object context." (https://vercel.com/geist/menu) "End an item with `…` only when activating it opens a follow-up dialog (`Rename…`, `Transfer to Team…`)." (https://vercel.com/geist/menu) "Group destructive items at the bottom, separated by a divider, and keep the destructive copy as `Verb + Noun` (`Delete Project`, never bare `Delete`)." (https://vercel.com/geist/menu) "Section headers (`MenuSection title`) are Title Case, 1–2 words (`Workspace`, `Recent Projects`)." (https://vercel.com/geist/menu) "Cap a Menu around 10 items. Past that, group with `MenuSection` or move secondary actions to a settings page." (https://vercel.com/geist/menu) Ours: sentence case, not Title Case: "Remove mailbox", never a bare "Delete"; a section title is sentence case too. Italian runs a third longer; rows wrap, never truncate.',
|
|
1511
1511
|
access: '"Up/Down arrows move focus through items, Home/End jump to first/last, Enter or Space activates." (https://vercel.com/geist/menu) "Typeahead jumps to the next item whose label starts with the typed character; keep the visible label first so typeahead matches what the user sees." (https://vercel.com/geist/menu) "Return focus to the trigger on close so keyboard users keep their place in the row." (https://vercel.com/geist/menu) Ours: `role="menu"` named by its trigger, items `menuitem`. Unlike Geist, which skips disabled rows, arrows land on a command that cannot run so its reason is read; it is still never highlighted and never runs. A command\'s icon is hidden; the value it acts on and its reason are its description. `DropdownMenuCommand` requires its `icon`; a command that cannot run takes `disabledReason`, and one a person may not run is `locked` with its `reason`, so nothing is greyed without saying why.',
|
|
1512
1512
|
},
|
|
@@ -1538,10 +1538,10 @@ export const USAGE = [
|
|
|
1538
1538
|
family: "Menus",
|
|
1539
1539
|
summary: "A small control set tied to its trigger, on a solid floating surface.",
|
|
1540
1540
|
when: "A small control set tied to its trigger: filters, a scope chooser, a date range, a feedback form. The page stays live behind it.",
|
|
1541
|
-
instead:
|
|
1542
|
-
behaviour: "Solid floating surface, 12 px padding and gap. Opens on click, closes on Escape or outside press, focus returns to the trigger.
|
|
1541
|
+
instead: 'For commands use `DropdownMenu`. For a form that adds a record use `FormDialog`. For one phrase use `Tooltip`; for facts on hover use `HoverCard`. DESIGN.md "Menus and commands" decides which surface a job takes.',
|
|
1542
|
+
behaviour: "Solid floating surface, 12 px padding and gap. Opens on click, closes on Escape or outside press, focus returns to the trigger. A control set applies at once, with no Save inside; a feedback form is the exception: its button sends it, it shows its pending and result state, and it keeps what was typed when sending fails.",
|
|
1543
1543
|
words: "Title names the control's job (*Filter skills*); an optional one-line description. Options are nouns.",
|
|
1544
|
-
access: "Trigger has a visible name and `aria-expanded`. The popup is a labelled dialog that does not trap focus; Tab leaves it in order.",
|
|
1544
|
+
access: "Trigger has a visible name and `aria-expanded`. The popup is a labelled dialog that does not trap focus; Tab leaves it in order. Nothing essential shows only on hover: what a person needs to read stays visible in the trigger or the page, never only in the popup's hover state.",
|
|
1545
1545
|
},
|
|
1546
1546
|
{
|
|
1547
1547
|
name: "Sheet",
|
|
@@ -1549,8 +1549,8 @@ export const USAGE = [
|
|
|
1549
1549
|
family: "Menus",
|
|
1550
1550
|
summary: "A panel from the edge of the window, beside what it describes.",
|
|
1551
1551
|
when: '"Use Sheet for persistent associated context like deployment details, log row inspection, or a member profile, where the underlying page stays useful." (https://vercel.com/geist/sheet) Ours: also the product\'s navigation when the window is too narrow for the sidebar.',
|
|
1552
|
-
instead: '"For a blocking decision, use `Modal`. For a mobile-only bottom sheet, use `Drawer`." (https://vercel.com/geist/sheet) "Don’t use Sheet to confirm destructive actions. The default `modal=false` keeps the page interactive, which weakens severity for a delete or revoke." (https://vercel.com/geist/sheet) Ours: a blocking decision is `ConfirmDialog` or `Dialog`. A catalogue
|
|
1553
|
-
behaviour: '"Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: `modal` dims the page and blocks it, for a sheet that owns the screen, and `noOverlay` on its content drops the scrim again. An inset card with 16 px corners, 12 px from the window\'s edges; a side panel
|
|
1552
|
+
instead: '"For a blocking decision, use `Modal`. For a mobile-only bottom sheet, use `Drawer`." (https://vercel.com/geist/sheet) "Don’t use Sheet to confirm destructive actions. The default `modal=false` keeps the page interactive, which weakens severity for a delete or revoke." (https://vercel.com/geist/sheet) Ours: a blocking decision is `ConfirmDialog` or `Dialog`. A catalogue is `CatalogDrawer` and a setup in steps is `FormDrawer` with `job="steps"`, the modal sheets built on this; one record looked at from a list is `RecordSheet`, which leaves the page usable. A sheet is never chosen because an action takes input. For editing use the row or the record\'s page.',
|
|
1553
|
+
behaviour: '"Sheet defaults to `modal=false` so toasts and other high-z elements stay reachable. Keep that default unless the sheet owns the screen." (https://vercel.com/geist/sheet) "Pick `side` from the trigger location: a row inspector slides from `right`, a global filter from `left`. Don’t change sides mid-session." (https://vercel.com/geist/sheet) "Outside-click does not auto-close, so always render an explicit close affordance and honor Escape." (https://vercel.com/geist/sheet) Ours: `modal` dims the page and blocks it, for a sheet that owns the screen, and `noOverlay` on its content drops the scrim again. An inset card with 16 px corners, 12 px from the window\'s edges; a side panel owns its width, 512 px from 1024 px and the window less its margins below it, and only `RecordSheet` narrows it to 420 px between 640 and 1024 px. A given kind of sheet always opens on the same side. A press outside never closes it, Escape does, except the phone navigation, which `closesOnScrimPress` closes on a press on its scrim. The body scrolls between a header and a footer with its actions at the trailing edge, as Vercel\'s drawers (Production Checklist): the footer keeps the sheet\'s fill and stands under a 1 px rule, so its actions stay put while the body scrolls. Neutral only, with no hue but the focus ring; the primary is the only filled control. `closeLabel` is required.',
|
|
1554
1554
|
words: '"Title is Title Case and names the entity (`Deployment Details`, `Member Profile`), not the page action." (https://vercel.com/geist/sheet) "Body is read-mostly: sentence case prose with Title Case sub-headings. Action buttons are optional; when present, follow `Verb + Noun`." (https://vercel.com/geist/sheet) "Don’t duplicate the page header inside the sheet; the sheet is the detail layer." (https://vercel.com/geist/sheet) Ours: titles and sub-headings are sentence case where Geist writes Title Case. A footer link names where it goes: *Open Rossi Ottica*.',
|
|
1555
1555
|
access: '"Trap focus inside the sheet while it’s open and return focus to the trigger row on close so keyboard users keep their place in the list." (https://vercel.com/geist/sheet) "Provide a visible close button labeled `Close` (or an icon button with `aria-label="Close"`) since clicking outside doesn’t dismiss." (https://vercel.com/geist/sheet) "Announce the sheet with `aria-labelledby` pointing at the title; pair it with `aria-describedby` only when the body is short and load-bearing." (https://vercel.com/geist/sheet) Ours: a dialog named by its title, with a Close named in the product\'s words by `closeLabel`. Focus returns to the row or the navigation button that opened it.',
|
|
1556
1556
|
},
|
|
@@ -1627,7 +1627,7 @@ export const USAGE = [
|
|
|
1627
1627
|
summary: "The card every chart in the kit renders: one header, the summary, the plot, its legend and the reading's time. The plots (`ChartBarStacked`, `ChartAreaStacked`, `ChartBarMultiple`, `ChartBarHorizontal`) hold only their own plot; a page never builds a chart card frame by hand.",
|
|
1628
1628
|
when: "Every chart and every reading built around one: the frame a page never builds by hand. A chart is a named section of its page.",
|
|
1629
1629
|
instead: "A single figure is a `KpiCard`, never nested inside a chart. A ranking beside the plot goes in the card's `aside`, not a second card.",
|
|
1630
|
-
behaviour: "Header grid: title and period in the first column, controls and then the link to the reading's owner (a small ghost button ending in a chevron, as OpenRouter's \"Explore ›\") in the second from a 28 rem card width, under the title below it; a link with no controls stays at the trailing edge at every width. Summary, plot, legend, footer in that order. An empty dataset shows its `emptyState` in the plot's place. The footer is a recessed strip behind a hairline: the reading's time left,
|
|
1630
|
+
behaviour: "Header grid: title and period in the first column, controls and then the link to the reading's owner (a small ghost button ending in a chevron, as OpenRouter's \"Explore ›\") in the second from a 28 rem card width, under the title below it; a link with no controls stays at the trailing edge at every width. Summary, plot, legend, footer in that order. An empty dataset shows its `emptyState` in the plot's place. The footer is a recessed strip behind a hairline: the reading's time left, at most one command on the reading (Refresh) right.",
|
|
1631
1631
|
words: 'Title names the measure and its split: "Model spend by Customer". Period as dates. The link: "Explore", or "Open in Analytics" where the owner is another page. Footer: the reading\'s time, "Updated 4 minutes ago" or "Read at 12:42". Sentence case throughout.',
|
|
1632
1632
|
access: "The title is a heading at the page's level (default 2) and labels the plot. The plot is focusable and shows the 2 px focus outline only on keyboard focus.",
|
|
1633
1633
|
},
|
|
@@ -1714,10 +1714,10 @@ export const USAGE = [
|
|
|
1714
1714
|
family: "Other",
|
|
1715
1715
|
summary: "A box that keeps its width-to-height ratio.",
|
|
1716
1716
|
when: "A picture or a captured screen that must keep its shape while its width changes.",
|
|
1717
|
-
instead: "A fixed-size mark is its own component (`AppMark`, `AgentOrb`).",
|
|
1718
|
-
behaviour: "The height follows the width at `ratio`; the content fills it.",
|
|
1717
|
+
instead: "A fixed-size mark is its own component (`AppMark`, `AgentOrb`). The box creates neither playback controls nor a device frame: video controls belong to the player, and a decorative frame is drawn by the page that needs it; there is no shared device-frame export.",
|
|
1718
|
+
behaviour: "The height follows the width at `ratio`; the content fills it. It holds a stable layout for real media while it loads, so supply a ratio that matches the media.",
|
|
1719
1719
|
words: "None of its own.",
|
|
1720
|
-
access: "A layout container; the content keeps its own alternative text.",
|
|
1720
|
+
access: "A layout container; the content keeps its own alternative text, and the caller supplies the correct one for the media.",
|
|
1721
1721
|
},
|
|
1722
1722
|
{
|
|
1723
1723
|
name: "BrandField",
|
|
@@ -1735,9 +1735,9 @@ export const USAGE = [
|
|
|
1735
1735
|
module: "carousel",
|
|
1736
1736
|
family: "Other",
|
|
1737
1737
|
summary: "A rail of slides moved with Previous and Next.",
|
|
1738
|
-
when: "A controlled rail of like items
|
|
1739
|
-
instead: "A product's records are a list or a table, never a carousel. One overflowing row is `ScrollArea`.",
|
|
1740
|
-
behaviour: "Slides move by swipe, drag or the Previous and Next buttons; the buttons disable at the ends.
|
|
1738
|
+
when: "A controlled, finite rail of like items whose neighbours have a meaningful sequence: screens, testimonials.",
|
|
1739
|
+
instead: "A product's records are a list or a table, never a carousel; a sortable record collection is a table or an entity list. One overflowing row is `ScrollArea`.",
|
|
1740
|
+
behaviour: "Slides move by swipe, drag or the Previous and Next buttons; the buttons disable at the ends. Left and Right arrow keys move when the carousel holds focus, in both orientations; Up and Down do nothing. The rail is horizontal; `orientation` also accepts `vertical`, which stacks the buttons above and below. The motion is Embla's, and the component does not read the reduced-motion setting, so a page that must honour it sets Embla's `duration` itself through `opts` (Embla jumps with a duration of 0).",
|
|
1741
1741
|
words: "The buttons name their move: *Previous slide*, *Next slide*.",
|
|
1742
1742
|
access: "A `region` described as a carousel; each slide is a `group` described as a slide.",
|
|
1743
1743
|
},
|
|
@@ -1757,11 +1757,11 @@ export const USAGE = [
|
|
|
1757
1757
|
module: "company-card",
|
|
1758
1758
|
family: "Other",
|
|
1759
1759
|
summary: "The company's nadicode card: what the person is making while they sign up, and what the website shows being issued.",
|
|
1760
|
-
when: "Sign-up, where a person watches their company's card being made, and the website, which shows one being issued.",
|
|
1760
|
+
when: "Sign-up, where a person watches their company's card being made, and the website, which shows one being issued. Beside a wide form it fills in as the form is typed; on a phone the form keeps the screen, and the card appears once it is issued. The account's Profile shows the person the same card. The Partner invitation panel opens with its card, the `partner` edition, on the identity plane, as the invitation it shares, and the Partner's email invitation dialog shows the card the invited company will be issued. Arriving through a Partner's referral link, the sign-up plane stands the Partner's card behind the one being made, leaning back, so the person's own card stays in front and whole.",
|
|
1761
1761
|
instead: "A company's facts are `SettingsFactsSection`. A company in a row is its `IdentityMark`.",
|
|
1762
|
-
behaviour: "
|
|
1762
|
+
behaviour: "One object of the ID-1 proportion (856 by 540), dark by nature in every theme. Its top line carries the wordmark and, once Sofia has found it on the company's site, the company's own logo after a hairline, both stamped in `{colors.identity-white}` as one ink; a company with no logo found has the wordmark alone. Below are the holder's name and the company's domain. No Agent stands on it, and nothing sits in the engraving: a guilloché engraving in `{colors.identity-white}` at 10%. It fills in as the form is typed and answers the pointer. It is the one object besides an Agent that moves on its own, once: when it first appears, and again when the account is asked for and it is issued, it turns in the light in 2.2 s, leaning one way and back while the light crosses it from corner to corner, so a person sees it is a card that answers the hand, and then it is still. The foil wakes as the pointer comes within one card width and eases toward it; a pointer that near ends the turn, and a pointer anywhere else on the page moves nothing. Where the browser has WebGPU, its foil is vgpu's holographic card shader: matte graphite (`{colors.card-graphite}`, its own tone, so the card keeps its look whatever the heights do) whose etched contours diffract the pointer's light, turning toward it by at most 9°. The diffraction and its pearlescent wash fall in pure bands of the identity hues, never a colour between two of them: a company's card takes the cool half, cobalto, azzurro and verde, and a Partner's the warm half, giallo, arancio and rosso. Without WebGPU the engraving catches the light as flat, hard-edged stripes of the same hues. Under reduced motion the card keeps still and the light still finds the foil.",
|
|
1763
1763
|
words: "Every word it shows is a prop from the product's catalogue.",
|
|
1764
|
-
access: "Hidden from assistive technology and holds nothing interactive; the form
|
|
1764
|
+
access: "Hidden from assistive technology and holds nothing interactive; the form owns every word the card repeats.",
|
|
1765
1765
|
},
|
|
1766
1766
|
{
|
|
1767
1767
|
name: "Disclosure",
|
|
@@ -1835,8 +1835,8 @@ export const USAGE = [
|
|
|
1835
1835
|
family: "Other",
|
|
1836
1836
|
summary: "Panels a person resizes by dragging the handle between them.",
|
|
1837
1837
|
when: "Side-by-side panes whose balance a person sets: a conversation and its side panel. Nadia's side panel is this, with the window's grid column as the group; it is not a hand-made separator.",
|
|
1838
|
-
instead: "The product frame's sidebar resizes through `Sidebar`'s `resize` handle or `DesktopAppShell`. A width a person never changes is a plain grid column.",
|
|
1839
|
-
behaviour: 'The handle moves on drag and on arrow keys, and returns the panels to their default size on a double click; each panel keeps its minimum and maximum. A number is pixels (`minSize={352}`), a string without a unit is a percentage (`defaultSize="60"`), and `"20rem"` and `"40vw"` are read as written, so a panel that must keep a readable width is set in pixels and the pane beside it takes the rest. The handle is a 1 px line with a 4 px
|
|
1838
|
+
instead: "The product frame's sidebar resizes through `Sidebar`'s `resize` handle or `DesktopAppShell`. A width a person never changes is a plain grid column. A window too narrow for the panes side by side gets a compact alternative, one pane at a time, instead of squeezed panes.",
|
|
1839
|
+
behaviour: 'The handle moves on drag and on arrow keys, and returns the panels to their default size on a double click; each panel keeps its minimum, set where its content still reads, and its maximum. A number is pixels (`minSize={352}`), a string without a unit is a percentage (`defaultSize="60"`), and `"20rem"` and `"40vw"` are read as written, so a panel that must keep a readable width is set in pixels and the pane beside it takes the rest. The handle is a 1 px line with a 4 px wide hit area centred on it; it takes the ring colour while hovered, dragged or focused, and `withHandle` adds a grip for a handle that must be found. Colour: neutral until it is touched, then the ring colour, and nothing else. The group holds sizes only while it is mounted; the product owns persisting them.',
|
|
1840
1840
|
words: "Required: the handle's name, a verb phrase from the product's catalogue: *Resize the side panel*. A panel has none of its own.",
|
|
1841
1841
|
access: "The handle is the resizable library's focusable `separator` with its value, named by the `aria-label` the type requires; the arrow keys move it and Home and End take it to the ends of its range.",
|
|
1842
1842
|
},
|
|
@@ -1847,7 +1847,7 @@ export const USAGE = [
|
|
|
1847
1847
|
summary: "An area that scrolls along one axis, with a fade where more is waiting.",
|
|
1848
1848
|
when: '“Use Scroller for an overflowing list of peer items along a single axis: chip rows, log streams, code snippets, command palettes.” (https://vercel.com/geist/scroller) “Pick `vertical` for stacked feeds, `horizontal` for chip and tile rails, `free` only when content genuinely scrolls in both directions (logs with very long lines).” (https://vercel.com/geist/scroller) “For paginated or virtualized data sets larger than a few hundred items, render with a virtualization library inside the Scroller rather than dumping every node into the DOM.” (https://vercel.com/geist/scroller) Ours: Geist\'s `Scroller` is `ScrollArea`, and `vertical`, `horizontal` and `free` are `overflow="y"`, `"x"` and `"both"`. A bounded area whose content overflows: a side panel\'s list, a menu\'s long body, a rail of chips or logos.',
|
|
1849
1849
|
instead: "Ours: a page scrolls as the document. A sequence of slides on the website is `Carousel`.",
|
|
1850
|
-
behaviour:
|
|
1850
|
+
behaviour: '“Buttons will automatically scroll to a given **direct** child.” (https://vercel.com/geist/scroller) “Auto-scroll buttons target direct children only. If items are wrapped in extra layout nodes the buttons won’t find their target.” (https://vercel.com/geist/scroller) “Show edge fade or shadow affordances on the clipped axis so users see there’s more content past the viewport.” (https://vercel.com/geist/scroller) “Keep item widths and gaps consistent in horizontal scrollers; ragged edges break the snap rhythm and make the rail feel broken.” (https://vercel.com/geist/scroller) Ours: `overflow` is required, and `buttonLabels` goes only with `overflow="y"` or `"x"`, never `"both"`. Give the area its height or width with `className`, and a border or radius with `frameClassName`, which draws only the scrolling box and leaves the buttons outside it. The scrollbar is hidden; a 40 px fade of the surface at each clipped edge is the only sign there is more, and it is gone at an edge with nothing beyond it. The viewport scrolls natively, so wheel, touch and keyboard all reach it. `buttonLabels` adds two round 32 px outline buttons, 16 px from the area: above it and centred for `y`, below it and at the start for `x`. Each moves to the nearest direct child that is clipped at that end, not by a fixed amount, and sits disabled at the end with nothing beyond it. Smooth scrolling gives way to an instant jump under reduced motion. Colour is neutral only.',
|
|
1851
1851
|
words: "“Scroller buttons need `aria-label`s that name the direction and content (`Scroll customer logos left`), not bare `Previous`/`Next`.” (https://vercel.com/geist/scroller) Ours: *Scroll installations left*; both labels are required with the buttons.",
|
|
1852
1852
|
access: "“Tab order follows DOM order, so place items in reading order regardless of visual scroll direction.” (https://vercel.com/geist/scroller) “Make sure focusing an off-screen item scrolls it into view; the default browser behavior covers this, but custom focus traps can break it.” (https://vercel.com/geist/scroller) Ours: the content keeps its own semantics. The viewport is a tab stop when it scrolls.",
|
|
1853
1853
|
},
|
|
@@ -1857,8 +1857,8 @@ export const USAGE = [
|
|
|
1857
1857
|
family: "Other",
|
|
1858
1858
|
summary: "A 1 px line between groups.",
|
|
1859
1859
|
when: "Ours: separating groups inside one surface: menu sections, a card's header from its toolbar, inline items in a row. Horizontal between stacked groups, vertical between items in a row.",
|
|
1860
|
-
instead: "Ours: a card's footer draws its own seam; a list in a card is a `divided` `ItemGroup`. Never a border around a region.",
|
|
1861
|
-
behaviour: "“Use `inset` when a divider meets a 1px frame painted over the content. The separator reserves 1px at each end and uses solid `gray-400`. A physical parent border already reserves its edge space and does not need this inset.” (https://vercel.com/geist/separator) Ours: horizontal or vertical, exactly 1 px, in the `border` colour (not `gray-400`) and never any other: a separator has no hue, no state and no padding. A vertical one takes its height from the flex row that holds it, so give the row a height and `items-center`.",
|
|
1860
|
+
instead: "Ours: `FieldSeparator` divides a form's fields and the menu and group separators divide a menu's or a group's items, each for its scoped role. A card's footer draws its own seam; a list in a card is a `divided` `ItemGroup`. Never a border around a region.",
|
|
1861
|
+
behaviour: "“Use `inset` when a divider meets a 1px frame painted over the content. The separator reserves 1px at each end and uses solid `gray-400`. A physical parent border already reserves its edge space and does not need this inset.” (https://vercel.com/geist/separator) Ours: horizontal or vertical, exactly 1 px, in the `border` colour (not `gray-400`) and never any other: a separator has no hue, no state and no padding. `inset` keeps 1 px clear at each end, for a divider that meets a frame painted over the content. A vertical one takes its height from the flex row that holds it, so give the row a height and `items-center`.",
|
|
1862
1862
|
words: "None.",
|
|
1863
1863
|
access: "Ours: hidden from assistive technology, since it only paints a line; pass `aria-hidden={false}` when the line divides content a reader should hear as divided.",
|
|
1864
1864
|
},
|