# Build with Canvas Repository package version: 4.4.0. This is source documentation, not proof that npm has published this version. Canvas (`@nannier/canvas`) is a React Native UI kit for native iOS and Android apps and the web through React Native Web. Use one component API with platform skins, semantic boolean props, layout primitives and theme providers. Native materials use optional native integrations; check the installation guide for your runtime rather than treating a browser skin preview as native verification. ## Check the version first This page describes the source package's onboarding workflow. As of 2026-11-01, the published `4.0.0` package does **not** include the CLI shown below. These commands require a package containing the CLI. Check the installed package's `package.json` for the `canvas` executable before using them; otherwise follow the [installation guide](/ai/4.4.0/installation.md) and installed declarations. Repository previews and generated source snapshots do not prove publication. In an existing project, read the resolved version in your lockfile or run `npm ls @nannier/canvas`. Use documentation and declarations matching that installed version. The currently generated [documentation index](/ai/4.4.0/index.md) identifies its package version, source snapshot status and source digest. ## Start a project with an available CLI Choose one recipe and a new destination: ```sh npx @nannier/canvas init --recipe settings --dir ./my-app ``` The other recipes are `dashboard` and `booking`. Canvas copies a starter and recipe files, refuses to overwrite existing files and prints the next steps. The CLI does not install dependencies or run a package manager. Review the generated files, then follow their installation and run instructions yourself. The `npx` invocation can fetch the package; the Canvas CLI itself does not make network requests. ## Work in an existing project After installing a release that contains the CLI, run these from the project root to use the installed package: ```sh npx --no-install canvas docs --list npx --no-install canvas docs button npx --no-install canvas doctor --json npx --no-install canvas skills install --client codex ``` Choose `--client claude` or `--client both` for the other supported destinations. The skill installer writes project-local guidance into `.agents/skills/canvas` for Codex and `.claude/skills/canvas` for Claude Code. It refuses file collisions; review and reconcile existing guidance before trying the installation again. It does not change account settings. The doctor reports setup findings locally; read its output before sharing it in a public issue. Give your coding agent a concrete task, the target platforms and the installed Canvas version. For example: “Build a settings form with Canvas components for web and iOS. Read the installed Canvas skill and component documentation, keep the existing app shell, and verify keyboard use and the native runtime.” Agent guidance helps retrieve the right API; it does not guarantee a model's choices or a working application without verification. ## Read the API - [Discovery index](/llms.txt) and [combined documentation](/llms-full.txt) - [Installation](/ai/4.4.0/installation.md) - [Theming](/ai/4.4.0/theming.md) - [Layout](/ai/4.4.0/layout.md) - [Overlays](/ai/4.4.0/overlays.md) - [Component catalog](/ai/4.4.0/catalog.json) and [source/content manifest](/ai/4.4.0/manifest.json) Compose existing Canvas components. Use boolean styling such as ` )} ``` ### Activity disclosure `activity` retains the original Argus run surface, instant trace reveal, dense header and 180-degree chevron transition. `compact` keeps the original phone insets. `renderHeader` arranges domain content inside the component-owned trigger; the supplied indicator carries the controlled disclosure state. Put nested actions in that header as Canvas controls so they retain independent targets. ```tsx ``` ## Do & Don't ### Peer sections **Do** — Keep one disclosure group for a set of related, peer sections, and let the chevron carry the open/closed affordance. ```tsx ``` **Don't** — Don't leave every panel open by default in single-open mode; the closed-by-default disclosure is the point of an accordion. ```tsx ``` ## Supported props ### AccordionProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | activity | boolean \| undefined | no | false | Original Argus run disclosure surface, header and trace insets. | | compact | boolean \| undefined | no | false | Compact activity header and trace indentation. | | renderHeader | ((item: AccordionItem, expanded: boolean, indicator: ReactNode) => ReactNode) \| undefined | no | | Activity header content; the Accordion owns its trigger and indicator. | | items | AccordionItem[] \| undefined | no | | The disclosure rows, top to bottom. | | value | string \| string[] \| undefined | no | | Controlled open state. A single key in single-open mode, an array of keys in `multiple` mode. Pair with `onValueChange`. Omit for uncontrolled. | | defaultValue | string \| string[] \| undefined | no | | Initial open state for the uncontrolled case (read once). | | onValueChange | ((value: string \| string[]) => void) \| undefined | no | | Called with the next open value whenever a header toggles. | | multiple | boolean \| undefined | no | false | Allow more than one panel open at once (default: single-open). | | card | boolean \| undefined | no | false | Card surface: wraps the group in an outlined card container with inset headers and content (web/Android). On iOS the default Accordion already renders as the inset-grouped card, so `card` is a no-op there. | | disabled | boolean \| undefined | no | false | Disable the whole group (every row stops toggling and dims). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # ActionPanel Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/action-panels/action-panels.md` Human reference: https://canvas.nannier.com/components/action-panels ## Public imports ```tsx import { ActionPanel } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Section panel with headline, body text, and one action. Used to surface a single decision or call-to-action. The panel is the v4 hand-off's on every platform (no platform ships an action panel): an 18 px inset at the card corner on a hairline over the card fill, a 600 15 title over a 13 px muted description. The action is an outline Button; `destructive` washes the panel in the danger tint, edges it in the destructive colour and makes the action destructive; `inline` pins the action beside the copy, wrapping it underneath when the panel is narrow. Embedded `children` sit under the copy, and the Button then closes the panel on the right as the primary action that commits them. ## Usage The panel's single action fires `onAction`; wire it to your own handler and every press runs it. ```tsx ``` ## Variants ### Destructive ```tsx ``` ### Inline ```tsx ``` ### Toggle ```tsx ``` ### Embedded fields ```tsx ``` ### Fields under a pinned action ```tsx ``` ### Confirm before delete ```tsx {(open, setOpen) => ( setOpen(true)} /> )} ``` ## Do & Don't ### Simple **Do** — Spell out the consequence above the button so the stakes are clear before the click. ```tsx ``` **Don't** — A destructive action with no consequence copy invites accidental, irreversible clicks. ```tsx ``` ### With toggle **Do** — Pair the switch with a one-line explanation of what turning it on or off does. ```tsx ``` **Don't** — A bare switch with no description leaves the user guessing what flipping it actually changes. ```tsx ``` ## Supported props ### ActionPanelProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | title | string \| undefined | no | | The headline: what the action acts on. | | description | string \| undefined | no | | The consequence copy beneath the title: what happens when the action fires. | | actionLabel | string \| undefined | no | | The action button label. | | onAction | (() => void) \| undefined | no | | Fired when the action button is pressed. | | destructive | boolean \| undefined | no | false | Tone (omit for the neutral, primary-action default). | | inline | boolean \| undefined | no | false | Layout (pick one; default stacks the action below the copy). | | toggle | boolean \| undefined | no | false | Affordance: render the action as an on/off Switch instead of a Button. The panel always lays out inline in this mode. | | checked | boolean \| undefined | no | false | The Switch on/off state when `toggle` is set (CONTROLLED). Omit for uncontrolled use. | | defaultChecked | boolean \| undefined | no | false | Initial Switch state for uncontrolled use (the toggle flips itself on press). | | onToggle | ((next: boolean) => void) \| undefined | no | | Fired with the next checked value when the toggle Switch is flipped. | | children | ReactNode | no | | Content embedded in the panel under the copy (a block of field rows in a settings panel, say). The Button then closes the panel on the right, under a hairline, as the action that commits it; a toggle stays pinned beside the copy. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # ActionSheet Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/action-sheet/action-sheet.md` Human reference: https://canvas.nannier.com/components/action-sheet ## Public imports ```tsx import { ActionSheet } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A modal sheet of contextual actions, the iOS action sheet idiom: a bottom sheet of choices summoned in response to a user action, over a dimmed scrim that glass on the web also blurs. It poses an optional title and message, lists a set of actions (any of which can be destructive), and offers a Cancel. Selecting an action runs it and closes the sheet; tapping the scrim or Cancel closes it without acting. Pass a `trigger` label for a self-contained button that opens the sheet, or drive `open` / `onOpenChange` yourself. For a small contextual menu anchored to a control, reach for Dropdown or RowMenu instead. On the web the sheet is two cards a little off the window's edges: a centered header over full-width rows in the accent's ink (a destructive one in the error ink), each under a hairline, and a separate Cancel card. iOS keeps its Liquid Glass sheet with detached capsules, and Android the Material 3 bottom sheet with a drag handle and list rows. iOS accessibility escape and Android system back request cancellation without running an action. An open child overlay handles the request first. Disabled actions do not disable cancellation, and a controlled owner can keep the sheet open. The visible Cancel row remains available to assistive technology. ## Usage ```tsx {} }, { label: "Choose from Library", onPress: () => {} } ]} /> ``` ## Variants ### With a title and message ```tsx {} }, { label: "Keep Editing", onPress: () => {} } ]} /> ``` ### Destructive action ```tsx {} }, { label: "Delete File", destructive: true, onPress: () => {} } ]} /> ``` ### Disabled action ```tsx {} }, { label: "Save As…", disabled: true, onPress: () => {} } ]} /> ``` ### Controlled ```tsx {(open, setOpen) => ( <> {} }, { label: "Send by Email", onPress: () => {} } ]} /> )} ``` ### Open in place `embedded` draws the sheet open at the foot of its parent over the parent's own dim, for a specimen or a preview: no modal, no focus trap, no Escape or hardware back, and a dim that passes touches through. A `trigger` always opens the live modal sheet instead. ```tsx {} }, { label: "Send by Email", onPress: () => {} }, { label: "Print", onPress: () => {}, disabled: true } ]} /> ``` ## Do & Don't ### Right tool for the job **Do** — Use an Action Sheet for a short list of choices triggered by a user action, with the dangerous option marked destructive. ```tsx {} }, { label: "Choose from Library", onPress: () => {} }, { label: "Remove Photo", destructive: true, onPress: () => {} }, ]} /> ``` **Don't** — Use an Action Sheet for a single yes/no confirmation; an Alert Dialog is the focused tool for that. ```tsx {} }]} /> ``` ### Name the action, not a generic verb **Do** — Label each row with the action it performs (Take Photo, Delete File, Move). ```tsx {} }, { label: "Move to Folder", onPress: () => {} }, ]} /> ``` **Don't** — Generic labels force the user to re-read the title to know what each row does. ```tsx {} }, { label: "No", onPress: () => {} }, ]} /> ``` ## Supported props ### ActionSheetProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (a `trigger` opens it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (trigger press, scrim tap, Cancel, an action, back/escape). | | trigger | string \| undefined | no | | Label for an optional trigger button. When set, the sheet renders the button and opens itself on press (uncontrolled). Omit when you drive `open` yourself. | | title | string \| undefined | no | | Optional header title (a left-aligned primary-label heading on iOS, a short centered heading on web, left-aligned on Android). | | message | string \| undefined | no | | Optional header message under the title. | | actions | ActionSheetAction[] | yes | | The action rows. Each runs its `onPress` then closes the sheet. | | cancelLabel | string \| undefined | no | "Cancel" | The Cancel row label (default "Cancel"). | | embedded | boolean \| undefined | no | false | Show the open sheet as a non-modal specimen inside its parent, for a catalogue or a showcase: the stack rests against the parent's bottom edge (the parent gives it its height) over the dim, with no Modal, no rise, no focus trap and no close on Escape, native back or a tap on the dim. Its rows still run their actions. Ignored with `trigger`, which always opens a live sheet. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Outer layout composition for the bottom-anchored stack, composed onto the stack container; not a restyle hook. (The device's bottom safe-area inset is applied automatically, so it clears the home indicator without a manual `paddingBottom`.) | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # AlertDialog Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/alert-dialog/alert-dialog.md` Human reference: https://canvas.nannier.com/components/alert-dialog ## Public imports ```tsx import { AlertDialog } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Catalyst-style confirmation dialog: a centered panel over a dimmed backdrop, which glass on the web also blurs, with a title, description, optional body, and action buttons. Reserve it for decisions that must block the rest of the app. On the web and Android the panel leads with a tone tile (a trash glyph on the destructive wash, otherwise an info glyph on the accent's), then a display title over its description, and closes on two equal buttons: a secondary Cancel and the primary or destructive confirm. On iOS it is the system alert: centered text over a two-cell action row under a hairline, the confirm in the error ink when destructive. Browser Escape and iOS accessibility escape cancel the dialog, clear its confirmation field and request `open=false`. The confirmation text gates only the confirm action; cancellation remains available while confirm is disabled. An open child overlay handles dismissal first. A controlled owner can refuse closure without passing that request to a parent or calling `onConfirm`. Add `embedded` to show an open alert dialog in a page as a specimen, the way a catalogue or a showcase does: it keeps its look, its alertdialog role and its labelling, but it is not modal. It does not take focus when it opens, trap Tab, return focus, set `aria-modal`, or cancel on Escape or native back, so the page around it keeps its scroll position and its keyboard order. `embedded` is ignored with `trigger`, which always opens a live modal. ## Usage ```tsx ``` ## Variants ### Narrow ```tsx ``` ### Small ```tsx ``` ### Large ```tsx ``` ### Body field `withInput` renders a confirmation field that gates the confirm action: the user must type the `confirmText` token (default `"DELETE"`) exactly before the confirm button enables, so it is a real safety check rather than a decorative field. Pass `confirmText` to require a different token. ```tsx ``` ### Controlled Drive the open state from outside the dialog: omit `trigger`, own `open` yourself, and pass `onOpenChange` so every way the dialog closes (confirm, cancel, browser Escape or iOS accessibility escape) reports back and keeps your state in sync. (`Stateful` is a docs-only state holder; in an app you would hold `open` with `useState`.) ```tsx {(open, setOpen) => ( )} ``` ## Do & Don't ### Reserve the dialog for blocking decisions **Do** — Use an inline banner or toast for passive feedback; reserve the dialog for decisions that must be confirmed. ```tsx ``` **Don't** — A blocking alert dialog for passive confirmation interrupts the user for no reason. ```tsx ``` ### Name the action on the confirm button **Do** — Label the confirm button with the verb it performs (Delete, Archive, Sign out). ```tsx ``` **Don't** — Generic Yes / No forces the user to re-read the title to know what they are confirming. ```tsx ``` ### xs **Do** — Reserve xs for a terse one-line question with short button labels and no body content. ```tsx ``` **Don't** — A long title, multi-line description, and wordy buttons get cramped in the xs width and wrap awkwardly. ```tsx ``` ### sm **Do** — Use sm for a single short confirmation with a one-line description; move real forms to a full dialog. ```tsx ``` **Don't** — Packing a multi-field form into sm makes it feel like a form crammed into a confirmation popup. ```tsx Transfer ownership New owner email Reason Type TRANSFER to confirm ``` ### md **Do** — md is the default home for a typical destructive confirm with a sentence or two of description. ```tsx ``` **Don't** — Squeezing a description-carrying confirm into a smaller width crowds the copy against the edges. ```tsx ``` ### lg **Do** — Reserve lg for dialogs that earn the width: a body field or a longer explanation to read. ```tsx ``` **Don't** — A bare yes/no in lg leaves a wide, empty panel that reads as heavier than the trivial decision it asks for. ```tsx ``` ## Supported props ### AlertDialogProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | title | string \| undefined | no | | Content (strings). | | description | string \| undefined | no | | Content (strings). | | trigger | string \| undefined | no | | Trigger button label. When set, the dialog renders the button and opens itself on press (uncontrolled). Omit when you drive `open` yourself. | | confirmLabel | string \| undefined | no | "Continue" | Action labels. | | cancelLabel | string \| undefined | no | "Cancel" | Action labels. | | open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (the trigger opens it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (trigger press, confirm, cancel). | | narrow | boolean \| undefined | no | false | Width (pick one; default is the medium panel). | | small | boolean \| undefined | no | false | Width (pick one; default is the medium panel). | | large | boolean \| undefined | no | false | Width (pick one; default is the medium panel). | | withInput | boolean \| undefined | no | false | Body: render a confirmation field that gates confirm. The user must type the `confirmText` token (default "DELETE") for the confirm action to enable; until it matches exactly, confirm is disabled and `handleConfirm` is a no-op. This is a real safety gate, not a decorative field. | | confirmText | string \| undefined | no | "DELETE" | The token the confirmation field must match to enable confirm (default "DELETE"). Only used when `withInput` is set; drives the placeholder ("type DELETE to confirm"), the field's accessible name, and the gate. | | destructive | boolean \| undefined | no | false | Confirm intent (default is a primary confirm). | | onConfirm | (() => void) \| undefined | no | | Action handlers. | | onCancel | (() => void) \| undefined | no | | Action handlers. | | overlay | boolean \| undefined | no | false | Presents the confirm OVER the page instead of inline where it is mounted. The default is a contained overlay, which the docs catalogue needs to show one inside an example stage. An application asking to confirm a destructive action needs the opposite: inline, the scrim appears wherever the component happens to sit, the page behind stays scrollable and clickable, and the dialog still claims `aria-modal`. Teleports into the nearest `OverlayProvider`; with none in the tree it renders in place. | | embedded | boolean \| undefined | no | false | Show the open dialog as a non-modal specimen in the page, for a catalogue or a showcase: it keeps its look, its alertdialog role and its labelling, but does not take focus when it opens, trap Tab, return focus, claim `aria-modal`, or cancel on Escape or native back. Ignored with `trigger`, which always opens a live modal. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Alert Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/alert/alert.md` Human reference: https://canvas.nannier.com/components/alert ## Public imports ```tsx import { Alert } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Inline notification banners: info, success, warning, and destructive, plus a full-width announcement bar. For a blocking confirmation prompt, see Alert Dialog. A banner fills the parent it is given, so a column of alerts is the same measure top to bottom and a banner over a form lines up with the fields; a Container step sets the measure when the layout calls for one. A banner is a soft callout: the tone's wash under a 1px edge of the tone's color, the tone's own glyph in that color, a foreground title and a muted body, from `statusColors` (the helper Badge and every toned surface read). The neutral banner is the quiet muted surface with an info glyph in the muted ink. Pass `icon` to draw another glyph (an `` takes the tone's color and the banner's 18px size), or `icon={null}` to draw none. ## Usage Pressing the trailing "×" (`dismissible`) hides the banner out of the box; `onDismiss` reports it, and a controlled `dismissed` prop hands that state to the parent instead. Action buttons are real Buttons: wire each one's `onPress`. ```tsx ``` ## Variants ### Success ```tsx ``` ### Warning ```tsx ``` ### Destructive ```tsx ``` ### Neutral ```tsx } title="Scheduled maintenance" description="The dashboard may be briefly unavailable on Sunday between 2:00 and 3:00 UTC." /> ``` ### Dismissible ```tsx ``` ### Actions ```tsx Upgrade plan} /> ``` ### Inline action `action` is one quiet link in the banner's actions, for the single next step a notice offers. ```tsx ``` ### Announcement `announcement` is the full-width product bar: the accent's gradient under its own ink, a sparkles glyph, the message, an inline `action` link and the dismiss box. It has its own look, so the tone booleans do not apply to it. ```tsx ``` ### Measures come from the parent ```tsx ``` ### Rich body ```tsx The field underline now reads from --p-field-underline instead of a raw hex value; update any local overrides before upgrading. ``` ## Do & Don't ### info **Do**: Reserve info for passive, non-urgent context (notices, tips); escalate to warning or destructive when action is required. ```tsx ``` **Don't**: Dressing an act-now message in the neutral info tone hides the urgency; users skim past it like an FYI. ```tsx ``` ### success **Do**: Make confirmations transient: auto-dismiss or give a Dismiss control so the success state clears once acknowledged. ```tsx ``` **Don't**: A success banner pinned with no way to dismiss it lingers as visual noise long after the action is done. ```tsx ``` ### warning **Do**: State the consequence, the deadline, and the action: name what's wrong and give a button to resolve it. ```tsx Upgrade plan} /> ``` **Don't**: A warning with no specifics or next step leaves the user guessing what to fix and by when. ```tsx ``` ### destructive **Do**: Match the variant to the severity: reserve destructive for genuine failures, success for confirmations. ```tsx ``` **Don't**: Using the destructive variant for non-errors cries wolf; users learn to tune out red and miss real failures. ```tsx ``` ## Supported props ### AlertProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | title | string \| undefined | no | | Content. | | description | string \| undefined | no | | Content. | | icon | ReactNode | no | | The leading glyph: an element (an ``, tinted to the tone's color unless it sets its own, and sized to the banner's glyph) or a single text character. Omitted, the banner draws its tone's own glyph (an info circle on info and neutral, a check on success, an alert circle on warning, a cross on destructive); `null` draws none. | | info | boolean \| undefined | no | false | Tone (pick one; omit for the neutral default). | | success | boolean \| undefined | no | false | Tone (pick one; omit for the neutral default). | | warning | boolean \| undefined | no | false | Tone (pick one; omit for the neutral default). | | destructive | boolean \| undefined | no | false | The danger tone. This is the name the intent axis uses across the kit (Button, AlertDialog, and every chart) and the one the design hand-off uses here too. | | announcement | boolean \| undefined | no | false | The announcement bar: one line on the accent gradient (the accent running into the cyan series hue) in the accent's own ink, under its glow, with its `action` inline at the end. For page-wide news ("v4 is live"), not for a status. It is its own look, so it ignores the tone axis. | | action | { label: string; onPress?: () => void; } \| undefined | no | | One action, as a label and a handler. On the announcement bar it sits inline at the end of the line, in the bar's ink; on the other looks it is a link Button in the actions region, before any `actions`. | | dismissible | boolean \| undefined | no | false | Shows a trailing dismiss control. Pressing it hides the banner out of the box (uncontrolled); pass `dismissed` to own that state instead. | | dismissed | boolean \| undefined | no | false | Controlled dismissal: `true` hides the banner. Omit to let the Alert manage it. | | defaultDismissed | boolean \| undefined | no | false | Uncontrolled seed: start hidden. Rarely useful; defaults to false. | | onDismiss | (() => void) \| undefined | no | | Fired when the dismiss control is pressed (both modes). | | children | ReactNode | no | | | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | actions | ReactNode | no | | A footer action region rendered under the body. The Alert owns the layout: it separates the actions from the description above and lays the buttons out in a row, so a call site passes the buttons directly (`actions={}`) with no wrapper. Pass a fragment for more than one action. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # AppFrame Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/app-frame/app-frame.md` Human reference: https://canvas.nannier.com/components/app-frame ## Public imports ```tsx import { AppFrame, Page } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. The app's root frame and its scrolling screens. `AppFrame` mounts the safe-area context and the app-root overlay host, pads all four safe-area edges over the theme background, renders the app's top chrome in a banner region and lets its body (a router's Stack) fill the height left. `Page` is one screen inside it: the main region, a scroller with page gutters whose content is capped and centered at a step of the width scale. Mount `AppFrame` once, inside `ThemeProvider`, at the root of the app. It is the app-root `OverlayProvider` itself, so menus, sheets and toasts render above the whole window and keep clear of the status bar, the Dynamic Island and the home indicator; do not mount a second root host around it. The safe-area context comes from `react-native-safe-area-context` when the app installs it (Expo Router apps already do); without it the frame keeps its layout and the insets resolve to 0. Each screen renders a `Page`. Its content stacks at the loose rhythm and is capped at `widest` (1152) by default, the reading width of an app page on a desktop window; below the cap it fills the window between its gutters, which is all a phone is. A measure step (`xxxs` through `page`) narrows or widens it, `start` pins it to the leading edge, and `fluid` removes the cap. The page is also an overlay host of its own, so an anchored menu opened in it is framed by the page. ## Usage The examples below render the frame inside the docs stage, where its parent is the stage instead of the window: the header, the body and the page measure behave as they do at an app's root, and the safe-area insets are 0. ```tsx }> Overview ``` ## Variants ### Narrower measure A form or a settings screen reads better at a narrower step; the page centers it. ```tsx }> Profile ``` ### Pinned to the start ```tsx }> Notifications Email digest ``` ### Overlays in an enclosing viewport host A screen whose modals must center in the visible viewport, not in the page's full scrolled content, wraps the page in a viewport-measured `OverlayProvider` and passes `inheritOverlays`, so the page is a plain content box and its overlays go to that host. ```tsx Billing ``` **Don't**: Hand-roll the scroller and cap a raw View with a number of your own; the width drifts off the shared scale and the page loses its main region and overlay host. ```tsx Profile ``` ### The app frame **Do**: Let AppFrame own the safe area, the background and the banner region. ```tsx }> Overview ``` **Don't**: Paint the background and wrap the header by hand; the insets, the overlay host and the banner role are each one more thing every app gets wrong. ```tsx Overview ``` ## Accessibility The header renders inside a `role="banner"` region and each Page is the screen's `role="main"` region, so assistive technology can jump between the app's chrome and its content. The page scroller keeps taps on its controls while the software keyboard is up (`keyboardShouldPersistTaps="handled"`), so a field's submit button works on the first tap. The frame adds no focus stops of its own. ## Supported props ### AppFrameProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | header | ReactNode | no | | The app's top chrome (a Navbar), rendered in a `role="banner"` region above the body. | | children | ReactNode | no | | The body, typically a router's Stack: it fills the height the header leaves. | | testID | string \| undefined | no | | E2E hook forwarded to the frame. | ### PageProps Includes `MeasureProps`; their supported fields are expanded below. | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | The page content, stacked in a Column at the loose (24) rhythm. | | fluid | boolean \| undefined | no | false | No cap: the content spans the page between its gutters. Wins over every step. | | inheritOverlays | boolean \| undefined | no | false | A plain content box instead of the page's own overlay host, so overlays opened in the page go to an enclosing host (a viewport-measured OverlayProvider around the page). | | testID | string \| undefined | no | | E2E hook forwarded to the page's scroller. | | xxxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | sm | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | md | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | lg | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wide | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wider | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | widest | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | page | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | start | boolean \| undefined | no | false | Pin a capped component to the leading edge instead of centering it in its column. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # AreaChart Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/area-chart/area-chart.md` Human reference: https://canvas.nannier.com/components/area-chart ## Public imports ```tsx import { AreaChart } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Categorical-x series fills: overlapping translucent areas by default, or running-sum bands with `stacked`. Shares the line chart's curve, density, furniture, and scrub-to-inspect axes. ## Usage ```tsx ``` ## Variants ### Stacked ```tsx ``` ## Do & Don't ### Stacking **Do**: Stack series that are parts of one total, like sessions by device; the top edge is the total and each band is its share. ```tsx ``` **Don't**: Stack rates that do not add up; conversion, bounce and churn rates summed into one band draw a total that means nothing. ```tsx ``` ## Supported props ### AreaChartProps Includes `CartesianSeriesProps`; their supported fields are expanded below. | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | stacked | boolean \| undefined | no | false | Stack the series (running sums) instead of overlapping translucent fills. | | labels | string[] | yes | | Category labels along the x axis, one per data column. | | series | ChartSeries[] | yes | | The series to plot; colors follow the chart-1..8 tokens in fixed order, or a series' own `success`/`destructive` tone. | | title | string \| undefined | no | | Optional heading shown above the plot. | | max | number \| undefined | no | | Y-axis maximum override. Defaults to a nice bound above the data max. | | min | number \| undefined | no | | Y-axis minimum override. Defaults to 0 (or the data min when negative). | | success | boolean \| undefined | no | false | Tone (single-series only; pick one; default is the primary fill). | | destructive | boolean \| undefined | no | false | Tone (single-series only; pick one; default is the primary fill). | | curved | boolean \| undefined | no | false | Curve (omit for straight segments). | | compact | boolean \| undefined | no | false | Density (omit for the default plot size). | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | hideLegend | boolean \| undefined | no | false | Furniture visibility. | | hideGrid | boolean \| undefined | no | false | Furniture visibility. | | hideAxes | boolean \| undefined | no | false | Furniture visibility. | | formatValue | ((v: number) => string) \| undefined | no | | Formats tick labels and accessible values (data formatting, not styling). | | selected | number \| null \| undefined | no | | Scrub-to-inspect: the selected category index (controlled). Pass null for none. | | defaultSelected | number \| undefined | no | | Scrub-to-inspect: the initially selected category (uncontrolled). | | onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press/scrub selects a category (or clears it with null). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Autocomplete Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/autocomplete/autocomplete.md` Human reference: https://canvas.nannier.com/components/autocomplete ## Public imports ```tsx import { Autocomplete } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Text input + dropdown: searchable single-select. Pass `label` (and `required`) to name the field: iOS and web render the label above the field, while Android floats the Material 3 in-container label once the list opens or a value fills the field. On Android the field grows with the user's text size, and a label longer than the field truncates on one line clear of the toggle while the field's accessible name stays the whole label. The required star is decorative: a screen reader hears the field as required, from `aria-required` on the web and from its name ("Assigned to, required") on iOS and Android. The field fills the parent it is given; a step of its own (`xs`, `lg`, …, with `start` to pin it to the leading edge) or a Container step sets its measure. On web the field is the Canvas v4 search field: 40px tall (32 small, 48 large) on the sunken fill behind the `field-border` hairline, a leading 16px search glyph, a 400 14px value (13 small) and a 15px chevrons-up-down 10px from its end. The pointer darkens the edge to the subtle ink, and focus rings it in the accent with a 4px halo. The suggestions are a card 6px under the field with a 4px inset and 34px rows; the typed letters are marked in each suggestion on the accent's soft wash, and the chosen suggestion sits on the `muted` wash. iOS ships no combo box, so it draws the same field and card at the iOS corners. Android keeps the Material 3 exposed dropdown: the filled field with its active indicator and drop-down triangle, and the Material menu whose active and chosen rows take the state layer. In glass mode the suggestion list is a dense-layer glass card under the field (or above it when it fits there); the field, its caret and its toggle stay in place, and picking a suggestion commits the value at once. Solid mode paints the skin's own list. Arrow Down and Arrow Up open the list and highlight an option while focus stays in the text field. Navigation stops at the first and last matches. Home and End jump to those limits once an option is highlighted; otherwise they retain their text-editing behavior. Enter chooses the highlighted option, Escape closes the list without changing the query, and Tab closes it while moving focus. Typing resets the highlight. Confirming an input-method candidate does not select an option or submit a surrounding Form. Use `value` with `onValueChange` to control the selection, and use `""` for a controlled empty value. The callback reports selections and clearing the field. `onSelect` remains a selection-only notification. The independent `query`/`onQueryChange` pair controls filtering; choosing an option resets the query to `""`. The disclosure button has a real target of at least 24px on web, 44pt on iOS, and 48dp on Android. The small iOS field keeps a 44pt height so its touch target fits inside the field; its typography remains compact. An option can carry a short `detail` (a version, a platform, a count): pass `{ label, detail }` objects in place of strings, and the detail sits at the row's end in the 11px mono face in the subtle ink. Android sets it in the row's type and muted ink. The label is what the query matches and what `value` holds; the row is announced as "label, detail". ## Usage ```tsx ``` ## Variants ### Required field ```tsx ``` ### With helper text ```tsx ``` ### Disabled ```tsx ``` ### Option details ```tsx ``` ### Controlled selection ```tsx {(value, setValue) => ( )} ``` ### Measure ```tsx ``` ## Do & Don't ### When to use **Do** — A plain select for short, fixed lists; reserve the autocomplete for long, searchable ones. ```tsx ``` ### Selection **Do** — Click an option: it fills the input and stays marked as selected. ```tsx ``` **Don't** — Click an option: the field is pinned to an empty `value`, so the list closes on nothing and you can't tell what you picked. ```tsx ``` ### With label **Do** — A persistent label keeps the field named after a selection has filled the input. ```tsx ``` **Don't** — Once a value replaces the placeholder, an unlabeled field has nothing left to name it. ```tsx ``` ### With helper text **Do** — A short placeholder plus persistent helper text keeps the rule visible while you type. ```tsx ``` **Don't** — Type a letter: guidance crammed into the placeholder vanishes the moment you start. ```tsx ``` ### Disabled **Do** — Show the locked value and say why it's fixed, so disabled reads as a settled choice. ```tsx ``` **Don't** — An empty, dimmed field with no value reads as broken, not as intentionally locked. ```tsx ``` ## Supported props ### AutocompleteProps Includes `MeasureProps`; their supported fields are expanded below. | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | query | string \| undefined | no | | The text typed into the field (controlled). Filters the option list. Omit and use `defaultQuery` for uncontrolled use: a bare Autocomplete is typeable out of the box. | | defaultQuery | string \| undefined | no | | Initial query for uncontrolled use. | | onQueryChange | ((query: string) => void) \| undefined | no | | Fired with the new query on each keystroke, and with "" when a select resets the filter (both modes). | | options | (string \| AutocompleteOption)[] \| undefined | no | | The full list of selectable options: bare labels, or `{ label, detail }` objects whose `detail` sits muted at the row's end (a version, a platform). The label is what the query matches and what `value` holds. | | value | string \| undefined | no | | The selected option label (CONTROLLED), or "" for no selection. Omit for uncontrolled use. | | defaultValue | string \| undefined | no | | Initial selected option for uncontrolled use (selecting a row updates it). | | onValueChange | ((value: string) => void) \| undefined | no | | Fired on selection and with "" when the field is cleared, in both controlled and uncontrolled modes. | | placeholder | string \| undefined | no | "Search…" | Prompt shown in the field when there is no query or value. | | open | boolean \| undefined | no | false | Whether the option list is open (CONTROLLED). Uncontrolled and closed by default; focusing or typing in the field opens it, the chevron toggles it. Pass `open` to pin it open, or `defaultOpen` to render it open initially while staying interactive. A disabled control stays closed regardless. | | defaultOpen | boolean \| undefined | no | false | Render the list open initially for uncontrolled use (selecting or the chevron closes it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (focus, typing, chevron, select). | | label | string \| undefined | no | | The field's persistent label. Its placement is platform-adaptive: iOS and web render it ABOVE the field; Android renders the Material 3 in-container FLOATING label (centered like a placeholder at rest, floating to the top once the list opens or a value fills the field). The label names the field for assistive tech. | | required | boolean \| undefined | no | false | Marks the field as required: appends a destructive "*" to the label (decorative, never in the accessible name) and announces the field as required, through aria-required on the web and with the word in its name ("Assigned to, required") on iOS and Android. The star and the spoken word take effect only alongside `label`. | | helperText | string \| undefined | no | | Optional muted helper line rendered below the option list. | | disabled | boolean \| undefined | no | false | Dims the control and blocks interaction. | | onSelect | ((option: string) => void) \| undefined | no | | Called when an option is chosen by touch, pointer, or keyboard. Clearing only fires onValueChange. | | testID | string \| undefined | no | | E2E hook forwarded to the text field. | | small | boolean \| undefined | no | false | Size (pick one; default is the medium field, matching Input's h-9). | | large | boolean \| undefined | no | false | Size (pick one; default is the medium field, matching Input's h-9). | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | | ref | React.Ref | no | | Ref to the underlying React Native host. Supports object refs and callback refs; native methods follow the platform's behavior. | | xxxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | sm | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | md | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | lg | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wide | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wider | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | widest | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | page | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | start | boolean \| undefined | no | false | Pin a capped component to the leading edge instead of centering it in its column. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Avatar Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/avatar/avatar.md` Human reference: https://canvas.nannier.com/components/avatar ## Public imports ```tsx import { Avatar, AvatarGroup, AvatarMenu } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A photo when the account has one, falling back to one or two initials on an identity disc: the name resolves to one of ten hues (or `hue` picks one), and the disc is that hue's soft wash under initials in the same hue, so one person keeps one colour on every platform and stays distinct in a stack or list. The initials sit at weight 600, 38% of the disc, and their ink is held to 4.5:1 on the wash in both schemes. Like a photo, the disc is identity content: it keeps its colours under glass, where only the neutral tile of an avatar with no name or initials takes the control material. `AvatarMenu` builds the signed-in account control on the same circle: one capsule trigger carrying the avatar, the name, and the email, opening the account menu under that same identity. Argus identity sizes are available as `annotation` (16), `compact` (30), `account` (32), `row` (36), `person` (38), `profile` (44), `detail` (52), `hero` (58), `featured` (60), and `spotlight` (74), in that precedence order. These retain their reference initials metrics, including the existing 9px annotation glyph. `hue` selects a stage identity independently of its name. `whiteInk` preserves white reference initials; `bold` uses the 700 weight. `sheen`, `mediumSheen`, and `strongSheen` add the 2, 2.5 and 3px conic ring (strongest wins). `inverse` is the translucent white identity disc, `orchid` the account gradient, and `glow` its selected account ring. These reference discs keep the identity gradient. On the four stock sizes, `glow` is a 2px gap in the page colour inside the accent's soft glow, and `ring` stays the separator ring a stack draws between discs. ## Usage ```tsx ``` ## Variants ### Brand mark ```tsx ``` `logo` retains the original 30px Argus mark, nine-pixel corners, action-to-ring gradient, 14px extra-bold initial, and selection-colored drop shadow. ### Photo ```tsx ``` ### Identity colours Each name resolves to one of ten hues through a stable hash, so the same person gets the same disc in every session and on every platform, and a list of people spreads across the set. The hue follows `name` when one is set, else the initials, so pass the full name: two people who share initials then usually get different discs. ```tsx ``` ### Sizes Four steps: `tiny` (20px), `small` (28px), the default 40px row avatar, and `large` (56px). The initials take 38% of the disc (11, 15 and 21px), and 10px on `tiny`, the kit's smallest type. `rounded` squares the disc with corners at 28% of its size. `small` is the disc `AvatarMenu` builds its capsule around, so a standalone small avatar and the one inside a pill are the same circle. Precedence when several are passed: `tiny` beats `small` beats `large`. ```tsx ``` ### Rounded ```tsx ``` ### Online `online` marks presence: the success dot in a ring of the card at the disc's lower trailing edge. The dot is decorative, so say the presence in the label where it matters. ```tsx ``` ### Ring outline ```tsx ``` ### Stacked ```tsx ``` ### Topbar A lone avatar as the account trigger: the Dropdown makes the disc its press target, so tapping it opens the account menu, no email or chevron needed. ```tsx ``` ### Argus account menu `account` preserves the original Argus 32px orchid disc, glow, name and role metrics, and compact menu. `triggerLabel` names the one press target; `menuLabel` independently names its menu. Bounds use `menuWidth` and `maxHeight`. ```tsx ``` ### Account menu Built on Dropdown, so in glass mode the menu is Dropdown's dense-layer glass card under the capsule, which keeps its place, its press and its accessible name throughout. The whole account control in one component. `AvatarMenu` renders a single capsule trigger (the avatar in the viewer's violet glow ring, the name over the muted email, a chevron) and the account menu itself, with `name` and `email` repeated as the menu's own identity header. One press target covers the capsule, so nothing beside it has to be wired up. The capsule is the hand-off's identity pill on every platform: a hairline edge round the popover fill at rest, the accent fill under the pointer and while its menu is open; the menu is each platform's own Dropdown. ```tsx ``` ### Menu specimen `inline` draws the menu open and in flow under the capsule rather than floating over the page, for a page that shows the menu itself. Rows still fire `onSelect`; nothing dismisses it. ```tsx ``` ### Compact menu `compact` drops the name block for a bar with no room for it, leaving the 28px avatar alone. The identity still travels with the menu, so the name and email head the rows the moment it opens. A topbar parks the pill at the trailing edge, so the menu hangs from that edge by default; `alignStart` moves it to the leading edge for a pill that sits at the start of a bar. ```tsx ``` ### Menu alignment The menu hangs from the pill's trailing edge by default, the edge a topbar parks the account control against and the only one a right-parked trigger can open from without running off the surface. `alignStart` swings it to the leading edge for a pill that sits at the start of a bar, and `alignEnd` spells the default out. Both are logical, so a right-to-left locale mirrors them. Press each pill to see which edge its menu meets. ```tsx ``` ### Disabled menu `disabled` takes the whole pill out of service: it dims to the platform's disabled treatment, the press is a no-op, the menu never opens, and the button announces itself disabled before anyone reaches for it. Reach for it while the session is still resolving, not to park a menu whose rows simply have nothing to offer. ```tsx ``` ## Do & Don't ### Single **Do** — One or two initials, about a third of the disc. ```tsx ``` **Don't** — Cramming in a full set of initials shrinks the type and crowds the circle. ```tsx ABCD ``` ### Stacked **Do** — Cap the stack with `max` and let AvatarGroup summarize the rest as a +N count. ```tsx ``` **Don't** — An unbounded stack (no `max`) runs off the row and stops being scannable. ```tsx ``` ### Topbar account menu **Do**: Press the avatar: `AvatarMenu compact` makes it a real trigger in a bar with no room for a name, and the account is named in the menu header the moment the menu opens. ```tsx ``` **Don't** — A bare avatar with no Dropdown around it is decorative, not a trigger, and opens nothing. ```tsx ``` ### Identity **Do** — Name primary; email muted and secondary. ```tsx ``` **Don't** — Equal weight on the name and email flattens the hierarchy. ```tsx Rachel Chen rachel.chen@example.com ``` ### Menu header **Do** — Keep one consistent circular avatar shape across contexts. ```tsx ``` **Don't** — Squaring the avatar here clashes with the circular avatars everywhere else. ```tsx Ada Lovelace admin@example.com ``` ### Account identity **Do**: Press the pill: `name` and `email` go through AvatarMenu, which owns both lines, the shared press target, and the identity header on the menu it opens. ```tsx ``` **Don't**: Hand-composing the identity beside the trigger drifts from the pill's type and spacing, and the menu that opens names no account at all. ```tsx Rachel Chen rachel.chen@example.com ``` ### Sign out **Do**: Open the menu: keep the destructive sign out last and behind a separator, a reach away from the rows people press every day. ```tsx ``` **Don't**: Sign out buried mid-list, in the same tone as its neighbours, is one slip away on the path to Settings. ```tsx ``` ### Compact trigger **Do**: Press the pill: where the row has space, the full form says who is signed in before anything is opened. ```tsx ``` **Don't**: `compact` on a roomy page that names the account nowhere else hides who is signed in behind a press, to save room the row never needed. ```tsx ``` ### Unavailable account **Do**: `disabled` on the pill itself: the whole trigger dims, the press is inert, and the button announces itself disabled instead of inviting a press that goes nowhere. ```tsx ``` **Don't**: Disabling every row instead leaves the pill live: it still opens, onto a menu where nothing can be pressed. ```tsx ``` ## Supported props ### AvatarMenuProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | account | boolean \| undefined | no | false | Original Argus account control with a 32px orchid disc and compact role menu. | | triggerLabel | string \| undefined | no | | Explicit name for the trigger. | | menuLabel | string \| undefined | no | | Menu heading and accessible name, independent of the account's identity lines. | | menuWidth | number \| undefined | no | | | | maxHeight | number \| undefined | no | | | | name | string \| undefined | no | | The account holder's full name: the pill's first line, the avatar's initials fallback and colour identity, and the menu header's title. | | email | string \| undefined | no | | The secondary line under the name in the pill, and the menu header's description (typically the account's email address). | | src | string \| undefined | no | | The account photo. Falls back to the initials exactly like `Avatar`. | | initials | string \| undefined | no | | Ready-made initials for the avatar, used verbatim when there is no photo. | | items | DropdownItem[] | yes | | The menu rows, top to bottom. Same rows as `Dropdown` (icon, shortcut, destructive, disabled, separatorBefore). | | compact | boolean \| undefined | no | false | Avatar and chevron only, no name block: the topbar form of the pill. | | alignStart | boolean \| undefined | no | false | Hang the menu from the pill's LEADING edge instead of its trailing one. | | alignEnd | boolean \| undefined | no | false | The default, spelled out: the menu's trailing edge meets the pill's. | | disabled | boolean \| undefined | no | false | Dimmed, non-opening pill: the menu never opens and the press is inert. | | inline | boolean \| undefined | no | false | Draw the menu open and in flow under the pill instead of floating it over the page, a specimen of the account menu (Dropdown's `inline`): the pill reads as open and its press is inert, a row still fires `onSelect`, and `open` / `onOpenChange` have no effect. | | open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (the pill opens/closes it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (pill press, row select, dismissal). | | onSelect | ((item: DropdownItem, index: number) => void) \| undefined | no | | Fired with the selected row and its index when a menu row is pressed. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Outer layout composition only (placement within a parent), never a restyle hook. | ### AvatarProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | logo | boolean \| undefined | no | false | Original 30px rounded brand mark with its action-to-selection gradient. | | annotation | boolean \| undefined | no | false | Reference identity geometry, smallest first in precedence. | | compact | boolean \| undefined | no | false | | | account | boolean \| undefined | no | false | | | row | boolean \| undefined | no | false | | | person | boolean \| undefined | no | false | | | profile | boolean \| undefined | no | false | | | detail | boolean \| undefined | no | false | | | hero | boolean \| undefined | no | false | | | featured | boolean \| undefined | no | false | | | spotlight | boolean \| undefined | no | false | | | hue | number \| undefined | no | | Stage identity hue, independent of the display name. | | whiteInk | boolean \| undefined | no | false | | | bold | boolean \| undefined | no | false | | | inverse | boolean \| undefined | no | false | | | orchid | boolean \| undefined | no | false | | | glow | boolean \| undefined | no | false | | | sheen | boolean \| undefined | no | false | | | mediumSheen | boolean \| undefined | no | false | | | strongSheen | boolean \| undefined | no | false | | | src | string \| number \| undefined | no | | The photo. A remote/URI string, or a bundled image module from `require(...)` / `import`. When set (and it loads), the image fills the circle and the initials fallback is hidden; if the image fails to load, it falls back to the initials. | | uri | string \| number \| undefined | no | | Alias for `src`, for callers that think in terms of a native image uri. | | name | string \| undefined | no | | Full name. Drives the initials fallback AND the accessible label/alt text. | | initials | string \| undefined | no | | Ready-made initials, used verbatim as the glyph (overrides `name`/`children`). | | children | ReactNode | no | | Same as `name`; the rendered initials when no photo is supplied. | | accessibilityLabel | string \| undefined | no | | Accessible name for the avatar (the photo's alt text and, when pressable, the button's label). Falls back to `name`, then string `children`. Set it for a photo-only or pressable avatar that has no `name`. | | tiny | boolean \| undefined | no | false | Size (pick one; default is the 40px row avatar, `tiny` the 20px disc, `small` 28, `large` 56). Precedence: `tiny` > `small` > `large`. | | small | boolean \| undefined | no | false | Size (pick one; default is the 40px row avatar, `tiny` the 20px disc, `small` 28, `large` 56). Precedence: `tiny` > `small` > `large`. | | large | boolean \| undefined | no | false | Size (pick one; default is the 40px row avatar, `tiny` the 20px disc, `small` 28, `large` 56). Precedence: `tiny` > `small` > `large`. | | circle | boolean \| undefined | no | false | Shape (pick one; default is a circle). | | rounded | boolean \| undefined | no | false | Shape (pick one; default is a circle). | | ring | boolean \| undefined | no | false | Separator outline, for avatars that overlap in a stack. | | online | boolean \| undefined | no | false | A presence dot: the `success` disc in a 2px ring of the card, at the bottom right, one pixel past the disc's edge. It is decoration (hidden from assistive tech), so name the presence in the label where it matters. | | onPress | (() => void) \| undefined | no | | When set, the avatar becomes pressable (e.g. a topbar account trigger). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | For layout composition only; overlap/stacking is owned by AvatarGroup, not this prop. | ### AvatarGroupProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | Avatar elements to stack. | | max | number \| undefined | no | | Cap the visible avatars; the remainder collapses into a "+N" chip. | | total | number \| undefined | no | | Override the total behind the "+N" chip (e.g. a server-known member count). | | tiny | boolean \| undefined | no | false | Size (pick one; forwarded to every child so the whole stack is uniform). Precedence: `tiny` > `small` > `large`. | | small | boolean \| undefined | no | false | Size (pick one; forwarded to every child so the whole stack is uniform). Precedence: `tiny` > `small` > `large`. | | large | boolean \| undefined | no | false | Size (pick one; forwarded to every child so the whole stack is uniform). Precedence: `tiny` > `small` > `large`. | | tight | boolean \| undefined | no | false | Overlap tightness (pick one; default `tight`). Precedence loose > snug > tight. | | snug | boolean \| undefined | no | false | Overlap tightness (pick one; default `tight`). Precedence loose > snug > tight. | | loose | boolean \| undefined | no | false | Overlap tightness (pick one; default `tight`). Precedence loose > snug > tight. | | accessibilityLabel | string \| undefined | no | | Accessible name for the whole group (e.g. "8 members"). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Badge Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/badge/badge.md` Human reference: https://canvas.nannier.com/components/badge ## Public imports ```tsx import { Badge, BadgeGroup } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Two families on one Badge component, picked by boolean props. The metadata badge labels a schema, role, or tag: with no tone it is the accent's soft pill, and the tones give `default` the solid call-to-action pill, `secondary` the quiet surface pill, `outline` a hairline pill and `destructive` the soft red pill; add `mono` for token names. A status tone (`success`, `warning`, `error`, `info`, `neutral`) paints the pill in that tone's wash and ink and leads it with a dot. The status badge (`status`) has no pill: a dot in the tone's color, ringed in its wash, beside a muted label, for live state like active, pending, or failed. The tone colors come from `statusColors`, the helper every toned surface in the kit reads, so a warning badge and a warning alert are the same state, and every ink is the solved text role that keeps 4.5:1 on its wash. `contrast` is the most prominent neutral: the scheme's ink (`foreground`) as the fill under the page colour as the label (under glass, the ink at the dense alpha, as the Tooltip bubble's). `dashed` is the placeholder outline: no fill inside a dashed `field-border` edge, the label in the second ink. If more than one tone is passed, Badge resolves the highest-precedence one: `default` > `contrast` > `destructive` > `secondary` > `outline` > `dashed` for metadata (the accent's soft pill when none is passed), and `success` > `error` > `warning` > `info` > `neutral` for status (`neutral` when none is passed). The metadata tones win over a status tone's colors; the status tone's dot stays. `cyan` and `mint` paint a metadata pill in a fixed series hue, the same whatever the palette (the hand-off's `--cyan` and `--mint`, the `chart-2` and `chart-3` tokens): the hue's 16% wash under its ink, solved to 4.5:1 on the wash, with no dot. They sit after the status tones in precedence (`success` > `warning` > `error` > `info` > `cyan` > `mint`), so a section can name three tiers mint, cyan and the accent and keep them apart under every palette. Lay out a series of badges with `BadgeGroup`, a wrapping row that owns the gap (`tight` 4 / `compact` 6 / `snug` 8 / `cozy` 12, default `compact`, the hand-off's 6; the largest gap passed wins: `cozy` > `snug` > `compact` > `tight`) and centers its badges, so a call site never hand-rolls a flex row around them. Give it an `accessibilityLabel` (say, "Rachel Chen's roles") and it is announced as a named group that keeps its badges' text; without one it is a plain row. The pill is 22 px tall with a 12 px label; `small` is 18 px with an 11 px label and `large` 26 px. `count` is the small pill with the heavier label, `navigationCount` the small solid count, and `reference` a linked key at the 6 px key corner in the code face, underlined, with an arrow when it has an `href`. `token` is a code token chip, the 22 px pill under the small 11 px label in the code face, for a role, a state or an attribute name listed beside its term; `tier` is a tier tag, a 24 px pill with a 10 px inset under an 11 px mono label in caps on its own line, the tag that names a component's tier beside its title, in whichever tone the tier takes. `tiny` is the 18 px pill under a 10 px label on its own line, at the regular weight even when `solid` (a navigation row's "New" tag). The metric precedence is `notification` > `marker` > `disc` > `step` > `reference` > `navigationCount` > `count` > `tier` > `band` > `tick` > `token` > `tiny` > `small`. `primary` and `action` take the accent's wash, `soft` names the soft wash every toned pill already has, `neutralFill` fills with the border color, `foreground` overrides the label ink, `dot` adds the leading dot without a status tone, and `inverse` and `solid` supply branded surfaces. `uppercase` tracks caps; `start` pins a badge in a host layout; `truncate` keeps the label on one line and lets the pill shrink in a tight row, ending the label with an ellipsis instead of overflowing its host. `notification` is a destructive count: a 20 px pill with a 600 11 px label, soft by default and solid with `solid`, set off from the page by a ring of the background; `action` adds a halo of the destructive wash. Button carries its own count when passed a `count`. `signal` is the original standalone 10px status marker. It uses the existing status-tone precedence and announces its supplied status label. `marker` is an anatomy figure's numbered disc: a 22 px pill with no inset, its number 600 11 px in the code face on the accent (the solid call-to-action fill under its ink), set off from the stage by a 3 px ring of the stage well's fill (`surface-sunken`) and a drop shadow, the way the component page's Anatomy section numbers a specimen's parts. It wins over the other metrics but `notification`. `disc` is the same 22 px numbered disc in a legend: the marker's disc and 600 11 px code-face number, in the tone axis's fill (`default` the solid accent for a part the figure draws, `secondary` the quiet surface for one it does not) with no ring and no shadow, the way the Anatomy legend numbers its rows. It sits below `marker` in precedence and above the pills. `tier` is the tier pill beside a page title (Atom, Molecule, Organism): 24 px tall with a 10 px inset and a 500 11 px label on its own line, tracked in caps with `uppercase` and in the code face with `mono`, in the tone axis's fill. It sits above `small` in precedence. `band` is a group band's pill, the "pick one" beside an axis's title in a props table: 20 px tall with a 7 px inset, no border, and a 500 11 px label, in the code face with `mono`, in the tone axis's fill (the accent's soft pill with none). It sits between `tier` and `small` in precedence; like `tick`, it never leads with a tone's dot. `tick` is a ruler tick's readout: 20 px tall with an 8 px inset, no border and a 500 10 px label in the code face on the pill's own 20 px line, usually `contrast` (the Layout page's "this window" pill under its marker). `step` is a numbered step mark: a 32 px square at the 10 px step corner with its number 600 13 px in the code face, centred, in the tone axis's fill: `default` the solid accent with the accent's glow beneath, none the accent's soft wash, `secondary` the quiet surface inside the `field-border` edge in the second ink. Pass it as a MediaObject's `icon` with `bareIcon` to number a list of steps. `keyCorners` sets any pill at the key cap's 6 px corner in place of the round one (the corner axis has this one member); `reference` and `step` keep their own corners. ## Usage ```tsx admin ``` ## Variants ### Notification count ```tsx 3 ``` ### Solid ```tsx admin ``` ### Outline ```tsx admin ``` ### Destructive ```tsx admin ``` ### Mono (token / event names) ```tsx refresh_token ``` ### Status ```tsx pending ``` ### Status tones ```tsx active degraded failed syncing archived ``` ### Bare dot `dot` with no label is the 8 px dot alone, in the tone's ink with no pill: a marker beside text that names it. Give it an `accessibilityLabel` when it carries meaning of its own. ```tsx Live ``` ### Hue tones ```tsx compact regular wide ``` ### Group ```tsx employee engineering active ``` ### Reference link ```tsx CORE-1842 ``` ### Anatomy marker ```tsx 1 2 3 ``` ### Legend disc ```tsx 1 2 3 ``` ### Tier pill ```tsx Atom Molecule Organism ``` ### Band pill ```tsx pick one stacks ``` ### Contrast tick ```tsx this window · 1440px ``` ### Step marks ```tsx 1 2 3 ``` ### Key corners and the dashed outline ```tsx fill hug View ScrollView ``` ### Token chips ```tsx busy disabled aria-expanded ``` ## Do & Don't ### Grouping **Do**: Reach for `BadgeGroup` to lay out a series of badges; it owns the wrap, the gap, and the vertical centering. ```tsx employee engineering remote ``` **Don't**: Hand-rolling a flex row for a badge series re-invents the gap and wrap and drifts from the kit's spacing scale. ```tsx employee engineering remote ``` ### Metadata badge **Do**: Neutral tags for metadata; reserve color and the status-badge dot for live state. ```tsx employee engineering remote active ``` **Don't**: Borrowing status colors for plain metadata reads as severity that isn't there; a red tag looks like an error. ```tsx employee engineering remote active ``` ### Status badge **Do**: Always pair the dot with a word: active, pending, failed. ```tsx Failed ``` **Don't**: A bare colored dot isn't a label and fails for color-blind users. ```tsx ``` ### Identity row **Do**: Show only the one or two badges relevant to this view. ```tsx Rachel Chen active employee ``` **Don't**: A wall of badges after a name buries the one that matters. ```tsx Rachel Chen active Verified employee engineering remote admin ``` ### Token / code badge **Do**: Use the mono variant for tokens, scopes, and event names. ```tsx authorization_code refresh_token client_credentials ``` **Don't**: Proportional type makes identifiers hard to scan and compare. ```tsx authorization_code refresh_token client_credentials ``` ### Default variant **Do**: Reserve the default fill for the single tag you want noticed first; keep the rest secondary. ```tsx admin engineering remote ``` **Don't**: The solid call-to-action fill is the loudest badge; using it for every tag makes the whole row shout and nothing leads. ```tsx employee engineering remote admin ``` ### Secondary variant **Do**: Keep secondary for static metadata (role, team) and switch to the status-badge for anything live. ```tsx employee engineering active ``` **Don't**: A muted gray pill reads as static metadata, so live state shown as a secondary badge looks inert and goes unnoticed. ```tsx active pending failed ``` ### Outline variant **Do**: Use outline on a plain surface where the quiet border has contrast, for low-priority secondary tags. ```tsx draft internal ``` **Don't**: The thin border is the whole badge; on a colored or busy surface it disappears and the label floats unboxed. ```tsx draft internal ``` ### Destructive variant **Do**: Reserve destructive for genuinely destructive or error semantics like revoked or banned. ```tsx Revoked Banned marketing ``` **Don't**: Solid red signals error or danger, so using it to color-code neutral categories raises a false alarm. ```tsx marketing finance legal ``` ## Supported props ### BadgeProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | | | status | boolean \| undefined | no | false | Family: metadata badge (default) vs. status badge (with a dot). | | soft | boolean \| undefined | no | false | Soft intent fill with matching ink (every toned pill is soft; kept for the intent grammar). | | solid | boolean \| undefined | no | false | | | action | boolean \| undefined | no | false | | | primary | boolean \| undefined | no | false | | | inverse | boolean \| undefined | no | false | | | neutralFill | boolean \| undefined | no | false | Neutral border-colored fill. | | foreground | boolean \| undefined | no | false | Foreground ink on the quiet surface. | | small | boolean \| undefined | no | false | The small pill: 18 px tall, 11 px label. | | tiny | boolean \| undefined | no | false | The tiny pill: 18 px tall under a 10 px label on the pill's own 18 px line, at the regular weight even when solid (a navigation row's "New" tag beside its count). | | count | boolean \| undefined | no | false | A count at the small size with the heavier label. | | navigationCount | boolean \| undefined | no | false | Solid count used in navigation. | | notification | boolean \| undefined | no | false | Compact counter carried by a notification control. | | reference | boolean \| undefined | no | false | A linked reference key at the key corner, in the code face. | | token | boolean \| undefined | no | false | A code token chip: the 22 px pill with its 8 px inset under the small 11 px label in the code face, for a role, a state or an attribute name beside its term. | | large | boolean \| undefined | no | false | The large pill: 26 px tall. | | marker | boolean \| undefined | no | false | An anatomy figure's numbered marker: a 22 px disc on the accent with its number in the code face, set off from the stage by a ring of the stage's fill and a shadow (Component.dc.html line 220). | | disc | boolean \| undefined | no | false | A legend's numbered disc: the marker's 22 px disc with its number in the code face, in the tone axis's fill (the solid accent as `default`, the quiet surface as `secondary`) with no ring and no shadow (Component.dc.html line 222, the anatomy legend's part numbers). | | tier | boolean \| undefined | no | false | A tier pill (Atom, Molecule, Organism beside a page title): 24 px tall with a 10 px inset and a 500 11 px label on its own line (Component.dc.html line 139), tracked in caps with `uppercase` and in the code face with `mono`, as the page sets it. | | band | boolean \| undefined | no | false | A group band's pill (the props table's "pick one" beside an axis's title): 20 px tall with a 7 px inset and a 500 11 px label (Component.dc.html line 237), in the code face with `mono` as the page sets it, in the tone axis's fill (the accent's soft pill with none). It sits between `tier` and `small` in precedence. | | tick | boolean \| undefined | no | false | A ruler tick's pill (the Layout page's "this window" readout under its marker): 20 px tall with an 8 px inset and a 500 10 px label in the code face on the pill's own 20 px line, with no border and no tone dot. Usually with `contrast`. After `band` in precedence. | | step | boolean \| undefined | no | false | A numbered step mark (the Layout page's "try in this order" list): a 32 px square at the step mark's corner with its number 600 13 px in the code face, centred. In the tone axis's fill: `default` the solid accent with its glow, none the accent's soft wash, `secondary` the quiet surface inside the `field-border` edge in the second ink. Pass it as a MediaObject's `icon` with `bareIcon`. After `disc` in precedence. | | keyCorners | boolean \| undefined | no | false | The key cap's corner in place of the round pill's, on any size: the sizing legend's mode tags and the primitive names under a Grid (Layout.dc.html). The corner axis has this one member; `reference` and `step` keep their own corners. | | dot | boolean \| undefined | no | false | Show the intent dot without switching to the status family. With no children it is the bare 8 px dot in the tone's ink, with no pill (a marker beside text that names it). | | signal | boolean \| undefined | no | false | Standalone 10px signal marker, retaining its status label. | | start | boolean \| undefined | no | false | Pin to the start when embedded in an unmeasured host layout. | | truncate | boolean \| undefined | no | false | Keep the label on one line and let the pill shrink in a tight row, ending the label with an ellipsis instead of overflowing its host. | | uppercase | boolean \| undefined | no | false | | | href | string \| undefined | no | | A reference can open its source in the system browser. | | default | boolean \| undefined | no | false | Metadata tone (pick one; default is the accent's soft pill). | | secondary | boolean \| undefined | no | false | Metadata tone (pick one; default is the accent's soft pill). | | outline | boolean \| undefined | no | false | Metadata tone (pick one; default is the accent's soft pill). | | dashed | boolean \| undefined | no | false | The placeholder outline: no fill inside a dashed `field-border` edge, the label in the second ink (the Layout page's primitive names). On the tone axis after `outline`. | | contrast | boolean \| undefined | no | false | The contrast pill: the scheme's ink (`foreground`) as the fill under the page colour (`background`) as the label, the most prominent neutral (the Layout ruler's "this window" readout). On the tone axis after `default`; under glass the ink at the dense alpha, as the Tooltip bubble's, so its label keeps its contrast. | | destructive | boolean \| undefined | no | false | | | mono | boolean \| undefined | no | false | Metadata modifier: monospace face for tokens, scopes, event names. | | success | boolean \| undefined | no | false | Status tone (pick one; on a metadata pill it paints the tone and adds the dot). | | warning | boolean \| undefined | no | false | Status tone (pick one; on a metadata pill it paints the tone and adds the dot). | | error | boolean \| undefined | no | false | Status tone (pick one; on a metadata pill it paints the tone and adds the dot). | | info | boolean \| undefined | no | false | Status tone (pick one; on a metadata pill it paints the tone and adds the dot). | | neutral | boolean \| undefined | no | false | Status tone (pick one; on a metadata pill it paints the tone and adds the dot). | | cyan | boolean \| undefined | no | false | The fixed cyan series hue (the hand-off's `--cyan`, `chart-2`) as a soft pill, whatever the palette: its 16% wash under its ink, solved to 4.5:1 on the wash. No dot. On the intent axis, after the status tones. | | mint | boolean \| undefined | no | false | The fixed mint series hue (the hand-off's `--mint`, `chart-3`) as a soft pill, like `cyan`. | | accessibilityLabel | string \| undefined | no | | Accessible name for the badge. Most useful for the status family: the bare-dot status form (`` with no children) is otherwise color-only and silent to screen readers. When omitted on a childless status badge, the status tone word (e.g. "error") is announced as a fallback. Mirrors the Spinner/Progress `accessibilityLabel` contract. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | ### BadgeGroupProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | The Badge elements to lay out in a wrapping row. | | tight | boolean \| undefined | no | false | Inter-badge gap (pick one; default `compact`, the hand-off's 6). Precedence: cozy > snug > compact > tight. | | compact | boolean \| undefined | no | false | Inter-badge gap (pick one; default `compact`, the hand-off's 6). Precedence: cozy > snug > compact > tight. | | snug | boolean \| undefined | no | false | Inter-badge gap (pick one; default `compact`, the hand-off's 6). Precedence: cozy > snug > compact > tight. | | cozy | boolean \| undefined | no | false | Inter-badge gap (pick one; default `compact`, the hand-off's 6). Precedence: cozy > snug > compact > tight. | | accessibilityLabel | string \| undefined | no | | Accessible name for the whole group (e.g. "Rachel Chen's roles"). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # BarList Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/bar-list/bar-list.md` Human reference: https://canvas.nannier.com/components/bar-list ## Public imports ```tsx import { BarList } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Ranked label and value rows: each row is a proportional tone wash behind a truncating label, with a right-aligned mono value and an optional delta. Bars size against the largest row by default (the ranking idiom); `share` sizes them against the sum of the rows and appends percent readouts (the composition idiom). Rows become drill-in buttons with `onPressItem`. For comparing magnitudes on a shared axis without deltas or shares, reach for the bar `Chart` and its `horizontal` mode instead. ## Usage ```tsx ``` ## Variants ### Share of total ```tsx ``` ### Drill-in rows ```tsx {}} items={[ { label: "news.ycombinator.com", value: 4210 }, { label: "reddit.com", value: 2380 }, { label: "linkedin.com", value: 1240 }, ]} /> ``` ### Plain, inside a card ```tsx ``` ## Do & Don't ### BarList **Do** - Let the component own the whole row anatomy: label, value, delta, and the proportional wash. ```tsx ``` **Don't** - A hand-rolled row of Text and colored Views splits the anatomy, drifts from the type scale, and hides its share from assistive tech. ```tsx /pricing 18.4k ``` ## Supported props ### BarListProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | items | BreakdownRow[] | yes | | The rows to render, in order. | | title | string \| undefined | no | | Optional heading shown above the rows. | | share | boolean \| undefined | no | false | Bars proportional to the sum of all rows, with percent readouts, instead of relative to the largest row. | | success | boolean \| undefined | no | false | Tone (single-hue lists; pick one; default is the ramp by index). Precedence: success > destructive; a row's chartN slot beats the tone. | | destructive | boolean \| undefined | no | false | Tone (single-hue lists; pick one; default is the ramp by index). Precedence: success > destructive; a row's chartN slot beats the tone. | | compact | boolean \| undefined | no | false | Density (omit for the default row spacing). | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | formatValue | ((v: number) => string) \| undefined | no | | Formats row values (data formatting, not styling). | | onPressItem | ((index: number) => void) \| undefined | no | | Rows become tappable drill-in targets when set. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Board Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/board/board.md` Human reference: https://canvas.nannier.com/components/board ## Public imports ```tsx import { Board, applyBoardMove } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A data-driven kanban board: columns scroll horizontally, each column is a drop zone in a sunken well headed by its label and item count, and every card carries a drag grip, an optional trailing badge, a two-line description, a free-form chips slot, and an optional kebab menu. Dragging is the kit's own drag-and-drop family, so it runs on iOS, Android, and the web from one code path and stays keyboard- and screen-reader-operable: press Space on a grip to grab, the arrow keys to move between positions and columns, Space to drop, Escape to cancel. The list stays controlled: a drop reports a `BoardMove` (source, target, insertion index, and the new neighbor ids) through `onMove`, and `applyBoardMove` is the standard reducer to apply it. Pass `defaultItems` instead of `items` for uncontrolled use, where the board applies each move itself. For an action-driven work board, `plain` drops the lane wells and keeps the lane headers and dashed empty slots. `renderItem` supplies the domain card with its own controls. Add `draggable` to enable drag grips and drop targets while retaining the plain layout. A custom `renderItem` receives `{ dragHandle }` as its second argument; place that supplied grip in the card's header. `disabled` prevents moves while a consumer saves a change. `bleed` extends horizontal scrolling through the owning container's inset. Plain lanes share the Board's measured width equally, with a 168px minimum per lane. Narrow containers scroll horizontally; long card text stays within its lane. Add `pageScroll` when the surrounding page or content pane owns horizontal scrolling. The Board then renders an overflow-visible lane row without an inner scrollport. Plain lanes still fill the available content width equally down to their 168px minimum; standard lanes keep their configured width. The surrounding scroll area must allow horizontal overflow and keyboard scrolling. Card actions and drag grips retain their keyboard behavior. Omit `pageScroll` to keep the default local scroller. ## Usage ```tsx ``` ## Variants ### Plain work lanes ```tsx ``` ### Draggable plain work lanes The grip supports pointer dragging and Space/arrow/Enter keyboard moves. Drop targets fill each column's height, including the space below an empty placeholder. Controlled consumers persist `onMove` before replacing their saved `items`. ```tsx ( {item.title}{dragHandle} )} /> ``` ### Compact The density axis tightens the lane padding, the card gaps, and the card insets for a board that has to show more at once. ```tsx ``` ### Card menus and press `onPressItem` makes each card's body pressable (the grip and the kebab stay separate controls), and a card with `menu` rows gains a kebab reported through `onSelectItemMenu`. `chips` is a free slot under the title for chips or avatars. ```tsx identity }, ]} onPressItem={(item) => {}} onSelectItemMenu={(item, menuItem) => {}} /> ``` ### Empty column and explicit badges An empty column shows `emptyLabel` in a dashed slot and stays a valid drop target. A column's `badge` prop replaces the automatic item count (a WIP limit, a status). ```tsx ``` ### Controlled `items` keeps the list in your hands: each drop reports a `BoardMove` through `onMove`, and `applyBoardMove` is the standard reducer to apply it. ```tsx {(items, setItems) => ( setItems(applyBoardMove(items, move))} /> )} ``` ## Do & Don't ### Moves stay in the consumer's data **Do** — Treat `items` as the single source of truth: apply every `onMove` to your own array (`applyBoardMove`), and persist `afterId`/`beforeId` if your backend ranks by neighbors. ```tsx {(items, setItems) => ( setItems(applyBoardMove(items, move))} /> )} ``` **Don't** — Don't pass a controlled `items` and drop the move on the floor: the drag completes, announces, and then the card snaps back because nothing updated the array. ```tsx ``` ## Supported props ### BoardProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | plain | boolean \| undefined | no | false | Unframed work lanes with custom domain cards. | | pageScroll | boolean \| undefined | no | false | Let the surrounding page own horizontal overflow instead of an inner scroller. | | draggable | boolean \| undefined | no | false | Enable drag handles and drop targets in plain lanes. | | disabled | boolean \| undefined | no | false | Temporarily prevent moves, for example while the consumer saves a drop. | | renderItem | ((item: BoardItem, controls: { dragHandle: ReactNode; }) => ReactNode) \| undefined | no | | Custom plain card; place the supplied dragHandle alongside its domain actions. | | bleed | number \| undefined | no | | Extend the scroller through its parent's horizontal inset. | | columns | BoardColumn[] | yes | | The columns (lanes), left to right. | | items | BoardItem[] \| undefined | no | | CONTROLLED list; order within a column is array order filtered by `columnId`. The kit never mutates it: apply each `onMove` yourself (`applyBoardMove` is the standard reducer). Omit it (optionally passing `defaultItems`) for uncontrolled use. | | defaultItems | BoardItem[] \| undefined | no | | Initial list for UNCONTROLLED use; Board then applies each move internally. | | onItemsChange | ((items: BoardItem[]) => void) \| undefined | no | | Fired with the would-be next list after a drop, in both modes. | | onMove | ((move: BoardMove) => void) \| undefined | no | | Fired on every completed drop with the computed move (source, target, index, neighbors). | | onPressItem | ((item: BoardItem) => void) \| undefined | no | | Makes each card body pressable and reports the pressed item. | | onSelectItemMenu | ((item: BoardItem, menuItem: RowMenuItem, menuIndex: number) => void) \| undefined | no | | Fired when a row of a card's kebab menu (`item.menu`) is selected. | | columnWidth | number \| undefined | no | 300 | Column width in px (default 300). | | compact | boolean \| undefined | no | false | Density axis (pass none for the default density). | | emptyLabel | string \| undefined | no | "No items" | Message shown in an empty column (default "No items"). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Outer layout composition only (width/flex), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # BoxPlot Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/box-plot/box-plot.md` Human reference: https://canvas.nannier.com/components/box-plot ## Public imports ```tsx import { BoxPlot } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Quartile boxes, whiskers, and outlier dots per category, computed by the chart from raw samples: quartiles by linear interpolation, whiskers at the most extreme data inside the 1.5 IQR fences, outliers beyond them. Scrub a category to flag its five-number summary; each category's summary also folds into the accessible name. ## Usage ```tsx ``` ## Variants ### Compact ```tsx ``` ### Inspected ```tsx ``` ## Do & Don't ### BoxPlot **Do** - Compare distributions with the full five-number anatomy: box, whiskers, median, outliers. ```tsx ``` **Don't** - A bar of averages hides the spread and the outliers, which are the point of comparing distributions. ```tsx ``` ## Supported props ### BoxPlotProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | data | BoxSample[] | yes | | One box per category, in order. | | title | string \| undefined | no | | Optional heading shown above the plot. | | success | boolean \| undefined | no | false | Tone (pick one; default primary). Precedence: success > destructive. | | destructive | boolean \| undefined | no | false | Tone (pick one; default primary). Precedence: success > destructive. | | compact | boolean \| undefined | no | false | Density (omit for the default plot size). | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | hideGrid | boolean \| undefined | no | false | Furniture visibility. | | hideAxes | boolean \| undefined | no | false | Furniture visibility. | | formatValue | ((v: number) => string) \| undefined | no | | Formats tick labels, the flag, and the accessible name. | | selected | number \| null \| undefined | no | | Scrub-to-inspect: the selected category index (controlled). Pass null for none. | | defaultSelected | number \| undefined | no | | Scrub-to-inspect: the initially selected category (uncontrolled). | | onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press/scrub selects a category (or clears it with null). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Breadcrumb Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/breadcrumb/breadcrumb.md` Human reference: https://canvas.nannier.com/components/breadcrumb ## Public imports ```tsx import { Breadcrumb, BreadcrumbItem } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Hierarchical navigation showing where you are. ## Usage ```tsx ``` ## Variants ### Slash ```tsx ``` ### Dot ```tsx ``` ### Home icon ```tsx ``` ### Subtle A page header's quieter trail: the ancestors in the subtle ink and the current page at the regular weight, so the trail recedes under the title. ```tsx ``` ## Do & Don't ### Current page **Do** — Ancestors are links; the page you're on is plain text at the end of the trail. ```tsx ``` **Don't** — Linking the current page implies there's somewhere to go; it's a dead link to itself. ```tsx (pressed ? { opacity: 0.7 } : null)}> Projects / (pressed ? { opacity: 0.7 } : null)}> Identity Platform / (pressed ? { opacity: 0.7 } : null)}> Settings ``` ### Deep paths **Do** — Collapse the middle to an ellipsis; keep the root and the last couple of levels. ```tsx ``` **Don't** — A fully expanded deep path wraps and competes with the page. ```tsx ``` ### Separator **Do** — Pick one separator and use it the whole way. ```tsx ``` **Don't** — Mixing separators in one trail looks broken. ```tsx (pressed ? { opacity: 0.7 } : null)}> Projects / (pressed ? { opacity: 0.7 } : null)}> Identity Platform › Settings ``` ### Home root **Do** — Give the home icon an aria-label so the root is announced. ```tsx ``` **Don't** — An icon-only root with no label is unclear to screen readers. ```tsx (pressed ? { opacity: 0.7 } : null)}> / Settings ``` ### Chevron **Do** — Point the chevron in the reading direction (right in LTR) so each one means 'drill into the next level'. ```tsx ``` **Don't** — A down (or back) chevron reads as a dropdown or a back affordance, not progression down the hierarchy. ```tsx (pressed ? { opacity: 0.7 } : null)}> Projects ⌄ (pressed ? { opacity: 0.7 } : null)}> Identity Platform ⌄ Settings ``` ### Slash **Do** — Keep the slash muted and lighter than the text so it reads as a quiet path divider. ```tsx ``` **Don't** — A full-weight, foreground slash competes with the labels and can read as part of a link. ```tsx (pressed ? { opacity: 0.7 } : null)}> Projects / (pressed ? { opacity: 0.7 } : null)}> Identity Platform / Settings ``` ### Dot **Do** — Use a centered middot (·) so the dot sits between the crumbs and clearly divides them. ```tsx ``` **Don't** — A baseline period looks like a typo or end-of-sentence, not a separator between crumbs. ```tsx (pressed ? { opacity: 0.7 } : null)}> Projects . (pressed ? { opacity: 0.7 } : null)}> Identity Platform . Settings ``` ## Supported props ### BreadcrumbProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | items | string[] \| undefined | no | | The trail, ancestor-first. The last entry renders as the current page (plain emphasized text); the rest render as muted links. | | chevron | boolean \| undefined | no | false | Separator style (pick one; default is the chevron glyph). | | slash | boolean \| undefined | no | false | Separator style (pick one; default is the chevron glyph). | | dot | boolean \| undefined | no | false | Separator style (pick one; default is the chevron glyph). | | homeIcon | boolean \| undefined | no | false | Leading affordance: prepend a muted home-icon crumb (aria-labelled "Home") before the trail, followed by the separator. Off by default. | | subtle | boolean \| undefined | no | false | A quieter trail for a page header: the ancestors (and the home glyph) in the subtle ink and the current page at the regular weight, so the trail recedes under the title. | | maxItems | number \| undefined | no | | Collapse a long trail: when the trail has more than `maxItems` crumbs, keep the first crumb and the last `maxItems - 1` (so `maxItems` crumbs in all), and replace the middle with a single "…" crumb, so a deep path stays on one scannable line. Takes effect from 2. | | onItemPress | ((item: string, index: number) => void) \| undefined | no | | Fired with the crumb label and its index when a link (non-last) is pressed. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | ### BreadcrumbItemProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | | | current | boolean \| undefined | no | false | Render as the current page: emphasized foreground text, non-interactive. | | subtle | boolean \| undefined | no | false | The quieter trail's type (see Breadcrumb's `subtle`). | | onPress | ((event: GestureResponderEvent) => void) \| undefined | no | | | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # BulletChart Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/bullet-chart/bullet-chart.md` Human reference: https://canvas.nannier.com/components/bullet-chart ## Public imports ```tsx import { BulletChart } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Goal-attainment rows: each datum is a leading label, a track holding qualitative background bands (`ranges`, ascending bounds in fading washes), the tone-colored measure bar, an optional vertical target tick, and the trailing formatted value. Following the classic bullet-graph anatomy each row carries its own scale, since goals rarely share units; pass `max` to force one shared scale when they do. The compact alternative to a dashboard of gauges: five goals read in five rows. `detailed` expands each track, keeps its target marker inside the band and adds the target/ranges key. ## Usage ```tsx ``` ## Variants ### Success tone ```tsx ``` ### Compact ```tsx ``` ### Row tones A datum takes its own `success`, `warning` or `destructive` tone (precedence success > warning > destructive): its measure bar is the solid status colour and its value the status ink. ```tsx `${v}%`} data={[ { label: "Availability", value: 99, target: 99, success: true }, { label: "Latency p95", value: 82, target: 90, warning: true }, { label: "Error budget", value: 41, target: 75, destructive: true } ]} /> ``` ## Do & Don't ### BulletChart **Do** - Give every goal its row, with the target as a tick and the qualitative context as bands. ```tsx ``` **Don't** - A bare progress bar per goal loses the target, the qualitative bands, and the shared scale that makes rows comparable. ```tsx ``` ## Supported props ### BulletChartProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | data | BulletDatum[] | yes | | The rows to render, in order, each on its own scale. | | title | string \| undefined | no | | Optional heading shown above the rows. | | max | number \| undefined | no | | Force one scale maximum shared by every row (only when the rows share a unit). Omit for the default per-row scale: each row's largest value, target, or range bound. | | success | boolean \| undefined | no | false | Measure tone (pick one; default primary). Precedence: success > destructive. | | destructive | boolean \| undefined | no | false | Measure tone (pick one; default primary). Precedence: success > destructive. | | compact | boolean \| undefined | no | false | Density (omit for the default row spacing). | | detailed | boolean \| undefined | no | false | Expanded goal rows with inset markers and a qualitative-band key. | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | formatValue | ((v: number) => string) \| undefined | no | | Formats row values (data formatting, not styling). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # ButtonGroup Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/button-group/button-group.md` Human reference: https://canvas.nannier.com/components/button-group ## Public imports ```tsx import { ButtonGroup } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Segmented controls, split buttons, attached groups. On the web the segmented control is the v4 hand-off's: the options sit in a rounded track on the raised fill, and the selected one rides a flat card thumb with a small shadow, the rest in the muted ink. It is 30, 36 or 44 px tall at the small, default and large sizes. The split button is the green call-to-action pill with its chevron half, and the stepper and the spaced peers are hairline pills the height of a Button beside them. Pass `rounded` for a capsule segmented track with equal cells and one sliding selection thumb. The small capsule is 32 px tall, as in the docs appearance picker. Its spring transition follows Reduce Motion. On iOS and Android the groups keep their platform shapes (the iOS segmented control, the Material 3 segmented button) in the theme's colours. All four kinds follow the theme's surface mode. Toggle Liquid Glass in the docs, or use `` in an app: segmented groups paint a glass selection puck, in the segment's own corners, on the selected segment, a split group is its call to action's brand-tinted glass (the action colour under the material, as a primary Button), a stepper shares a glass capsule, and spaced peers get individual glass surfaces. Every kind keeps its solid size under glass: the material covers the track or cell's whole footprint and a hairline border gives way to the material's edge, so switching the surface mode never moves the page. Solid mode keeps each platform's existing skin. The material uses native Liquid Glass on supported iOS versions, the native blur or tint fallback on Android and older iOS, and the hand-off's frost in the browser. Reduce Transparency and Increase Contrast use the shared material's opaque fallbacks. A web segment is 8 px shorter than its track, padded 14 px a side (8 px for an icon alone) with a 500 label of 12 px at the small size and 13 px otherwise. `compact` is the hand-off component page's platform switch: the segments keep their size's height and take the 12 px label on a 12 px inset, on the web alone (a platform that ships the control keeps its one density; the split, stepper and spaced kinds ignore it). `steadyPress` preserves the resting opacity of a segment while pressed; `start` pins the group in an app-owned host arrangement. ## Usage ```tsx ``` ## Variants ### Small ```tsx ``` ### Large ```tsx ``` ### Compact ```tsx ``` ### Disabled ```tsx ``` ### Block ```tsx ``` ### Block spaced ```tsx ``` ### Measure ```tsx ``` ### Attached `attached` joins the segments into one box: a single edge round the group, a hairline between the cells, and every cell its full height, the selected one on the raised fill. Use it for a toolbar's view or formatting choices. An item's `display` draws the cell's face from any node (a glyph set in a weight or a style), with the item's `label` kept as the cell's accessible name. ```tsx B }, { label: "Italic", display: I }, { label: "Underline", display: U } ]} /> ``` ### Stepper ```tsx ``` ### Stepper signs `decrementIcon` and `incrementIcon` replace the stepper's back and forward glyphs with any icon, for a stepper that changes a value rather than moving through a list. ```tsx } incrementIcon={} items={["100%"]} /> ``` ### Split ```tsx ``` In glass mode the split menu is a dense-layer glass card under the group, whose shared glass, primary label, divider and chevron stay in place. The primary action and the chevron stay two fixed targets throughout, and a group disabled while its menu is open closes it. Solid mode paints the skin's own menu. ### Icon segments An item may pair its label with a kit glyph (`{ label, icon }`); `iconsOnly` renders each segment as the glyph alone, with the label as the segment's accessible name. An icon-only segmented control: a view switcher, a form-factor switcher. ```tsx ``` ## Do & Don't ### Segmented **Do** — Keep a segmented control to a few mutually-exclusive views. ```tsx ``` **Don't** — Past ~4 options a segmented control gets cramped and hard to scan; reach for a select. ```tsx ``` ### Attached **Do** — Reserve attached groups for closely-related actions like prev / today / next. ```tsx ``` **Don't** — Attaching unrelated actions implies they belong to one control. ```tsx ``` ### Split **Do** — Separate the chevron with a hairline so the secondary menu reads as distinct. ```tsx ``` **Don't** — With no divider the chevron looks like part of one button, hiding the menu. ```tsx [ { flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, paddingHorizontal: 18, borderTopLeftRadius: 9999, borderBottomLeftRadius: 9999, borderTopRightRadius: 0, borderBottomRightRadius: 0, backgroundColor: tokens.action }, pressed ? { opacity: 0.9 } : null ]} > Save [ { flexDirection: "row", alignItems: "center", justifyContent: "center", height: 36, paddingLeft: 8, paddingRight: 12, borderTopRightRadius: 9999, borderBottomRightRadius: 9999, borderTopLeftRadius: 0, borderBottomLeftRadius: 0, backgroundColor: tokens.action }, pressed ? { opacity: 0.9 } : null ]} > ``` ## Supported props ### ButtonGroupProps Includes `MeasureProps`; their supported fields are expanded below. | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | items | ButtonGroupItem[] \| undefined | no | | Segments for segmented/spaced: a label string, or `{ label, icon }` for a leading kit glyph. The stepper cycles the labels (icons ignored there). | | active | number \| undefined | no | | Selected segment index (segmented, CONTROLLED), or the stepper's initial index. Omit for uncontrolled use. | | defaultActive | number \| undefined | no | | Initial selected segment index for uncontrolled use (a bare segmented control selects on press). | | onSelect | ((index: number, item: string, event: GestureResponderEvent) => void) \| undefined | no | | Called with the pressed/selected index and item (and, for the stepper, the new index). | | segmented | boolean \| undefined | no | false | Kind (pick one; default is segmented). | | split | boolean \| undefined | no | false | Kind (pick one; default is segmented). | | stepper | boolean \| undefined | no | false | Kind (pick one; default is segmented). | | spaced | boolean \| undefined | no | false | Kind (pick one; default is segmented). | | attached | boolean \| undefined | no | false | Buttons joined in one hairline box on the raised fill, divided by full-height rules, the selected one on the raised step (an alignment or a view picker drawn as buttons rather than a track and thumb). It selects as `segmented` does and takes `active`, `defaultActive` and `block`. | | decrementIcon | ReactNode | no | | The stepper's decrement end, in place of its built-in glyph (a `zoomOut` Icon for a zoom control). The stepper kind only. | | incrementIcon | ReactNode | no | | The stepper's increment end, in place of its built-in glyph (a `zoomIn` Icon). | | menu | string[] \| undefined | no | | Related actions shown in the split kind's chevron dropdown. | | small | boolean \| undefined | no | false | Size (pick one; default is the medium size). | | large | boolean \| undefined | no | false | Size (pick one; default is the medium size). | | compact | boolean \| undefined | no | false | Density of the segmented row: the segments keep their size's height and take the small label on a 12 px inset (the hand-off component page's platform switch, a default-height track of 12 px labels). On the web only: a platform that ships the control keeps its one density. The split, stepper and spaced kinds ignore it. | | rounded | boolean \| undefined | no | false | A capsule segmented track with equal cells and a sliding selection thumb. | | iconsOnly | boolean \| undefined | no | false | Segmented/spaced only: each segment renders its `icon` alone and the item's `label` becomes the segment's ACCESSIBLE name (an icon-only view or form-factor switcher). An item without an icon keeps its visible label, with a dev-only warning. Split and stepper ignore it (dev-only warning). | | steadyPress | boolean \| undefined | no | false | Keep segmented and spaced cells at their resting opacity during a press. | | accessibilityLabel | string \| undefined | no | | Accessible name for the segmented row (the `tablist`), announcing what the group switches (e.g. "Preview form factor"). Segmented kind only: spaced peers are independent buttons with no group role. | | block | boolean \| undefined | no | false | Stretch the group to the container width, the segments sharing the space equally. An orthogonal layout modifier for the segmented and spaced kinds; split and stepper ignore it (their cells are fixed-width chrome) with a dev-only warning. A measure step (`sm`, `lg`, …) does the same up to that width of the scale. | | disabled | boolean \| undefined | no | false | | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | | xxxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | sm | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | md | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | lg | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wide | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wider | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | widest | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | page | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | start | boolean \| undefined | no | false | Pin a capped component to the leading edge instead of centering it in its column. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Button Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/button/button.md` Human reference: https://canvas.nannier.com/components/button ## Public imports ```tsx import { AnatomyPart, AnatomyProvider, Button } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Six intents × three sizes (plus the icon square) × disabled / focus / hover states. Always semantic: the intent communicates what the button does (default = the primary action, destructive = irreversible, ghost = chrome). On the web a button is 32, 40 or 48 tall (`small`, default, `large`) with the 10 px control corner, a 500-weight label at 13, 14 or 16 px, and an 8 px gap beside its icons. The call to action fills with the accent, `destructive` with the status red, both under a lit top edge; `secondary` is the quiet well with a boundary, `outline` the boundary alone, `ghost` and `link` bare (the link underlined). A filled button brightens and rises 1 px under the pointer over 150 ms and settles back when it leaves; the other looks take the well's wash at once. A press scales the button to 96% and darkens it, unless `steadyPress`. A disabled button fades to 40%; a loading one keeps its full look while its spinner runs. A disabled or loading button stays put, and Reduce Motion makes the lift instant. On iOS the button is the 50 pt capsule with a semibold 17 pt label, on Android the 40 dp Material 3 stadium, both in the same colours. Pass `ref` to access the interactive Pressable, including link buttons. Use `useRef>(null)` from React, or `useRef(null)` with React Native's `View` type. Object and callback refs are supported and detach on unmount. Calling `ref.current?.focus()` or `.blur()` delegates to the host without activating the control. Browser focus is supported; native focus depends on the platform and React Native version, and is separate from accessibility focus. `softHover` dims a link to 85% on hover in place of the wash. Inverse actions use a translucent white hover wash over a branded surface. Utility icon actions use `icon` with `compactIcon` (28), `mediumIcon` (32), or `largeIcon` (34). `wideIcon` gives the compact box two extra pixels of horizontal room; `circle` rounds it fully. Pair with `ghost` for a plain surface, `outline` for a boundary, or `ghost inset` for a quiet fill. `steadyPress` keeps the resting look while pressed, and `count` carries a notification badge over the upper trailing corner. The button still owns its accessible name, hover wash, and native touch-target extension. Use `inlineLink link small` for an inline text action: an underlined 13-pixel label with no box, read as a link and washed on hover. The default size is 14 pixels; `large` is 16. `extraBold`, `foreground`, and `inverse` set its weight and ink. `external` opens `href` through the system browser on every platform, while keeping its browser link semantics. `plainLink` is a plain accent text action with no box and no underline, still read as a button: a panel's Reset, a table's show-all row. On the web it is a 500 13 px label in the accent text ink (12 px with `small`) that brightens to the foreground ink under the pointer; natively it is the platform's own tint-coloured text button. Among the bodies, `inlineLink` wins over `plainLink` when both are passed. `scopeTrigger ghost steadyPress` is a bare 32 px menu trigger with a 600-weight 14px label; `filterTrigger ghost steadyPress` the same with a muted 13px label. Both take the well's wash on hover. Supply the trailing chevron through `iconRight`, and announce the menu with `expanded` and `haspopup`. `identityAction` is a 28 px hairline pill around a leading avatar and its name; `referenceLink` an underlined monospace key in the link ink; `inlineIcon` an unboxed 22 px icon action; `themeChoice` a 32 px pill around the theme's colour disc, a `` passed through `iconLeft`, whose `selected` state is the accent edge with a soft accent halo. `pageLink` is a page header's link, a 34 px hairline box around a muted 13 px label (a page's section, its source); add `mono` when the label is a path, which sets it in the 400 12 px mono face. `jumpLink` is a page's jump to one of its sections, a 28 px hairline pill around a muted 500 12 px label. Both header links brighten their label to the full ink and take the `secondary` wash under the pointer. ## Usage A button's whole job is to fire `onPress`. Wire it to your own handler and every press runs it. ```tsx ``` ## Variants The variant only changes how a button looks; every one of them fires `onPress` the same way. ### Attention required Use `attention` for a required setup action. Soft red glow swells outward and returns in a heartbeat rhythm: a strong beat, a softer beat, then a pause every 1.8 seconds. Its inner edge, label and button dimensions stay still. Reduce Motion keeps a steady halo; disabled and loading buttons suppress it. Remove the button or the prop when the required setup is complete. ```tsx ``` ### Inline identity and reference actions ```tsx ``` ### Scope and filter triggers ```tsx ``` ### Utility icon ```tsx ``` ### Secondary ```tsx ``` ### Ghost ```tsx ``` ### Destructive ```tsx ``` ### Link ```tsx ``` ### Plain link ```tsx ``` ### Small ```tsx ``` ### Large ```tsx ``` ### Icon only ```tsx ``` ### Loading ```tsx ``` ### Raised `raised` rests the page's main call to action on a soft glow in its own colour. Only a primary button takes it, and never while disabled. ```tsx ``` ### Rounded `rounded` gives a button fully rounded ends: the pill in place of the control corner, and a circle on an `icon` square. The special bodies keep their own corners. ```tsx ``` ### Block ```tsx ``` ### Dashed `dashed` draws the edge dashed, for an add-a-row or drop-here action. It shows on the looks that draw an edge, `secondary` and `outline`. ```tsx ``` ### Measure Use `shrink` for a content-sized action in a narrow row. The button keeps its normal width when space permits and wraps its full label when constrained. ```tsx ``` ### With icon An `Icon` in `iconLeft` or `iconRight` takes the button's label ink when it carries no tone of its own: the primary's contrast ink on a primary button, the foreground on an outline or ghost one, the muted ink on a quiet trigger, the link ink on a link, and it dims with the label while the button is disabled. Size the glyph (16 at the base size, 14 small); pass a tone (`destructive`, `muted`) only to colour it apart from the label. ```tsx ``` ### Icon ink per intent The same bare glyph on each intent, in that intent's label ink. The row wraps, so the six buttons fold onto a second line inside a narrow card instead of running past its edge. ```tsx ``` ## Do & Don't ### Default (primary) **Do** — One clear primary action; everything else is supporting. ```tsx ``` **Don't** — Multiple primaries compete; nothing stands out. ```tsx ``` ### Outline **Do** — Promote the main action to default; keep the rest outline. ```tsx ``` **Don't** — All-outline leaves no signal which action is primary. ```tsx ``` ### Secondary **Do** — Default for the primary action; secondary for the next one down. ```tsx ``` **Don't** — A secondary button as the main call to action under-sells it. ```tsx ``` ### Ghost **Do** — Use ghost for tertiary and toolbar actions; keep the CTA filled. ```tsx ``` **Don't** — A ghost button is too quiet to carry the primary action. ```tsx ``` ### Destructive **Do** — Reserve the destructive variant for irreversible actions like delete. ```tsx ``` **Don't** — Red on a safe action cries wolf; users learn to ignore it. ```tsx ``` ### Link **Do** — Link variant for inline navigation; a filled button for the submit. ```tsx ``` **Don't** — A link-styled submit doesn't look pressable and gets lost. ```tsx ``` ## Real links (href) A button that NAVIGATES should be a real link, not a press handler that sets `location`. Pass `href` and the web render becomes a genuine browser link: middle-click, cmd-click, and open-in-new-tab all work, crawlers see the destination, and assistive tech hears a link. Native platforms have no anchors, so pair `href` with an `onPress` that runs the same navigation through your router; while `disabled` or `loading` the anchor is suppressed exactly like `onPress`. `hrefAttrs` carries the anchor attributes react-native-web forwards (`target`, `rel`, `download`). ```tsx ``` ## Anatomy A Button registers its parts with an enclosing `AnatomyProvider`, in the hand-off's order: `Container` (the pressable box), `Label`, `Leading icon`, `Trailing icon` and `Count badge`. The provider (`AnatomyProviderProps`) fills its stage and centres the specimen, and reports each rendered part's rectangle (an `AnatomyRect`, relative to its own box and in the space the specimen is seen in) to its `onMeasure` callback as `AnatomyRects` by part name, with its own box as its parent laid it out, so a docs Anatomy section can number the parts with markers drawn over the specimen. `zoomed` draws the specimen at the hand-off's anatomy zoom (1.6, for a component that hugs its content): the provider lays its inner box out at the stage width over the zoom and scales it back up about its centre, reserves the scaled height so the stage grows with the figure, and maps the rectangles before reporting them. Outside a provider the registration costs one context read and adds no node. Every kit component registers its own parts the same way, under the names its docs page lists, and a part belongs to the outermost component that names it: the Badge inside a Button's count, or the Buttons in a Dialog's footer, register nothing of theirs. `AnatomyPart` registers an element of your own as a named part (`...`), measured in a wrapping view inside a provider and rendered bare outside one; its `AnatomyPartProps` take the part's `name`, or a `part` a component already took, which renders its children bare when undefined. ## Workflow actions Use `outline quiet` for a muted secondary operation and `outline inverse` on a dark branded surface. `consistent` gives short workflow actions a shared minimum width; `comfortable` supplies the roomier surround used for a compact featured action. ## Touch area On iOS and Android a Button smaller than the platform's minimum touch target (44pt, 48dp) keeps its size and extends only its touch area to the minimum, from its first frame, so nothing moves. React Native bounds that area by two rules of its own. It never reaches past a native container that does not contain it: a container of yours that clips (overflow hidden, a scroll view's edge) stops it at that edge. A container that paints, carries a `testID` or handles pointer events admits it, because such a container records the touch area at its layout and the Button has it from its first one. Every kit control does the same for itself; a control the kit lacks belongs in the kit, never in a Pressable of your own (see DESIGN.md, Shapes). And where two touch areas overlap, the later sibling takes the tap: on Android two small icon Buttons 8 apart in a Row both reach across the gap between them, so a tap anywhere in it presses the second. Leave at least twice the extra touch area between small Buttons you place side by side (16 between two small icon Buttons on Android, 8 on iOS). ## Supported props ### ButtonProps Includes `MeasureProps`; their supported fields are expanded below. | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | | | iconLeft | ReactNode | no | | An icon element rendered before the label (e.g. a lucide or Canvas `Icon`). It renders directly (not wrapped in the label `Text`, which cannot host an SVG), spaced from the label by the skin's gap. Pass `iconLeft` alone with the `icon` size prop for an icon-only square button. The caller sizes the icon; a Canvas `Icon` with no tone of its own takes the button's label ink (the primary's contrast ink on a primary button, the muted ink on a quiet one, dimmed with the label while disabled), and an explicit tone keeps it. | | iconRight | ReactNode | no | | An icon element rendered after the label (e.g. a trailing chevron/arrow). | | accessibilityLabel | string \| undefined | no | | Accessibility label, required for an icon-only button (no text to read). | | onPress | ((event: GestureResponderEvent) => void) \| undefined | no | | | | href | string \| undefined | no | | Renders the button as a REAL link on the web: react-native-web gives the root Pressable an ``, so middle-click, cmd-click, open-in-new-tab, and crawlers all see the destination, and assistive tech hears a link. Native platforms have no anchors and ignore it, so pair `href` with an `onPress` that runs the same navigation through the host's router. While `disabled` or `loading` the anchor is suppressed, exactly like `onPress`. | | hrefAttrs | { target?: "_blank" \| "_self"; rel?: string; download?: boolean \| string; } \| undefined | no | | Anchor attributes forwarded with `href` on the web (react-native-web's `hrefAttrs`): `target`, `rel`, `download`. Ignored without `href`, and everywhere natively. | | onHoverIn | ((event: MouseEvent) => void) \| undefined | no | | Called when a pointer starts hovering the button. Fires on pointer devices only (web via react-native-web, pointer-equipped iPads/desktops natively); touch never triggers it. For hover-driven disclosure such as Tooltip. | | onHoverOut | ((event: MouseEvent) => void) \| undefined | no | | Called when the pointer stops hovering the button (see `onHoverIn`). | | onFocus | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Called when the button gains focus (keyboard tab, or click on web). | | onBlur | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Called when the button loses focus. | | primary | boolean \| undefined | no | false | Intent (pick one; default is the primary action). | | secondary | boolean \| undefined | no | false | Intent (pick one; default is the primary action). | | inverse | boolean \| undefined | no | false | White outlined action over a dark or branded surface. | | quiet | boolean \| undefined | no | false | Muted outlined action for a low-emphasis operation. | | softHover | boolean \| undefined | no | false | The reference link hover dims to 85% while keeping its normal ink. | | scopeTrigger | boolean \| undefined | no | false | A bare project-scope menu trigger: its label set in 600, a hover wash, the chevron through `iconRight`. | | identityAction | boolean \| undefined | no | false | An identity action: a 28-tall hairline pill around a leading avatar and its name. | | pageLink | boolean \| undefined | no | false | A page header's link: a 34-tall hairline box around a muted label (a source path, the page's section). | | jumpLink | boolean \| undefined | no | false | A page's jump to one of its sections: a 28-tall hairline pill around a muted 12px label. | | mono | boolean \| undefined | no | false | A `pageLink` whose label is a path or code: the 400 12px mono face. | | referenceLink | boolean \| undefined | no | false | A reference-key link: an underlined monospace key in the link ink. | | inlineIcon | boolean \| undefined | no | false | An unboxed 22px icon action. | | themeChoice | boolean \| undefined | no | false | A theme choice pill whose `selected` state is the accent edge and a soft accent halo. | | selected | boolean \| undefined | no | false | | | filterTrigger | boolean \| undefined | no | false | A bare filter-menu trigger: a muted label, a hover wash, the chevron through `iconRight`. | | inlineLink | boolean \| undefined | no | false | Inline text action: an underlined link label with no box, read as a link, washed on hover. | | plainLink | boolean \| undefined | no | false | A plain accent text action with no box and no underline, still read as a button: a panel's Reset, a table's show-all row. On the web a 500 13 px label in the accent text ink (12 px with `small`) that brightens to the foreground ink under the pointer; natively the platform's own tint-coloured text button. | | foreground | boolean \| undefined | no | false | Foreground ink on an inline text action. | | extraBold | boolean \| undefined | no | false | Extra-bold inline text action label. | | external | boolean \| undefined | no | false | Open href in the system browser on every platform. | | consistent | boolean \| undefined | no | false | Align a row of short workflow actions on the same minimum measure. | | comfortable | boolean \| undefined | no | false | A roomier hit surround for a compact featured action. | | testOnly_hovered | boolean \| undefined | no | false | Visual reference harness only; pointer hover remains the production source. | | destructive | boolean \| undefined | no | false | | | outline | boolean \| undefined | no | false | | | ghost | boolean \| undefined | no | false | | | link | boolean \| undefined | no | false | | | small | boolean \| undefined | no | false | Size (pick one). | | large | boolean \| undefined | no | false | Size (pick one). | | icon | boolean \| undefined | no | false | Size (pick one). | | compactIcon | boolean \| undefined | no | false | Compact utility icon box (28px). | | mediumIcon | boolean \| undefined | no | false | Medium utility icon box (32px). | | largeIcon | boolean \| undefined | no | false | Large utility icon box (34px). | | wideIcon | boolean \| undefined | no | false | Give a compact icon box two extra pixels of horizontal room. | | circle | boolean \| undefined | no | false | Round a utility icon box into a circle. | | rounded | boolean \| undefined | no | false | Fully rounded ends: the control's corner becomes the pill (an `icon` square a circle). The special bodies (the links, the triggers, the utility boxes) keep their own corners; a utility box rounds with `circle`. The pill's ends sit 2 px wider at each side (18 at the default size). | | dashed | boolean \| undefined | no | false | A dashed edge in place of the solid one: an add-a-row or drop-here affordance. It shows on the intents that draw an edge (`secondary`, `outline`); the filled, ghost and link looks have no visible edge to dash. | | unread | boolean \| undefined | no | false | An unread mark: the accent dot inside the box's upper trailing corner, ringed in the card colour. A quieter sibling of `count` for "something new" without a number; name the state in `accessibilityLabel` (the dot is decorative). `count` wins when both are passed. | | inset | boolean \| undefined | no | false | Quiet inset fill for a utility icon action. | | steadyPress | boolean \| undefined | no | false | Keep the resting look while pressed (no dim, no press scale). | | count | string \| number \| undefined | no | | Notification count carried in the upper trailing corner. | | block | boolean \| undefined | no | false | Layout and state. A Button is HUG (src/style/sizing.ts): it keeps its content width inside a stretching Column and sits content-sized in a Row; `block` makes it FILL (full width in a Column, an equal share in a Row), and a measure step (`sm`, `lg`, …, from MeasureProps) makes it FILL up to that width of the scale. | | shrink | boolean \| undefined | no | false | Keep the content-sized button in a tight row, allowing its label to wrap within the available width. | | loading | boolean \| undefined | no | false | | | disabled | boolean \| undefined | no | false | | | raised | boolean \| undefined | no | false | Lifts a primary button off the page with a soft glow in its own colour: the page's main call to action. Other intents, and a disabled button, ignore it. | | attention | boolean \| undefined | no | false | An outward red glow for an action requiring attention. Static under Reduce Motion; hidden while disabled or loading. | | expanded | boolean \| undefined | no | false | Marks the Button as a disclosure/menu trigger and announces its open state to assistive tech (aria-expanded, which react-native-web forwards and RN maps back to the native expanded state). Pass the live open boolean; omit for a plain button. So a Button that toggles a Dropdown/Popover stays accessible. | | haspopup | true \| "menu" \| "dialog" \| "grid" \| "tree" \| "listbox" \| undefined | no | | Announces that the button opens a popup of this kind (aria-haspopup), for a menu / dialog / listbox trigger. Pair it with `expanded` so assistive tech reads both the popup relationship and its open state. Web-only; a no-op natively. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: `block`, the measure steps, and the parent layout container own the width. | | ref | React.Ref | no | | Ref to the interactive Pressable, including link buttons. Typed as a React Native View. On web, React Native Web exposes its DOM host; focus() and blur() move browser focus. Native host behavior depends on the platform and React Native version. Calling focus() does not activate the control or call accessibility focus APIs. | | xxxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | sm | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | md | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | lg | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wide | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wider | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | widest | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | page | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | start | boolean \| undefined | no | false | Pin a capped component to the leading edge instead of centering it in its column. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Calendar Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/calendar/calendar.md` Human reference: https://canvas.nannier.com/components/calendar ## Public imports ```tsx import { Calendar } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Month grid, week timeline, and day timeline in one component. Events mark their days with a dot in the month grid and week strip, and timed events render as blocks on the week/day hour timelines. The view is a semantic boolean axis: pass `week` or `day`, or neither for the month grid (`day` wins over `week` when both are set). In glass mode the selected day is a brand-tinted control-layer puck behind its number and event dot, in the month grid and along the week strip; in `range` mode both endpoints carry the puck over the range band. The day peek and the hover card are functional-layer glass cards beside their day. Solid mode keeps the skin's own filled day. ## Usage A bare calendar is uncontrolled: pressing a day selects it, and in the week/day views the chevrons page the selection a week or a day at a time (crossing into another month hands the press to `onPrev`/`onNext`, since the month itself is a prop). ```tsx ``` ## Variants ### Events Each event carries the `day` it falls on, plus an optional `title` and `start`/`end` hours. The month grid marks event days with a dot and reads the count to assistive tech. ```tsx ``` ### Day peek With `dayPeek`, pressing a marked day opens that day's hour timeline (the same timeline the day view renders) beside the cell: at its trailing side (the right in a left-to-right layout), at its leading side when that lacks room, and above or below it when neither side fits. A tap anywhere else or Escape dismisses it. ```tsx ``` ### Week `week` renders the selected day's week: a strip of seven selectable day cells over an hour timeline, with each timed event as a block in its day column. The timeline spans the full day; the scroller opens on the 8 AM to 5 PM window and scrolls to the rest, and the 12h/24h control flips the hour labels (12-hour by default). Overlapping events split their column into side-by-side lanes, hovering a block floats its details, and the chevrons page a week at a time. ```tsx ``` ### Day `day` renders a single day's hour timeline with each block carrying its title and time span. The timeline covers the whole day (narrow it with `startHour`/`endHour`); the scroller opens on 8 AM to 5 PM and scrolls to the remaining hours, and the 12h/24h control flips the labels. Hovering a block floats a detail card with the event's `description`. The chevrons page a day at a time. ```tsx ``` ### Range `range` turns the month grid into a check-in/check-out picker for booking flows: the first press sets the start day, a later press sets the end, and a tinted band spans the days between; pressing an earlier day restarts the pick. Control it with `rangeStart`/`rangeEnd` and `onRangeChange`, or seed it with the `default` pair. ```tsx ``` ### Compact `compact` tightens the cells and type for dense surfaces, in every view. ```tsx ``` ## Do & Don't ### Single date **Do**: Exactly one selected day (primary), with today marked separately in the accent tone. ```tsx ``` **Don't**: Painting several days with the primary selected style fakes a multi-select; when you mean a span of days, use `range` instead. ```tsx 8 14 23 ``` ### Event marks **Do**: A dot marks a day with events and the count lives in the accessible name; titles belong to the week/day timelines or a side panel. ```tsx ``` **Don't**: Cramming event titles into month cells clips them into unreadable slivers and breaks the grid's rhythm. ```tsx 8 Design review 9 10 Sprint planning meeting ``` ### Day details **Do**: `dayPeek` reveals a pressed day's schedule in place, anchored beside the cell, so detail arrives on demand and the grid stays the single source of truth. ```tsx ``` **Don't**: Selecting May 24 but leaving a detached panel on a placeholder breaks the link between the grid and its day. ```tsx Pick a date to see events. ``` ### Hour bounds **Do**: Keep the full-day timeline and let the scroller do the work: it opens on the 8 AM to 5 PM window, and every event stays reachable. ```tsx ``` **Don't**: Narrowing `startHour`/`endHour` below the real schedule silently clips events out of view; the 6:30 PM review never renders here. ```tsx ``` ### Booking a stay **Do**: One `range` calendar carries the whole check-in/check-out pick: the endpoints fill, the nights between band together, and an earlier press restarts. ```tsx ``` **Don't**: Splitting the stay across two single-date calendars doubles the interaction cost and never shows the span at a glance. ```tsx Check-in Check-out ``` ## Supported props ### CalendarProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | month | string \| undefined | no | "June 2026" | Month + year label shown in the header, e.g. "June 2026". | | selected | number \| undefined | no | | The day number currently selected (CONTROLLED, primary highlight). Omit for uncontrolled use. | | defaultSelected | number \| undefined | no | | Initial selected day for uncontrolled use (a bare calendar highlights the day on press). | | today | number \| undefined | no | | The day number that is today (primary highlight when unselected). | | daysInMonth | number \| undefined | no | 30 | Number of days in the month. | | startWeekday | number \| undefined | no | 0 | Weekday (0=Sun .. 6=Sat) the 1st falls on; sets leading blank cells. | | events | CalendarEvent[] \| undefined | no | | Events to mark: dots on their days in the month grid and week strip, timed blocks in the week/day timelines. | | onSelect | ((day: number) => void) \| undefined | no | | Fired with the day number when a day cell is pressed (and when week/day chevron paging moves the selection). | | onEventPress | ((event: CalendarEvent) => void) \| undefined | no | | Fired with the event when a timed block in the week/day timeline is pressed. | | onPrev | ((event: GestureResponderEvent) => void) \| undefined | no | | Fired when the previous chevron is pressed in the month view, or when week/day paging would cross into the previous month. | | onNext | ((event: GestureResponderEvent) => void) \| undefined | no | | Fired when the next chevron is pressed in the month view, or when week/day paging would cross into the next month. | | week | boolean \| undefined | no | false | Seven-day strip over an hour timeline; chevrons page by week. | | day | boolean \| undefined | no | false | Single-day hour timeline; chevrons page by day. | | dayPeek | boolean \| undefined | no | false | Month view: pressing a day that has events also opens that day's hour timeline in an anchored overlay (a tooltip-style day peek) beside the cell, at its trailing side (the right in a left-to-right layout), at its leading side when that lacks room, and above or below it when neither side fits; dismissed by an outside tap or Escape. | | range | boolean \| undefined | no | false | Month view: two-tap range selection — the first press sets the start day, the second (later) press the end; a press before the start restarts. Days between carry a tinted band. | | rangeStart | number \| undefined | no | | The range's start day (CONTROLLED with `rangeEnd`). Omit both for uncontrolled use. | | rangeEnd | number \| undefined | no | | The range's end day (CONTROLLED with `rangeStart`). | | defaultRangeStart | number \| undefined | no | | Initial range start for uncontrolled use. | | defaultRangeEnd | number \| undefined | no | | Initial range end for uncontrolled use. | | onRangeChange | ((start?: number, end?: number) => void) \| undefined | no | | Fired as the range is picked: with (start, undefined) after the first press, (start, end) after the second. | | startHour | number \| undefined | no | | First hour of the week/day timeline (0-24; default 0, the full day). | | endHour | number \| undefined | no | | Last hour of the week/day timeline (0-24; default 24). The scroller initially windows 8 AM–5 PM and scrolls to the rest. | | hour24 | boolean \| undefined | no | false | Timeline labels in 24-hour format (CONTROLLED). Omit to let the built-in 12h/24h toggle manage it. | | defaultHour24 | boolean \| undefined | no | false | Initial hour format for uncontrolled use (default false = 12-hour AM/PM). | | onHour24Change | ((hour24: boolean) => void) \| undefined | no | | Fired when the hour format changes (true = 24-hour). | | compact | boolean \| undefined | no | false | Tighter cells and smaller type, for dense surfaces. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # CandlestickChart Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/candlestick-chart/candlestick-chart.md` Human reference: https://canvas.nannier.com/components/candlestick-chart ## Public imports ```tsx import { CandlestickChart } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. The trading instrument view: OHLC candles colored by direction from the success/destructive tokens, an optional docked volume pane, and optional overlay series (moving averages) in the series tokens. Press or scrub a candle to read its Open/High/Low/Close and volume. ## Usage ```tsx ``` ## Variants ### Compact sessions ```tsx ``` ### Dual moving averages ```tsx ``` ### Press to inspect ```tsx ``` ### Legend caption The legend names Up and Down and each overlay; `legendCaption` adds a note at the end of its row, pushed to the right edge. An overlay marked `dashed` draws dashed. ```tsx ``` ## Do & Don't ### OHLC data **Do**: Feed the candles real open, high, low and close prices; each body shows the session's move and each wick its range. ```tsx ``` **Don't**: Fill the candles from a close-only series by repeating the close as open, high and low; every candle collapses to a flat tick, and the chart shows less than a line of the closes would. ```tsx ``` ## Supported props ### CandlestickChartProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | labels | string[] | yes | | Period labels along the x axis, one per candle. | | candles | Candle[] | yes | | One OHLC candle per label. | | volume | number[] \| undefined | no | | Optional volume per candle, drawn as a docked pane under the price plot. | | overlays | ChartSeries[] \| undefined | no | | Optional overlay series (e.g. moving averages), colored from `chart-2` on (the candles own the status pair and the lead accent), or by an overlay's own `success`/`destructive` tone. A `dashed` overlay draws dashed. | | title | string \| undefined | no | | Optional heading shown above the plot. | | max | number \| undefined | no | | Y-axis overrides. Defaults to nice bounds around the lows/highs (not zero-based). | | min | number \| undefined | no | | | | compact | boolean \| undefined | no | false | Density (omit for the default plot size). | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | hideLegend | boolean \| undefined | no | false | Furniture visibility (the legend names the up and down candles, then the overlays). | | legendCaption | string \| undefined | no | | A note at the end of the legend's row, pushed to its right edge (what the colours mean, a data source). | | hideGrid | boolean \| undefined | no | false | | | hideAxes | boolean \| undefined | no | false | | | formatValue | ((v: number) => string) \| undefined | no | | Formats tick labels and accessible values (data formatting, not styling). | | selected | number \| null \| undefined | no | | Scrub-to-inspect: the selected candle index (controlled). Pass null for none. | | defaultSelected | number \| undefined | no | | Scrub-to-inspect: the initially selected candle (uncontrolled). | | onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press/scrub selects a candle (or clears it with null). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Card Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/card/card.md` Human reference: https://canvas.nannier.com/components/card ## Public imports ```tsx import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardMedia, CardSeparator, CardTitle } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A content surface, used three ways: a stat card (a single metric, a big number and a delta), a section card (a labeled surface with an optional eyebrow, header and footer), and the generic card that brings its own structure. It is the Canvas v4 panel: the card fill under a hairline at the card corner (16 px on the web, 12 natively), the small shadow at rest and the large one `raised`; `flat` drops the shadow and steps the fill up a surface, `sunken` drops the shadow and sinks the fill to the well beneath the page (`surface-sunken`, the hand-off's `--bg2` tiles, stages and panels set into a card) under the hairline, and `outlined` drops the shadow and keeps the card fill under the hairline (the hand-off's page cards; a platform whose card control draws an outline instead of a shadow draws both the same), and `heroShadow` is the medium shadow (the hand-off's `--sh-md`, its section hero's stage), between the resting card and the raised one. Precedence is `raised`, `flat`, `sunken`, `outlined`, `heroShadow`. On Android `sunken` is the Material 3 filled card: the well's fill with no outline. A pressable card lifts 2 px on hover and takes the raised shadow. `selected` draws the primary border with a ring of the accent's soft wash around it. Corners (pick one; the smaller step wins): the card corner by default, `smallCorners` for the tile corner (12), and the three steps the hand-off's component page frames with, `calloutCorners` (14: a note under a table, a compact link tile), `largeCorners` (18: a card framing a section of a page) and `stageCorners` (20: a staging frame, a figure and its legend), and the two its section pages frame with, `showcaseCorners` (20: a section page's showcase card) and `heroCorners` (24: the section hero's stage). The web skin draws each from its row; the native skins keep their own card corner on every step but the tile. A card with content is padded by default, and a padded surface also spaces its flat children (the card owns the vertical rhythm, so a stack of Typography lines needs no layout wrapper); pass `flush` to opt out of both, the inset and the spacing, for edge-to-edge content (a table, a nav bar) or when you compose the self-padding `CardHeader`/`CardContent`. The section props (`eyebrow` / `badge` / `title` / `description` / `icon` / `actions` / `footer`) render self-padding sections, so a sectioned card needs no `padded`: a tracked mono eyebrow with a badge opposite, the 20 px title in the display face over its muted description, the body, and a footer under a hairline in the subtle ink. They compose with a string `body` OR with raw children, so a panel can carry a titled header above a table or a form; children win when both are passed. For a cover image, `CardMedia` is the full-bleed top slot: it spans the card edge to edge, its top corners follow the card's corner, and its bottom edge stays flat; compose it with `flush` and let `CardContent` pad the text below. Density: the card's own inset is 20 px with an 8 px rhythm; pass `compact` (14 / 8) or `comfortable` (28 / 8) to tighten or relax the card's own padding and the gap between flat children (`compact` takes precedence, and a density prop pads the surface on its own). On a sectioned card (an eyebrow, a title or a footer) the surface never pads, so a density moves its inset and rhythm onto the sections instead: the first section takes the inset above, every section the inset beside, the last the inset below, and each section sits the rhythm under the one before it. `grow` fills the height a parent Row or Column hands the card (the body takes the slack, so a footer stays on its floor); inside a `Grid` cell it is implied, so the tiles of one row are equal-height without being asked. The hand-off's component page insets are their own density steps, the same on every platform: `panel` (18 with a 12 px rhythm: a section panel, an accessibility card, a figure's legend), `guide` (18×20 with 10: a guidance card), `linkTile` (14×16 with 6: a link tile) and `pager` (16×18 with 4: a previous or next card); the section pages add `well` (16 with a 12 px rhythm: a loading-state well, a wizard's preview). Gap-free content densities preserve dense application layouts: `dense` (12), `snug` (18), `cozy` (20), `relaxed` (22×24), `spacious` (28), `insetContent` (14), `rail` (12×14), `summary` (14×16), `metric` (14×18), `request` (18/22/18/20), and `menu` (12 vertical). `roomy` sits between `relaxed` and `spacious` and keeps a rhythm: a 24 px inset with 20 px between flat children, the Canvas v4 section page's showcase card, the same on every platform. Precedence follows that order, then the page steps (`panel`, `guide`, `linkTile`, `pager`, `well`), before compact/comfortable. `gapless` removes flat-child spacing; `borderless`, `inset`, `dimmed` and `clip` control surface anatomy. `tileShadow`, `rowShadow`, and `emptyShadow` extend the elevation axis after raised/flat/outlined/heroShadow. Accessible names and hover-lift reporting are supported on interactive cards. `interactiveContents` keeps a clickable card from nesting a button role around its own buttons and links. `steadyPress` preserves its resting opacity while keeping the hover lift and native ripple. `gatePanel`, `versionPanel` and `notePanel` preserve the original Argus inset content surfaces. Precedence is note, version, then gate; they own their secondary fill, exact insets and corners. ## Usage ```tsx Active identities 12,348 +142 today ``` ## Variants ### Featured approval `featured` washes the card with the accent's soft tint from its top-left corner and rims it in the accent. Supply the identity, eyebrow, stage badge, summary, gate caption, metadata and domain action controls. `stacked` sets a second card behind it. A pressable featured card lifts two pixels on hover. ```tsx Approve} /> ``` ### Artwork alone Give `artwork` a parent with definite height. It paints two radial blooms, the accent and the brand cyan (swapped on `promo`), over the quiet surface, without text, padding or shadow. Add `illustration` with children for a 12px inset around an identity. ```tsx ``` ### Promotional artwork ```tsx Read the charter} /> ``` `promo` sweeps from the accent to the brand cyan, each mixed into the card fill, under the card's own inks. It stays opaque content in glass mode. ### Linked references `referenceInline`, `referenceStacked`, `referenceTile` and `referenceDetail` preserve the original Argus reference layouts. `referenceKey` is the key, `title` the summary and `description` the status/context. `referenceSource` adds the source affordance in the detail panel. An `href` opens through the system browser. ```tsx ``` ### Board tickets `board` owns the ticket anatomy while retaining Card's existing surface, corner, elevation and press behavior. It reads `ticketNumber`, `priorityLabel`, `title`, `description`, `statusLabel` and `typeLabel`. Priority and type appear in compact rounded Badge pills without visible field-name prefixes; accessible names retain their field context. High and Critical priorities use the soft error treatment, Medium uses warning, and other priorities use a quiet secondary pill. Types use the soft primary treatment. Type comes first, then priority, in one 18 px metadata row below the status on every platform. `noticeLabel` adds a quiet outline pill after them for a short notice, such as "Workflow v1" on a ticket that follows an older workflow; its text is its accessible name. It is the only pill that gives way in a narrow card, ending in an ellipsis while type and priority keep their size. Omitted labels render no pill; the shared row retains its height. The header contains only the ticket number and move handle. Pass the Board render callback's `moveHandle` unchanged; Card places it at the top right beside the ticket number. `blocked` displays “Blocked by Discovery” when `blockedReason` is “Discovery”, or just “Blocked” when there is no reason. `aiActive` reveals “AI work in progress” with a shimmer in the text and cycling dots. Reduced motion keeps that label static. Title and description clamp to two lines; each other text row has one line. The metadata row and optional progress keep their reserved slots, so short and long tickets have the same text height. `imageSource` accepts an actual React Native image source (including authentication headers), with `imageLabel` for its accessible name. It adds a taller, full-bleed header, with matching top corners and the number and move handle over an opaque, legible backing. Images use contain fitting. A failed image collapses to the compact metadata header; a new source retries. Only the image header adds height. Board anatomy takes precedence over raw children and section props. ```tsx ``` ### Generic ```tsx Anything goes here ``` ### Flat children ```tsx The surface owns the rhythm Each line lands a steady step below the last. No layout wrapper between them. ``` ### Flat ```tsx Outlined and quiet ``` ### Raised ```tsx Lifted above the page ``` ### Outlined ```tsx A hairline and no shadow ``` ### Sunken ```tsx A well beneath the page ``` ### Elevation samples `sample` draws an empty card 72 px tall on the popover fill, so a row of them shows the elevation steps side by side; its edge is the field border at `heroShadow` and `raised`. ```tsx ``` ### Floating `floating` marks a card that floats over the page: under glass on the web it takes the floating material (the functional layer's tint over the backdrop blur), where a resting card keeps its solid skin. ```tsx A pane over the page ``` ### Corners ```tsx calloutCorners largeCorners stageCorners showcaseCorners heroCorners ``` ### Showcase frame The section pages' frame: a showcase card at the showcase corner with the `roomy` density (24 in, 20 between its parts), and the hero's stage at the hero corner under the medium shadow. ```tsx Showcase card 24 in, a 20 px rhythm, the 20 corner. Hero stage The 24 corner under the medium shadow. ``` ### Page densities ```tsx Guide 18 by 20, a 10 px rhythm. Link tile 14 by 16, a 6 px rhythm. Pager 16 by 18, a 4 px rhythm. Well 16 all round, a 12 px rhythm. ``` ### Sectioned panel A density on a sectioned card reshapes the sections: here the eyebrow sits 18 px in and the list 12 px under it. ```tsx ``` ### Density ```tsx Compact Tight padding and rhythm. Comfortable Roomy padding and rhythm. ``` ### Section ```tsx ``` ### Section with children ```tsx ``` ### Icon and footer ```tsx } body="Name, primary email, and sign-in methods." footer="Updated 2 minutes ago" /> ``` ### Media ```tsx Kira Tanaka ``` ### Specimen well Given children instead of `src`, `CardMedia` is a sunken well that stages live content: the page's sunken fill (an ink tint under glass), the content centered, at least 160 px tall with a 28 x 20 inset, growing with what it holds. `compact` takes the 130 px well with a 24 x 16 inset and `tall` the 240 px one with a 32 x 20 inset; `stage` is a figure's stage, 320 px with an 80 x 40 inset, and `playground` a live specimen's, 340 px with a 44 x 28 inset that also grows to the height and the remaining width of a panel beside it (precedence `compact`, `tall`, `stage`, `playground`). `height` sizes the image band only. Children win over `src`. ```tsx Primary ``` ### Caption density `CardContent` pads its body 20 px on every side. Under a media well a caption takes a tighter inset: `dense` is 10 x 14 (a one-line label), `compact` 12 x 16 (a title over a meta line) and `cozy` 14 x 16 (a title, a note and a code line). Precedence is `dense`, then `compact`, then `cozy`. ```tsx size small ``` ### Specimen well sizes ```tsx compact tall ``` ### Actions ```tsx Rename the workspace. The URL updates everywhere. ``` ### Pressable Pass `onPress` and the whole card becomes a control, with the button role. On the web it lifts under the pointer: it rises 2 px over 180 ms while its resting shade deepens to the raised one in step, and settles back when the pointer leaves (a `raised` or `flat` card rises with its shade unchanged). While it lifts and settles it stacks above the cards around it, so its deeper shade falls over the next one. Reduce Motion makes the change instant. iOS and Android keep their own press feedback. ```tsx {}} title="Scout" description="Research agent, v2.3" /> {}} title="Mint" description="Release agent, v1.8" /> ``` ### Selectable ```tsx {}}> Pro ``` ## Do & Don't ### stat **Do** — One big number, a short label, a small delta. The metric is scannable in a glance. ```tsx Active identities 12,348 +142 today ``` **Don't** — Prose where the number should be: the eye has nothing big to land on, so the card stops being a stat. ```tsx This month We onboarded 12,348 active identities, up 142 today, with churn holding steady. ``` ### section **Do** — Give the header its title and description through the card's own parts; the header's spacing sets it apart from the body. ```tsx Recent activity Events from the last 24 hours. Two events today. ``` **Don't** — Hand-style a title in the body; it loses the card's title type and the header's spacing. ```tsx Recent activity Two events today. ``` ### generic **Do** — Use the surface once and layout the content with plain spacing inside it. ```tsx Anything goes here The card surface gives you the border, radius, and shadow. You bring the content. ``` **Don't** — Nesting one card surface inside another stacks border on border and shadow on shadow; the inner block looks dropped in. ```tsx Nested surface A card inside a card doubles the border and shadow. ``` ## Supported props ### CardProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | featured | boolean \| undefined | no | false | Original illustrated approval banner. | | artwork | boolean \| undefined | no | false | Show only the authored artwork, filling bounds supplied by the parent. | | illustration | boolean \| undefined | no | false | Inset an illustration in authored artwork. | | controls | boolean \| undefined | no | false | | | promo | boolean \| undefined | no | false | Original promotional gradient card. | | stacked | boolean \| undefined | no | false | Stack the featured illustration above its content. | | hue | number \| undefined | no | | | | initials | string \| undefined | no | | | | eyebrow | string \| undefined | no | | | | badge | string \| undefined | no | | | | caption | string \| undefined | no | | | | metadata | string \| undefined | no | | | | referenceInline | boolean \| undefined | no | false | A compact unframed reference summary in a row. | | referenceStacked | boolean \| undefined | no | false | A reference key/status row above its summary. | | referenceTile | boolean \| undefined | no | false | A compact inset reference panel. | | referenceDetail | boolean \| undefined | no | false | A roomy reference panel with context and source. | | referenceKey | string \| undefined | no | | | | referenceSource | string \| undefined | no | | | | href | string \| undefined | no | | Open the reference through the system browser. | | children | ReactNode | no | | | | title | string \| undefined | no | | Simple content props for the children-less / data-driven case. | | icon | ReactNode | no | | A glyph rendered before the title, for a section panel that names itself with an icon. Takes effect on the data-driven path (a card given `title` rather than raw children). | | actions | ReactNode | no | | Trailing header content, typically a button. Sits opposite the title so the header reads as one row: name on the left, action on the right. | | description | string \| undefined | no | | | | body | string \| undefined | no | | | | footer | string \| undefined | no | | | | raised | boolean \| undefined | no | false | Elevation (pick one; default is a soft resting shadow). | | flat | boolean \| undefined | no | false | Elevation (pick one; default is a soft resting shadow). | | sunken | boolean \| undefined | no | false | The sunken well: the `surface-sunken` fill under the hairline with no shadow, the hand-off's `--bg2` tiles, stages and panels set into a card. On Android it is the Material 3 filled card (the well's fill, no outline). | | outlined | boolean \| undefined | no | false | The hairline on the card fill with no shadow (the hand-off component page's cards). | | heroShadow | boolean \| undefined | no | false | The section hero's stage shadow: the medium step of the ladder (the hand-off's `--sh-md`, canvas-hero.js `.st`), between the resting card and the raised one. | | sample | boolean \| undefined | no | false | An elevation sample: an empty card 72 px tall on the popover fill (the hand-off's `--sf2`) that shows the elevation it is given, its edge the field border at the `heroShadow` and `raised` steps (the Foundations page's depth tiles). Composes with the elevation and corner axes; it takes no children. | | floating | boolean \| undefined | no | false | The card floats over the page: under glass on the web it takes the floating material (the functional layer's tint over the backdrop blur, design-language item 7), where a resting card keeps its solid skin. Native glass and solid mode are unchanged. | | padded | boolean \| undefined | no | false | Padding (orthogonal booleans). A card with raw children is padded by default, and the data-driven string path (no children) pads through its self-padding sections instead, so neither prop moves the surface inset there. `flush` strips the raw-children inset (edge-to-edge content, or composing the self-padding CardHeader/CardContent); `padded` is the explicit form of the padded-by-default. | | flush | boolean \| undefined | no | false | Padding (orthogonal booleans). A card with raw children is padded by default, and the data-driven string path (no children) pads through its self-padding sections instead, so neither prop moves the surface inset there. `flush` strips the raw-children inset (edge-to-edge content, or composing the self-padding CardHeader/CardContent); `padded` is the explicit form of the padded-by-default. | | onPress | (() => void) \| undefined | no | | When set, the whole card becomes pressable (a card that behaves as a control). | | interactiveContents | boolean \| undefined | no | false | A clickable container whose nested controls keep their own button/link roles. | | steadyPress | boolean \| undefined | no | false | Keep the resting opacity while pressed; hover lift and native ripple remain. | | selected | boolean \| undefined | no | false | Active/selected surface: a primary border and a soft primary tint. For selectable cards (a card-style radio or checkbox option that is chosen). | | grow | boolean \| undefined | no | false | Grow to fill the main axis of a parent Row/Column (flexGrow: 1). On a card that renders sections, the BODY takes up whatever slack the growth won, so the content region fills the taller surface and a footer stays on its floor rather than floating up under the content. Implied inside a Grid cell: the grid stretches its cells to the row's height and the card fills that box, so the tiles of one row are equal-height without being asked. | | compact | boolean \| undefined | no | false | Density (pick one; default is the standard inset). Scales the card's own content padding and the gap between flat children. | | comfortable | boolean \| undefined | no | false | Density (pick one; default is the standard inset). Scales the card's own content padding and the gap between flat children. | | dense | boolean \| undefined | no | false | Gap-free content densities, tightest first in precedence. | | snug | boolean \| undefined | no | false | | | cozy | boolean \| undefined | no | false | | | relaxed | boolean \| undefined | no | false | | | roomy | boolean \| undefined | no | false | The showcase card's density: a 24 px inset with a 20 px rhythm between flat children (the hand-off's section page card). Unlike the other named densities it keeps the gap. | | spacious | boolean \| undefined | no | false | | | insetContent | boolean \| undefined | no | false | | | rail | boolean \| undefined | no | false | | | summary | boolean \| undefined | no | false | | | metric | boolean \| undefined | no | false | | | request | boolean \| undefined | no | false | | | menu | boolean \| undefined | no | false | | | panel | boolean \| undefined | no | false | A section panel: an 18 px inset with a 12 px rhythm (the hand-off's accessibility cards and anatomy legend). | | guide | boolean \| undefined | no | false | A guidance card: 18 by 20 with a 10 px rhythm (the hand-off's usage guidance and "From the docs" cards). | | linkTile | boolean \| undefined | no | false | A link tile: 14 by 16 with a 6 px rhythm (the hand-off's related-component tiles). | | well | boolean \| undefined | no | false | A well: a 16 px inset with a 12 px rhythm (the section pages' skeleton card and wizard preview). | | pager | boolean \| undefined | no | false | A pager card: 16 by 18 with a 4 px rhythm (the hand-off's previous and next cards). | | gapless | boolean \| undefined | no | false | Keep the reference inset without the compact density's flat-child gap. | | borderless | boolean \| undefined | no | false | | | smallCorners | boolean \| undefined | no | false | The tile corner. | | calloutCorners | boolean \| undefined | no | false | The callout corner (14 on the web): a note under a table, a compact link tile. | | largeCorners | boolean \| undefined | no | false | The section panel corner (18 on the web): a card framing a section of a page. | | stageCorners | boolean \| undefined | no | false | The stage corner (20 on the web): a staging frame, a figure and its legend. | | showcaseCorners | boolean \| undefined | no | false | The showcase card's corner (20 on the web): the v4 section page's card. | | heroCorners | boolean \| undefined | no | false | The hero stage's corner (24 on the web): the v4 section hero's stage (`.st`). | | inset | boolean \| undefined | no | false | | | gatePanel | boolean \| undefined | no | false | Original inset human-gate content panel. | | versionPanel | boolean \| undefined | no | false | Original compact version-history row. | | notePanel | boolean \| undefined | no | false | Original inset metric-tool note. | | dimmed | boolean \| undefined | no | false | | | clip | boolean \| undefined | no | false | | | tileShadow | boolean \| undefined | no | false | | | rowShadow | boolean \| undefined | no | false | | | emptyShadow | boolean \| undefined | no | false | | | accessibilityLabel | string \| undefined | no | | | | testOnly_hovered | boolean \| undefined | no | false | Reference harness: hold the existing hover lift. | | onLiftChange | ((raised: boolean) => void) \| undefined | no | | | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | | board | boolean \| undefined | no | false | A board ticket with fixed text slots and an optional full-bleed image header. | | ticketNumber | string \| undefined | no | | | | priorityLabel | string \| undefined | no | | | | statusLabel | string \| undefined | no | | | | blocked | boolean \| undefined | no | false | Display Blocked, or Blocked by the supplied reason, as the status. | | blockedReason | string \| undefined | no | | | | typeLabel | string \| undefined | no | | | | noticeLabel | string \| undefined | no | | A short notice shown as a quiet outline pill after the type and priority, such as "Workflow v1" for a ticket on an older workflow. | | aiActive | boolean \| undefined | no | false | Display functional text shimmer and cycling dots while work is active. | | onAIProgressPress | (() => void) \| undefined | no | | Open the active AI work from its progress label, independently of the card. | | moveHandle | ReactNode | no | | Board's provided move handle, preserving its pointer/keyboard bindings. | | imageSource | ImageSourcePropType \| undefined | no | | Actual supplied image; authenticated React Native sources retain their headers. | | imageLabel | string \| undefined | no | | | ### CardSectionProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | ### CardTextProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | ### CardContentProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | | dense | boolean \| undefined | no | false | The tightest inset, 10 x 14: a one-line label under a specimen well. | | compact | boolean \| undefined | no | false | A 12 x 16 inset: a short caption (a title over a meta line). | | cozy | boolean \| undefined | no | false | A 14 x 16 inset: a fuller caption (a title, a note, a code line). | ### CardMediaProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | src | string \| number \| undefined | no | | The cover image: a URI string (or a docs sample path), or a bundled `require(...)` asset. | | height | number \| undefined | no | 180 | Height of the image band in px (the image covers it edge to edge). The well that hosts `children` grows with its content instead, from the step its booleans pick. | | alt | string \| undefined | no | | Accessible name / alt text announced for the image. | | accessibilityLabel | string \| undefined | no | | Accessible name for the image, as `alt` gives; when both are set it wins, as on Image. | | children | ReactNode | no | | Live content to stage in place of an image: CardMedia becomes a sunken well (the page's `surface-sunken` fill, an ink tint under glass) that centers what it holds, a specimen above a caption. Children win over `src` when both are passed. | | compact | boolean \| undefined | no | false | A shorter well: 130 px at least, a 24 x 16 inset (a size or state specimen). | | tall | boolean \| undefined | no | false | A taller well: 240 px at least, a 32 x 20 inset (a platform skin specimen). | | stage | boolean \| undefined | no | false | A figure's stage: 320 px at least, an 80 x 40 inset (an anatomy figure under its marks). | | playground | boolean \| undefined | no | false | A playground's stage: 340 px at least, a 44 x 28 inset, growing to the height and the remaining width of a panel beside it (a live specimen beside its knobs). | | testID | string \| undefined | no | | E2E hook forwarded to the image, or to the well. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Carousel Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/carousel/carousel.md` Human reference: https://canvas.nannier.com/components/carousel ## Public imports ```tsx import { Carousel } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A horizontally paged slide viewer: swipe (or use the prev/next arrows and the dot indicators) to move one slide at a time. Paging snaps to the viewport width, the current slide drives the dots, and the arrows step the index (clamped, or wrapped when `loop`). Slides hold any content; pass an `items` array of `{ key, content }`. Each slide is itself the card: it paints the surface, rounds the corners and clips its content to them. So pass the slide's content, not a Card. A plain string renders in the slide's own type, inset from its edge; any other node fills the slide inside its 1px edge, so a picture is masked to the slide's shape. A Card nested inside frames the slide twice, and on Android the Material 3 item's 28dp corners cut the Card's own 12dp edge. The prev and next arrows sit beside the slides, never over them, so nothing near a slide's edge is hidden behind an arrow; the slides narrow by the two arrow gutters instead. Every slide is mounted from the first frame and stays mounted, so a slide's own state (a field's text, a playing video) survives the carousel measuring its viewport, on the first layout and whenever a hidden carousel is shown again. Until it has measured, the current slide fills the viewport on its own. A carousel does not window its slides, so a long gallery loads every slide's content up front. On web, Tab visits the carousel in the order it reads: the previous arrow, the slide viewport, the next arrow, then the slide picker (an arrow disabled at the end of a non-looping carousel is skipped). Tab reaches the viewport while its slides overflow it, and the focused viewport draws the theme's focus ring around the slide. Left and Right move between slides, while Home and End reach the first and last. Controls inside a slide keep their own keyboard behavior. The named slide-picker buttons report the current slide and its position in the set; activating the current slide does nothing. Picker targets measure at least 24px on web, 44pt on iOS, and 48dp on Android, independently of the small painted dots. The active dot follows the committed slide (a dot press, the arrows, the keyboard, a controlled `index`, a finished swipe) in both surface modes; the strip is absent with one slide, with `showDots={false}`, and by default on Android. ## Usage ```tsx ``` ## Variants ### Arrows hidden (dots only) Hide the prev/next chevrons with `showArrows={false}`; swipe and the dots still page the carousel, the iOS page-control idiom. ```tsx ``` ### Default index Start on a later slide with `defaultIndex`; the matching dot reads selected. ```tsx ``` ### Arrows and dots everywhere The chrome defaults are platform-adaptive (web shows both, iOS dots only, Android neither); pass `showArrows` and `showDots` to force the full arrows-plus-dots anatomy on every platform. ```tsx ``` ### Loop With `loop` the index wraps: both arrows stay enabled at the ends, and next on the last slide returns to the first. ```tsx ``` ## Do & Don't ### Current slide **Do** — Keep one current slide and let the dots mirror it, so the position in the set is always clear. ```tsx {/* docgen-allow-style: demo placeholder slide surface */} Step 1 ) }, { key: "do2", content: ( {/* docgen-allow-style: demo placeholder slide surface */} Step 2 ) } ]} defaultIndex={0} onIndexChange={() => {}} /> ``` **Don't** — Stack the slides yourself with a manual row of pressables; it loses the snap paging, the synced dots, and the per-slide accessibility. ```tsx Slide 1 Slide 2 ``` ## Supported props ### CarouselProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | items | CarouselItem[] | yes | | The slides, left to right. | | index | number \| undefined | no | | Controlled current slide. Pair with `onIndexChange`. Omit for uncontrolled. | | defaultIndex | number \| undefined | no | 0 | Initial current slide for the uncontrolled case (read once; default 0). | | onIndexChange | ((index: number) => void) \| undefined | no | | Called with the next current index whenever the slide changes. | | loop | boolean \| undefined | no | false | Wrap from the last slide to the first (and first to last) on arrow nav. | | showArrows | boolean \| undefined | no | false | Show the prev/next chevron buttons beside the slides. Default is platform-adaptive: on for web, off for iOS + Android (swipe idioms); pass `true` to opt in. | | showDots | boolean \| undefined | no | false | Show the centered dot indicators below the slides. Default is platform-adaptive: on for web + iOS (UIPageControl), off for Android M3. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Chart Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/chart/chart.md` Human reference: https://canvas.nannier.com/components/chart ## Public imports ```tsx import { Chart } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A single- or multi-series bar chart: vertical columns (or horizontal rows) sized against the axis max, with per-category values and labels. Pass `labels` + `series` for grouped clusters colored by the `chart-1`..`chart-8` tokens, add `stacked` to accumulate those series within one column per category, or give a series its own `success` / `destructive` tone when it means success or failure; press or scrub a category to inspect it. ## Usage ```tsx ``` ## Variants ### Grouped bars ```tsx ``` ### Stacked columns ```tsx ``` ### Success vs failure ```tsx ``` ### Horizontal rows ```tsx ``` ### Tones ```tsx ``` ### Formatted values `formatValue` formats the value axis, the inspected value and the accessible values; it is data formatting, not styling. ```tsx v.toLocaleString("en-US")} /> ``` ## Do & Don't ### Bar **Do** — Keep a labelled axis row and a single bar tone so the buckets read at a glance. ```tsx ``` **Don't** — Every bar the same full-strength fill and no labels: nothing is emphasized and the axis is unreadable. ```tsx ``` ## Supported props ### ChartProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | data | ChartDatum[] \| undefined | no | | The bars to render, in order (single-series mode). | | labels | string[] \| undefined | no | | Category labels along the x axis (grouped mode, with `series`). | | series | ChartSeries[] \| undefined | no | | Multiple series rendered as grouped columns per category, colored by the chart-1..8 tokens (or a series' own `success`/`destructive` tone). | | stacked | boolean \| undefined | no | false | Stack the grouped series within one column per category (composition) instead of clustering them side by side. | | title | string \| undefined | no | | Optional heading shown above the plot. | | max | number \| undefined | no | | Axis maximum. Defaults to the largest value in the data (the largest category TOTAL when `stacked`). | | success | boolean \| undefined | no | false | Tone (pick one; default is the primary fill). Single-series only. | | destructive | boolean \| undefined | no | false | Tone (pick one; default is the primary fill). Single-series only. | | horizontal | boolean \| undefined | no | false | Orientation (default is a vertical column chart). Single-series only. | | compact | boolean \| undefined | no | false | Density (default is the standard plot size). | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | hideLegend | boolean \| undefined | no | false | Hide the series legend (grouped mode shows one by default). | | selected | number \| null \| undefined | no | | Press-to-inspect: the selected category index (controlled). Pass null for none. Vertical charts only. | | defaultSelected | number \| undefined | no | | Press-to-inspect: the initially selected category (uncontrolled). | | onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press selects a category (or clears it with null). | | formatValue | ((v: number) => string) \| undefined | no | | Formats the value axis, the inspected value and the accessible values (data formatting, not styling). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Chat Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/chat/chat.md` Human reference: https://canvas.nannier.com/components/chat ## Public imports ```tsx import { Chat, ChatComposer, ChatMessage, ChatThread, Markdown } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A controlled assistant conversation with independently scrolling messages, clear author and provider identities, and an anchored multiline composer. The parent supplies the bounds; Chat fills the available height without choosing a fixed height of its own. Messages remain application-owned `ChatMessageData`: keep each `id` stable while updating `content` during streaming. `pending` shows a three-dot thinking indicator until an explicitly `streaming` message arrives; pass `pendingIcon` to identify its provider. Streaming replies retain their own Responding indicator. Both animations stop when their state ends and stay still under Reduce Motion. `providerIcon` accepts an application-supplied provider logo beside the author; label the image or icon with the provider name for assistive technology. `provider` remains an optional supplementary text label such as the transport name. `error` adds a visible failure label; `notice` preserves delivery or fallback information. `footer` anchors connection, review, or other actions below the composer and outside the scrollport. Chat draws the conversation as a card: messages 14 points apart inside a 16 point inset, assistant replies as plain 14 point text with no author line unless the app names an `author` or a provider (several AIs in one conversation), the person's own turns as a bubble in the accent's soft wash on the trailing side, and the composer under a hairline, its sunken field holding the draft and a round send button. Set `flush` to drop the card frame so the conversation sits directly on its host's fill, for example inside a Drawer or a Wizard body. Set `assistant` to lay the conversation out like a desktop assistant's window instead. The thread and the composer share one centered column whose composer box is 768 points wide at most, with the user's bubble 16 points in from its edge. Assistant replies are plain 15 / 21 text across the column, 20 points in from the composer's edge, with list items on the line rhythm and paragraphs 12 apart. The person's own turns are a neutral bubble, 12 by 16 points in. Turns that change speaker sit 40 points apart and the blocks of one turn 20 apart. The composer is one raised, rounded 44 point box with no divider: a bare add icon, the draft, and a send control that stays quiet until there is something to send. The thread and the composer keep 24 point gutters; when either is narrower than 480 points (a phone, a side panel, a wizard body) the gutters shrink to 12 so the composer's text keeps its room. With `assistant`, `meta` names what will answer (a model, a provider) in one muted line at the end of the column under the composer. `assistant` and `flush` combine: a Wizard `assistant` body or a Drawer usually wants both. Set `activity` on a message for work the assistant did rather than said, such as a command, a tool call or a process log. It shows as one muted line, its `author` then its `notice`, flush with the reply text, and expands to its markdown `content` when pressed. Consecutive activity messages fold into a single line naming each step ("Ran the tests, Process finished"), the way a desktop assistant folds its tool use; pressing it shows each step's own line. Pass `actions` on any message for app-owned choices beneath its content: each has a stable `id`, a literal `label`, and optional `primary`. Chat, ChatThread, and ChatMessage call `onAction(messageId, actionId)` when chosen. Choices wrap at narrow widths and use each platform’s Button. `disabled`, `pending`, any streaming message in the thread, or an absent callback prevents activation. The app owns saving the choice, setting busy state while it is accepted, duplicate prevention, and removing resolved or historical actions. Actions do not send a composer draft or submit question answers. Assistant content supports paragraphs, headings, lists, quotes, fenced code, bold, emphasis, and inline code through React Native text. HTML, images, and links stay literal text and never load or execute remote content. `plain` renders all source punctuation literally; user messages are always plain text. This is a small safe markdown subset rather than a complete CommonMark renderer. `ChatThread` follows new content while the reader is within 48 points of the bottom. Scrolling up to read older messages pauses following; returning to the bottom resumes it. Message updates, streaming text, question-height changes, and viewport resizing coalesce into a tail adjustment after layout settles. Scrolling is immediate and never animated, so it does not add motion when text streams or Reduce Motion is enabled. The overflowing thread is keyboard focusable with a visible focus frame. The native app or owning Dialog handles safe areas and keyboard avoidance. `ChatComposer` keeps Enter available for newlines. Its explicit Send control receives the trimmed draft through `onSend`; empty, disabled, pending, or uploading sends are blocked. An attached file permits an empty text draft. `sendDisabled` blocks only Send while leaving drafts, attachment controls, and message choices interactive; use it when a saved choice must be made before another freeform message. Pending replies leave the draft editable. The app owns draft clearing, transport, cancellation, persistence, and retry behavior. Pass `terminal` to Chat or its standalone pieces for a flat, left-aligned monospace transcript. Messages use `❯` for the user and `●` for the assistant beside their author, preserving provider text and current delivery state. No user bubble or conversation card is added. The bare composer places `❯` and its multiline editor between two full-width hairlines, with an explicit rectangular Send action. Hardware Enter sends; Shift+Enter inserts a newline. IME composition and native soft-keyboard Enter retain their normal text-entry behavior. Sending returns focus to the editor; `autoFocus` optionally focuses it on mount. The app still owns clearing the draft, delivery, and preventing duplicate requests. Pending replies keep the draft editable and block sending. `Markdown terminal` preserves the same safe document grammar with 13px monospace text, compact spacing, and semantic headings. Tables remain aligned table rows in a horizontal scrollport instead of becoming stacked cards. Fenced code and tabs remain literal whitespace. `Textarea terminal` supplies the prompt's bare monospace metrics and implies `embedded`; existing Textarea defaults remain unchanged. Set `onAttach` to show the accessible paperclip action. The app owns file picking and uploading, passes composer `attachments`, and removes a staged file through `onRemoveAttachment(id)`. A `ChatAttachmentData` has an `id`, `name`, optional `uploading` or `error` flags, and optional `notice` text. Uploading and failed files block sending; remove a failed file or replace it after retrying. `attaching` also blocks attaching and sending while the app opens a picker or uploads. Saved message attachments use the same metadata; `onOpenAttachment(attachment)` on Chat, ChatThread, or ChatMessage delegates opening or downloading to the app. Canvas never requests, uploads, or opens file URLs itself. An assistant message can include `questions`: each `ChatQuestionData` has a stable `id`, `title`, optional `multiple`, and `options` with an `id`, `label`, and optional `description`. Supply controlled `ChatQuestionAnswerData` entries in `questionAnswers` containing `questionId`, `optionId` for a single choice or `optionIds` for multiple choices, and optional `customText`. Set `multiple` on the question for checkboxes; a custom answer can be combined with regular choices. A single-choice question also offers Select multiple at the bottom-right of its action row: switching it on records `multiple: true` in its answer and uses `optionIds`, preserving the original question metadata. The switch retains existing selections and custom text. Switching it off returns to the first selected choice, or clears an empty draft. Every question offers Write my own answer under the reserved `__custom` option id, limited to 1000 characters. The active form shows one question with a counter derived from the actual question count. Selecting an ordinary radio saves and advances before the last question. Custom text stays on the current question and uses Next; a blank custom answer cannot advance. Back is a neutral outline button that preserves saved selections and text. Revisited valid single choices offer Next. Checkboxes use Submit to advance; Submit appears only for multiple-choice questions and the last question. The last question always requires explicit Submit before the complete batch is sent, and every question must have a real answer. An incomplete earlier answer returns the reader to the first unresolved question instead of submitting. Submitted custom text is trimmed. Chat, ChatThread, and ChatMessage expose `onQuestionAnswerChange(messageId, answers)`, `onQuestionIndexChange(messageId, index)`, and `onSubmitQuestionAnswers(messageId, answers)`. Record answer changes in message data and pass the zero-based `questionIndex` to control navigation, including saved continuation after reload. Omitting `questionIndex` uses local navigation state. The position is clamped to the actual list. The final callback receives fresh answers for the whole batch, including the current answer; set `questionsSubmitted` after accepting it. Repeated activations are guarded while navigation or submission settles. A busy state that ends without a saved receipt permits retry. The app owns persistence, transport, and duplicate prevention across requests. Both answer callbacks and an enabled form are required for interaction. `disabled`, `pending`, and active streaming lock the current question. Use `questionsDisabled` for an inactive earlier batch: it shows every original question and option read-only, without navigation actions. `questionsSubmitted` shows saved answers with an Answers sent receipt. Multiple-choice receipts include every selected label and custom text; an answer-mode override is identified as Multiple answers selected by you. Question labels and descriptions remain literal text; RadioGroup supplies arrow navigation and accessible group names. The counter is announced as Question X of N in a polite live region; navigation moves focus there through the kit's shared browser and native accessibility focus mechanism. The current question is framed by the existing compact, flat selected Card: a soft theme tint and primary border distinguish its counter, answers and actions from the surrounding conversation. Saved receipts and inactive question history remain unframed. The Card is visual only, so answer controls retain their own accessible roles and focus behavior. `Markdown` reuses Chat's safe grammar and renderer for standalone documents. It preserves heading levels one through six, emphasis, inline and fenced code, quotes, ordered and unordered lists, nested indentation, static checked/unchecked task lists, horizontal rules, and pipe tables. Tables use Canvas DataTable's container-responsive stacked layout on narrow surfaces, retaining every column on iOS too. Checklists are read-only Canvas selection controls; reading a document never changes its source. HTML, links and image syntax remain inert text: no source is executed and no remote image is requested. `MarkdownProps` accepts the source as children, an optional accessibility label and test ID; `compact` uses dense document typography. The parent supplies width. ## Usage ```tsx {}} onSend={() => {}} /> ``` ## Variants ### Assistant layout `assistant` lays the conversation out like a desktop assistant's window: a centered reading column, plain replies, a neutral user bubble, folded activity, and a raised composer with the model line under it. ```tsx {}} onSend={() => {}} /> ``` ### Flush with a model line `flush` drops the card frame, so an `assistant` conversation sits on its host's fill as a desktop window's does. ```tsx {}} onSend={() => {}} /> ``` ### Terminal transcript ```tsx {}} onSend={() => {}} footer={Enter to send · Shift+Enter for newline} /> ``` ### Long conversation ```tsx ({ id: index, role: index % 2 === 0 ? "user" : "assistant", content: index % 2 === 0 ? `Review requirement ${index / 2 + 1}.` : "Confirm the expected behavior, check mobile layouts, and record the evidence before continuing." }))} value="A follow-up question" onChangeText={() => {}} onSend={() => {}} footer={The composer stays here while earlier messages scroll.} /> ``` ### Streaming ```tsx {}} onSend={() => {}} pending /> ``` ### Pending ```tsx {}} onSend={() => {}} pending pendingIcon={} pendingLabel="Planner is reviewing the project…" /> ``` ### Anchored actions ```tsx {}} onSend={() => {}} emptyState={} footer={} /> ``` ### Message choices ```tsx {(choice, setChoice) => ( setChoice(actionId)} /> )} ``` ### Delivery notice ```tsx ``` ### Attachments ```tsx {}} onSend={() => {}} onAttach={() => {}} attachments={[{ id: "draft-notes", name: "notes.txt" }]} onRemoveAttachment={() => {}} onOpenAttachment={() => {}} /> ``` ### Selectable questions Move through one question at a time. This example controls the saved position and answer draft separately so navigation and answer callbacks can update together. ```tsx {(state, setState) => ( {(questionIndex, setQuestionIndex) => ( setState({ ...state, answers })} onQuestionIndexChange={(_messageId, index) => setQuestionIndex(index)} onSubmitQuestionAnswers={(_messageId, answers) => setState({ answers, submitted: true })} /> )} )} ``` ### Separate pieces ```tsx {}} onSend={() => {}} footer={Only reviewed requirements continue to execution.} /> ``` ### Document Markdown ```tsx {`# Release scope ## Acceptance criteria - [ ] Behavior verified - [x] Requirement recorded | Story | Priority | Status | | --- | --- | --- | | Saved scope | Must | Backlog |`} ``` ## Do & Don't ### Bound the thread **Do** — Give the conversation a bounded parent or an explicit composition height so only its messages scroll. ```tsx ``` **Don't** — An unbounded thread grows with every message and pushes its composer away. ```tsx {}} onSend={() => {}} /> ``` ## Supported props ### ChatMessageProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | onAction | ((messageId: string \| number, actionId: string) => void) \| undefined | no | | The app owns action handling, persistence, and removing resolved actions. | | onQuestionAnswerChange | ((messageId: string \| number, answers: ChatQuestionAnswerData[]) => void) \| undefined | no | | Update the message's controlled questionAnswers; the app owns saving the draft. | | onSubmitQuestionAnswers | ((messageId: string \| number, answers: ChatQuestionAnswerData[]) => void) \| undefined | no | | Submit the final complete answer set; the app owns transport and setting questionsSubmitted. | | onQuestionIndexChange | ((messageId: string \| number, index: number) => void) \| undefined | no | | Save the zero-based current question index; omit questionIndex for local navigation state. | | disabled | boolean \| undefined | no | false | Disable question forms while other application work is in progress. | | pending | boolean \| undefined | no | false | | | message | ChatMessageData | yes | | | | onOpenAttachment | ((attachment: ChatAttachmentData) => void) \| undefined | no | | | | plain | boolean \| undefined | no | false | Render assistant markdown punctuation literally. User messages are always plain text. | | terminal | boolean \| undefined | no | false | Flat, compact monospace terminal transcript. | | assistant | boolean \| undefined | no | false | Lay the conversation out like a desktop assistant's window (the Claude desktop window, reference claude-desktop-chat): one centered reading column, plain 15 / 21 replies, a neutral user bubble, a wider rhythm between turns, and a raised 44 px composer with a quiet send control and the `meta` line. Without it, Chat draws the v4 hand-off's look. | | testID | string \| undefined | no | | | ### ChatThreadProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | onAction | ((messageId: string \| number, actionId: string) => void) \| undefined | no | | The app owns action handling, persistence, and removing resolved actions. | | onQuestionAnswerChange | ((messageId: string \| number, answers: ChatQuestionAnswerData[]) => void) \| undefined | no | | Update the message's controlled questionAnswers; the app owns saving the draft. | | onSubmitQuestionAnswers | ((messageId: string \| number, answers: ChatQuestionAnswerData[]) => void) \| undefined | no | | Submit the final complete answer set; the app owns transport and setting questionsSubmitted. | | onQuestionIndexChange | ((messageId: string \| number, index: number) => void) \| undefined | no | | Save the zero-based current question index; omit questionIndex for local navigation state. | | disabled | boolean \| undefined | no | false | Disable question forms while other application work is in progress. | | messages | readonly ChatMessageData[] | yes | | | | onOpenAttachment | ((attachment: ChatAttachmentData) => void) \| undefined | no | | | | pending | boolean \| undefined | no | false | A reply is being prepared; no artificial message is added to the data. | | pendingLabel | string \| undefined | no | "Assistant is thinking…" | | | pendingIcon | ReactNode | no | | Application-supplied provider mark for a reply that has not started streaming. | | label | string \| undefined | no | "Conversation" | | | emptyState | ReactNode | no | | | | plain | boolean \| undefined | no | false | | | terminal | boolean \| undefined | no | false | Flat, compact monospace terminal transcript. | | flush | boolean \| undefined | no | false | Draw no frame: the conversation sits on its host's fill, as a desktop assistant's window does. | | assistant | boolean \| undefined | no | false | Lay the conversation out like a desktop assistant's window (the Claude desktop window, reference claude-desktop-chat): one centered reading column, plain 15 / 21 replies, a neutral user bubble, a wider rhythm between turns, and a raised 44 px composer with a quiet send control and the `meta` line. Without it, Chat draws the v4 hand-off's look. | | testID | string \| undefined | no | | | | style | LayoutStyle | no | | Parent composition only. Supply a bounded height, or a bounded flex parent. | ### ChatComposerProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | terminal | boolean \| undefined | no | false | Bare terminal prompt; hardware Enter sends and Shift+Enter inserts a newline. | | autoFocus | boolean \| undefined | no | false | Focus the message input on mount. | | value | string | yes | | | | onChangeText | (value: string) => void | yes | | | | onSend | (value: string) => void | yes | | Receives the trimmed draft. The app owns clearing the draft and starting transport. | | placeholder | string \| undefined | no | "Message the assistant…" | | | composerLabel | string \| undefined | no | "Message" | | | sendLabel | string \| undefined | no | "Send message" | | | sendDisabled | boolean \| undefined | no | false | Block only sending, keeping the draft, attachments, and message choices interactive. | | maxLength | number \| undefined | no | | Optional application input limit; enforced by the native text field. | | pending | boolean \| undefined | no | false | Prevent sending while a reply is pending; the draft remains editable. | | disabled | boolean \| undefined | no | false | | | onAttach | (() => void) \| undefined | no | | Show an attachment control; the app owns its file picker and upload service. | | attachments | readonly ChatAttachmentData[] \| undefined | no | | | | onRemoveAttachment | ((id: string) => void) \| undefined | no | | | | attaching | boolean \| undefined | no | false | File selection or upload is in progress; prevents attaching or sending again. | | footer | ReactNode | no | | Anchored actions or help below the composer, outside the scrolling thread. | | meta | string \| undefined | no | | With `assistant`, a short muted line at the composer's end below it, naming the model or mode that will answer. | | assistant | boolean \| undefined | no | false | Lay the conversation out like a desktop assistant's window (the Claude desktop window, reference claude-desktop-chat): one centered reading column, plain 15 / 21 replies, a neutral user bubble, a wider rhythm between turns, and a raised 44 px composer with a quiet send control and the `meta` line. Without it, Chat draws the v4 hand-off's look. | | testID | string \| undefined | no | | | ### ChatProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | onAction | ((messageId: string \| number, actionId: string) => void) \| undefined | no | | The app owns action handling, persistence, and removing resolved actions. | | onQuestionAnswerChange | ((messageId: string \| number, answers: ChatQuestionAnswerData[]) => void) \| undefined | no | | Update the message's controlled questionAnswers; the app owns saving the draft. | | onSubmitQuestionAnswers | ((messageId: string \| number, answers: ChatQuestionAnswerData[]) => void) \| undefined | no | | Submit the final complete answer set; the app owns transport and setting questionsSubmitted. | | onQuestionIndexChange | ((messageId: string \| number, index: number) => void) \| undefined | no | | Save the zero-based current question index; omit questionIndex for local navigation state. | | disabled | boolean \| undefined | no | false | Disable question forms while other application work is in progress. | | messages | readonly ChatMessageData[] | yes | | | | onOpenAttachment | ((attachment: ChatAttachmentData) => void) \| undefined | no | | | | pending | boolean \| undefined | no | false | A reply is being prepared; no artificial message is added to the data. | | pendingLabel | string \| undefined | no | | | | pendingIcon | ReactNode | no | | Application-supplied provider mark for a reply that has not started streaming. | | label | string \| undefined | no | | | | emptyState | ReactNode | no | | | | plain | boolean \| undefined | no | false | | | terminal | boolean \| undefined | no | false | Flat, compact monospace terminal transcript. | | flush | boolean \| undefined | no | false | Draw no frame: the conversation sits on its host's fill, as a desktop assistant's window does. | | assistant | boolean \| undefined | no | false | Lay the conversation out like a desktop assistant's window (the Claude desktop window, reference claude-desktop-chat): one centered reading column, plain 15 / 21 replies, a neutral user bubble, a wider rhythm between turns, and a raised 44 px composer with a quiet send control and the `meta` line. Without it, Chat draws the v4 hand-off's look. | | testID | string \| undefined | no | | | | style | LayoutStyle | no | | Parent composition only. Supply a bounded height, or a bounded flex parent. | | autoFocus | boolean \| undefined | no | false | Focus the message input on mount. | | value | string | yes | | | | onChangeText | (value: string) => void | yes | | | | onSend | (value: string) => void | yes | | Receives the trimmed draft. The app owns clearing the draft and starting transport. | | placeholder | string \| undefined | no | | | | composerLabel | string \| undefined | no | | | | sendLabel | string \| undefined | no | | | | sendDisabled | boolean \| undefined | no | false | Block only sending, keeping the draft, attachments, and message choices interactive. | | maxLength | number \| undefined | no | | Optional application input limit; enforced by the native text field. | | onAttach | (() => void) \| undefined | no | | Show an attachment control; the app owns its file picker and upload service. | | attachments | readonly ChatAttachmentData[] \| undefined | no | | | | onRemoveAttachment | ((id: string) => void) \| undefined | no | | | | attaching | boolean \| undefined | no | false | File selection or upload is in progress; prevents attaching or sending again. | | footer | ReactNode | no | | Anchored actions or help below the composer, outside the scrolling thread. | | meta | string \| undefined | no | | With `assistant`, a short muted line at the composer's end below it, naming the model or mode that will answer. | ### MarkdownProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | string | yes | | Untrusted Markdown source. HTML, URLs and images remain inert text. | | compact | boolean \| undefined | no | false | Denser document typography using Canvas's body reading scale. | | terminal | boolean \| undefined | no | false | Compact monospace transcript, with flat horizontally scrolling tables. | | accessibilityLabel | string \| undefined | no | | | | testID | string \| undefined | no | | | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Checkbox Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/checkbox/checkbox.md` Human reference: https://canvas.nannier.com/components/checkbox ## Public imports ```tsx import { Checkbox } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Multi-select option, single yes/no, grouped lists. One job, a platform's own control: a Checkbox that is one setting (a yes/no, a consent, an opt-in) renders the platform's switch on iOS and Android, where a single setting is a switch (Material 3 on phones included), and stays a checkbox on the web; its role follows what renders (`switch` there, `checkbox` here). A Checkbox that selects items in a list, a table's rows or a "select all" parent takes `selection`: the web keeps the checkbox, Android the Material 3 checkbox, and iOS draws the edit-mode selection circle. `indeterminate` implies `selection`, since a mixed state belongs to a "select all" parent and a switch has none. In glass mode the box keeps its solid look on every platform: it is a small mark inside content, not a surface, so it draws with no pane and no rim, a checked box in its solid brand fill under its check and an empty box with its solid outline. Pass `ref` to access the interactive checkbox row, including its label. Use `useRef>(null)` from React, or `useRef(null)` with React Native's `View` type. Object and callback refs are supported and detach on unmount. Calling `ref.current?.focus()` or `.blur()` delegates to the host without activating the control. Browser focus is supported; native focus depends on the platform and React Native version, and is separate from accessibility focus. On the web, Space activates the focused control on key release, and Enter also activates it. Holding Space does not repeat the change. Moving focus away, disabling the control, or composing text cancels a pending Space press. ## Usage ```tsx Email notifications ``` ## Variants ### Unchecked ```tsx Email notifications ``` ### Disabled ```tsx Email notifications ``` ### Sizes ```tsx Small Default Large ``` ### With description ```tsx Email notifications ``` ### Nested group A parent "select all" over a group of children: a list selection, so every box takes `selection`. Wire the parent's `checked` and `indeterminate` from the children's state: `checked` when every child is on, `indeterminate` when only some are, and toggling the parent selects or clears the whole group. (`Stateful` is a docs-only state holder so this fence can show the controlled wiring; in an app that state lives in your own component.) ```tsx {(selected, setSelected) => { const perms = ["Read", "Write", "Delete"]; const all = perms.every((p) => selected.includes(p)); const some = selected.length > 0 && !all; return ( setSelected(next ? perms : [])} > Select all {perms.map((p) => ( setSelected(next ? [...selected, p] : selected.filter((x) => x !== p)) } > {p} ))} ); }} ``` ## Do & Don't ### Unchecked **Do**: Leave opt-in consent unchecked so agreeing is a deliberate act the user takes. ```tsx Email me product news, offers, and survey invitations. ``` **Don't**: A consent box that starts checked opts users in by default; under GDPR pre-ticked consent is not consent. ```tsx Email me product news, offers, and survey invitations. ``` ### Checked **Do**: Show the parent indeterminate (a dash, not a tick) when only some children are checked. ```tsx Select all Read Write Delete ``` **Don't**: A fully checked parent claims every child is selected when only one is, so the state reads as a lie. ```tsx Select all Read Write Delete ``` ### Disabled **Do**: Say why it's unavailable, like a plan gate, or don't show it at all. ```tsx Export to CSV ``` **Don't**: A disabled option with no reason leaves users stuck and guessing. ```tsx Export to CSV ``` ### Selection **Do**: Radios for one-of-many; reserve checkboxes for independent multi-select. ```tsx Plan Free Pro Enterprise ``` **Don't**: Checkboxes allow multiple selections; for a one-of choice they let users pick contradictory options. ```tsx Plan Free Pro Enterprise ``` ### With description **Do**: Pass a `description` and the control stacks the title over its secondary line for you, box aligned to the first text line and the whole row tappable. ```tsx Email notifications ``` **Don't**: A detached checkbox makes only the 18px box tappable; the label text does nothing. ```tsx Email notifications Get notified when activity happens on your account. ``` ## Supported props ### CheckboxProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | Label text (the option's title) shown beside the box. | | description | ReactNode | no | | Optional muted secondary line rendered under the label, for the common title-plus-description option (a notification setting, a consent row). Supplying it builds the stacked title/description layout inside the control, so the caller never hand-composes a Row + Column + two Typography nodes and the whole row stays tappable. | | checked | boolean \| undefined | no | false | Controlled checked state; omit for uncontrolled use. | | defaultChecked | boolean \| undefined | no | false | Initial state for uncontrolled use (a bare is interactive). | | indeterminate | boolean \| undefined | no | false | Mixed state: some-but-not-all selected. Shown as a dash, not a tick. Takes visual precedence over `checked`. Implies `selection`: a mixed state belongs to a list's select-all, and a switch has none. | | selection | boolean \| undefined | no | false | A checkbox that selects an item in a list or a bulk selection (a table's rows, a list in edit mode, a select-all), rather than one on/off setting. The web draws the checkbox, iOS the edit-mode selection circle, Android the Material 3 checkbox. Without it a Checkbox is one setting, which iOS and Android render as their switch (role `switch`), so a settings row reads the way each platform writes it. | | onChange | ((next: boolean) => void) \| undefined | no | | Fired with the next checked value when the row is pressed (both modes). Web keyboard activation supports Space on release and Enter. | | onValueChange | ((next: boolean) => void) \| undefined | no | | Alias of onChange, for parity with RN's value-style callbacks. | | accessibilityLabel | string \| undefined | no | | Accessible name for a LABEL-LESS checkbox (one with no `children`, e.g. a row selector in a table). With a visible label the label itself is the name; without one this is what a screen reader announces. | | testID | string \| undefined | no | | E2E hook forwarded to the pressable row. | | small | boolean \| undefined | no | false | Size (pick one; default is the base box). | | large | boolean \| undefined | no | false | Size (pick one; default is the base box). | | disabled | boolean \| undefined | no | false | State. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | | ref | React.Ref | no | | Ref to the interactive checkbox row, including its label. Typed as a React Native View. On web, React Native Web exposes its DOM host; focus() and blur() move browser focus. Native host behavior depends on the platform and React Native version. Calling focus() does not activate the control or call accessibility focus APIs. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Chip Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/chip/chip.md` Human reference: https://canvas.nannier.com/components/chip ## Public imports ```tsx import { Chip, ChipGroup } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. An interactive pill for filter chips, tags, and selectable tokens. It carries an optional leading icon and a label, becomes tappable with `onPress`, and grows a trailing "×" remove button with `onRemove`. A Chip is a LOW-emphasis tag, not a call to action: the neutral chip is a 28 px outlined pill (the raised neutral fill inside a hairline, its label at weight 500). A status chip is its soft pill (the status wash under the status ink, the recipe Alert and the other toned surfaces share); a palette hue is that hue's 16% wash inside the same hue at 30%, under a deep ink in the hue that holds 4.5:1 on the wash. A selectable chip rests as the bare pill in the field border, and once selected it is the accent's soft wash in the accent ink, inside an accent edge and leading with a check. On Android the chip is Material 3's: a 32dp outlined container at the 8dp chip corner, its selected filter chip tonal with an 18dp check and no outline. The chip owns that whole look, so no call site hand-composes a `borderRadius` + `backgroundColor` + padding Pressable. Two orthogonal axes drive the look: - **Color** (pick one; default the neutral tag). A semantic status (`success`, `warning`, `destructive`, `info`, `neutral`) or a free-form palette hue (`red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `fuchsia`, `purple`, `pink`, `rose`, `gray`). Status names read the theme's status colors (`statusColors`), so a success chip matches a success Badge and Alert; `info` is the primary color. Precedence when more than one is set: status names first, then hues in the order above. - **Emphasis**. `outline` drops the fill for the field-border edge around the color's ink; `primary` is the solid accent; `secondary` is the higher raised fill with no edge (on Android, the filled chip beside the bare outlined container that is its idle chip). `neutral` is the muted tag with no edge; `gray` is the same muted fill and ink inside the chip's own edge. `outline` composes with any color, e.g. ``. Precedence: a color wins over `primary`, and `outline` wins over `primary`'s fill. `count` adds a dimmed trailing count in the mono face. A hovering pointer brightens the chip on the web. `start` pins a chip when its host layout does not publish a Canvas sizing context. `steadyPress` retains the resting opacity during a press, while selection, hover feedback and the native ripple keep working. `href` makes the chip a link to a route or a URL: a real anchor on the web (so middle-click and open-in-new-tab work and assistive tech hears a link), with `hrefAttrs` forwarding `target`, `rel` and `download`. Native platforms have no anchors, so pair `href` with an `onPress` that navigates through the app's router; it receives the press event, so a web app routing on the client calls `event?.preventDefault()` there. A hovering pointer turns a link chip's ink and edge to the accent instead of brightening it. A link is not a toggle: `href` is ignored on a selectable chip or a ChipGroup option. `small` gives passive metadata tags a 24 px container and 11 px label where the skin supports it. Platform-native chips and the option, token and link looks keep their own dimensions. ## Usage ```tsx Design ``` ## Variants ### Colors ```tsx Bug Chore Docs Feature Design Backend Frontend Research Infra Growth Archived ``` ### Status ```tsx Passing Flaky Failing Queued Skipped ``` ### Emphasis ```tsx Neutral Accent Outline ``` ### With leading icon A leading `` is auto-tinted to the chip's color, so a bare `` matches without threading the color through. ```tsx }>Verified ``` ### Removable filters `onRemove` grows the trailing "×"; wire it to your own state to drop the filter. ```tsx {}}>Role: Admin ``` ### Link A chip that names a destination, such as the component chips at the head of a docs card. A trailing `` says it leaves the page. `link` is the look made for it, the link pill of a section card: 26 px tall at the full round, 10 px at both sides and 6 px between the label and its arrow, a 500 12 px label in the secondary ink on the popover fill inside the field-border edge. A hovering pointer turns the label, the arrow and the edge to the accent. A press keeps the resting look, as the hand-off draws it. Precedence: `token` > `option` > `link`. On Android a link is the Material 3 assist chip. ```tsx }>Switch }>Checkbox ``` ### Selectable A selectable chip is a filter chip: the outlined pill at rest, and the accent's soft wash inside an accent edge, leading with a check, once selected. ```tsx Design Engineering ``` ### Pick one `ChipGroup` makes a set of chips a single choice: wrap `` options in it and exactly one is selected, the selected filter chip's look. It is a radiogroup of radio chips and ONE tab stop: Tab lands on the chosen chip (the first one when nothing is chosen), the arrow keys move the choice and wrap, Home and End take the ends, and a disabled chip is skipped. Controlled via `value` + `onChange`, uncontrolled via `defaultValue`, the RadioGroup contract. `label` renders a heading that also names the group; `accessibilityLabel` names it when the heading is already on the page. The group draws no chrome of its own: it wraps its chips 8 px apart, and fills its parent. ```tsx Compact Comfortable Spacious Custom ``` ### Options `option` is the compact pick of a settings panel: 26 px tall at a small corner, a 500 12 px label and no check, a bare field-border edge around the muted ink at rest and the accent's soft wash inside its edge once chosen. An icon-only option is a 30 px square, and `square` holds any option to that square, its label then the tile's own 500 10 px mono line (the "none" pick at the head of an icon knob). `ChipGroup option` makes every chip in the set an option and sets them 4 px apart. On Android an option is the Material 3 chip. ```tsx small base large ``` ### Square options ```tsx none } accessibilityLabel="plus" /> } accessibilityLabel="sparkles" /> ``` ### Token `token` is the tag of a colour role: a 32 px pill in the card fill inside the hairline, 8 px between its elements, a 500 12 px mono label with 12 px beside it, and 6 px beside its leading element, which is sized for a `tiny` `ring` Swatch. `token` wins over `option` when both are passed. On Android a token tag is the Material 3 chip. ```tsx }>primary }>border }>foreground ``` ## Do & Don't ### Removable filter **Do**: Use a Chip with `onRemove` so the pill and its "×" stay consistent and accessible. ```tsx {}}>Status: Active ``` **Don't**: Hand-build the pill from a raw Pressable with border-radius, padding, and a text "×". ```tsx Status: Active × ``` ## Touch area On Android a tappable Chip's touch area grows to the 48dp minimum without the chip changing size: 8dp above and below the 32dp chip, and nothing sideways once it is 48dp wide. On iOS it keeps 8pt of extra touch area on every side, which clears the 44pt minimum around the 28pt chip. The remove "×" pads out to the minimum on both, biased away from the label. Inside one chip the two never overlap: the chip splits the gap between its label and its "×", so a tap on the label never removes it. Between chips, React Native's own rule applies: where two touch areas overlap, the later chip takes the tap. Android chips that meet the minimum's width reach nothing sideways, so a Row of them never overlaps; on iOS two tappable chips closer than 16pt do, and a tap on the edge of one toggles the next. Leave room between chips on iOS where a mistaken toggle matters. ## Supported props ### ChipGroupProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | value | string \| number \| undefined | no | | The chosen chip's value (CONTROLLED). Omit for uncontrolled use. | | defaultValue | string \| number \| undefined | no | | Initial chosen value for uncontrolled use (a bare group selects on press). | | onChange | ((value: string \| number) => void) \| undefined | no | | Fired with the newly chosen chip's value. | | disabled | boolean \| undefined | no | false | Disable every chip in the group. | | option | boolean \| undefined | no | false | Draw every chip as an `option` chip (Chip's compact settings pick), 4 px apart: the pick-one sets of a settings panel. | | label | string \| undefined | no | | The group's name, rendered as a heading above the chips and announced as the radiogroup's accessible name. The group owns this label anatomy; never hand-compose a heading Text beside a bare group. | | description | ReactNode | no | | Optional muted supporting line rendered under the label. | | accessibilityLabel | string \| undefined | no | | The group's accessible name when the page already shows its heading elsewhere (no visible label is rendered). `label` wins when both are passed. | | children | ReactNode | no | | The `` options that make up the set. | | testID | string \| undefined | no | | E2E hook forwarded to the group container. | | style | LayoutStyle | no | | Outer layout composition only (flex within a parent), never a restyle hook. | ### ChipProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | The chip label. | | count | string \| number \| undefined | no | | A dimmed trailing count, set in the mono face. | | start | boolean \| undefined | no | false | Pin a chip within an app-owned host layout. | | icon | ReactNode | no | | A leading element (e.g. an `` or a small ``). An `` is auto-tinted to the chip's label color unless it sets its own color. | | trailing | ReactNode | no | | A trailing element (e.g. a chevron for a menu trigger). Rendered after the label. | | onPress | ((event?: GestureResponderEvent) => void) \| undefined | no | | Makes the whole chip tappable (e.g. toggling a filter). Called with the press event, which a ChipGroup option's Space key does not have, so an `href` chip that routes on the client calls `event?.preventDefault()` here. | | href | string \| undefined | no | | Makes the chip a LINK to this destination (a route or a URL): on the web react-native-web renders the chip as an ``, so middle-click, cmd-click and open-in-new-tab work and assistive tech hears a link; it takes the link chip's hover (the accent ink inside an accent edge) in place of the brightening. Native platforms have no anchors, so pair it with an `onPress` that navigates through the host's router, as Button's `href` does. While `disabled` the anchor is suppressed. A link is not a toggle, so it is ignored on a selectable chip or a ChipGroup option. | | hrefAttrs | { target?: "_blank" \| "_self"; rel?: string; download?: boolean \| string; } \| undefined | no | | Anchor attributes forwarded with `href` on the web (react-native-web's `hrefAttrs`): `target`, `rel`, `download`. Ignored without `href`, and everywhere natively. | | onRemove | (() => void) \| undefined | no | | Adds a trailing "×" remove button firing this handler. | | selectable | boolean \| undefined | no | false | Turns the chip into a filter toggle that owns its own selected state: pressing it lights up (to the accent's soft wash, inside an accent edge on the web and iOS) and back. Selection is controlled via `selected`, uncontrolled via `defaultSelected`. | | selected | boolean \| undefined | no | false | Selected state for a selectable chip (CONTROLLED). Omit for uncontrolled use. | | defaultSelected | boolean \| undefined | no | false | Initial selected state for an uncontrolled selectable chip. | | onSelectedChange | ((selected: boolean) => void) \| undefined | no | | Fired with the next selected state when a selectable chip is pressed. | | value | string \| number \| undefined | no | | The chip's option value inside a ChipGroup: the chip is one option of the pick-one set, selected when the group's value is this one, and reads as a radio. Ignored outside a group. | | success | boolean \| undefined | no | false | Color axis (pick one; default the neutral tag). Two families, both a soft tint: Semantic status (matches Badge): success / warning / error / info / neutral. | | warning | boolean \| undefined | no | false | Color axis (pick one; default the neutral tag). Two families, both a soft tint: Semantic status (matches Badge): success / warning / error / info / neutral. | | destructive | boolean \| undefined | no | false | The danger tone. This is the name the intent axis uses across the kit (Button, AlertDialog, and every chart) and the one the design hand-off uses here too. | | info | boolean \| undefined | no | false | | | neutral | boolean \| undefined | no | false | | | red | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | orange | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | amber | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | yellow | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | lime | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | green | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | emerald | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | teal | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | cyan | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | sky | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | blue | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | indigo | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | violet | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | fuchsia | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | purple | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | pink | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | rose | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | gray | boolean \| undefined | no | false | Free-form palette hues, plus `gray` for an explicit neutral tag. | | secondary | boolean \| undefined | no | false | Emphasis (orthogonal to color). `outline` is border-only; `primary` is the solid accent. `secondary` is the raised neutral fill (the default on the web and iOS; on Android it fills the otherwise bare outlined chip). | | primary | boolean \| undefined | no | false | Emphasis (orthogonal to color). `outline` is border-only; `primary` is the solid accent. `secondary` is the raised neutral fill (the default on the web and iOS; on Android it fills the otherwise bare outlined chip). | | outline | boolean \| undefined | no | false | Emphasis (orthogonal to color). `outline` is border-only; `primary` is the solid accent. `secondary` is the raised neutral fill (the default on the web and iOS; on Android it fills the otherwise bare outlined chip). | | option | boolean \| undefined | no | false | A compact option in a settings panel, the hand-off playground's knob pick: 26 tall at a small corner, a 500 12px label and no check, idle a bare field-border edge around the muted ink, selected the accent's soft wash inside its edge. An icon-only option is a 30 px square. A ChipGroup's `option` sets it on every chip. On Android an option is the Material 3 chip. | | token | boolean \| undefined | no | false | A token tag, the hand-off's Tokens chip: a 32 px pill in the card fill inside the hairline, 8 between elements, a 500 12 px mono label, 12 on the text side and 6 beside its leading element, which is sized for a 20 px swatch (`}>`). On Android a token tag is the Material 3 chip. | | link | boolean \| undefined | no | false | A link to another page, the hand-off's section card link pill: 26 tall at the full round, 10 at both sides, a 500 12 px label in the secondary ink on the popover fill inside the field-border edge; a hovering pointer turns the label and the edge to the accent. Pair it with `href` (and a trailing ``, which takes the label's ink). A press keeps the resting look, as the hand-off draws it. On Android a link is the Material 3 assist chip. | | square | boolean \| undefined | no | false | Forces an `option` chip to the 30 px square an icon-only option takes, even when it carries a label and no icon; a label inside the square is set in the tile's own type, a 500 10 px mono line (the hand-off's "none" pick in an icon knob). Ignored off the `option` look. | | small | boolean \| undefined | no | false | A small metadata tag; option, token and link looks retain their own dimensions. | | disabled | boolean \| undefined | no | false | | | steadyPress | boolean \| undefined | no | false | Keep resting opacity while pressed, preserving the reference filter treatment. | | accessibilityLabel | string \| undefined | no | | | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | For layout composition only (not styling). | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # CodeBlock Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/code-block/code-block.md` Human reference: https://canvas.nannier.com/components/code-block ## Public imports ```tsx import { CodeBlock } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Syntax-highlighted code display with clipboard copy, horizontal scrolling, line emphasis, diff rendering, collapsible folding, and tabbed alternatives. The variant axis picks the surface (`transcript` > `terminal` > `numbered` > `inline` > plain, first match wins); the density axis is `compact` (the tightened ramp), `playground` (the hand-off component page's JSX card: the block at the card corner, its header a 40 px bar on the card fill, 16 in and 8 out, its Copy a 28 px hairline button, and the code 16 above and below and 18 in at the default ramp), `snippet` (the page's own snippet, the Usage section's block: the code-card corner with the code 16 above and below and 18 in at the default ramp) or `guide` (a card's snippet, the block in a guidance card: a 10 px corner with the code 12 above and below and 14 in at 12 / 1.65), in that order of precedence; `copy`, `wrap`, `diff`, `collapsible`, `attached`, `headerless`, and `unbadged` stack orthogonally. `language` drives the in-kit highlighter (ts/tsx/js/jsx, json, bash, css, html, python; anything else renders monochrome) and, with no `filename`, labels a header bar above the code; with a `filename` it is a trailing badge beside the copy chip instead, which `unbadged` drops for a filename that already names the language (the hand-off's playground bar, "JSX · ButtonProps" and a Copy button only). `headerless` keeps the highlighting and drops the whole bar (a `tabs` strip and the terminal chrome stay), so `copy` floats over the code's corner. Long lines scroll horizontally instead of truncating, and every run of code, highlighted tokens included, is set in the theme's mono face. The block is the v4 hand-off's code card on every platform: the sunken well on a hairline at a 14 px corner, a 36 px header of 12 px mono labels, 13 / 1.7 code, line numbers in the subtle ink, and the hand-off's code inks (each light-scheme ink held to 4.5:1 on the well). A ts or js snippet takes the hand-off's CodeBlock renderer: keywords, strings, numbers and tag names in the accent, mint, amber and cyan. A tsx or jsx snippet takes the hand-off's component-page highlighter, which paints four things and nothing else: a Capitalised tag name in the accent (the brackets and slash neutral), an attribute name before its `=` in cyan, a string literal in mint and a comment in the subtle ink; a bare boolean attribute, a keyword, a number and the children text stay in the code ink, so `` reads as one label after one accent name. The terminal is a fixed dark window in every scheme. `inline` honours `copy` alone of the orthogonal booleans: `` is the import chip, a raised monospace pill (34 tall, the control corner, the `popover` fill and `field-border` hairline, 12 px mono in `muted-foreground`) whose whole surface is one copy button, with a trailing copy glyph that turns to a check for a moment once the code is on the clipboard. The chip copies the code verbatim, announces itself as "Copy" followed by the code, sets the code in one ink (no syntax colours), and on the web its hairline turns to the accent under the pointer. Under glass it is a control-layer pane. When a line is wider than the block, Tab reaches the scrollable code and the platform's arrow keys scroll it; while it has keyboard focus, the block's card draws the theme's focus ring. Short content, `wrap`, and `inline` do not add a scrolling tab stop. Native scrolling continues to use the native ScrollView. ## Usage ```tsx ``` ## Variants ### Execution transcript `transcript` renders raw execution output as selectable, soft-wrapped monospace text in the active theme's foreground. Tabs, newlines and prompt markers remain verbatim. It has no window chrome, card fill, border, syntax highlighting or expander. It takes precedence over the other variants and ignores the display modifiers, including `copy`, `diff` and `collapsible`; density still controls the type size. ```tsx ``` ### Syntax highlighting ```tsx Hi {name}; }`} /> ``` ### Terminal ```tsx ``` ### Numbered ```tsx ``` ### Inline ```tsx ``` ### Inline copy ```tsx ``` ### Compact ```tsx ``` ### Copy button ```tsx ``` ### Wrap long lines ```tsx ``` ### Terminal label ```tsx ``` ### Highlight lines ```tsx {label}; }`} /> ``` ### Excerpt ```tsx ``` ### Diff ```tsx ``` ### Collapsible ```tsx ``` ### Tabs ```tsx ``` ### Headerless ```tsx Save changes`} /> ``` ### Unbadged ```tsx }>Add item`} /> ``` ### Playground card ```tsx Save changes`} /> ``` ### Page snippet ```tsx Save changes`} /> ``` ### Guide snippet ```tsx `} /> ``` ## Do & Don't ### Plain **Do** — Use CodeBlock so whitespace, line breaks, and indentation survive verbatim. ```tsx ``` **Don't** — A paragraph collapses the line breaks and indentation, so multi-line code reads as one run-on string. ```tsx const theme = getTheme(); setTheme(theme === "dark" ? "light" : "dark"); ``` ### Terminal **Do** — Use the terminal variant: the prompt stays out of the selection, so a copy yields only the command, not the shell glyph. ```tsx ``` **Don't** — Selectable prompt text means a reader who copies the line drags the $ marker into their shell. ```tsx $ npm install @nannier/canvas ``` ### Numbered **Do** — Use the numbered variant: the gutter stays out of the selection, so copying the block returns clean, runnable code. ```tsx ``` **Don't** — Selectable line numbers get swept into the selection and pasted as 1 2 ahead of every line. ```tsx 1 2 const theme = getTheme(); setTheme(theme); ``` ### Diff **Do** — Use diff mode: markers stay out of the selection and the copy chip yields the post-change code. ```tsx ``` **Don't** — Hand-colored +/- rows leave the markers selectable, so a copied block pastes broken code. ```tsx -const theme = "light"; +const theme = getTheme(); setTheme(theme); ``` ### Tabs **Do** — One tabbed block keeps the package-manager alternatives in one place; the reader picks once. ```tsx ``` **Don't** — Stacked one-per-manager blocks repeat the same install three times and triple the page height. ```tsx ``` ### Inline **Do** — Reserve inline code for short tokens; move anything multi-line into a block. ```tsx Run the setup command: ``` **Don't** — A long, multi-step command crammed inline wraps mid-token and offers no horizontal scroll. ```tsx Run npm install @nannier/canvas && npm run build && npm run preview to start. ``` ## Supported props ### CodeBlockProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | code | string \| undefined | no | | The code to render. Newlines are preserved (RN Text honors "\n"). | | filename | string \| undefined | no | | Optional filename label shown in a header bar above the surface. Honored by the plain and numbered variants (and the terminal chrome label); the inline pill has no header. When both are set, `filename` takes the header label and `language` renders as a trailing badge. | | language | string \| undefined | no | | The snippet's language. Drives syntax highlighting (ts/tsx/js/jsx, json, bash/sh, css, html, python — anything else renders monochrome) and the header/terminal label when no `filename` is given. | | transcript | boolean \| undefined | no | false | Bare execution output: selectable, wrapped monochrome text with no card, header or expander. Takes precedence over terminal, numbered and inline; other display modifiers are ignored. | | terminal | boolean \| undefined | no | false | | | numbered | boolean \| undefined | no | false | | | inline | boolean \| undefined | no | false | | | compact | boolean \| undefined | no | false | Density (pick one; default is the comfortable ramp). | | playground | boolean \| undefined | no | false | The hand-off component page's JSX card (Component.dc.html lines 171 to 174, the playground's output): the block at the card corner, its header a 40 px bar on the card fill, 16 in and 8 out, with its Copy a 28 px hairline button, and the code 16 above and below, 18 in, at the default 13 / 1.7 ramp. `compact` wins when both are passed. | | snippet | boolean \| undefined | no | false | The hand-off component page's own snippet (Component.dc.html line 283, the Usage section's block under its paragraph): the code-card corner, with the code 16 above and below and 18 in at the default 13 / 1.7 ramp. `compact` and `playground` win over it. | | guide | boolean \| undefined | no | false | The hand-off component page's card snippet (Component.dc.html line 288, the block in a Usage guidance card): the card-snippet corner, with the code 12 above and below and 14 in at 12 / 1.65. Every other density wins over it. | | copy | boolean \| undefined | no | false | Show a copy affordance (header-hosted when a header/chrome exists, else pinned top-right). On the `inline` variant the whole pill becomes the copy button: a raised mono chip with a trailing copy glyph that turns to a check once copied (an import line a reader takes away). | | wrap | boolean \| undefined | no | false | Soft-wrap long lines instead of scrolling them horizontally. An overflowing unwrapped block is a keyboard tab stop; arrow keys use the platform's normal scrolling. | | diff | boolean \| undefined | no | false | Render the code as a unified diff: the first column of every line is the marker ("+" added, "-" removed, anything else context). Added/removed rows tint green/red, markers stay out of the selection, and the copy chip yields the post-change code (context + added lines, markers stripped). | | attached | boolean \| undefined | no | false | Square the top edge (no top radius/border) to sit flush under an adjoining bar. | | headerless | boolean \| undefined | no | false | Draw no header bar: `language` still drives the highlighting, but neither it nor a `filename` labels the block, and `copy` floats over the code's top-right corner. For a snippet whose context already names it (a docs page's usage block). Plain and numbered variants; a `tabs` strip still needs its header, and the terminal keeps its chrome. | | unbadged | boolean \| undefined | no | false | Keep the header bar but draw no trailing language badge beside the copy chip: `language` still drives the highlighting, for a header whose `filename` already names the language (the hand-off's playground bar, Component.dc.html lines 171 to 174, is "JSX · " and a Copy button only). Nothing changes when there is no `filename`, since the language is then the header's label, not a badge. | | highlightLines | readonly (string \| number)[] \| undefined | no | | Emphasize lines: entries are 1-based line numbers or "start-end" ranges (e.g. `[2, "5-7"]`). Numbers refer to the displayed numbering, so they respect `startLine`. Plain and numbered variants only. | | startLine | number \| undefined | no | 1 | First displayed line number for the numbered variant (default 1). | | collapsible | boolean \| undefined | no | false | Fold long blocks behind a "Show all" expander (plain + numbered variants). | | collapsedLines | number \| undefined | no | 8 | How many lines a collapsed block shows (default 8). | | expanded | boolean \| undefined | no | false | Controlled expanded state for `collapsible`. | | defaultExpanded | boolean \| undefined | no | false | Uncontrolled initial expanded state (default false). | | onExpandedChange | ((expanded: boolean) => void) \| undefined | no | | Called when the expander toggles (fires in both modes). | | tabs | readonly CodeBlockTab[] \| undefined | no | | Labeled code alternatives rendered as a tab strip in the header (or the terminal chrome) — the classic npm/yarn/bun switcher. Each tab supplies its own code and optional language/filename. | | active | number \| undefined | no | | Controlled active tab index. | | defaultActive | number \| undefined | no | | Uncontrolled initial tab index (default 0). | | onTabChange | ((index: number) => void) \| undefined | no | | Called with the next tab index when a tab is selected (fires in both modes). | | prompt | string \| undefined | no | "$" | The terminal prompt marker (default "$"). Lines starting with it render as commands. | | onCopy | ((code: string, event: GestureResponderEvent) => void) \| undefined | no | | Called when the copy affordance is pressed, with the copied text. The chip also writes the clipboard itself (navigator.clipboard on web, the optional expo-clipboard peer on native), so this is for side effects, not plumbing. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Collapsible Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/collapsible/collapsible.md` Human reference: https://canvas.nannier.com/components/collapsible ## Public imports ```tsx import { Collapsible } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A single disclosure: one header (its `title`, or a custom `trigger`, plus a chevron that rotates when open) over one collapsible content panel. Closed by default; pass `defaultOpen` to start open. Open state is controlled (`open` + `onOpenChange`) or uncontrolled (`defaultOpen`). A `description` adds a muted secondary line under the title; `card` wraps the disclosure in an outlined card surface on web and Android (on iOS the default look already is the inset-grouped card, so `card` is a no-op there). For a set of related, peer sections, reach for an Accordion (a group of these); Collapsible is the standalone primitive. ## Usage ```tsx Free 2-day shipping on orders over $50. Delivery in 3 to 5 business days otherwise. ``` ## Variants ### Open by default ```tsx A universal React Native UI kit that renders natively on iOS and Android and on the web through React Native Web. ``` ### Disabled ```tsx Not available yet. ``` ### Card surface `card` gives the disclosure an outlined card container with inset header and content on web and Android. On iOS the default Collapsible already renders as the inset-grouped card, so `card` changes nothing there (a documented no-op). ```tsx Free 2-day shipping on orders over $50. Delivery in 3 to 5 business days otherwise. ``` ### Reference card `reference` is the reference-sheet disclosure, a component page's sub-component props: the card corner on a hairline over the card fill, a 46 px toggle with the title in 600 14 and the `description` inline after it in the mono meta ink, and the content flush to the card's edges, so a `reference attached` DataTable's rows meet the card. On iOS the default Collapsible already renders as the inset-grouped card, so `reference` changes nothing there. ```tsx void", "–"], ["items", "AvatarMenuItem[]", "–"] ]} /> ``` ### With descriptions ```tsx Choose which events reach your inbox and which stay in the app. ``` ### Custom trigger ```tsx Order #1024, 3 items}> Wireless mouse, USB-C cable, laptop stand. Estimated total $84.00. ``` ## Do & Don't ### One block of detail **Do** — Use one Collapsible to hide a single block of secondary detail behind a clear label, and let the chevron carry the open/closed affordance. ```tsx 30-day returns, no questions asked. Refunds post within 5 business days. ``` **Don't** — Don't reach for a Collapsible when you have several related, peer sections; that is an Accordion's job, and a stack of standalone disclosures loses the single-open coordination. ```tsx Manage your plan. Invite teammates. Two-factor authentication. ``` ## Supported props ### CollapsibleProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | title | string \| undefined | no | | The default trigger label text. | | description | string \| undefined | no | | The muted secondary line under the title in the default trigger anatomy (ignored when a custom `trigger` is given, which owns its own anatomy). | | trigger | ReactNode | no | | A custom trigger node; if provided, it renders as the pressable's content instead of the `title` text (the chevron still renders alongside). | | card | boolean \| undefined | no | false | Card surface: wraps the disclosure in an outlined card container with inset header and content (web/Android). On iOS the default Collapsible already renders as the inset-grouped card, so `card` is a no-op there. | | reference | boolean \| undefined | no | false | The reference-sheet card (a component page's sub-component props): the card corner on a hairline over the card fill, a 46 px toggle with the title in 600 14 and the `description` inline after it in the mono meta ink, and the content flush to the card's edges, for a `reference attached` DataTable (web/Android). On iOS the default Collapsible already renders as the inset-grouped card, so `reference` changes nothing there. | | children | ReactNode | yes | | The collapsible content. A string renders in the skin's content type; a ReactNode renders as-is. | | open | boolean \| undefined | no | false | Controlled open state. Pair with `onOpenChange`. Omit for uncontrolled. | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Called with the next open state whenever the header toggles. | | defaultOpen | boolean \| undefined | no | false | Initial open state for the uncontrolled case (read once, default false). | | disabled | boolean \| undefined | no | false | A disabled header does not toggle and reads dimmed. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Command Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/command/command.md` Human reference: https://canvas.nannier.com/components/command ## Public imports ```tsx import { Command } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Cmd+K search: navigation, actions, recent items. The search row is a real input: typing filters the grouped rows to the matching labels, and a query that matches nothing shows a muted "No results" row. On every platform the palette is the v4 card: 520 wide at an 18-pixel corner, a 48-pixel search row with an `esc` cap over a hairline, mono eyebrows over 38-pixel rows (44 on iOS, 48 on Android) with quiet icons and mono shortcuts, the accent's soft wash under the active row, and an optional footer of mono hints. Neither iOS nor Android ships a command palette, so both take this look in their own face. In glass mode the triggered palette is a functional-layer glass card under the search bar, with its own search field; the search editor keeps focus while typing filters the rows. Solid mode paints the skin's own palette. The search placeholder names the input and its result list. Supply `accessibilityLabel` when a more specific purpose is needed. `defaultActive={-1}` starts without a highlighted result: Enter selects nothing until navigation or hovering chooses a row. Either arrow key starts at the first result from this state. Controlled indices above the visible result count clamp to the last match; negative, fractional and non-finite indices leave no active result. Use `filtered` when a search service or index supplies ranked groups; Command keeps those rows and their order instead of filtering their labels again. An item's `description` supplies its secondary text. `emptyMessage` provides both the initial prompt and the no-match message. Use `embedded` inside a `Dialog` or `Drawer` to inherit its surface and fill its bounds. It has no effect when `trigger` is set. Long results scroll within the palette. `autoFocus` focuses the inline field after the containing overlay captures its opener, so closing restores the original control. `onKeyPress` runs before built-in navigation; call `preventDefault()` to handle an application shortcut. ## Usage ```tsx ``` ## Variants ### Inline ```tsx ``` ### Footer with key hints ```tsx ``` ### Embedded search ```tsx ``` ### Filtering Typing narrows the rows; `defaultQuery` seeds the filter (here only the file actions match), and groups left with no match drop out. ```tsx ``` ## Do & Don't ### Trigger **Do** — Surface the shortcut in a trailing kbd so the trigger advertises the faster keyboard path. ```tsx ``` **Don't** — A bare search button hides the keyboard shortcut, so power users never learn the ⌘K entry point. ```tsx Search... ``` ### Inline **Do** — Group commands under labels with separators and highlight the first match so results stay scannable. ```tsx ``` **Don't** — Dumping every command into one flat list with no labels makes the palette hard to scan. ```tsx ``` ## Supported props ### CommandProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | placeholder | string \| undefined | no | "Search commands..." | Prompt shown in the empty search input. | | accessibilityLabel | string \| undefined | no | | Accessible purpose for the search input and result list. Defaults to the search placeholder. Supply this when the placeholder does not explain the task. | | query | string \| undefined | no | | The text typed into the search input (CONTROLLED). Filters the rows to the labels matching it (case-insensitive). Omit and use `defaultQuery` for uncontrolled use: a bare Command is searchable out of the box. | | defaultQuery | string \| undefined | no | | Initial query for uncontrolled use. | | onQueryChange | ((query: string) => void) \| undefined | no | | Fired with the new query on each keystroke (both modes). | | groups | CommandGroup[] \| undefined | no | | Grouped result rows. | | filtered | boolean \| undefined | no | false | Groups are already filtered and ranked by the owner; preserve their order and contents. | | embedded | boolean \| undefined | no | false | Fill the parent and inherit its surface, for composition inside a Dialog or Drawer. Ignored with trigger. | | autoFocus | boolean \| undefined | no | false | Focus the inline search field after mounting, allowing the containing overlay to capture its opener first. | | onKeyPress | ((e: TextInputKeyPressEvent) => void) \| undefined | no | | Input keyboard handler, called before built-in navigation. preventDefault cancels built-in handling. | | emptyMessage | string \| undefined | no | | Content shown when there are no result rows, including an empty query. | | active | number \| undefined | no | | Flat index of the highlighted row (CONTROLLED), counted across the visible (query-matching) rows. Omit for uncontrolled use. | | defaultActive | number \| undefined | no | | Initial highlighted row for uncontrolled use (hovering a row moves it, typing resets it to the first match). | | open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (the search trigger toggles it). | | defaultOpen | boolean \| undefined | no | false | Render the palette open initially for uncontrolled use (selecting a row closes it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes. | | trigger | boolean \| undefined | no | false | Render a collapsed full-width search trigger above the palette (a search glyph + "Search..." + a trailing kbd cap). The palette card still renders inline below the trigger (gated by `open`), mirroring how Dropdown shows its trigger plus the open menu in the docs. | | footer | boolean \| undefined | no | false | Append a footer hint bar below the list (↑ ↓ to navigate, ↵ to select, esc to close). | | onSelect | ((item: CommandItem, index: number) => void) \| undefined | no | | Called with the chosen item and its flat index (within the visible, query-matching rows) when a row is pressed. | | testID | string \| undefined | no | | E2E hook forwarded to the root element; the scrolling results list gets `${testID}-results`. | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # ComposedChart Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/composed-chart/composed-chart.md` Human reference: https://canvas.nannier.com/components/composed-chart ## Public imports ```tsx import { ComposedChart } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Bars, lines, and gradient-washed areas sharing one categorical axis: each series picks its mark with a `line` or `area` boolean (bars by default; precedence line > area > bars). The classic "revenue bars with a margin line" chart, on one shared zero-based y axis, with the same scrub-to-inspect, legend, and accessible-name contract as the other cartesian charts. For bar-only grouped data, reach for the bar `Chart`'s grouped mode instead; composed earns its keep when the marks mix. ## Usage ```tsx ``` ## Variants ### Area backdrop ```tsx ``` ### Dots and inspection ```tsx ``` ### Dashed target line A `line` series marked `dashed` draws dashed, with a dashed rule as its legend swatch. ```tsx ``` ## Do & Don't ### ComposedChart **Do** - Mix marks when the series mean different things: bars for the magnitude, a line for the derived rate. ```tsx ``` **Don't** - All-bar composed data belongs on the bar Chart's grouped mode; composed adds nothing but indirection. ```tsx ``` ## Supported props ### ComposedChartProps Includes `Omit`; their supported fields are expanded below. | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | series | ComposedSeries[] | yes | | The series to plot; each picks its mark (bars by default, `line`, or `area`). | | dots | boolean \| undefined | no | false | Mark each line/area datum with a dot (auto-suppressed when bands are under 14px). | | labels | string[] | yes | | Category labels along the x axis, one per data column. | | title | string \| undefined | no | | Optional heading shown above the plot. | | max | number \| undefined | no | | Y-axis maximum override. Defaults to a nice bound above the data max. | | min | number \| undefined | no | | Y-axis minimum override. Defaults to 0 (or the data min when negative). | | success | boolean \| undefined | no | false | Tone (single-series only; pick one; default is the primary fill). | | destructive | boolean \| undefined | no | false | Tone (single-series only; pick one; default is the primary fill). | | curved | boolean \| undefined | no | false | Curve (omit for straight segments). | | compact | boolean \| undefined | no | false | Density (omit for the default plot size). | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | hideLegend | boolean \| undefined | no | false | Furniture visibility. | | hideGrid | boolean \| undefined | no | false | Furniture visibility. | | hideAxes | boolean \| undefined | no | false | Furniture visibility. | | formatValue | ((v: number) => string) \| undefined | no | | Formats tick labels and accessible values (data formatting, not styling). | | selected | number \| null \| undefined | no | | Scrub-to-inspect: the selected category index (controlled). Pass null for none. | | defaultSelected | number \| undefined | no | | Scrub-to-inspect: the initially selected category (uncontrolled). | | onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press/scrub selects a category (or clears it with null). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Container Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/container/container.md` Human reference: https://canvas.nannier.com/components/container ## Public imports ```tsx import { Container } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. The bounds provider. A Canvas component never dictates its own width: `Container` conforms to its parent (full width, no cap) by default, and a named step of the shared width scale (`xxxs` 192 through `page` 1280) caps and centers it, so the reading measure of a form, an article, or a card stack is a step you can name instead of a width invented at the call site. A component fills the parent it is given (a field, a card, a table) or hugs its content (a button, a badge), and the nearest layout container provides the bounds. `Container` is the layout container whose job is a MEASURE, and by default the parent is the conformance factor. A cap is fluid: inside anything narrower than the step the container simply fills its parent, which is all a phone screen is. `start` pins a capped box to the leading edge instead of centering it, and `padTight` / `pad` / `padLoose` add horizontal gutters from Row and Column's own padding scale. The step booleans and `start` are the kit's measure axis, and the fields, Field, Form, Button, and ButtonGroup carry it too: `` says exactly what `` around it says, a cap at the step and fluid below it. Reach for Container when the measure belongs to a group (a form, an article, a card stack) rather than to one component. ## Usage ```tsx Sign in ``` ## Variants ### Steps ```tsx xs, 320 md, 448 xl, 576 ``` ### Pinned to the start ```tsx Settings ``` ### Full width with gutters ```tsx The default measure is the parent's own width; pad adds 16px gutters on both sides. ``` ## Do & Don't ### Naming a measure **Do**: Wrap the block in a Container step; the component inside fills it. ```tsx Billing Plan, invoices, and payment method. ``` **Don't**: Cap a raw View with a maxWidth of your own; the number is invented at the call site, drifts off the shared width scale, and pins the box to the start where a step would center it. ```tsx Billing Plan, invoices, and payment method. ``` ## Supported props ### ContainerProps Includes `MeasureProps`; their supported fields are expanded below. | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | | | fluid | boolean \| undefined | no | false | No cap, the default: the container spans its parent. Explicit `fluid` wins over every step when several are passed. | | padTight | boolean \| undefined | no | false | Gutters: horizontal padding (pick one; omit for none). | | pad | boolean \| undefined | no | false | Gutters: horizontal padding (pick one; omit for none). | | padLoose | boolean \| undefined | no | false | Gutters: horizontal padding (pick one; omit for none). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Layout containers are the exception to the no-sizing rule: for composition inside an app frame only (a `flex` in a screen shell), never a restyle hook. | | xxxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xs | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | sm | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | md | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | lg | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | xxxl | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wide | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | wider | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | widest | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | page | boolean \| undefined | no | false | Measure (pick one; omit to take the parent's bounds). Caps the component at a step of the width scale, centered in its column; `start` pins it to the leading edge. | | start | boolean \| undefined | no | false | Pin a capped component to the leading edge instead of centering it in its column. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # DashboardGrid Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/dashboard-grid/dashboard-grid.md` Human reference: https://canvas.nannier.com/components/dashboard-grid ## Public imports ```tsx import { DashboardGrid, clearStoredDashboardOrder } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A 12-column widget board for overview screens. Each widget declares a span in twelfths, and the grid measures its OWN width (never the window) to pick a tier: a wide container honors every span, a narrower one reflows widgets to their `narrowSpan`, and a phone-sized one stacks them full width. Cells render bare, because a widget arrives with its own surface (a Card, a Chart, a Stats row) and a second frame around it would only double up. A widget's `title` is its accessible name rather than a header the grid paints: customize mode uses it to name the widget's drag grip and its drop zone. Locked, it is a plain static grid and no drag machinery is mounted at all; `unlocked` turns on customize mode, where every cell gains a grip and the board reorders by pointer, keyboard, or screen reader through the kit's own drag-and-drop. The order is a plain array of ids, controlled through `order` and `onOrderChange` so the app can persist it, or left to the grid with `defaultOrder`. The board takes its widgets through `items`, the collection prop every other collection-taking component in the kit uses. ## Usage ```tsx }, { id: "signups", span: 4, title: "Signups", content: }, { id: "endpoints", span: 12, title: "Slowest endpoints", content: }, ]} /> ``` ## Variants ### Customize mode `unlocked` is the app's switch, never the grid's: turning it on paints the cell affordance, shows a grip on every widget, and makes the board reorderable. The grips stay visible for as long as the mode lasts, because React Native has no portable hover event and an edit mode already announces itself. `defaultOrder` hands the order to the grid, which applies each drop itself and reports the result through `onOrderChange`, so a bare board is interactive out of the box. ```tsx }, { id: "signups", span: 6, title: "Signups", content: }, ]} /> ``` ### Controlled order The primary path: the app owns the id array and persists it through its own API, applying every `onOrderChange`. Ids the widget list no longer holds are ignored and widgets missing from the order are appended, so a layout saved before a release keeps working. ```tsx {(order, setOrder) => ( }, { id: "signups", span: 6, title: "Signups", content: }, ]} /> )} ``` ### Narrow spans A wide board is not a narrow one cut in half, so a widget can declare the width it wants when the grid reflows. Without `narrowSpan`, a widget of five columns or more takes the full board and anything smaller pairs up at half; every widget stacks full width at phone sizes. ```tsx }, { id: "signups", span: 4, narrowSpan: 6, title: "Signups", content: }, { id: "errors", span: 4, narrowSpan: 6, title: "Errors", content: }, ]} /> ``` ### Compact The density axis tightens the space between cells. ```tsx }, { id: "signups", span: 4, title: "Signups", content: }, { id: "errors", span: 4, title: "Errors", content: }, ]} /> ``` ## Do & Don't ### The order lives in the consumer's data **Do**: Treat `order` as the single source of truth and persist every `onOrderChange` through your own API, so a layout survives a reload on any device. ```tsx {(order, setOrder) => ( }, { id: "signups", span: 6, title: "Signups", content: }, ]} /> )} ``` **Don't**: Don't pass a controlled `order` and drop the change on the floor: the drop completes and announces, and then the widget snaps back because nothing updated the array. ```tsx }, { id: "signups", span: 6, title: "Signups", content: }, ]} /> ``` ### `title` names the widget for assistive tech **Do**: Give every widget the title its own header already shows. Nothing paints it, but customize mode reads it out: the grip announces "Reorder Signups", and moving the widget announces which one moved and what it landed against. ```tsx Signups 1,204 ), }, { id: "revenue", span: 6, title: "Revenue", content: }, ]} /> ``` **Don't**: Don't pass a slug, an id, or a placeholder. The board looks identical, and that is the trap: a keyboard or screen-reader user in customize mode is left dragging "widget-2" around with nothing to tell them which tile it is. ```tsx }, { id: "revenue", span: 6, title: "widget-2", content: }, ]} /> ``` ### Widgets bring their own surface **Do**: Hand each widget a component that already carries a surface. The cell is bare on purpose, so a Card, a Chart, a Stats row, or a BarList lands with exactly one frame around it. ```tsx Signups 1,204 ), }, { id: "revenue", span: 6, title: "Revenue", content: }, ]} /> ``` **Don't**: Don't hand-roll the widget frame out of a styled View. That rebuilds Card at the call site, drifts from the kit's radius and elevation, and is exactly the shim the bare cell exists to avoid. ```tsx 1,204 ), }, ]} /> ``` ## Supported props ### DashboardGridProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | items | DashboardWidget[] \| undefined | no | | The widgets to lay out, and the canonical collection prop. Their array order is the fallback order: any widget whose id is missing from the active order is appended, so adding one to a release never breaks a saved layout. | | order | string[] \| undefined | no | | CONTROLLED widget order, as ids. The primary path: the consuming app owns the array and persists it through its own API, applying each `onOrderChange`. Ids the widget list does not hold are ignored, and widgets missing from it are appended, so a stored order survives a release that adds or drops a widget. | | defaultOrder | string[] \| undefined | no | | Initial order for UNCONTROLLED use; the grid then applies each drop itself. Defaults to the `items` array order. | | onOrderChange | ((order: string[]) => void) \| undefined | no | | Fired with the full, reconciled next order after every drop that changes it, in BOTH the controlled and the uncontrolled mode. | | unlocked | boolean \| undefined | no | false | Customize mode: cells gain the edit affordance and a drag grip, and the board becomes reorderable. The grid never flips this itself; the app owns the "Done" / "Customize" control. Locked (the default) the drag provider is not mounted at all. | | storageKey | string \| undefined | no | | Web-only convenience for the UNCONTROLLED path: seed the initial order from browser storage under this key and write each drop back to it. Ignored when `order` is passed (the controlled order owns persistence). On native there is no storage, so the order is session-only there; `clearStoredDashboardOrder` forgets a saved layout. A server-rendered board renders `defaultOrder` first and adopts the saved layout in the commit right after hydration, so React always hydrates against the markup the server actually sent. | | compact | boolean \| undefined | no | false | Density axis (pass none for the default density). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # DataTable Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/data-table/data-table.md` Human reference: https://canvas.nannier.com/components/data-table ## Public imports ```tsx import { DataTable } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A data table rendered from column and row data, with sorting, row selection, pagination, and the loading and empty states built in. Columns are plain header labels or descriptors (`{ label, numeric, width, sortable, ... }`) for per-column alignment, fixed widths, and sorting; compose a toolbar above when the screen needs one. Density tweaks affect padding live. Pass `onRowEdit` / `onRowDelete` for a trailing actions column: the pencil opens the row's string cells as fields with Save/Cancel (committing via `onRowCommit`), and Delete asks for a confirming second press before it fires. `inlineEdit` skips the pencil and lets a string cell be pressed straight into a field, committing via `onCellCommit`. `onRowPress` makes the whole row act on press and puts the keyboard and screen-reader path on a button in the row's first column, so keep your own controls out of that column when you use it. Data stays yours either way: the table reports intent and re-renders whatever rows it is handed back. Treat `rows` and `columns` as immutable inputs. When editing data, replace the outer `rows` array and each changed row, as the examples below do. When changing a column, replace its descriptor and the `columns` array. Keep unchanged arrays and callbacks stable for large tables: Canvas caches sorting, paging, and selection summaries so typing a draft or opening a row action does not process the whole data set again. `sortValue` and `rowKey` must be pure functions of their inputs. If either closes over changing external data, supply a new callback identity; for `sortValue`, also replace its column descriptor and the `columns` array. A saved edit appears in sorted order when the updated `rows` arrive. On narrow web and Android layouts, the columns still share the table width and wrap their text, each keeping a 110 px minimum (a column with a fixed `width` keeps that width), and they pan horizontally only when those minimums exceed the table width. A long cell wraps inside its column rather than widening the table. The overflowing scrollport is a keyboard tab stop, so the platform's arrow keys can reveal the remaining columns, and while it has keyboard focus the table's surface draws the theme's focus ring. A table that fits adds no scrolling tab stop, and on Android it takes no sideways drag either, so a tap that drifts sideways still presses its row and a swipe that starts across it scrolls the page (while TalkBack explores by touch the scroller stays enabled, so the exploring finger reaches every cell). On an iPhone-width table iOS shows only the first column instead, as SwiftUI Table does. A table that passes `stacks` does neither: at that width each row stacks its cells (see Stacked below). On the web and Android, and for a table that stacks on any platform, crossing that width changes the layout only: rows, custom cells, an open editor with its text and focus, and a windowed body's scroll position stay as they are when a window is resized across it or a phone measures the table on its first frame. The iOS first-column layout is the exception: below that width the columns after the first leave the table, with any editor or custom cell in them, and mount again when it widens. A `virtualized` table's body scrolls its rows under the fixed header. On the web, while those rows overflow, the body is a keyboard tab stop of its own (after the column scroller when a narrow table pans, and before the rows' controls when they have any), the keyboard can scroll it, and while it has keyboard focus the table's surface draws the same focus ring. The body is the table's row group, so the rows it renders stay inside the table for a screen reader rather than behind an unnamed focusable node. ## Usage ```tsx ``` ## Variants ### Bordered ```tsx ``` ### Attached A standalone table frames itself: a hairline at the table corner over the card fill, its header band on the raised fill under it. `bordered` draws that frame on the firmer field boundary. A table that sits flush inside a frame its parent draws (a `flush` Card, a bordered panel that clips to its corners) passes `attached`, so it draws no frame of its own and the parent's is the only one. An attached table draws its keyboard focus ring just inside its edge, where the parent's clip cannot cut it. ```tsx ``` ### Striped ```tsx ``` ### Compact ```tsx ``` ### Comfortable ```tsx ``` ### Loading ```tsx ``` ### Empty ```tsx ``` ### Sortable ```tsx ``` ### Selectable ```tsx ``` ### Paginated ```tsx [ `Employee ${i + 1}`, ["Design", "Platform", "Growth"][i % 3] ])} paginated pageSize={5} /> ``` ### Numeric and custom columns ```tsx Paid, "$1,250.00"], ["INV-0042", Due, "$450.00"], ["INV-0043", Draft, "$8,120.00"] ]} /> ``` ### Column weights A column without a fixed `width` takes an equal share of what the fixed columns leave; `grow` weights that share (`grow: 2` takes twice a `grow: 1` column's) and `minWidth` floors the column's box, as a grid's `minmax(150px, 2fr)` track does. Weights, floors and fixed widths mix freely. Once the declared floors add up to more than the table's width, the table pans horizontally at any width rather than crushing them. ```tsx ``` ### Grouped rows Pass `groups` in place of `rows` to title runs of rows: each group draws a band spanning every column (its `label`, an optional `adornment` node and an optional `count`, on the sunken fill) above its own rows. Sorting reorders rows within their group, paging draws the band of every group a page shows, and row indices count through the groups in order. ```tsx pick one, count: 3, rows: [ ["primary", "boolean", "false"], ["destructive", "boolean", "false"], ["ghost", "boolean", "false"] ] }, { label: "State", count: 2, rows: [ ["loading", "boolean", "false"], ["disabled", "boolean", "false"] ] } ]} /> ``` ### Reference sheet Pass `reference` for the reference sheet, a component page's props table: the column headings as the page's small uppercase mono label (PROP, TYPE, DEFAULT and DESCRIPTION) on a header row with no fill of its own, 12 px above and below whatever the density; rows 10 px above and below with a rule above each; the columns 16 px apart and 16 px in from the frame's sides; and the frame at the panel corner on the `border` hairline over the card fill. `bordered` only firms the hairline, and `attached` still drops the frame. Pair it with `alignTop` when a cell wraps. On iOS the table keeps its own look and the prop changes nothing. ```tsx ``` ### Bare table Pass `bare` for the hairline table with no frame and no fill: the headings in the reference sheet's small uppercase mono label over a `field-border` rule 10 px below them, a `border` rule under every row, the first column flush with the table's start, each cell keeping 12 px clear at its end inside its column's width (so a fixed `width` is the whole column), and 12 px above and below whatever the density. `reference` wins when both are passed. On iOS the table keeps its own look and the prop changes nothing. ```tsx ``` ### Matrix Pass `matrix` for a specimen grid: no frame, no fill and no rules, the headings in the reference sheet's small uppercase mono label, the cells unpadded, the rows 12 px apart and the columns 20 px apart. A column with `fit` takes its widest cell's width, its heading included (a grid's `max-content` track), so a column of buttons lines up at its longest label; a table with `fit` columns pans once they add up to more than it has. `reference` and `bare` win over `matrix`. On iOS the table keeps its own look and the prop changes nothing. ```tsx Save changes, ], ["outline", , ] ]} /> ``` ### Top-aligned cells Pass `alignTop` when a cell may wrap to several lines: every cell sits at the top of its row, so a row's name, type and default stay beside the first line of its description instead of drifting to the row's middle. The selection box and the row actions center on that first line, as they do in a stacked row. ```tsx ``` ### Footer row Pass `footer` to draw a row inside the table's frame under the last row, and under the pagination bar when the table is `paginated`: 44 px tall with its content centered, for a "Show all" control or a note. One hairline separates it from what sits above it. ```tsx Show all 58 props} /> ``` ### Sparkline trend column ```tsx , "12,480"], ["/docs", , "8,102"], ["/blog", , "5,914"] ]} /> ``` ### Avatar identity cells ```tsx , "Admin"], [, "Editor"], [, "Admin"] ]} /> ``` ### Row actions ```tsx {(rows, setRows) => ( {}} onRowCommit={(i, cells) => setRows(rows.map((row, r) => (r === i ? cells.map(String) : row)))} onRowDelete={(i) => setRows(rows.filter((_row, r) => r !== i))} /> )} ``` ### Inline editing ```tsx {(rows, setRows) => ( setRows(rows.map((row, r) => (r === i ? row.map((cell, ci) => (ci === c ? next : cell)) : row))) } /> )} ``` ### Stacked Pass `stacks` when the cells carry long text. When the table's own container is at or below `stackBreakpoint` (default `sm`, 640), each row lays its cells out top to bottom at the full width: the first cell leads as the row's title and every later cell sits under its column label, so a paragraph reads at the table's width instead of a column's share of it. The header row stays only while it holds a control (a sortable column, as here, or the box that selects every row), the selection box and row actions keep their places, and on iOS the table stacks instead of collapsing to its first column. Above the breakpoint it is the ordinary table. ```tsx ``` ## Do & Don't ### default **Do** — Use the built-in paginated footer so the search result is always anchored to the total. ```tsx ``` **Don't** — Wiring search but dropping the footer leaves the user with no result count or way to page through 142 rows. ```tsx ``` ### bulk **Do** — Pair the selection with a bulk bar that leads with the non-destructive action and keeps Delete visually distinct. ```tsx 2 selected ``` **Don't** — Surfacing only the destructive Delete on a selection invites accidental data loss with no safer path. ```tsx 3 selected ``` ### filter **Do** — Echo the live result count next to the filter so its effect is visible. ```tsx Status: ``` ### empty **Do** — Keep the header and let the built-in emptyMessage span a centered row so the structure stays intact. ```tsx ``` **Don't** — Hiding the body entirely on no results collapses the table and looks broken. ```tsx ``` ### loading **Do** — Use the built-in loading skeletons so the load reads as the table taking shape in place. ```tsx ``` **Don't** — A bare "Loading…" string gives no sense of progress and reads like static content. ```tsx Loading… ``` ## Supported props ### DataTableProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | columns | (string \| DataTableColumn)[] | yes | | Columns: a header label per column, or a `DataTableColumn` descriptor for per-column alignment (`numeric`/`centered`), a fixed `width`, and sorting (`sortable`, `sortValue`). Strings and descriptors mix freely. Treat the array and descriptors as immutable: replace both when a column changes. | | rows | ReactNode[][] \| undefined | no | | Row data: an array of rows, each an array of cells (one per column). A cell is a string (rendered in the default cell type) or any ReactNode for a custom cell, such as a link, a `Badge`, or a monospace name (rendered directly). Treat this data as immutable: replace the outer array and each changed row when data changes, so cached sorting and pagination update correctly. | | groups | DataTableGroup[] \| undefined | no | | Group the rows under titled bands (the props table's "Intent", "Size" and "State" groups): each group draws a band spanning every column, its label, an optional adornment and an optional count on the sunken fill, then its own rows. Given `groups`, the table's rows are the groups' rows in order and `rows` is ignored; every row index a callback reports (`onRowPress`, `onRowEdit`, `rowKey` and the rest) counts through them in that order. Sorting reorders rows within their group and never moves a group; paging pages the rows, and each page draws the band of every group it shows. A group with no rows draws no band. | | striped | boolean \| undefined | no | false | Tint every other data row for easier horizontal scanning. | | bordered | boolean \| undefined | no | false | Draw the table's frame on the firmer field boundary rather than the hairline. | | attached | boolean \| undefined | no | false | The table sits flush inside a frame its parent draws (a `flush` Card, a bordered panel that clips to its corners), so it draws no frame of its own. Without it a standalone table frames itself (on the web and Android, a hairline at the table corner over the card fill), which is the standalone look. | | stacks | boolean \| undefined | no | false | Responsive: when the table's own CONTAINER is at or below `stackBreakpoint` (default `sm` = 640), each row lays its cells out top to bottom at the full width instead of side by side, so long text reads at the width of the table rather than a column's share of it. The first cell leads the row as its title; every later cell carries its column label above its value. The header row is drawn only while it holds a control (a sortable column, or the box that selects every row), as a band of its labels that wraps; the selection box and the row actions stay at the row's leading and trailing edges. A stacked table never pans, and on iOS it stacks instead of collapsing to its first column. Container-measured with a viewport seed for the first frame, like Row `stacks`; crossing the breakpoint changes the layout only. | | stackBreakpoint | BreakpointKey \| undefined | no | | The breakpoint at and below which `stacks` stacks the rows (default `"sm"`). Only meaningful with `stacks` (DEV warns without it). | | compact | boolean \| undefined | no | false | Tighter vertical padding on header and data cells. | | comfortable | boolean \| undefined | no | false | Looser vertical padding on header and data cells. | | reference | boolean \| undefined | no | false | The reference sheet (a component page's props table): the column headings as the page's small uppercase mono label on a header row with no fill of its own, 12 px above and below whatever the density; rows 10 px above and below with a rule above each; the columns 16 px apart and 16 px in from the frame's sides; and the frame at the panel corner on the `border` hairline over the card fill, so `bordered` only firms the hairline and `attached` still drops the frame. On iOS the table keeps its own look and this changes nothing. | | bare | boolean \| undefined | no | false | The bare hairline table: no frame and no fill, a `field-border` rule under the headings (the reference sheet's mono uppercase label) and a `border` rule under every row, the first column flush with the table's start, each cell keeping 12 px clear at its end inside its column's width, and 12 px above and below whatever the density. `reference` wins over it. On iOS the table keeps its own look and this changes nothing. | | matrix | boolean \| undefined | no | false | The specimen matrix (a section page's states grid): no frame, no fill and no rules, the headings as the page's small uppercase mono label in the third ink, the cells unpadded, the rows 12 px apart and the columns 20 px apart. Pair it with `fit` columns for the hand-off's content-sized tracks. `reference` and `bare` win over it. On iOS the table keeps its own look and this changes nothing. | | alignTop | boolean \| undefined | no | false | Align every cell to the top of its row rather than its middle, so a row whose description wraps keeps its name, type and default beside the description's first line. The selection box and the row actions center on that first line, as they do in a stacked row. | | footer | ReactNode | no | | A row drawn inside the table's frame under the last row (and under the pagination bar when `paginated`): 44 px tall with its content centered, for a "Show all" control or a note. A hairline separates it from what sits above it: the one the last row draws already, or its own where nothing above it draws one. | | sortable | boolean \| undefined | no | false | Make every column sortable: pressing a header cycles ascending, descending, off. A column descriptor opts back out with `sortable: false`. Sorting is self-managed out of the box; control it via `sort`/`onSortChange`. | | sort | DataTableSort \| null \| undefined | no | | Controlled sort state (`null` = unsorted). Omit for uncontrolled use. | | defaultSort | DataTableSort \| null \| undefined | no | | Initial sort for uncontrolled use. | | onSortChange | ((sort: DataTableSort \| null) => void) \| undefined | no | | Fired with the next sort (or `null` when cleared) on header press. | | selectable | boolean \| undefined | no | false | Row selection: a leading checkbox column with a select-all header checkbox (indeterminate on a partial selection). Selection is keyed by `rowKey` (the row's original index when omitted) and self-managed out of the box; control it via `selectedKeys`/`onSelectionChange`. When no `onRowPress` is set, pressing anywhere on a row toggles it. | | selectedKeys | (string \| number)[] \| undefined | no | | Controlled selected row keys. Omit for uncontrolled use. | | defaultSelectedKeys | (string \| number)[] \| undefined | no | | Initial selected row keys for uncontrolled use. | | onSelectionChange | ((keys: Array) => void) \| undefined | no | | Fired with the next selected keys when a row or the select-all is toggled. | | paginated | boolean \| undefined | no | false | Page the rows through a built-in footer (the kit Pagination): a "Showing X-Y of N" range with Prev/Next, plus the selection count when `selectable`. Sorting applies before paging. Self-managed out of the box; control the page via `page`/`onPageChange`. | | pageSize | number \| undefined | no | | Rows per page (CONTROLLED; default 10, or the first of `pageSizes`). | | defaultPageSize | number \| undefined | no | | Initial rows per page for uncontrolled use. | | pageSizes | number[] \| undefined | no | | Selectable rows-per-page values. Supplying them upgrades the footer to the Pagination `withSize` variant (a "Rows per page" selector). | | onPageSizeChange | ((size: number) => void) \| undefined | no | | Fired with the next rows-per-page value when the footer selector changes it. | | page | number \| undefined | no | | Current page, 1-based (CONTROLLED). Omit for uncontrolled use. | | defaultPage | number \| undefined | no | | Initial page for uncontrolled use. | | onPageChange | ((page: number) => void) \| undefined | no | | Fired with the next 1-based page when the footer pagination navigates. | | loading | boolean \| undefined | no | false | Loading state: the body renders pulsing skeleton placeholder rows (one per would-be row on the page) instead of data. | | emptyMessage | ReactNode | no | | What to render, centered under the header, when `rows` is empty (and not `loading`). A string gets the muted caption type; any ReactNode renders as given (an `EmptyState`, a retry Button). Omitted = just the header row, as before. | | onRowPress | ((row: ReactNode[], index: number) => void) \| undefined | no | | When set, each data row is pressable, reporting the row data and its index in `rows`. A press anywhere on the row acts; the keyboard and screen-reader path is a button inside the row's FIRST column, named from the row's plain text cells (or, when it has none, by that cell's own content). The row itself stays a `row`, so keep your own controls out of the first column here: a control there would nest inside the activator. | | onRowEdit | ((rowIndex: number) => void) \| undefined | no | | Row editing: passing this adds a trailing actions column with a pencil button per row. Pressing the pencil opens the row's edit mode (its string cells become fields with a trailing Save/Cancel pair) and fires this with the row's original index in `rows`. Save reports the edited cells through `onRowCommit`; Cancel (or Escape in a field) closes without committing. | | onRowDelete | ((rowIndex: number) => void) \| undefined | no | | Row deletion: passing this adds a delete (bin) button to the trailing actions column. Delete is second-press confirm: the first press ARMS it (the bin turns destructive and its accessible name says confirm), a second press within the confirm window fires this with the row's original index, and pressing anywhere else in the table (or the window lapsing) disarms. | | inlineEdit | boolean \| undefined | no | false | Let string cells be edited in place: pressing one swaps it to a field. Enter or blur commits through `onCellCommit`; Escape restores the cell. Custom ReactNode cells (badges, links) are not editable. | | onCellCommit | ((rowIndex: number, colIndex: number, next: string) => void) \| undefined | no | | Fired when an `inlineEdit` cell commits (Enter or blur) with a changed value: the row's original index in `rows`, the column index, and the next text. Data stays yours: re-render with updated `rows` to keep the edit. | | onRowCommit | ((rowIndex: number, cells: ReactNode[]) => void) \| undefined | no | | Fired when a pencil-opened row edit is saved: the row's original index in `rows` and the full cells array with the edited strings in place (custom ReactNode cells pass through unchanged). | | virtualized | boolean \| undefined | no | false | Render the data rows through a windowed `FlatList` instead of mounting every row up front, for large tables. The header row stays fixed above it. Give the table a bounded height (via `style`, e.g. `{ maxHeight: 400 }`) so the body can scroll; without one it warns and renders eagerly. Default mounts all rows. | | rowKey | ((row: ReactNode[], index: number) => string \| number) \| undefined | no | | Stable key for a row, derived from the row data and its index in `rows`. Selection is keyed by it, and it keeps React row identity stable when rows reorder (sorting, live data) and a cell is a stateful custom ReactNode. When omitted, rows key off their original array index (safe for string/number cells and stateless custom cells; selection then tracks row POSITIONS, so supply a real key when the data itself can be reordered, inserted, or deleted). Keep this function pure; replace its identity when external data changes how keys are derived, so the cached selection summary updates. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # DepthChart Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/charts/depth-chart/depth-chart.md` Human reference: https://canvas.nannier.com/components/depth-chart ## Public imports ```tsx import { DepthChart } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. The order-book view: cumulative bid and ask step areas mirrored around the spread, bids in the success tone and asks in destructive, on a numeric price axis. ## Usage ```tsx ``` ## Variants ### Compact ```tsx ``` ### Spread guide `spread` draws a dashed guide midway between the best bid and the best ask, with its label in a pill at the guide's top. ```tsx ``` ## Do & Don't ### Both sides of the book **Do**: Pass the bids and the asks together; the two sides meet at the spread, the gap a trader reads first. ```tsx ``` **Don't**: Plot one side alone: with no asks the price axis covers only the bids, nothing marks the spread, and the Asks legend entry labels nothing. ```tsx ``` ## Supported props ### DepthChartProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | bids | DepthLevel[] | yes | | Bid levels (any order; sorted internally). | | asks | DepthLevel[] | yes | | Ask levels (any order; sorted internally). | | title | string \| undefined | no | | Optional heading shown above the plot. | | compact | boolean \| undefined | no | false | Density (omit for the default plot size). | | plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). | | hideLegend | boolean \| undefined | no | false | Furniture visibility. | | hideGrid | boolean \| undefined | no | false | Furniture visibility. | | hideAxes | boolean \| undefined | no | false | Furniture visibility. | | formatValue | ((v: number) => string) \| undefined | no | | Formats tick labels and accessible values (data formatting, not styling). | | spread | string \| undefined | no | | The spread's label ("spread 0.12%"): drawn as a dashed guide midway between the best bid and the best ask, with this label in a pill at its top. Omit for no guide. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # DescriptionList Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/description-lists/description-lists.md` Human reference: https://canvas.nannier.com/components/description-lists ## Public imports ```tsx import { DescriptionList } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Key-value pairs in stacked, two-column, or inline-edit layouts. Used for detail panels, settings, and profile views. Rows flagged `update` carry a working in-place editor: Update opens it, Enter or Save commits the new value and fires `onUpdate`, Escape or Cancel dismisses it. `muted` sets each text value as a note beside its term, in the small type and the muted ink, for a list that explains its terms (a key and its effect) rather than reporting data; a node value renders as given, and a platform skin whose value detail is already the secondary ink keeps its type. The rows axis (pick one; `ruled` wins over `divided`): `divided` draws a hairline under every row but the last, the two-column detail look. `ruled` draws the v4 hand-off's accessibility-card rows: a 1 px top rule on every row, the first included, so the list reads as a table that starts under its caption, each row padded 9 above and below with nothing beside, and in the two-column layout the term hugging its content over a 120 floor with 14 to the value. The density axis: `compact` tightens the ruled rows to the hand-off's semantics-card rows, each padded 8 above and below with 12 to the value, the two-column term a fixed 110 column, and the value at the top of its row (so a wrapping row of badges starts level with its term); the plain and divided rows have no compact step, so `compact` without `ruled` changes nothing. ## Usage ```tsx ``` ## Variants ### Card ```tsx ``` ### Inline Each value sits on the right of its term. A value too wide to fit beside its term (a long id with its Copy button in a narrow sidebar) wraps onto its own line under the term, still at the trailing edge, so the list never runs past its container. ```tsx ``` ### Rich values A row's value can compose real atoms: `status`/`badge` render Badges, `avatars` renders an overlapping AvatarGroup (`overflow` folds the rest into its "+N" chip), and `copyValue` appends a ghost Copy button that hands the string to `onCopy` (wire your clipboard there). ```tsx ``` ### Node values A `term` or `value` may be any node, as a DataTable cell may: a string or number takes the list's own type and the per-item forms (`badge`, `status`, `mono`, `copyValue`, `update`), and any other node renders as given in its place, here a row of Badges. A node value yields to its cell, so a wrapping Row wraps at the cell's width instead of running past the list. An `update` row needs a text value to edit. ```tsx button }, { term: "States", value: disabledbusyloadingpressedfocusedhovered }, { term: "Keyboard", value: "Enter and Space activate it" } ]} /> ``` ### Ruled Every row carries a top rule, the first included, under a caption the parent sets; the term hugs its content over a 120 floor. ```tsx Enter, value: "Activates" }, { term: Space, value: "Activates" }, { term: Tab, value: "Moves focus to the next control" } ]} /> ``` ### Compact ruled The semantics-card rows: 8 above and below, 12 to the value, the term a fixed 110 column and the value at the top of its row. ```tsx button }, { term: "States", value: disabledbusyloadingpressedfocusedhovered }, { term: "ARIA on web", value: aria-busyaria-disabled } ]} /> ``` ### Inline-edit ```tsx ``` ### Muted notes Each value explains its term: a key and what it does, the value in the small type and the muted ink. ```tsx Enter, value: "Activates" }, { term: Space, value: "Activates" }, { term: Tab, value: "Moves focus on" } ]} /> ``` ## Do & Don't ### Two-column **Do** — Fix the label column wide enough for the longest term so every value lines up on one edge. ```tsx ``` **Don't** — A too-narrow label column wraps the longest term and knocks the two columns out of alignment. ```tsx Client identifier clnt_01H2X8K9P3Q7VN4W6R5T0JYMZF Status Active ``` ### Inline-edit **Do** — Give every editable row a trailing Update affordance so the inline editor is discoverable; pressing it opens the editor in place, and Enter or Save commits. ```tsx ``` **Don't** — Editable rows that look identical to read-only ones give no hint a value can be changed. ```tsx ``` ### Stacked **Do** — Keep the label small, uppercase, and muted above a full-weight value so the data stays primary. ```tsx ``` **Don't** — Muting the value and bolding nothing inverts the hierarchy; the label outweighs the data it describes. ```tsx Full name Rachel Chen Email rachel.chen@example.com ``` ## Supported props ### DescriptionListProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | items | DescriptionListItem[] | yes | | | | title | string \| undefined | no | | Card header band (only shown when `card`): a title and optional subtitle. | | subtitle | string \| undefined | no | | Card header band (only shown when `card`): a title and optional subtitle. | | inline | boolean \| undefined | no | false | Layout (pick one; precedence inline > twoColumn > stacked). | | twoColumn | boolean \| undefined | no | false | Layout (pick one; precedence inline > twoColumn > stacked). | | stacked | boolean \| undefined | no | false | Layout (pick one; precedence inline > twoColumn > stacked). | | divided | boolean \| undefined | no | false | Rows (pick one; precedence ruled > divided): a hairline divider beneath every row but the last. | | ruled | boolean \| undefined | no | false | Rows: a 1 px top rule on every row, the first included, with the row padded 9 above and below and the term hugging its content (a 120 floor, 14 to the value), the v4 hand-off's accessibility-card rows. Wins over `divided`. | | compact | boolean \| undefined | no | false | Density: tightens the ruled rows to the v4 hand-off's semantics-card rows (Component.dc.html line 263): 8 above and below the text, 12 to the value, the term a fixed 110 column and the value at the top of its row. Only the ruled rows have a compact step; a plain or divided list is unchanged by it. | | muted | boolean \| undefined | no | false | The values as notes beside their terms: a text value in the small type and the muted ink (400 13/19) instead of the full-weight foreground data, for a list that explains each term (a key and its effect). A node value renders as given, and a platform skin whose value detail is already the secondary ink keeps its type. | | card | boolean \| undefined | no | false | Surface: wrap the list in a card surface. | | onUpdate | ((index: number, value: string) => void) \| undefined | no | | Fires when a row's inline editor commits (Enter or Save): the item index and the new value. | | onCopy | ((value: string) => void) \| undefined | no | | Fires with a row's `copyValue` when its "Copy" button is pressed. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Dialog Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/dialog/dialog.md` Human reference: https://canvas.nannier.com/components/dialog ## Public imports ```tsx import { Dialog } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A modal dialog: a centered panel over a dimmed backdrop, which glass on the web also blurs, with a title, an optional description, a body for real content like a form, and right-aligned actions. Use it for a focused task that warrants interrupting the page; reach for the Alert Dialog for a terse yes/no confirmation. On light glass, the description and currency prefix use the stronger foreground color. For custom body content, use the default `Typography` foreground for message copy over the dimmed background. Browser Escape and iOS accessibility escape cancel the foremost open child overlay before the dialog. Cancelling the dialog calls `onCancel` and requests `open=false`, including destructive dialogs and dialogs with custom children. A controlled owner may keep it open; that request never confirms an action. Pass `dismissible` to let a backdrop press follow the same cancel policy. It is off by default, so existing dialogs and destructive confirmations keep their current outside-press behavior. The scrim is excluded from keyboard navigation. Use `form overlay dismissible` for the original Argus application dialog: a persistent title and optional `description`, a close action, and scrolling body and `footer` content. `panelWidth` bounds the form (560 pixels by default), and the viewport keeps a twenty-pixel safe-area inset. The web treatment preserves Argus’s 28-pixel content inset, 17-pixel title, rounded card, shadow, and lightly frosted dim. A form with no trigger contributes no layout wrapper to its caller. Nested menus receive Escape before the dialog; focus stays in the open modal. For a conversation or other workspace with its own scroll region, use `form workspace overlay`. The dialog measures a safe-area-bounded viewport height; its header and optional `footer` stay fixed while `children` fill the remaining space. Put the workspace's scrollable content inside `children` and keep its composer or other controls as siblings of that scroll region. The workspace body does not add an outer scrollport. `workspaceHeader` adds content below the title and description in the fixed header, such as a step indicator. This mode is intended for an overlay form; ordinary forms keep their existing scrolling body. Add `compact` to a workspace when short content should determine the panel's height: `form workspace compact overlay`. The panel hugs the conversation and composer, then grows up to the same safe-area-bounded viewport maximum. A long conversation scrolls inside its own thread while the header, composer, and dialog footer remain visible. The default workspace still fills its available height; `compact` has no effect without `form workspace`. Add `embedded` to show an open dialog in a page as a specimen, the way a catalogue or a showcase does: it keeps its look, its dialog role and its labelling, but it is not modal. It does not take focus when it opens, trap Tab, return focus, set `aria-modal`, or cancel on Escape, native back or a backdrop press, so the page around it keeps its scroll position and its keyboard order. `embedded` is ignored with `trigger`, which always opens a live modal. ## Usage ```tsx ``` ## Variants ### Application form ```tsx Save changes}> ``` ### Banded with an icon `banded` draws a form dialog in three bands: the header (the `icon`, the title row with the close and any `workspaceHeader`) over a rule, the body, and the footer under a rule. `icon` draws its glyph in the accent's soft tile above the title. ```tsx } trigger="Publish" title="Publish release" description="Ship the changes to everyone in the workspace." footer={}> ``` ### Toolbar header `toolbar` with `banded` turns the header into a 44 point top bar like a desktop app window's: the title in the 14 medium face on one line, `headerAccessory` after it, and `headerActions` before the close action. The description is not drawn, so name the dialog's purpose in its title. With `workspace` the sidebar runs the full height beside the bar and the body. ```tsx {}} title="Plan pacman" headerAccessory={Autosaved} headerActions={}> The conversation fills the body. ``` ### Dismissible ```tsx ``` ### Extra small ```tsx ``` ### Small ```tsx ``` ### Medium ```tsx ``` ### Large ```tsx ``` ### Wide ```tsx ``` ### Destructive action ```tsx ``` ## Do & Don't ### When to use **Do** — Use the Dialog for a focused task with real content, like a short form. ```tsx Edit profile Update how your name and email appear to teammates. ``` **Don't** — A bare yes/no question does not need the roomier Dialog; that is what the Alert Dialog is for. ```tsx ``` ### Description **Do** — Keep the description to one supporting line; link out for the full policy. ```tsx ``` **Don't** — A multi-sentence policy dump in the description buries the task under reading. ```tsx ``` ### Body form **Do** — Keep the body to the few fields the task needs; send long forms to a full page. ```tsx Create project ``` **Don't** — A seven-field form crammed into a small dialog feels like a page stuffed into a popup. ```tsx Create project Name Key Description Lead Team Visibility Template ``` ### Actions **Do** — One primary plus quieter secondaries, right-aligned and labeled with their verb. ```tsx Unsaved changes You have edits that are not saved. ``` **Don't** — Three look-alike primary buttons give no signal about the default, safe choice. ```tsx Unsaved changes You have edits that are not saved. ``` ### Destructive action **Do** — Reserve the destructive tone for the irreversible action it names. ```tsx ``` **Don't** — A red button on a routine Save trains users to ignore the colour that should mean danger. ```tsx ``` ### Size **Do** — Size the dialog to its content: a single field belongs in xs or sm. ```tsx Rename ``` **Don't** — A 2xl panel around a single field leaves a vast empty expanse beside the input. ```tsx Rename Name ``` ## Supported props ### DialogProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | form | boolean \| undefined | no | false | Structured form: a named header and close action above scrolling body/footer content. | | workspace | boolean \| undefined | no | false | Give an overlay form a viewport-bounded body that fills between its fixed header and footer. | | compact | boolean \| undefined | no | false | Let a workspace hug short content and grow only up to its viewport bound. | | workspaceHeader | ReactNode | no | | Extra content in the fixed header of a workspace form, below its title and description. | | headerAccessory | ReactNode | no | | Content beside the form title, such as a workflow's progress status. | | workspaceSidebar | ReactNode | no | | A fixed side rail beside a workspace's scrolling body. | | footer | ReactNode | no | | Custom actions after the form body; fixed below the body in workspace mode. | | banded | boolean \| undefined | no | false | Draw the reference's shared workflow panel as three bands split by hairline rules: the header (title, `headerAccessory` and optional `workspaceHeader`), body, and footer. A `workspaceSidebar` stays beside the body. Embedded panels keep their opaque in-page surface without a veil or close action; live panels keep modal presentation. | | toolbar | boolean \| undefined | no | false | Banded only: the header is a 44 px top bar like a desktop app window's (the Claude desktop window's): the title in the 14 medium face, `headerAccessory` after it, `headerActions` before the close action. The description is not drawn. | | headerActions | ReactNode | no | | Actions at the end of a `toolbar` header, before the close action. | | icon | ReactNode | no | | A leading glyph for the panel, drawn as the accent's soft tone tile (`Emblem primary soft`) above the title. Pass an ``; the tile tints and sizes it. | | panelWidth | number \| undefined | no | | Maximum form panel width in pixels; defaults to 560. | | children | ReactNode | no | | | | title | string \| undefined | no | | Content (strings, for the children-less / data-driven case). | | description | string \| undefined | no | | Content (strings, for the children-less / data-driven case). | | withBody | boolean \| undefined | no | false | Body: render a short form (Amount + Reason fields) inside the panel for the data-driven case. | | trigger | string \| undefined | no | | Trigger button label. When set, the dialog renders the button and opens itself on press (uncontrolled). Omit when you drive `open` yourself. | | confirmLabel | string \| undefined | no | "Confirm" | Action labels. | | cancelLabel | string \| undefined | no | "Cancel" | Action labels. | | open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (the trigger opens it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (trigger press, confirm, cancel). | | destructive | boolean \| undefined | no | false | Confirm intent (default is a primary confirm). | | xs | boolean \| undefined | no | false | Width (pick one; default is the standard panel width). First-match precedence: xs, small, medium, large, wide. | | small | boolean \| undefined | no | false | Width (pick one; default is the standard panel width). First-match precedence: xs, small, medium, large, wide. | | medium | boolean \| undefined | no | false | Width (pick one; default is the standard panel width). First-match precedence: xs, small, medium, large, wide. | | large | boolean \| undefined | no | false | Width (pick one; default is the standard panel width). First-match precedence: xs, small, medium, large, wide. | | wide | boolean \| undefined | no | false | Width (pick one; default is the standard panel width). First-match precedence: xs, small, medium, large, wide. | | onConfirm | (() => void) \| undefined | no | | Action handlers. | | onCancel | (() => void) \| undefined | no | | Action handlers. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | overlay | boolean \| undefined | no | false | Presents the dialog OVER the page instead of inline where it is mounted. The default is a contained overlay: a scrim with its own height, sitting in normal flow, which is what the docs catalogue needs to show a dialog inside an example stage. An application confirming a destructive action needs the opposite, and the inline form is actively wrong there: it appends a scrim wherever the component happens to sit, leaves the page behind scrollable and clickable, and still claims `aria-modal`. With `overlay`, the dialog teleports into the nearest `OverlayProvider` and fills it, so `aria-modal` becomes true rather than aspirational. Mount an `OverlayProvider` at the app root for this to cover the screen. | | dismissible | boolean \| undefined | no | false | Cancel when the backdrop is pressed. Off by default, including destructive dialogs. | | embedded | boolean \| undefined | no | false | Show the open dialog as a non-modal specimen in the page, for a catalogue or a showcase: it keeps its look, its dialog role and its labelling, but does not take focus when it opens, trap Tab, return focus, claim `aria-modal`, or cancel on Escape, native back or a backdrop press. Ignored with `trigger`, which always opens a live modal. | | accessibilityLabel | string \| undefined | no | | The dialog's accessible name, for the case where `children` supply the panel body. Children REPLACE the built-in title/description, so there is no element left for `aria-labelledby` to point at, and the dialog would otherwise be announced with no name at all. That is merely poor for a `dialog` and invalid for the `alertdialog` a destructive confirm renders. | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Divider Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/divider/divider.md` Human reference: https://canvas.nannier.com/components/divider ## Public imports ```tsx import { Divider } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Horizontal, vertical, with label, with action. ## Usage A divider spans the width of its parent, so give it a bounded container. ```tsx Profile Account ``` ## Variants ### Vertical ```tsx Edit Delete ``` ### Inset A vertical rule of its own short length, centred in its row with a little space on each side: the separator between groups in a toolbar or a floating bar, where a rule the row's full height would read as a wall. ```tsx ``` ### Emphasis The hairline by default; `strong` for a firmer line, `soft` for a quieter one. ```tsx ``` ### Label ```tsx Or continue with ``` ### Action ```tsx ``` ## Do & Don't ### Plain **Do** — Click a row: group with spacing and reserve a divider for a real break like Sign out. ```tsx Profile Account Notifications Sign out ``` **Don't** — Click a row: a divider between every one is noise that competes with the content. ```tsx Profile Account Notifications Billing ``` ### With label **Do** — Click a provider: keep the label to a few words and let the buttons carry the options. ```tsx or continue with ``` **Don't** — Click Sign in: a full sentence in the label divider buries the choice. ```tsx or continue with one of your previously linked third-party accounts ``` ### With action **Do** — Click Show more: the button toggles its label and reveals the rest. ```tsx Logged in from 2 new devices · 3 more entries ``` **Don't** — Click the button: an action divider that does nothing is just decoration. ```tsx ``` ### Vertical **Do** — Click an action: the vertical rule separates inline actions in a row. ```tsx Edit Delete Share ``` **Don't** — Click an action: a vertical rule between stacked items reads as a glitch. ```tsx Edit Delete ``` ## Supported props ### DividerProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | Optional middle content. With children, a horizontal divider renders a flanking-line + centered-label row (the "with label" / "with action" patterns). A string renders as the uppercase mono caption label; arbitrary nodes (e.g. a button) render as-is for the action pattern. Ignored when `vertical`. | | horizontal | boolean \| undefined | no | false | Orientation (pick one; default is horizontal). | | vertical | boolean \| undefined | no | false | Orientation (pick one; default is horizontal). | | soft | boolean \| undefined | no | false | A quieter rule: the hairline at 60% of its strength. | | strong | boolean \| undefined | no | false | A firmer rule: the `field-border` line, twice the hairline's strength. | | inset | boolean \| undefined | no | false | Vertical only: a short rule (18 px) centred in its row with 4 px kept on each side, instead of a rule that stretches to the row's height. The separator between groups in a toolbar or a floating bar. A horizontal divider ignores it. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Drag & drop Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/drag-drop/drag-drop.md` Human reference: https://canvas.nannier.com/components/drag-drop ## Public imports ```tsx import { DragDropProvider, DragHandle, Draggable, DropZone } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A reusable drag-and-drop context: wrap a surface in a `DragDropProvider`, mark droppable regions with `DropZone`, and make items draggable with `Draggable` plus a `DragHandle` grip. Items lift into a floating ghost and reorder within or across zones, position-aware. It runs on iOS, Android, and the web from one PanResponder + Animated code path, and the grip is keyboard- and screen-reader-operable: press Space to grab, the arrow keys to move, Space to drop, Escape to cancel. The reading direction is respected throughout. In a right-to-left locale a `horizontal` zone lays its items out right to left, so the first position is the rightmost one, the drop indicator meets the row's right edge, and the two horizontal arrows swap roles (Left Arrow moves forward, Right Arrow back, per the WAI-ARIA practices). Vertical lists and the up/down arrows read the same way in both directions. ## Usage ```tsx {(cards, setCards) => ( setCards(applyDrop(cards, e))}> {cards.map((c) => ( {c.title} ))} )} ``` ## Variants ### Bordered zone Pass `bordered` to a `DropZone` to draw it as a visible drop well: a dashed, sunken surface with the zone's `label` as an eyebrow over its items. While a drag is over it, its edge and fill take the accent. Without `bordered` the zone is only a drop target around its children (a Board lane or a StackedList draws its own surface), and a drag over it lays the same accent edge and wash across it. ```tsx Tokens ``` ### Locked item Pass `disabled` to a `Draggable` to pin it in place: its grip dims and becomes inert while the rest of the list stays draggable. ```tsx Design review Locked task ``` ### Between columns A `DropZone` per column; dragging a card moves it into another column at the drop position. The shared list is a flat array with a `zone` field, and `applyDrop` re-homes the card. ```tsx {(cards, setCards) => ( {["todo", "doing"].map((zone) => ( setCards(applyDrop(cards, e))}> {cards.filter((c) => c.zone === zone).map((c) => ( {c.title} ))} ))} )} ``` ## Do & Don't ### Grip and fallback **Do** — Give each item a labeled `DragHandle` and keep a non-drag way to move it. The grip is a keyboard tab stop that grabs on Space and moves with the arrows; a `RowMenu` "Move to" beside it is a good extra path for pointer and assistive-tech users. ```tsx Design review ``` **Don't** — Don't make a bare card the only drag surface with no grip and no alternative: whole-card dragging fights taps and scrolling, and it leaves keyboard and screen-reader users with no way to move the item at all. ```tsx {}}> Design review (drag the whole card?) ``` ## Supported props ### DragDropProviderProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | yes | | | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. The provider wraps the whole draggable surface and hosts the floating drag ghost. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | ### DropZoneProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | id | string | yes | | Stable id for this drop target, used in the drop event and for keyboard navigation. | | label | string | yes | | Accessible name announced when a drag hovers or drops here (e.g. the column title). | | onDrop | ((e: DropEvent) => void) \| undefined | no | | Fired on THIS zone when an item is dropped into it (from any zone, including itself). | | horizontal | boolean \| undefined | no | false | Lay the items out in a row so the insertion axis is horizontal (default is a vertical column). | | disabled | boolean \| undefined | no | false | Refuse drops: the zone is skipped by hit-testing and the keyboard cursor, and never highlights. | | bordered | boolean \| undefined | no | false | Draw the zone as a visible drop well: a dashed sunken surface with its `label` as an eyebrow over the items. Without it the zone is only a drop target around its children. | | children | ReactNode | no | | | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | ### DraggableProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | id | string | yes | | Stable id for this item (unique within the provider). | | data | T | yes | | Arbitrary payload handed back in the drop event. | | label | string \| undefined | no | | Accessible name of the item, used in drag announcements (falls back to `id`). | | disabled | boolean \| undefined | no | false | Disable dragging: the grip inside becomes inert. | | preview | ReactNode | no | | Custom floating drag preview; defaults to the item's own `children`. | | children | ReactNode | no | | | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | ### DragHandleProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | label | string \| undefined | no | | Accessible name for the grip (e.g. "Reorder Rotate webhook secrets"). | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Drawer Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/organisms/drawer/drawer.md` Human reference: https://canvas.nannier.com/components/drawer ## Public imports ```tsx import { Drawer } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A full-screen panel that slides in from an edge: a navigation drawer, a mobile menu, or a bottom action sheet. Built on React Native's Modal, so it floats over the whole app on iOS, Android, and the web, over a dimmed scrim that glass on the web also blurs. For a small contextual menu, reach for Dropdown or RowMenu instead. Drawer provides an overlay host inside its own window. Dropdown, Select, and other anchored children render above the panel without being clipped by its corners; no additional OverlayProvider is needed inside the drawer. A tap outside an open child menu dismisses that menu, and Escape dismisses the child before the drawer. Nested drawers each keep their overlays in their own window. On iOS, VoiceOver's accessibility escape gesture requests dismissal of the foremost open child before the drawer. Android system back uses the same child ownership. A controlled child that stays open continues to own later requests; one request never falls through to its parent. The scrim remains a pointer target, and the built-in trigger opens the drawer. Sidebar drill-down navigation has its own back behavior; accessibility escape dismisses the overlay rather than navigating a sidebar level. Focus restoration remains platform-dependent. Give a controlled drawer an `accessibilityLabel` when it has no built-in `trigger`. A drawer with a trigger uses the trigger label as its modal name. `header` and `footer` pin content around an independently scrolling body. Its application inset is 20 pixels vertically and 24 horizontally, with 22 between children. `navigation` gives that body no inset or gap and fills its height. `plain` preserves the original square Argus side panel and lightly frosted dim on web; native skins retain their platform panel. `instant` presents and dismisses without a slide or fade. Existing drawers retain their animated default. ## Usage ```tsx Menu ``` ## Variants ### Terminal sidebar `terminal` opens a solid execution screen at the right edge, using the active light or dark theme even under glass. All four corners round to 12 pixels; the screen sits 12 pixels inside wider windows and 8 pixels inside windows up to 640 pixels wide. Its width is capped to the available viewport. Header and footer remain pinned around the scrolling body; `navigation` removes the body inset. `terminal` wins over edge flags and `plain`. The existing slide, Escape, focus and Reduced Motion behavior remain in effect (`instant` still skips animation). `onClosed` fires once after a completed dismissal, allowing a parent to retain the drawer through its exit animation; an initially closed drawer does not fire it. ```tsx Execution}> ``` ### Application panel ```tsx Scout} footer={}> Recent work and trace details. ``` ### Right ```tsx Details A full-height panel on the right. ``` ### Open in place `embedded` draws the drawer open inside its parent (the panel at its edge over the parent's own dim) for a specimen or a preview: it is a labelled region, not a modal, so it traps no focus, takes no Escape or hardware back, and its dim passes touches through. It needs no trigger and fills the parent it is given. ```tsx Invite people}> ``` ### Bottom sheet ```tsx Actions A sheet that rises from the bottom edge. ``` ### Top sheet ```tsx What's new A sheet that drops down from the top edge. ``` ## Do & Don't ### Right tool for the job **Do** — Use a Drawer for primary navigation or a full sheet of actions that should take over the screen on a phone. ```tsx Navigation Home Components ``` **Don't** — Use a full-screen Drawer for a small contextual menu; covering the whole screen for two choices is disorienting. Use a Dropdown or RowMenu anchored to the trigger. ```tsx Rename Delete ``` ## Supported props ### DrawerProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | header | ReactNode | no | | Pinned header above the independently scrolling body. | | footer | ReactNode | no | | Pinned actions below the independently scrolling body. | | navigation | boolean \| undefined | no | false | Navigation body: no content inset or inter-item gap; fills the available height. | | terminal | boolean \| undefined | no | false | Solid, rounded execution sidebar inset from the viewport. Overrides edge and plain; width remains configurable. | | plain | boolean \| undefined | no | false | Square web side panel and the original lightly frosted application dim. | | instant | boolean \| undefined | no | false | Present and dismiss immediately, preserving the original application interaction. | | embedded | boolean \| undefined | no | false | Show the open drawer as a non-modal specimen inside its parent, for a catalogue or a showcase: the panel docks against its edge of the parent (which gives it its height) over the dim, with no Modal, no slide, no focus trap, and no close on Escape, native back or a tap on the dim. It keeps its labelling as a named region. Ignored with `trigger`, which always opens a live drawer. | | children | ReactNode | no | | Panel content. | | open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (a `trigger` opens it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (trigger press, scrim tap, back/escape). | | onClosed | (() => void) \| undefined | no | | Called once after an opened drawer finishes dismissal; never called for its initial closed state. | | trigger | string \| undefined | no | | Label for an optional outline trigger button. When set, the drawer renders the button and opens itself on press (uncontrolled). Omit when you drive `open` yourself. | | left | boolean \| undefined | no | false | A full-height panel on the left edge (default). | | right | boolean \| undefined | no | false | A full-height panel on the right edge. | | bottom | boolean \| undefined | no | false | A sheet spanning the width, rising from the bottom. | | top | boolean \| undefined | no | false | A sheet spanning the width, dropping from the top. | | width | number \| undefined | no | 300 | Width of a side drawer in px (default 300). Ignored for the bottom/top sheet. | | accessibilityLabel | string \| undefined | no | | Accessible name for the modal surface. Required when the drawer has no trigger whose label can name it. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Outer layout composition for the panel surface, never a restyle hook. (Device safe-area insets are applied automatically.) | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Dropdown Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/dropdown/dropdown.md` Human reference: https://canvas.nannier.com/components/dropdown ## Public imports ```tsx import { Dropdown } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Floating menus triggered by a button: actions, options, navigation. On the web the menu is the v4 panel: a card with a hairline and a soft shadow, 36-pixel rows with quiet icons that take a raised wash under the pointer at once, a mono uppercase eyebrow over a section, mono shortcuts in their own column, destructive rows in the destructive ink, and a disabled row in the muted ink rather than faded. iOS and Android keep their platform menus (the iOS pull-down menu, the Material 3 menu) in the theme's colours. In glass mode the menu is a dense-layer glass card under its trigger (or above it when it fits there); the trigger keeps its place, its press and its accessible name and state, and a selection commits and closes the menu the moment a row is pressed. AvatarMenu and the collapsed Navbar menu are built on Dropdown. Solid mode paints the skin's own menu. `compact` tightens the web rows to 30 pixels and lets the menu scroll inside `maxHeight`; with `steadyPress` a press does not tint the row. Selected rows take the action ink. Items can carry a stored `value`, `detail`, `description`, `selected` state, and a `sectionBefore` heading. `keepOpen` leaves refresh-style commands open. A `note` adds a muted footer. `menuWidth` and `maxHeight` bound the scrolling menu. Pass `anchorRef` to use an existing trigger without adding another control or layout wrapper; `accessibilityLabel` names the menu independently of its heading. Checked choices use browser menu-radio semantics and the native radio trait. ## Usage ```tsx ``` ## Variants ### Project menu ```tsx ``` ### Disabled trigger ```tsx ``` ### Disabled item ```tsx ``` ### Destructive item ```tsx ``` ### Section label ```tsx ``` ### Identity header ```tsx ``` ### Keyboard shortcuts ```tsx ``` ### Choices and submenus A `selected` item is the menu's current choice: on the web it takes the accent's soft wash and a leading check, and the menu keeps the check's column on its other rows. A `submenu` item draws a trailing chevron, the hint that its `onSelect` opens another menu. ```tsx ``` ### Custom trigger ```tsx ``` ### End alignment ```tsx ``` ### Inline specimen `inline` draws the menu open and in flow under the trigger instead of floating it over the page: a specimen for a docs page or an empty state. The trigger reads as expanded and its press is inert, a row still fires `onSelect`, and nothing dismisses the card. ```tsx ``` ## Do & Don't ### Trigger **Do** — Click Actions to open; click outside to dismiss. ```tsx ``` **Don't** — Always open: it clutters the page and there's no way to dismiss it. ```tsx ``` ### Sectioning **Do** — Click an item: group related actions under labels with a separator. ```tsx ``` **Don't** — Click an item: a long, flat menu of eight actions is hard to scan. ```tsx ``` ### Leading icons **Do** — Click an item: give every row a leading icon so labels share one start column. ```tsx ``` **Don't** — Click an item: icons on some rows but not others leave labels misaligned and the column ragged. ```tsx ``` ### Keyboard shortcuts **Do** — Click an item: push shortcuts to a muted, right-aligned column so the eye can scan them. ```tsx ``` **Don't** — Click an item: hints inline after the label crowd the text and never line up into a readable column. ```tsx ``` ### Disabled item **Do** — Click Archive: nothing happens; a real disabled item doesn't respond. ```tsx ``` **Don't** — Click Archive: it looks disabled but still fires, a greyed item that works is a trap. ```tsx [{ flexDirection: "row", alignItems: "center", gap: 10, borderRadius: 8, paddingHorizontal: 10, paddingVertical: 8 }, pressed ? { backgroundColor: tokens.accent } : null]}> Edit [{ flexDirection: "row", alignItems: "center", gap: 10, borderRadius: 8, paddingHorizontal: 10, paddingVertical: 8 }, pressed ? { backgroundColor: tokens.accent } : null]}> Archive [{ flexDirection: "row", alignItems: "center", gap: 10, borderRadius: 8, paddingHorizontal: 10, paddingVertical: 8 }, pressed ? { backgroundColor: tokens.accent } : null]}> Duplicate ``` ### Destructive item **Do** — Click an item: separate destructive actions with a divider, color them, and place them last. ```tsx ``` **Don't** — Click an item: a destructive action wedged between routine ones invites a costly misclick. ```tsx ``` ## Supported props ### DropdownProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | anchorRef | RefObject \| undefined | no | | Use an existing trigger without adding a trigger or layout wrapper. | | accessibilityLabel | string \| undefined | no | | Explicit accessible name for the menu. | | compact | boolean \| undefined | no | false | Exact compact web application menu frame and row metrics. | | steadyPress | boolean \| undefined | no | false | Preserve resting row fill and opacity during a press. | | menuWidth | number \| undefined | no | | Width of the floating menu, clamped inside the overlay outlet. | | maxHeight | number \| undefined | no | | Height cap for the compact menu's scrolling contents. | | note | ReactNode | no | | Muted footnote separated from menu commands by a hairline. | | trigger | string \| undefined | no | | Label for the default outline trigger button. Omit when supplying a custom trigger via `children`. | | children | ReactNode | no | | A custom trigger rendered in place of the default outline button, e.g. an avatar account chip. It is wrapped in a Pressable that toggles the menu; the menu itself still comes from `items`. | | triggerLabel | string \| undefined | no | | The accessible name for a CUSTOM trigger (`children`). Without it the button is named from its own contents, which runs the trigger's text together into one unpunctuated string ("Rachel Chenrachel@example.com") and repeats any label a nested control already carries. Ignored by the default `trigger` button, which is named by its own text. | | label | string \| undefined | no | | Optional muted section heading rendered above the rows (e.g. "Actions"). | | title | string \| undefined | no | | Identity header title, rendered in the popover foreground ABOVE everything else in the menu (an account name over its email, say). Pair it with `description`; passing neither omits the header block entirely. | | description | string \| undefined | no | | Identity header second line, muted, under `title`. | | items | DropdownItem[] | yes | | The menu rows, top to bottom. | | alignEnd | boolean \| undefined | no | false | Align the menu's TRAILING edge with the trigger's trailing edge instead of the leading edge (the default). For a trigger parked at the end of a bar, where a leading-aligned menu would run off the surface. Logical, so a right-to-left locale mirrors it. | | disabled | boolean \| undefined | no | false | Dimmed, inert trigger: the menu never opens and the press is a no-op. | | inline | boolean \| undefined | no | false | Draw the menu open and in flow under the trigger instead of floating it over the page: a specimen of the menu for a docs page or an empty state. The trigger reads as expanded and the press is inert, a row still fires `onSelect`, and nothing dismisses the card, so `open` and `onOpenChange` have no effect. `alignEnd` lines the card up with the trigger's trailing edge, as it does for the floating card. | | open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (the trigger opens/closes it). | | onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (trigger press, select, etc.). | | onSelect | ((item: DropdownItem, index: number) => void) \| undefined | no | | Fired with the selected item and its index when a row is pressed. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Emblem Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/atoms/emblem/emblem.md` Human reference: https://canvas.nannier.com/components/emblem ## Public imports ```tsx import { Emblem } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A tinted rounded square (or circle) that holds a single `Icon` or a short monogram, the recurring "icon on a soft background" used in cards, media objects, empty states, and feeds. Emblem owns the surface and the icon color: pick a tone and it tints the square and paints the glyph to match, so a call site never hand-composes `borderRadius` + `backgroundColor` to build an icon background. ## Usage ```tsx ``` ## Variants ### Tones ```tsx ``` The precedence is `primary` > `destructive` > `success` > `warning` > `cyan` > `secondary` > `muted` (the default). ### Cyan and secondary `cyan` mixes the tile from the fixed cyan series hue (`chart-2`, the same whatever the palette), its glyph in the hue's solved ink. `secondary` is the solid neutral tile: the `secondary` fill inside the hairline, the glyph in the second ink, with no lit edge. ```tsx ``` ### Sizes ```tsx ``` ### Monogram ```tsx ``` ### Circle ```tsx ``` ### Soft `soft` is the plain tone tile a dialog draws over its title: the tone's own soft wash, with no ring and no lit edge, at a 12 px corner. It composes with every tone and size. ```tsx ``` ## Do & Don't ### Surface **Do**: Use Emblem so the tint and icon color stay in sync from one tone prop. ```tsx ``` **Don't**: Hand-compose the tinted square with raw `borderRadius`, `backgroundColor`, and padding. ```tsx ``` ## Supported props ### EmblemProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | children | ReactNode | no | | A single `` element; Emblem tints it to match the tone. | | label | string \| undefined | no | | A monogram letter (or two) instead of an icon, painted in the tone color. | | primary | boolean \| undefined | no | false | Tone (pick one; default `muted`). Sets the tinted surface and the icon color. Precedence: primary > destructive > success > warning > cyan > secondary > muted. | | destructive | boolean \| undefined | no | false | Tone (pick one; default `muted`). Sets the tinted surface and the icon color. Precedence: primary > destructive > success > warning > cyan > secondary > muted. | | success | boolean \| undefined | no | false | Tone (pick one; default `muted`). Sets the tinted surface and the icon color. Precedence: primary > destructive > success > warning > cyan > secondary > muted. | | warning | boolean \| undefined | no | false | Tone (pick one; default `muted`). Sets the tinted surface and the icon color. Precedence: primary > destructive > success > warning > cyan > secondary > muted. | | cyan | boolean \| undefined | no | false | The fixed cyan hue (the hand-off's `--cyan`, the `chart-2` series): its wash and ring mixed from it, the glyph in the hue's ink, solved to 4.5:1 per scheme (Typography's `cyan`). | | secondary | boolean \| undefined | no | false | The solid neutral tile: the `secondary` fill inside the `border` hairline, the glyph in the second ink (`muted-foreground`), with no lit top edge (the hand-off's neutral tile). | | muted | boolean \| undefined | no | false | | | small | boolean \| undefined | no | false | Size (pick one; default medium). | | large | boolean \| undefined | no | false | Size (pick one; default medium). | | circle | boolean \| undefined | no | false | Circle instead of the default rounded square. | | soft | boolean \| undefined | no | false | The plain soft tile: the tone's own soft wash (`primary-soft`, `destructive-soft`, ...) with no ring and no lit top edge, at the soft tile's own corner. The tone tile a dialog draws over its title (the v4 hand-off's AlertDialog renderer, `--ion-soft` / `--bad-soft`). Composes with the tone and size axes. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | For layout composition only (not styling): the tint, radius, and size come from props. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # EmptyState Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/empty-state/empty-state.md` Human reference: https://canvas.nannier.com/components/empty-state ## Public imports ```tsx import { EmptyState } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Centered, calm, never blame the user. Always tell them what could be here, and ideally how to get there. The column is padded 40 by 24 at an 18px corner with a 12px rhythm: a 52px glyph tile in the accent's soft wash, a 19px display-face title, a muted 14px description capped at 320px, and a secondary action. `bordered` draws a dashed edge, `elevated` lifts it onto a shaded card, and `compact` tightens the padding, rhythm, tile and title for a table cell. ## Usage ```tsx } title="No results found" description="Try adjusting your search filters." /> ``` ## Variants ### Bordered ```tsx } title="No users" description="Invite your first team member." /> ``` ### Action ```tsx } title="No files" description="Upload or drag files here." actionLabel="Upload files" /> ``` ### Compact ```tsx } title="No activity" description="Events will appear as they happen." /> ``` ### Success ```tsx } title="No errors" description="Everything is running smoothly." /> ``` ### Inside a table The empty state's action resolves the emptiness it describes: pressing "Clear filters" drops the filter, so the rows return and the empty state leaves with it. (`Stateful` is a docs-only helper standing in for your own state; in an app you would hold the filter with `useState` and clear it in `onAction`.) ```tsx {(filtered, setFiltered) => ( } title="No results found" description="Try adjusting your search filters." actionLabel="Clear filters" onAction={() => setFiltered(false)} /> } /> )} ``` ## Do & Don't ### search **Do** — State the result neutrally and point at the lever the user can pull (filters, query). ```tsx } title="No results found" description="Try adjusting your search filters." /> ``` **Don't** — Blaming the searcher for an empty result set makes a normal outcome feel like a mistake. ```tsx } title="Nothing matched" description="You searched for the wrong thing. Check your spelling and try again." /> ``` ### users **Do** — When the user can fix it, give them the one action that does (Invite, Create). ```tsx } title="No users" description="Invite your first team member." actionLabel="Invite member" /> ``` **Don't** — A dead-end that only restates the emptiness leaves the user with nowhere to go. ```tsx } title="No users" description="There are no team members in this workspace." /> ``` ### files **Do** — Name the gesture that fills the empty space so the next step is obvious. ```tsx } title="No files" description="Upload or drag files here." /> ``` **Don't** — Generic 'nothing here' copy never tells the user how files would get into the folder. ```tsx } title="Nothing here" description="This folder is empty." /> ``` ### activity **Do** — Keep a routinely-empty state calm: the quiet accent tile, reassuring copy, no urgency. ```tsx } title="No activity" description="Events will appear as they happen." /> ``` **Don't** — An alarming red icon turns a calm, expected empty feed into a false error. ```tsx } title="No activity" description="Events will appear as they happen." /> ``` ### notifications **Do** — Celebrate a cleared queue: 'All caught up' reads as success, not absence. ```tsx } title="All caught up" description="No new notifications." /> ``` **Don't** — Apologetic, sad-toned copy frames an inbox-zero win as a letdown. ```tsx } title="Nothing to see" description="You have no notifications. Sorry, it's quiet in here." /> ``` ### errors **Do** — Signal the good news with a green check: zero errors is a passing state, not an empty one. ```tsx } title="No errors" description="Everything is running smoothly." /> ``` **Don't** — A grey 'nothing found' disc reads as a failed query, not as a healthy, error-free system. ```tsx } title="No errors" description="Nothing was found." /> ``` ### all-clear **Do** — Reserve the green all-clear for genuinely empty queues: nothing locked, nothing waiting. ```tsx } title="All clear" description="No locked accounts or pending reviews." /> ``` **Don't** — A green all-clear over copy that admits work is pending hides the action the user must take. ```tsx } title="No pending reviews" description="3 accounts are locked and waiting on you." /> ``` ## Supported props ### EmptyStateProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | icon | string \| ReactElement> \| undefined | no | | The glyph shown in the disc: a monochrome `` element (cloned with the tone's tint and the disc's glyph size, so it follows `success`/default like the disc wash does; a semantic color boolean on the element still wins) or an emoji/character string. | | title | string \| undefined | no | | Short, neutral headline naming the empty result. | | description | string \| undefined | no | | One reassuring line, ideally pointing at the next step. | | actionLabel | string \| undefined | no | | When set, renders a secondary action button below the description (small when `compact`). | | onAction | (() => void) \| undefined | no | | Called when the action button is pressed. | | success | boolean \| undefined | no | false | Tone axis (pick one; default keeps the disc muted). | | bordered | boolean \| undefined | no | false | Container axis: wrap the column in a bordered card. | | elevated | boolean \| undefined | no | false | A shaded card: the `card` fill under the medium shade. | | compact | boolean \| undefined | no | false | Density axis: tightens the padding, rhythm, tile and title. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Feed Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/feeds/feeds.md` Human reference: https://canvas.nannier.com/components/feeds ## Public imports ```tsx import { Feed } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. Vertical activity streams with icons and timestamps. Used for audit logs, change history, and notification lists. The events are a list, as in Tailwind UI, and each event is one of its items: a screen reader announces the list with its number of events and moves through it event by event. A pressable event is a button inside its item. The `label` prop names the list (for example "Recent activity"). A `virtualized` feed with a bounded height scrolls its events in a list of its own. On the web, while those events overflow, that list is a keyboard tab stop (before the rows themselves when `onItemPress` makes them buttons), the keyboard can scroll it, and while it has keyboard focus the feed's card draws the theme's focus ring. Only the events near its viewport are mounted, and each says where it sits in the whole feed, so a screen reader still counts every event. Give a windowed feed a `label`: focused, a list with no name is named in Chromium from the text of every event it has rendered, so the kit warns in development when it has none. `activity` provides the original Argus time gutter, sentence, and dividing rule. `trace` provides a colored marker, optional halo, optional numeric time gutter, and inline category label. These unframed rows inherit their owning panel. Pass rich `content`, marker `color` and `halo`, and optional `kind` on each item. ## Usage ```tsx ``` ## Variants ### Activity ```tsx ``` ### Trace ```tsx ``` ### Icon lead An item may name a kit glyph (`{ icon }`) for the connector node, which suits an audit or automation stream where a glyph reads the kind of event faster than a dot. Node content resolves in one order: the glyph on its disc, then a dot, the first event's in the accent. The avatar lead ignores `icon` and keeps leading with the person's 28px avatar on the same connector. ```tsx ``` ### Avatar ```tsx ``` ## Do & Don't ### Connector **Do** — Drop the connector on the last item so the line terminates cleanly at the final event. ```tsx ``` **Don't** — Running the connector line past the final event leaves a dangling tail pointing at nothing. ```tsx RC Rachel Chen approved the request 2 hours ago SY System created the project 3 days ago ``` ### Avatar **Do** — Lead with the person's avatar, bold the actor, and keep the action plus a relative timestamp muted. ```tsx ``` **Don't** — An anonymous avatar with no actor name and no timestamp strips the row of the who and the when. ```tsx ? Pushed 3 commits ``` ## Supported props ### FeedProps | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | items | FeedItem[] \| undefined | no | | Events to render, top to bottom. | | activity | boolean \| undefined | no | false | Lead axis (pick one; default is the connector node + vertical line). | | trace | boolean \| undefined | no | false | Lead axis (pick one; default is the connector node + vertical line). | | connector | boolean \| undefined | no | false | Lead axis (pick one; default is the connector node + vertical line). | | avatar | boolean \| undefined | no | false | Lead axis (pick one; default is the connector node + vertical line). | | compact | boolean \| undefined | no | false | Density modifier: tightens the row padding and connector spacing. | | onItemPress | ((index: number) => void) \| undefined | no | | When set, each event row is pressable, reporting the row index. | | label | string \| undefined | no | | The feed's accessible name (e.g. "Recent activity"): a screen reader announces it with the list of events. A `virtualized` feed needs one: on the web its scroller is a keyboard stop while the events overflow, and Chromium names an unnamed stop from the text of every event it has rendered. | | virtualized | boolean \| undefined | no | false | Render the rows through a windowed `FlatList` instead of mounting every row up front, for large lists. Give the list a bounded height (via `style`, e.g. `{ maxHeight: 400 }`) so it can scroll; without one it warns and renders eagerly anyway. Default (omitted) mounts all rows, unchanged. On the web, while the rows overflow, the windowed list is a keyboard tab stop the keyboard can scroll, and the feed's card draws the focus ring while it has keyboard focus. | | testID | string \| undefined | no | | E2E hook forwarded to the root element. | | style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. | See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks. --- # Field Package: @nannier/canvas Version: 4.4.0 Status: source-snapshot (not proof of an npm release) Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783 Source: `src/molecules/field/field.md` Human reference: https://canvas.nannier.com/components/field ## Public imports ```tsx import { Field } from "@nannier/canvas"; ``` The following examples are source excerpts. Supply application data and handlers; snippets using `tokens` read `const { tokens } = useTheme()` inside a component. Import React hooks from React. Intentional “Don't” examples illustrate unsupported patterns. A form row: a label, the control, and one message line under it. Field exists for the part no control owns on its own: every field family already owns its label, but nothing else in the kit renders helper or error text, so that message is what Field adds. When the row wraps a single field-family control that has no label of its own, Field hands the label down to it rather than drawing one alongside, so each platform still places it its own way: a static title above on web and iOS, the floating in-container label on Android, and `required` goes down with it. Over any other control (a Switch, a group) Field draws the label above with its star, and on iOS and Android the label says "required" in its own name. Set `error` and it replaces `helper` in the same slot, so the row never changes height and nothing below it jumps; on the web an error leads with a small alert glyph. ## Usage ```tsx ``` ## Variants ### Error ```tsx ``` ### Email address ```tsx ``` ### Password ```tsx ``` ### Username ```tsx ``` ### Phone number ```tsx ``` ### Date of birth ```tsx ``` ### Amount ```tsx ``` ### Helper text ```tsx ``` ### Wrapping a control that keeps its own label ```tsx Release activity ``` ### In a form ```tsx