# 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
`` and layout components such as `Container`, `Row`,
`Column` and `Grid`. Read each component's props before using it, preserve
accessible names and keyboard behavior, and test each target platform. Typechecks,
web bundles and native runtime checks provide different evidence.
## Package and source terms
Canvas ships as one npm package. The npm package's LICENSE covers its packaged
files, including the consumer skill and recipes. Repository source retains its
all-rights-reserved terms; a public source preview is not a separate source grant.
Use the license and copyright notice included with the installed tarball for
the distributed package's terms. The public onboarding and API references linked
above do not require access to the private source repository.
---
# Accordion
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/molecules/accordion/accordion.md`
Human reference: https://canvas.nannier.com/components/accordion
## Public imports
```tsx
import { Accordion } 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 vertically stacked group of disclosure rows: each `items` entry is a header (its `title` plus a chevron that rotates when the row opens) over a collapsible content panel.
Single-open by default (opening one row closes the others); pass `multiple` to let any number stay open. Open state is controlled (`value` + `onValueChange`) or uncontrolled (`defaultValue`). An item's `description` adds a muted secondary line under its title;
`card` wraps the whole group 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).
## Usage
```tsx
```
## Variants
### Multiple open
```tsx
```
### Card surface
`card` gives the group an outlined card container with inset headers and content on
web and Android. On iOS the default Accordion already renders as the inset-grouped
card, so `card` changes nothing there (a documented no-op).
```tsx
```
### With descriptions
```tsx
```
### Disabled row
```tsx
```
### Controlled
Drive the open step from outside the accordion: a parent owns the active step and
passes it as `value`, so the group reflects that external state rather than toggling on
a header press. Here the Next step button is the trigger. (`Stateful` is a
docs-only helper standing in for your own state; in an app you would hold `step` with
`useState` and pass `value={step}` plus `onValueChange` if you also want header presses
to update it.)
```tsx
{(step, setStep) => (
setStep(step === "step-1" ? "step-2" : step === "step-2" ? "step-3" : "step-1")}>Next step
)}
```
### 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) => (
<>
setOpen(true)}>Share…
{} },
{ 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) => (
setOpen(true)}>Transfer ownership…
)}
```
## 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
Cancel
Transfer
```
### 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={Upgrade plan }`) 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
Save
```
### 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
```
## Do & Don't
### The page measure
**Do**: Name the measure with a step on the page; the content fills it and centers.
```tsx
Profile
```
**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
```
**Don't** — Type or click: a search field for three fixed options is overhead with nothing to filter.
```tsx
```
### Filtering
**Do** — Type a few letters: the list narrows as you go, so a long list stays usable.
```tsx
```
**Don't** — Try typing: a plain dropdown wearing a search placeholder ignores every keystroke, so a long list stays as long as it started.
```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
Save changes
```
## 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
Configure AI
```
### Inline identity and reference actions
```tsx
}>Scout v2.3
}>CORE-1790
} accessibilityLabel="Open repository" />
```
### Theme choices
The disc is the theme's own colour, data the example carries rather than a token: the
hand-off's `THM` table (cv-kit-code.js line 67) gives blush `oklch(.8 .09 10)` and mint
`oklch(.84 .11 165)`, which are `#f1a6b0` and `#80e2b9` in sRGB.
```tsx
}>blush
}>mint
```
### Scope and filter triggers
```tsx
}>All my projects
}>Filter by
```
### Utility icon
```tsx
} />
```
### Notification action
```tsx
} />
```
### Unread mark
`unread` marks something new without a number: the accent dot inside the box's upper trailing corner, ringed in the card colour. The dot is decorative, so name the state in the accessible label; `count` wins when both are passed.
```tsx
} />
```
### Outline
```tsx
Save changes
```
### Secondary
```tsx
Save changes
```
### Ghost
```tsx
Save changes
```
### Destructive
```tsx
Save changes
```
### Link
```tsx
Save changes
```
### Plain link
```tsx
Show all 54 props
Reset
```
### Small
```tsx
Save changes
```
### Large
```tsx
Save changes
```
### Icon only
```tsx
} />
```
### Disabled
A disabled button ignores presses: `onPress` never runs.
```tsx
Save changes
```
### Loading
```tsx
Saving
```
### 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
Mint agent
```
### 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
Rounded
```
### Block
```tsx
Create account
```
### 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
Add a row
```
### 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
xs step, pinned to the start (320)
md step, centered (448)
Block: fills the parent
```
### 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
}>Save changes
```
### 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
} iconRight={ }>Save changes
}>Save changes
}>Save changes
}>Delete
}>Save changes
```
## Do & Don't
### Default (primary)
**Do** — One clear primary action; everything else is supporting.
```tsx
Save
Cancel
```
**Don't** — Multiple primaries compete; nothing stands out.
```tsx
Save
Apply
Continue
```
### Outline
**Do** — Promote the main action to default; keep the rest outline.
```tsx
Publish
Save draft
Schedule
```
**Don't** — All-outline leaves no signal which action is primary.
```tsx
Save
Publish
Schedule
```
### Secondary
**Do** — Default for the primary action; secondary for the next one down.
```tsx
Create account
Import instead
```
**Don't** — A secondary button as the main call to action under-sells it.
```tsx
Create account
```
### Ghost
**Do** — Use ghost for tertiary and toolbar actions; keep the CTA filled.
```tsx
Cancel
Save changes
```
**Don't** — A ghost button is too quiet to carry the primary action.
```tsx
Save changes
```
### Destructive
**Do** — Reserve the destructive variant for irreversible actions like delete.
```tsx
Save changes
Delete account
```
**Don't** — Red on a safe action cries wolf; users learn to ignore it.
```tsx
Save changes
```
### Link
**Do** — Link variant for inline navigation; a filled button for the submit.
```tsx
Submit
Learn more
```
**Don't** — A link-styled submit doesn't look pressable and gets lost.
```tsx
Submit form
```
## 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
Read the docs
npm package
```
## 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
Save changes
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
Save
size small
```
### Specimen well sizes
```tsx
Small
compact
Large
tall
```
### Actions
```tsx
Rename the workspace. The URL updates everywhere.
Cancel
Save
```
### 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={AI connections Review requirements
} />
```
### 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 `Save changes ` 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
Pricing
`}
/>
```
## 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, Save changes ],
["outline", Preview , Preview ]
]}
/>
```
### 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
Export
```
**Don't** — Wiring search but dropping the footer leaves the user with no result count or way to page through 142 rows.
```tsx
Export
```
### bulk
**Do** — Pair the selection with a bulk bar that leads with the non-destructive action and keeps Delete visually distinct.
```tsx
2 selected
Bulk edit
Delete
```
**Don't** — Surfacing only the destructive Delete on a selection invites accidental data loss with no safer path.
```tsx
3 selected
Delete
```
### filter
**Do** — Echo the live result count next to the filter so its effect is visible.
```tsx
Status:
142 results
```
**Don't** — A filter control with no result count leaves the user guessing whether the filter narrowed anything.
```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: disabled busy loading pressed focused hovered
},
{ 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: disabled busy loading pressed focused hovered
},
{ term: "ARIA on web", value: aria-busy aria-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={Publish }>
```
### 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={Reset }>
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.
Cancel
Save changes
```
**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
Cancel
Create
```
**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
Cancel
Create
```
### Actions
**Do** — One primary plus quieter secondaries, right-aligned and labeled with their verb.
```tsx
Unsaved changes
You have edits that are not saved.
Discard
Keep editing
Save
```
**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.
Save
Discard
Keep editing
```
### 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
Cancel
Save
```
**Don't** — A 2xl panel around a single field leaves a vast empty expanse beside the input.
```tsx
Rename
Name
Cancel
Save
```
## 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
Bold
Italic
Link
```
### Emphasis
The hairline by default; `strong` for a firmer line, `soft` for a quieter one.
```tsx
```
### Label
```tsx
Or continue with
```
### Action
```tsx
Show more
```
## 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
Sign in
or continue with
Google
GitHub
```
**Don't** — Click Sign in: a full sentence in the label divider buries the choice.
```tsx
Sign in
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
Show less
```
**Don't** — Click the button: an action divider that does nothing is just decoration.
```tsx
Show more
```
### 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
Home
Components
Settings
```
## 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={Pause }>
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
```
### Measure
```tsx
```
## Do & Don't
### The message line
**Do** — Put helper and error text on the Field, so the row owns one message slot and the type and
color stay right in both states.
```tsx
```
**Don't** — Stack a raw Text under the control: it drifts from the caption scale, misses the
destructive tone, and is never announced as an error.
```tsx
Enter a valid email address.
```
### The label
**Do** — Let Field hand the label to the control it wraps,
so Android can float it inside the box.
```tsx
```
**Don't** — Label the control from outside with your own Text: the label can never float, and the
tap target no longer includes it.
```tsx
Email
```
## Supported props
### FieldProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| label | string \| undefined | no | | Row label. When the lone child is a field-family control (Input, Textarea, Select, Autocomplete) that carries no `label` of its own, the label and `required` are DELEGATED into that control so each platform places them per its own contract. Any other control keeps the static label above. |
| helper | string \| undefined | no | | Muted hint under the control. Replaced by `error` when that is set. |
| error | string \| undefined | no | | Error text. Setting it puts the row in the error state: the message turns destructive and replaces `helper` in place, so the row never changes height and nothing below it jumps. |
| required | boolean \| undefined | no | false | Marks the label with a destructive asterisk (decorative, never in an accessible name). A delegated control takes `required` and announces itself as required; over any other control, the label says it in its own name on iOS and Android ("Notifications, required"). |
| children | ReactNode | no | | The control. |
| 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.
---
# FilterPanel
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/filter-panel/filter-panel.md`
Human reference: https://canvas.nannier.com/components/filter-panel
## Public imports
```tsx
import { FilterPanel } 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.
Sidebar filter rail: grouped checkbox options under mono group headings, each
with its count, and a "Clear N" action in the header while any filter is on (a plain
accent text action). On the web an option's label is the 400 14px row label, 10px from its box.
Add `responsive` and at and below `drawerBreakpoint` (default `sm` = 640) the
docked panel collapses to a small secondary "Filters" button, carrying the live
count, that opens the same
panel inside a start-edge drawer, so a phone keeps its width for the results;
`open` / `defaultOpen` / `onOpenChange` drive the drawer for controlled use.
On iOS an option row marks a chosen filter the way an iOS list does: the label
leads, the count follows, and a trailing check closes the row. The web and
Android lead each row with the platform's selection checkbox. The native rows
keep the platform's touch height (44 pt, 48 dp).
## Usage
```tsx
```
## Variants
### Responsive drawer
At desktop widths this renders the docked panel unchanged; at and below the
`sm` breakpoint it renders the "Filters" trigger with its count and the panel opens in a
drawer.
```tsx
```
## Do & Don't
### Group by facet
**Do**: Give each facet its own titled group, so a reader sees which options narrow the same field.
```tsx
```
**Don't**: Pour every option into one group; statuses and regions run together, and nothing says which choices narrow the same field.
```tsx
```
## Supported props
### FilterPanelProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| groups | FilterGroup[] | yes | | Filter groups, each a heading plus its checkbox options. |
| value | string[] \| undefined | no | | Controlled selection: the keys of the checked options (each option's `value`, falling back to `"groupIndex:optionIndex"`). Provide it with `onSelectionChange` to drive or reset selection from a parent (e.g. from a URL query). |
| defaultValue | string[] \| undefined | no | | Initial selection for uncontrolled use. When omitted, the panel seeds from each option's `checked` flag. |
| activeCount | number \| undefined | no | | Active-filter count shown in the header's "Clear N" action. Omit to derive it from the checked options. |
| onClear | (() => void) \| undefined | no | | Fired when the header "Clear N" action is pressed (it shows while a filter is on). |
| onSelectionChange | ((selected: string[]) => void) \| undefined | no | | Fired with the full set of checked keys whenever the selection changes (both modes) — the controlled signal a parent mirrors into `value`. |
| onChange | ((groupIndex: number, optionIndex: number, next: boolean) => void) \| undefined | no | | Fired when a single option row toggles, with its group/option indexes and next value. Fires alongside `onSelectionChange`. |
| bordered | boolean \| undefined | no | false | Surface (pick one path): a rounded, bordered card vs. a bare panel. |
| compact | boolean \| undefined | no | false | Density (pick one): tighten the panel's padding and row spacing. |
| responsive | boolean \| undefined | no | false | Opt in to the responsive panel->drawer behavior. Off by default: a bare panel is the docked column at every width, exactly as before. |
| drawerBreakpoint | BreakpointKey \| undefined | no | | The width at and below which `responsive` collapses to the drawer (default `sm` = 640). |
| open | boolean \| undefined | no | false | The responsive drawer's open state (CONTROLLED). Omit for the self-managed trigger button. |
| defaultOpen | boolean \| undefined | no | false | Initial open state for the uncontrolled responsive drawer (default closed). |
| onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the responsive drawer opens or closes (trigger, scrim, back). |
| 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.
---
# FlowEditor
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/flow-editor/flow-editor.md`
Human reference: https://canvas.nannier.com/components/flow-editor
## Public imports
```tsx
import { FlowEditor } 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 node-graph editor: cards placed freely on a pannable, zoomable canvas, joined by directed edges. Use it to let people shape a process, such as a delivery workflow whose edges carry conditions. The application owns the graph; FlowEditor draws the `nodes` and `edges` it is given and reports what the user asked for (`onMoveNode`, `onConnect`, `onAddNode`, `onDelete`, `onSelectionChange`).
Drag a card to move it (it snaps to the grid on release), drag from a card's right-hand port onto another card to connect them, and drag a palette entry onto the canvas to add a node. Drag the background to pan; the wheel, a pinch or the toolbar zoom buttons zoom, and Fit shows the whole graph. A rework edge, one that returns work upstream, is drawn dashed.
## Usage
```tsx
{(graph, setGraph) => (
setGraph({ ...graph, nodes: graph.nodes.map((n) => n.id === id ? { ...n, ...point } : n) })}
onConnect={(from, to) => setGraph({ ...graph, edges: [...graph.edges, { id: `${from}-${to}`, from, to, label: "Ready" }] })}
onAddNode={(kind, point) => setGraph({ ...graph, nodes: [...graph.nodes, { id: `${kind}-${graph.nodes.length}`, label: "Review", ...point }] })}
onDelete={(selection) => setGraph(selection.kind === "node"
? { nodes: graph.nodes.filter((n) => n.id !== selection.id), edges: graph.edges.filter((e) => e.from !== selection.id && e.to !== selection.id) }
: { ...graph, edges: graph.edges.filter((e) => e.id !== selection.id) })} />
)}
```
## Variants
### Read only
Pass `readOnly` to show a graph without editing it: the toolbar, the ports and card drags are gone, while panning, zooming and selection stay, so a selected card or edge can drive a detail panel beside the editor.
```tsx
```
### Free placement
Cards snap to a 24-point grid by default. Pass `freePlacement` to keep them exactly where they are dropped.
```tsx
```
## Do & Don't
### Say what an edge means
**Do**: Label each edge with the condition it carries, so the graph reads without opening every edge.
```tsx
```
**Don't**: Leave edges unlabeled when several leave one card; the reader cannot tell which path a ticket takes.
```tsx
```
## Accessibility
Every pointer action has a keyboard and screen-reader path. Each card is a focusable button named by its title and description that announces its selected state; the arrow keys move a focused card one grid step and Delete removes it, and VoiceOver and TalkBack offer the same as named actions (move left, right, up and down, delete). The toolbar's From and To selects with Connect join two cards without dragging a port; when the two are joined already the button reads Disconnect and removes that edge, the keyboard path to an edge. Each palette entry adds its node in view when pressed. The canvas itself takes focus: plus and minus zoom and 0 fits the graph. Edges are selected by pressing them or their label; Delete removes the selection.
## Supported props
### FlowEditorProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| label | string | yes | | Accessible name of the canvas, such as "Workflow version 3". |
| nodes | FlowNode[] | yes | | The cards, controlled. |
| edges | FlowEdge[] | yes | | The connections, controlled. |
| palette | FlowPaletteItem[] \| undefined | no | | Kinds of node the user can add; omit for no palette. |
| selected | FlowSelection \| null \| undefined | no | | Controlled selection; null selects nothing. Omit for uncontrolled use. |
| defaultSelected | FlowSelection \| null \| undefined | no | | Initial selection for uncontrolled use. |
| onSelectionChange | ((selection: FlowSelection \| null) => void) \| undefined | no | | Fired when the selection changes, in both modes. |
| onMoveNode | ((id: string, point: FlowPoint) => void) \| undefined | no | | A card was moved to a new top-left corner (snapped unless `freePlacement`). |
| onConnect | ((from: string, to: string) => void) \| undefined | no | | The user asked for an edge between two cards. |
| onAddNode | ((paletteId: string, point: FlowPoint) => void) \| undefined | no | | The user asked to add a node of a palette kind with its top-left corner at `point`. |
| onDelete | ((selection: FlowSelection) => void) \| undefined | no | | The user asked to delete the selected node or edge. |
| readOnly | boolean \| undefined | no | false | Show the graph without editing: no toolbar edits, drags or ports; pan, zoom and selection stay. |
| freePlacement | boolean \| undefined | no | false | Place cards exactly where they are dropped instead of on the grid. |
| 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.
---
# Form
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/molecules/form/form.md`
Human reference: https://canvas.nannier.com/components/form
## Public imports
```tsx
import { Form, FormSection } 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.
Stitch your own fields inside it; Form adds the stacked or two-column rhythm, titled sections, the submit and cancel actions row, and Enter to submit.
The actions row sits under a hairline rule, a ghost cancel before the primary submit; each FormSection closes with the same rule. A `disabled` form dims as a whole and disables its actions.
`twoColumn` lays the rows out in exactly two equal columns 16px apart, however wide the form: the fields fill their columns left to right, an odd last field keeps its half, and a FormSection takes the whole row, after which the next field starts a new one. The form measures itself, and at 512px or narrower (the `lg` width step) the rows stack in one full-width column instead.
`FormSection aside` sets the section's heading in a rail beside its rows, the layout of a
settings page: the rail at the `xs` width step (320px), 48px from the rows, a 20px title
over a 14px description, the rows 18px apart, 28px above and below the rows and a rule
closing the section. The section measures itself, and at 512px or narrower (the `lg`
width step) the rail stacks over the rows. A form whose last row is an aside section
drops the actions row's rule (the section's closes the rows). `hints` puts leading
content in the actions row, before the buttons (the keyboard hints "↵ to save"), and the
row then spreads the two to its ends.
## Usage
On the web, Enter confirms an active Autocomplete suggestion before it can submit the form. A subsequent Enter submits after the list closes. Enter used to confirm an input-method candidate never submits, and holding Enter does not repeat submission. Multiline fields retain Enter for newlines. On native platforms, each field's `onSubmitEditing` owns the return-key action.
Form is a composition surface: you stitch the field atoms as children and keep their state; Form supplies the vertical rhythm, the actions row, and `onSubmit`, which fires from the submit button or from Enter in a single-line field on the web.
On the web a Form is a form landmark. Name it with `accessibilityLabel`, a brief statement of its purpose such as "Sign in", so a screen reader's landmark list can tell it apart; a page that holds more than one form needs a distinct name on each.
```tsx
```
## Variants
### Two-column
```tsx
```
### Block actions
`block` gives both actions `block`, so the cancel and submit buttons split the actions row equally, the compact form's full-width buttons.
```tsx
```
### Measure
```tsx
```
### Sections
```tsx
Email notifications
SMS alerts
```
### Aside sections
```tsx
↵
to save
}
>
Allow guests
```
### Account details
```tsx
```
### Credit card
```tsx
```
### Credit card with errors
```tsx
```
### Address
```tsx
```
### Address with errors
```tsx
```
### Select and switch controls
```tsx
Notifications
```
## Do & Don't
### Stacked
**Do** — Keep short forms one field per row so each label sits directly above its input and the eye flows straight down.
```tsx
```
**Don't** — Pairing an email and password side by side cramps a sign-in form and breaks the natural top-to-bottom reading order.
```tsx
```
### Two-column
**Do** — Compose mixed rows inside the stacked form: give a full-width field like the street its own line and pair the similar-width city and ZIP in a Row.
```tsx
```
**Don't** — Putting a wide field next to a tiny one in the same two-column row leaves the short input awkwardly oversized.
```tsx
```
### Sections
**Do** — Group related fields in a `FormSection` so each cluster carries its heading, its supporting line, and group semantics a screen reader announces.
```tsx
```
**Don't** — Hand-rolled headings spliced between fields carry no grouping semantics, so assistive tech never hears which section a field belongs to.
```tsx
Personal info
Billing
```
### Inline form
**Do** — Keep the input and its submit button on one row so the input + action reads as one step.
```tsx
Subscribe to updates
We'll send you a weekly digest of what changed.
Subscribe
```
**Don't** — Stacking the field above its button breaks the single-decision rhythm and adds a row of dead space.
```tsx
Subscribe to updates
We'll send you a weekly digest of what changed.
Subscribe
```
## Supported props
### FormSectionProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| title | string \| undefined | no | | Section heading naming this group of controls. |
| description | ReactNode | no | | Muted supporting line under the heading. |
| aside | boolean \| undefined | no | false | Sets the heading in a rail beside the section's rows (a settings page's layout): the rail at the `xs` width step, 48 from the rows, with a 20 px display title over a 14 px description, the rows 18 apart, and 28 above and below the rows closed by a rule. In a section 512 px wide or narrower (the `lg` step, measured on the section itself) the rail stacks over the rows. Omit for the stacked default. |
| children | ReactNode | no | | The section's stitched controls. |
| 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. |
### FormProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no | | The form rows, stitched by the caller: Input, Select, Checkbox, a FormSection, any node. Each control keeps its own state (controlled or uncontrolled); Form never intercepts it. |
| twoColumn | boolean \| undefined | no | false | Two-column: rows flow into a grid of exactly two equal columns, 16 apart, where a FormSection takes the whole line; in a form 512 px wide or narrower (the `lg` width step, measured on the form itself) the rows stack in one full-width column instead. Omit for the stacked default. |
| hints | ReactNode | no | | Leading content of the actions row, before the buttons (keyboard hints such as "↵ to save"). The row then spreads the hints and the buttons to its two ends. |
| submitLabel | string \| undefined | no | | Label for the primary submit button. Passing it (or `cancelLabel`) renders the actions row. |
| cancelLabel | string \| undefined | no | | Renders a ghost cancel button before the submit button. |
| block | boolean \| undefined | no | false | The actions take `block`: the cancel and submit buttons split the actions row equally (the Layout page's compact invite form, where the buttons go full width). |
| onSubmit | (() => void) \| undefined | no | | Fired when the submit button is pressed, and on the web when Enter is pressed inside one of the form's single-line text fields. Form owns no field state, so the caller reads its own values (no payload). |
| onCancel | (() => void) \| undefined | no | | Fired when the cancel button is pressed. |
| disabled | boolean \| undefined | no | false | Disables the actions row and Enter-to-submit, and dims the form. |
| accessibilityLabel | string \| undefined | no | | The form's name for assistive tech, a brief statement of its purpose ("Sign in", "Invite a teammate"). Form is a form landmark on the web, and ARIA asks every form landmark for a name: it is how a landmark list tells two forms on one page apart. Omit it and the form stays unnamed, as before. |
| 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.
---
# FunnelChart
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/funnel-chart/funnel-chart.md`
Human reference: https://canvas.nannier.com/components/funnel-chart
## Public imports
```tsx
import { FunnelChart } 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.
Stage-by-stage conversion bars: one centered row per stage, labelled on the left and valued on the right.
The stages stack as a column of rows, each a ramp-colored bar centered in its track at the stage's share of the first stage's value, so the column tapers as the funnel drops off. The stage's label sits at the start of the row and its value, in the mono face, at the end; `share` shows every stage's percent of the first stage in the value slot instead. Each stage's accessible name carries its percent of the previous stage (of the first stage with `share`). Press a stage to select it (the others dim).
## Usage
```tsx
```
## Variants
### Share of the first stage
```tsx
```
### Compact, inspected
```tsx
```
### Tapered stages
```tsx
```
## Do & Don't
### FunnelChart
**Do** - Order the stages widest first and let the taper carry the drop-off.
```tsx
```
**Don't** - A funnel that widens mid-way is not a funnel; a stage exceeding its predecessor warns and reads as an error.
```tsx
```
## Supported props
### FunnelChartProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| stages | StackedSegment[] | yes | | Ordered stages, widest first. |
| title | string \| undefined | no | | Optional heading shown above the funnel. |
| share | boolean \| undefined | no | false | Show each stage as a share of the FIRST stage instead of the conversion from the previous stage. |
| tapered | boolean \| undefined | no | false | Draw connected stage trapezoids and show counts alongside stage conversion. |
| compact | boolean \| undefined | no | false | Density (omit for the default funnel height). |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| formatValue | ((v: number) => string) \| undefined | no | | Formats stage values (data formatting, not styling). |
| selected | number \| null \| undefined | no | | Press-to-inspect: the selected stage index (controlled). Pass null for none. |
| defaultSelected | number \| undefined | no | | Press-to-inspect: the initially selected stage (uncontrolled). |
| onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press selects a stage (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.
---
# Gauge
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/gauge/gauge.md`
Human reference: https://canvas.nannier.com/components/gauge
## Public imports
```tsx
import { Gauge } 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 semicircular dial: a 180 degree top arc (muted track plus a tone-colored value arc) for a 0-100 value, the percent readout in the open center of the semicircle, and an optional label below the graphic.
## Usage
```tsx
```
## Variants
### Success tone
```tsx
```
### Warning tone
```tsx
```
### Wide dial
```tsx
```
## Do & Don't
### Gauge
**Do** — Put a muted track behind the value arc, the percent readout in the semicircle's open center, and the label below the graphic.
```tsx
```
**Don't** — An arc with no track and no number: there is no baseline to read the fill against and no exact value.
```tsx
```
## Supported props
### GaugeProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | number | yes | | The value to display, 0–100. |
| label | string \| undefined | no | | A short caption beneath the number (e.g. "Uptime"). |
| primary | boolean \| undefined | no | false | Tone of the fill arc (pick one; default primary). Precedence when several are passed: success > warning > destructive (first match wins). |
| large | boolean \| undefined | no | false | Use the wider 250 px adoption dial. |
| success | boolean \| undefined | no | false | |
| warning | boolean \| undefined | no | false | |
| destructive | boolean \| undefined | no | false | |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For sizing/composition only. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# GeoMap
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/geo-map/geo-map.md`
Human reference: https://canvas.nannier.com/components/geo-map
## Public imports
```tsx
import { GeoMap } 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 world map with a bubble per place, its area proportional to its count, over coastlines and country borders precomputed at build time. Optionally zoomable, merging crowded places into one bubble that splits as you zoom in.
The land silhouette and the shared country borders are two paths precomputed from Natural Earth 1:50m (no runtime fetch, no map dependency, no tiles), and each point is a circle at its projected coordinate whose AREA is proportional to its count, so doubling the count doubles the disc rather than its width.
Add `zoomable` and the wheel zooms about the pointer, two fingers pinch, a drag pans, and places too close together to draw separately merge into one bubble carrying their summed count, splitting into their members as you zoom in.
Single-identity encoding, so there is no tone axis and no legend. Press a bubble to flag its label and count (the others dim); the accessible name carries the biggest places with their values, because a screen reader user cannot see bubbles.
`detailed` frames the geography and outlines every place separately, keeping all places visible while an inline readout identifies the selected city. Zoom, pan and keyboard controls remain available.
## Usage
```tsx
```
## Variants
### Compact
```tsx
```
### Zoomable
```tsx
```
### Inspected
```tsx
```
### Formatted counts
```tsx
`${v} TB`}
points={[
{ label: "Los Angeles", lat: 34.0522, lng: -118.2437, count: 88 },
{ label: "Amsterdam", lat: 52.3676, lng: 4.9041, count: 64 },
{ label: "Nairobi", lat: -1.2921, lng: 36.8219, count: 12 },
{ label: "Osaka", lat: 34.6937, lng: 135.5023, count: 39 }
]}
/>
```
### Detailed
An outlined city panel with an inline inspection readout. Hover a city to expand its mark; select a city to inspect its count.
```tsx
```
## Do & Don't
### GeoMap
**Do** - Plot a modest number of places whose counts really differ, so the area encoding has something to say.
```tsx
```
**Don't** - Places that all carry the same count draw identical bubbles, so the map costs a lot of space to say nothing; rank them in a BarList instead.
```tsx
```
### Zooming
**Do** - Turn on `zoomable` when places crowd each other, so a cluster can be opened up rather than drawn as overlapping discs.
```tsx
```
**Don't** - Turn it on for places that are already far apart. Nothing will ever merge, so the controls and the gesture are cost with no benefit, and a reader is invited to explore a map that has nothing further to show.
```tsx
```
## Supported props
### GeoMapProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| points | GeoMapPoint[] | yes | | The places to plot; every bubble takes the primary token. |
| title | string \| undefined | no | | Optional heading shown above the map. |
| compact | boolean \| undefined | no | false | Density (omit for the default map size). |
| detailed | boolean \| undefined | no | false | Bordered geography panel with individually outlined place bubbles and an inline readout. |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| formatValue | ((v: number) => string) \| undefined | no | | Formats the flagged and accessible counts (data formatting, not styling). |
| selected | number \| null \| undefined | no | | Press-to-inspect: the selected point index (controlled). Pass null for none. |
| defaultSelected | number \| undefined | no | | Press-to-inspect: the initially selected point (uncontrolled). |
| onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press selects a point (or clears it with null). |
| zoomable | boolean \| undefined | no | false | Zoom and pan the map, and aggregate crowded places into one bubble that splits into its members as the map is driven in. The wheel zooms about the pointer, two fingers pinch, a drag pans once zoomed, and the zoom controls and arrow keys give the same reach without a pointer. |
| zoom | number \| undefined | no | | The zoom factor, 1 (the whole world) upward (controlled). Needs `zoomable`. |
| defaultZoom | number \| undefined | no | | The zoom factor the map opens at (uncontrolled). Needs `zoomable`. |
| onZoomChange | ((zoom: number) => void) \| undefined | no | | Fired whenever the zoom factor changes, by gesture, control or key. |
| onSelectPlaces | ((indices: number[]) => void) \| undefined | no | | Fired alongside `onSelect` with EVERY point index inside the pressed bubble. A single place reports one index and an aggregated bubble reports all of its members, so `onSelect`'s own meaning never silently changes. |
| 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.
---
# GridList
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/molecules/grid-lists/grid-lists.md`
Human reference: https://canvas.nannier.com/components/grid-lists
## Public imports
```tsx
import { GridList } 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.
Tiled card grids for people directories, item collections, and image galleries.
The tiles are a list, as in Tailwind UI, and each tile is one of its items: a
screen reader announces the list with its number of tiles and moves through it
tile by tile. A tappable tile is a button inside its item.
The `label` prop names the list (for example "Team members").
A `virtualized` grid with a bounded height scrolls its tiles on its own. On the
web, while those tiles overflow, the grid is a keyboard tab stop (before the
tiles themselves when `onPressItem` or tile actions make them buttons), the
keyboard can scroll it, and while it has keyboard focus the grid, which has no
card around it, draws the theme's focus ring around itself. Only the tiles near
its viewport are mounted, and each says where it sits in the whole grid, so a
screen reader still counts every tile. Give a windowed grid a `label`: focused,
a list with no name is named in Chromium from the text of every tile it has
rendered, so the kit warns in development when it has none.
## Usage
Each tile action carries its own `onPress`, so every button on a card fires your handler.
```tsx
{} }] },
{ title: "Ada Lovelace", subtitle: "Staff Engineer", avatar: "AL", badge: "Active", actions: [{ label: "Message", outline: true, onPress: () => {} }] },
{ title: "Kevin Turner", subtitle: "Product Designer", avatar: "KT", badge: "Away", actions: [{ label: "Message", outline: true, onPress: () => {} }] }
]}
/>
```
## Variants
### Gallery
```tsx
```
### Tappable
`onPressItem` makes every tile a button and reports the pressed index.
```tsx
{}}
items={[
{ title: "Rachel Chen", subtitle: "Engineering Lead", avatar: "RC" },
{ title: "Ada Lovelace", subtitle: "Staff Engineer", avatar: "AL" },
{ title: "Kevin Turner", subtitle: "Product Designer", avatar: "KT" }
]}
/>
```
## Do & Don't
### People (card grid)
**Do** — Let auto-fill minmax columns size to the available width so cards wrap cleanly at any breakpoint.
```tsx
```
**Don't** — A fixed column count with hard-width cards overflows the row on narrow viewports instead of reflowing.
```tsx
RC
Rachel Chen
Engineering Lead
AL
Ada Lovelace
Staff Engineer
KT
Kevin Turner
Product Designer
```
### Image gallery
**Do** — Lock a consistent aspect ratio so the grid stays even and nothing reflows once thumbnails load.
```tsx
```
**Don't** — Letting each thumbnail keep its intrinsic height makes a ragged grid and shifts the layout as images load.
```tsx
hero-banner.png
icon-set.svg
product-shot.jpg
avatar-default.png
```
## Supported props
### GridListProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | GridListItem[] | yes | | The tiles to render, one card per entry. |
| cols3 | boolean \| undefined | no | false | Columns (pick one; default is three-up). |
| cols2 | boolean \| undefined | no | false | Columns (pick one; default is three-up). |
| gallery | boolean \| undefined | no | false | Gallery mode: render each tile as a borderless thumbnail (a square color block from `item.color`, with a left-aligned filename and size below). Avatars, badges, and actions are not shown in this mode. Omit for the default people/card tile. |
| compact | boolean \| undefined | no | false | Density modifier: tighter gap and tile padding. |
| onPressItem | ((index: number) => void) \| undefined | no | | Make each tile a tappable target (open a detail, select a tile). When set, every tile renders as a Pressable with a button role and a pressed affordance, so a tappable grid needs no hand-rolled Pressable. |
| label | string \| undefined | no | | The grid's accessible name (e.g. "Team members"): a screen reader announces it with the list of tiles. A `virtualized` grid needs one: on the web its scroller is a keyboard stop while the tiles overflow, and Chromium names an unnamed stop from the text of every tile it has rendered. |
| virtualized | boolean \| undefined | no | false | Render the tiles through a windowed `FlatList` instead of mounting every tile up front, for large grids. Give the grid a bounded height (via `style`, e.g. `{ maxHeight: 480 }`) so it can scroll; without one it warns and renders eagerly anyway. Default (omitted) mounts all tiles, unchanged. On the web, while the tiles overflow, the windowed grid is a keyboard tab stop the keyboard can scroll, and it draws the focus ring around itself 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.
---
# Grid
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/grid/grid.md`
Human reference: https://canvas.nannier.com/components/grid
## Public imports
```tsx
import { Grid, GridItem } 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 container-measured auto-fit tile grid. `minTileWidth` sets the floor
(default 240): the grid fits as many equal-width columns of at least that width
as its own measured container allows, and re-fits as the container changes, so
the same grid renders three-up on a desktop, two-up on a tablet, and a single
column on a phone with no breakpoint at the call site.
`columns` caps the count at the desktop number, the gap scale is Row and
Column's own (`flush` / `tight` / `snug` / `cozy` / `relaxed` / `loose`), and a
`GridItem` child with `wide` spans two cells. The grid spans its parent's full
width (it is a FILL component, so it needs no wrapper to reach the edges), and
its tiles are equal-height as well as equal-width: every cell stretches to the
height of the row it wrapped onto, and a `Card` in a cell grows to fill it, so a
row of cards shares a flush bottom edge however unevenly their content runs (CSS
Grid's `align-items: stretch`). A field or a hug component keeps its own height.
Equal-width tiles belong here; content-sized rows that should stack at narrow
widths belong to `Row stacks`.
`flow` preserves natural card heights and the original Argus one-pixel allowance
when fitting columns. `GridItem full` spans a whole row; `raised` and `front` keep
banner and lifted-card shadows above adjacent cells. `rowsCozy` and `rowsSection`
set the vertical rhythm to 12 or 26 independently of the horizontal gap.
The shared gap scale also includes `comfortable` (10), `roomy` (14), `generous`
(22), `panel` (28), `apart` (32), `ample` (40, the step under a section page's hero) and
`broad` (48), preserving the original application layout measurements,
and Row and Column's widest step, `chapter` (64), which wins over every other step.
## Usage
```tsx
```
## Variants
### Capped columns
```tsx
```
### Wide tile
```tsx
```
### Equal heights
Uneven content, one bottom edge: each card fills the height of its row.
```tsx
```
### Tight gap
```tsx
```
## Do & Don't
### Column math
**Do** — Give the grid a tile floor (and optionally a desktop cap) and let it
fit its container.
```tsx
Overview
Deploys
Alerts
```
**Don't** — Hand-roll percent tile widths on a wrapping Row; the column count
is then frozen at the call site and every consumer re-derives the phone
collapse by hand.
```tsx
Overview
Deploys
Alerts
```
## Supported props
### GridProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| flow | boolean \| undefined | no | false | Natural card heights with the original Argus subpixel width allowance. |
| rowsCozy | boolean \| undefined | no | false | Separate vertical rhythm for wrapped rows. |
| rowsSection | boolean \| undefined | no | false | |
| fine | boolean \| undefined | no | false | |
| dense | boolean \| undefined | no | false | |
| compact | boolean \| undefined | no | false | |
| close | boolean \| undefined | no | false | |
| nested | boolean \| undefined | no | false | |
| comfortable | boolean \| undefined | no | false | |
| balanced | boolean \| undefined | no | false | |
| roomy | boolean \| undefined | no | false | |
| airy | boolean \| undefined | no | false | |
| spacious | boolean \| undefined | no | false | |
| generous | boolean \| undefined | no | false | |
| section | boolean \| undefined | no | false | |
| panel | boolean \| undefined | no | false | |
| page | boolean \| undefined | no | false | |
| apart | boolean \| undefined | no | false | The step between side-by-side panels of one composition (32). |
| ample | boolean \| undefined | no | false | The step between a section page's hero and its body, and between the hero's halves (40). |
| broad | boolean \| undefined | no | false | The gap between the two halves of a page-wide split (48): the hand-off's hero grid. |
| chapter | boolean \| undefined | no | false | The widest gap step (64), between a page's chapters; wins over every other step. |
| children | ReactNode | no | | |
| minTileWidth | number \| undefined | no | 240 | Minimum tile width in px (default 240): the grid fits as many columns of at least this width as its measured container allows (auto-fit). |
| columns | number \| undefined | no | | Column cap: never more than this many columns (the desktop count). The count still drops below the cap as the container narrows, so a capped grid still collapses to one column on phones. |
| flush | boolean \| undefined | no | false | Gap scale (pick one; default `snug`): Row/Column's axis and precedence. |
| tight | boolean \| undefined | no | false | Gap scale (pick one; default `snug`): Row/Column's axis and precedence. |
| snug | boolean \| undefined | no | false | Gap scale (pick one; default `snug`): Row/Column's axis and precedence. |
| cozy | boolean \| undefined | no | false | Gap scale (pick one; default `snug`): Row/Column's axis and precedence. |
| relaxed | boolean \| undefined | no | false | Gap scale (pick one; default `snug`): Row/Column's axis and precedence. |
| loose | boolean \| undefined | no | false | Gap scale (pick one; default `snug`): Row/Column's axis and precedence. |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | StyleProp | no | | For sizing/composition only (e.g. `maxWidth` to bound the grid), never a restyle hook; layout comes from the props above. |
### GridItemProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| full | boolean \| undefined | no | false | Span the whole row. |
| raised | boolean \| undefined | no | false | Stack above adjacent resting cells. |
| front | boolean \| undefined | no | false | Stack above a raised banner while lifting. |
| children | ReactNode | no | | |
| wide | boolean \| undefined | no | false | Span two cells when the grid has more than one column (a hero tile). One cell in a one-column grid. |
| testID | string \| undefined | no | | E2E hook forwarded to the item element. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Heatmap
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/heatmap/heatmap.md`
Human reference: https://canvas.nannier.com/components/heatmap
## Public imports
```tsx
import { Heatmap } 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.
Density cells whose fill encodes each value, in seven-row columns inside the chart frame, titled by `label` (or `caption`) and closed by a less-to-more legend; `plain` strips the frame for nesting inside a card. Pass `calendar` for a GitHub-style contribution graph: each cell is a day, and a press or hover inspects its count and date. The calendar layout IS the kit's activity calendar, so a contribution graph needs no separate component.
`detailed` keeps contribution-calendar cells at their authored size, unframed, with compact month and weekday labels and the contribution-count color levels. A narrow parent scrolls the same calendar.
## Usage
```tsx
```
## Variants
### Calendar
```tsx
{
const noise = Math.sin(i * 12.9898) * 43758.5453;
const r = noise - Math.floor(noise);
const value = r < 0.5 ? 0 : (r - 0.5) / 0.5;
return { value, count: Math.round(value * 14), date: new Date(Date.UTC(2025, 6, 6 + i)).toISOString().slice(0, 10) };
})}
/>
```
## Do & Don't
### Heatmap
**Do** — Pair the grid with its less-to-more legend so the density scale is legible.
```tsx
```
**Don't** — A density grid with no legend leaves the alpha-to-value mapping a mystery.
```tsx
```
## Supported props
### HeatmapProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| values | HeatmapValue[] | yes | | The cells: intensities 0–1, or `{ value, count?, date?, label? }` for inspectable data. |
| label | string \| undefined | no | | What the grid measures (e.g. "Contribution activity"); leads the accessible name. |
| calendar | boolean \| undefined | no | false | GitHub-style: the cells are days, each inspectable for its count and date. |
| detailed | boolean \| undefined | no | false | Unframed contribution calendar with the authored month, weekday and level styling. |
| caption | string \| undefined | no | | A summary line above the grid (e.g. "248 contributions in the last year"); shown in place of `label`. |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| hideLegend | boolean \| undefined | no | false | Hide the less-to-more legend row. |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For sizing/composition only. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Histogram
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/histogram/histogram.md`
Human reference: https://canvas.nannier.com/components/histogram
## Public imports
```tsx
import { Histogram } 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 auto-binned frequency distribution: pass raw sample `values` and the chart bins them into nice-edged uniform buckets (Sturges' rule by default, `bins` to override) and draws contiguous bars on a numeric axis. Press or scrub a bar to inspect its range and count. The accessible name lists every bin with its bounds and tally.
## Usage
```tsx
```
## Variants
### Explicit bins
```tsx
```
### Compact, no grid
```tsx
```
## Do & Don't
### Histogram
**Do** - Hand the chart raw samples and let it choose nice bin edges.
```tsx
```
**Don't** - Pre-bucketing the samples into a bar Chart throws away the distribution's edges and the inspectable ranges.
```tsx
```
## Supported props
### HistogramProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| values | number[] | yes | | Raw sample values; the chart bins them. |
| bins | number \| undefined | no | | Bucket count override. Defaults to Sturges' rule on the sample size. |
| title | string \| undefined | no | | Optional heading shown above the plot. |
| label | string \| undefined | no | | Names the distribution (e.g. "Response times"); leads the accessible name. |
| 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 bin edges in ticks, the flag, and the accessible name. |
| selected | number \| null \| undefined | no | | Press-to-inspect: the selected bin index (controlled). Pass null for none. |
| defaultSelected | number \| undefined | no | | Press-to-inspect: the initially selected bin (uncontrolled). |
| onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press selects a bin (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.
---
# Icon
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/icon/icon.md`
Human reference: https://canvas.nannier.com/components/icon
## Public imports
```tsx
import { ICON_CATEGORIES, ICON_NAMES, Icon } 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.
Lucide-style outline. 1.75 stroke width, rounded caps. The stroke defaults to the theme foreground token and tracks light and dark automatically; pick other hues with the semantic color booleans (`primary`, `destructive`, `success`, `warning`, `muted`, `subtle`; that order is the precedence). `muted` is the third ink (`subtle-foreground`) and `subtle` the second (`muted-foreground`), the names Typography gives the same two inks. Inside a control that owns a label ink (a Button's `iconLeft` and `iconRight` slots) a glyph with no tone takes that ink instead, the way the hand-off's icons inherit `currentColor`: the primary's contrast ink on a primary button, the muted ink on a quiet one. The precedence is the explicit tone, then the host's ink, then the foreground, so a toned glyph keeps its tone anywhere.
## Usage
```tsx
```
## Variants
### Primary
```tsx
```
### Destructive
```tsx
```
### Success
```tsx
```
### Warning
```tsx
```
### Muted
```tsx
```
### Subtle
The second ink, a glyph that reads quieter than the foreground but louder than `muted`.
```tsx
```
### Cell
`cell` centres the glyph in a square cell that fills its parent at a 10 px corner, the
tile of a glyph gallery laid out by a `Grid`. On the web a hovered cell takes the
accent's soft wash and its glyph the accent ink.
```tsx
```
### Disclosure indicator
`disclosure` turns the glyph 180 degrees over the original Argus 180ms ease when
`expanded` changes. Reduce Motion snaps to the target. The existing glyph, color
and stroke props keep their usual meaning.
```tsx
```
### Gallery
`ICON_NAMES` lists every glyph in gallery order, each with its prop `key`, its
typed `label` and its `category`, for an icon picker or a catalog like this one.
`ICON_CATEGORIES` lists the categories in menu order, typed `IconCategory`. This
gallery filters by both: search a name, or pick a category.
```tsx
```
## Do & Don't
### Stroke coherence
**Do** — One outline style at 1.75 stroke across the whole set.
```tsx
```
**Don't** — Mixed stroke weights and a stray filled glyph make a set look incoherent.
```tsx
```
### foreground
**Do** — Leave the stroke at its default: it paints the theme foreground and follows light and dark on its own, no color prop needed.
```tsx
```
**Don't** — Hard-coding a hex stroke pins the icon to one theme; it stays black on a dark surface and disappears.
```tsx
```
### primary
**Do** — Reserve text-primary for the one active or selected icon; keep the rest muted.
```tsx
```
**Don't** — Painting a whole toolbar primary spends the accent on everything, so nothing reads as emphasized.
```tsx
```
### destructive
**Do** — Keep text-destructive for genuinely destructive actions like delete, so red always means consequence.
```tsx
```
**Don't** — A red download icon implies danger on a perfectly safe action and trains users to ignore the warning color.
```tsx
```
### muted
**Do** — Use the muted tone for secondary, inline hint icons where its color matches the helper text.
```tsx
Optional, used only for recovery
```
**Don't** — A muted icon inside a solid primary button reads as disabled and clashes with the high-contrast label.
```tsx
New project
```
## Supported props
### IconProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| disclosure | boolean \| undefined | no | false | Rotate the glyph as an Argus disclosure indicator. |
| expanded | boolean \| undefined | no | false | Open state of a disclosure indicator. |
| heavyStroke | boolean \| undefined | no | false | Strong action/link stroke from the Argus reference. |
| boldStroke | boolean \| undefined | no | false | Standard emphasized metadata stroke. |
| mediumStroke | boolean \| undefined | no | false | The medium toolbar stroke. |
| regularStroke | boolean \| undefined | no | false | The fine navigation stroke. |
| primary | boolean \| undefined | no | false | Color axis: pass one (default foreground). First match wins. |
| primaryForeground | boolean \| undefined | no | false | Contrast color for a glyph on a primary surface (e.g. a primary button). |
| destructive | boolean \| undefined | no | false | |
| success | boolean \| undefined | no | false | Positive/green status (matches Alert's success tone, scheme-aware). |
| warning | boolean \| undefined | no | false | Caution/amber status (the warning token, scheme-aware; matches Alert/Toast warning). |
| muted | boolean \| undefined | no | false | The third ink (`subtle-foreground`, the hand-off's `--tx3`): a glyph that recedes. |
| subtle | boolean \| undefined | no | false | The second ink (`muted-foreground`, the hand-off's `--tx2`), as Typography's `subtle`. |
| cell | boolean \| undefined | no | false | The glyph centred in a square cell that fills its parent (a Grid tile of a glyph gallery) at a 10 px corner. On the web a hovered cell takes the accent's soft wash and its glyph the accent ink. |
| size | number \| undefined | no | | Single-glyph size in px (default 24). |
| accessibilityLabel | string \| undefined | no | | Accessible name for a MEANINGFUL standalone icon (one that conveys information not repeated in adjacent text, e.g. an icon-only button's glyph). Marks the glyph `role="img"` with this label on web and native. |
| decorative | boolean \| undefined | no | false | Mark a DECORATIVE/ornamental glyph (one paired with a text label, a separator chevron, an empty-state illustration) so assistive tech skips it. Hides it via aria-hidden on web and importantForAccessibility on native. |
| 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. |
| accessibility | boolean \| undefined | no | | `accessibility` |
| activity | boolean \| undefined | no | | `activity` |
| alarmClock | boolean \| undefined | no | | `alarm-clock` |
| alertTriangle | boolean \| undefined | no | | `triangle-alert` |
| alignLeft | boolean \| undefined | no | | `text-align-start` |
| anchor | boolean \| undefined | no | | `anchor` |
| apple | boolean \| undefined | no | | `apple` |
| appWindow | boolean \| undefined | no | | `app-window` |
| archive | boolean \| undefined | no | | `archive` |
| arrowDown | boolean \| undefined | no | | `arrow-down` |
| arrowDownLeft | boolean \| undefined | no | | `arrow-down-left` |
| arrowDownRight | boolean \| undefined | no | | `arrow-down-right` |
| arrowLeft | boolean \| undefined | no | | `arrow-left` |
| arrowRight | boolean \| undefined | no | | `arrow-right` |
| arrowUp | boolean \| undefined | no | | `arrow-up` |
| arrowUpLeft | boolean \| undefined | no | | `arrow-up-left` |
| arrowUpRight | boolean \| undefined | no | | `arrow-up-right` |
| asterisk | boolean \| undefined | no | | `asterisk` |
| atSign | boolean \| undefined | no | | `at-sign` |
| award | boolean \| undefined | no | | `award` |
| baby | boolean \| undefined | no | | `baby` |
| badge | boolean \| undefined | no | | `badge` |
| badgeCheck | boolean \| undefined | no | | `badge-check` |
| banknote | boolean \| undefined | no | | `banknote` |
| barChart2 | boolean \| undefined | no | | `chart-no-axes-column` |
| battery | boolean \| undefined | no | | `battery` |
| batteryCharging | boolean \| undefined | no | | `battery-charging` |
| batteryFull | boolean \| undefined | no | | `battery-full` |
| batteryLow | boolean \| undefined | no | | `battery-low` |
| bell | boolean \| undefined | no | | `bell` |
| bellOff | boolean \| undefined | no | | `bell-off` |
| bellRing | boolean \| undefined | no | | `bell-ring` |
| bike | boolean \| undefined | no | | `bike` |
| binary | boolean \| undefined | no | | `binary` |
| bird | boolean \| undefined | no | | `bird` |
| blend | boolean \| undefined | no | | `blend` |
| bluetooth | boolean \| undefined | no | | `bluetooth` |
| book | boolean \| undefined | no | | `book` |
| bookmark | boolean \| undefined | no | | `bookmark` |
| bookOpen | boolean \| undefined | no | | `book-open` |
| bookOpenText | boolean \| undefined | no | | `book-open-text` |
| bot | boolean \| undefined | no | | `bot` |
| box | boolean \| undefined | no | | `box` |
| braces | boolean \| undefined | no | | `braces` |
| brackets | boolean \| undefined | no | | `brackets` |
| briefcase | boolean \| undefined | no | | `briefcase` |
| brush | boolean \| undefined | no | | `brush` |
| bug | boolean \| undefined | no | | `bug` |
| building | boolean \| undefined | no | | `building` |
| building2 | boolean \| undefined | no | | `building-2` |
| calculator | boolean \| undefined | no | | `calculator` |
| calendar | boolean \| undefined | no | | `calendar` |
| calendarCheck | boolean \| undefined | no | | `calendar-check` |
| calendarClock | boolean \| undefined | no | | `calendar-clock` |
| calendarDays | boolean \| undefined | no | | `calendar-days` |
| camera | boolean \| undefined | no | | `camera` |
| car | boolean \| undefined | no | | `car` |
| carrot | boolean \| undefined | no | | `carrot` |
| cast | boolean \| undefined | no | | `cast` |
| cat | boolean \| undefined | no | | `cat` |
| chartArea | boolean \| undefined | no | | `chart-area` |
| chartBar | boolean \| undefined | no | | `chart-bar` |
| chartColumn | boolean \| undefined | no | | `chart-column` |
| chartLine | boolean \| undefined | no | | `chart-line` |
| chartPie | boolean \| undefined | no | | `chart-pie` |
| check | boolean \| undefined | no | | `check` |
| checkSquare | boolean \| undefined | no | | `square-check` |
| chevronDown | boolean \| undefined | no | | `chevron-down` |
| chevronLeft | boolean \| undefined | no | | `chevron-left` |
| chevronRight | boolean \| undefined | no | | `chevron-right` |
| chevronsLeft | boolean \| undefined | no | | `chevrons-left` |
| chevronsLeftRight | boolean \| undefined | no | | `chevrons-left-right` |
| chevronsRight | boolean \| undefined | no | | `chevrons-right` |
| chevronsUpDown | boolean \| undefined | no | | `chevrons-up-down` |
| chevronUp | boolean \| undefined | no | | `chevron-up` |
| circle | boolean \| undefined | no | | `circle` |
| circleAlert | boolean \| undefined | no | | `circle-alert` |
| circleCheck | boolean \| undefined | no | | `circle-check` |
| circleCheckBig | boolean \| undefined | no | | `circle-check-big` |
| circleDashed | boolean \| undefined | no | | `circle-dashed` |
| circleDot | boolean \| undefined | no | | `circle-dot` |
| circleMinus | boolean \| undefined | no | | `circle-minus` |
| circlePlay | boolean \| undefined | no | | `circle-play` |
| circlePlus | boolean \| undefined | no | | `circle-plus` |
| circleQuestionMark | boolean \| undefined | no | | `circle-question-mark` |
| circleUser | boolean \| undefined | no | | `circle-user` |
| circleX | boolean \| undefined | no | | `circle-x` |
| clapperboard | boolean \| undefined | no | | `clapperboard` |
| clipboard | boolean \| undefined | no | | `clipboard` |
| clipboardCheck | boolean \| undefined | no | | `clipboard-check` |
| clipboardCopy | boolean \| undefined | no | | `clipboard-copy` |
| clipboardList | boolean \| undefined | no | | `clipboard-list` |
| clock | boolean \| undefined | no | | `clock` |
| clock3 | boolean \| undefined | no | | `clock-3` |
| cloud | boolean \| undefined | no | | `cloud` |
| cloudDownload | boolean \| undefined | no | | `cloud-download` |
| cloudLightning | boolean \| undefined | no | | `cloud-lightning` |
| cloudRain | boolean \| undefined | no | | `cloud-rain` |
| cloudSnow | boolean \| undefined | no | | `cloud-snow` |
| cloudSun | boolean \| undefined | no | | `cloud-sun` |
| cloudUpload | boolean \| undefined | no | | `cloud-upload` |
| code | boolean \| undefined | no | | `code` |
| codeXml | boolean \| undefined | no | | `code-xml` |
| coffee | boolean \| undefined | no | | `coffee` |
| cog | boolean \| undefined | no | | `cog` |
| coins | boolean \| undefined | no | | `coins` |
| columns2 | boolean \| undefined | no | | `columns-2` |
| command | boolean \| undefined | no | | `command` |
| compass | boolean \| undefined | no | | `compass` |
| contact | boolean \| undefined | no | | `contact` |
| copy | boolean \| undefined | no | | `copy` |
| cornerDownRight | boolean \| undefined | no | | `corner-down-right` |
| cpu | boolean \| undefined | no | | `cpu` |
| creditCard | boolean \| undefined | no | | `credit-card` |
| cross | boolean \| undefined | no | | `cross` |
| crosshair | boolean \| undefined | no | | `crosshair` |
| crown | boolean \| undefined | no | | `crown` |
| database | boolean \| undefined | no | | `database` |
| diamond | boolean \| undefined | no | | `diamond` |
| disc | boolean \| undefined | no | | `disc` |
| divide | boolean \| undefined | no | | `divide` |
| dog | boolean \| undefined | no | | `dog` |
| dollarSign | boolean \| undefined | no | | `dollar-sign` |
| download | boolean \| undefined | no | | `download` |
| drill | boolean \| undefined | no | | `drill` |
| droplet | boolean \| undefined | no | | `droplet` |
| droplets | boolean \| undefined | no | | `droplets` |
| earth | boolean \| undefined | no | | `earth` |
| ellipsisVertical | boolean \| undefined | no | | `ellipsis-vertical` |
| equal | boolean \| undefined | no | | `equal` |
| eraser | boolean \| undefined | no | | `eraser` |
| euro | boolean \| undefined | no | | `euro` |
| expand | boolean \| undefined | no | | `expand` |
| externalLink | boolean \| undefined | no | | `external-link` |
| eye | boolean \| undefined | no | | `eye` |
| eyeOff | boolean \| undefined | no | | `eye-off` |
| factory | boolean \| undefined | no | | `factory` |
| fastForward | boolean \| undefined | no | | `fast-forward` |
| feather | boolean \| undefined | no | | `feather` |
| file | boolean \| undefined | no | | `file` |
| fileCheck | boolean \| undefined | no | | `file-check` |
| fileCode | boolean \| undefined | no | | `file-code` |
| fileImage | boolean \| undefined | no | | `file-image` |
| fileInput | boolean \| undefined | no | | `file-input` |
| fileOutput | boolean \| undefined | no | | `file-output` |
| filePen | boolean \| undefined | no | | `file-pen` |
| filePlus | boolean \| undefined | no | | `file-plus` |
| files | boolean \| undefined | no | | `files` |
| fileSearch | boolean \| undefined | no | | `file-search` |
| fileText | boolean \| undefined | no | | `file-text` |
| fileX | boolean \| undefined | no | | `file-x` |
| film | boolean \| undefined | no | | `film` |
| filter | boolean \| undefined | no | | `funnel` |
| fish | boolean \| undefined | no | | `fish` |
| flag | boolean \| undefined | no | | `flag` |
| flagTriangleRight | boolean \| undefined | no | | `flag-triangle-right` |
| flame | boolean \| undefined | no | | `flame` |
| flashlight | boolean \| undefined | no | | `flashlight` |
| flower | boolean \| undefined | no | | `flower` |
| folder | boolean \| undefined | no | | `folder` |
| folderCheck | boolean \| undefined | no | | `folder-check` |
| folderOpen | boolean \| undefined | no | | `folder-open` |
| folderPlus | boolean \| undefined | no | | `folder-plus` |
| footprints | boolean \| undefined | no | | `footprints` |
| forward | boolean \| undefined | no | | `forward` |
| frown | boolean \| undefined | no | | `frown` |
| fuel | boolean \| undefined | no | | `fuel` |
| gauge | boolean \| undefined | no | | `gauge` |
| gem | boolean \| undefined | no | | `gem` |
| gift | boolean \| undefined | no | | `gift` |
| gitBranch | boolean \| undefined | no | | `git-branch` |
| gitCommitHorizontal | boolean \| undefined | no | | `git-commit-horizontal` |
| gitCompare | boolean \| undefined | no | | `git-compare` |
| gitMerge | boolean \| undefined | no | | `git-merge` |
| gitPullRequest | boolean \| undefined | no | | `git-pull-request` |
| globe | boolean \| undefined | no | | `globe` |
| graduationCap | boolean \| undefined | no | | `graduation-cap` |
| grip | boolean \| undefined | no | | `grip` |
| gripHorizontal | boolean \| undefined | no | | `grip-horizontal` |
| gripVertical | boolean \| undefined | no | | `grip-vertical` |
| group | boolean \| undefined | no | | `group` |
| hammer | boolean \| undefined | no | | `hammer` |
| hand | boolean \| undefined | no | | `hand` |
| handGrab | boolean \| undefined | no | | `hand-grab` |
| handshake | boolean \| undefined | no | | `handshake` |
| hardDrive | boolean \| undefined | no | | `hard-drive` |
| hash | boolean \| undefined | no | | `hash` |
| headphones | boolean \| undefined | no | | `headphones` |
| heart | boolean \| undefined | no | | `heart` |
| heartHandshake | boolean \| undefined | no | | `heart-handshake` |
| heartPulse | boolean \| undefined | no | | `heart-pulse` |
| hexagon | boolean \| undefined | no | | `hexagon` |
| history | boolean \| undefined | no | | `history` |
| home | boolean \| undefined | no | | `house` |
| hourglass | boolean \| undefined | no | | `hourglass` |
| idCard | boolean \| undefined | no | | `id-card` |
| image | boolean \| undefined | no | | `image` |
| images | boolean \| undefined | no | | `images` |
| inbox | boolean \| undefined | no | | `inbox` |
| info | boolean \| undefined | no | | `info` |
| keyboard | boolean \| undefined | no | | `keyboard` |
| keyRound | boolean \| undefined | no | | `key-round` |
| landmark | boolean \| undefined | no | | `landmark` |
| laptop | boolean \| undefined | no | | `laptop` |
| layers | boolean \| undefined | no | | `layers` |
| layout | boolean \| undefined | no | | `panels-top-left` |
| layoutDashboard | boolean \| undefined | no | | `layout-dashboard` |
| layoutGrid | boolean \| undefined | no | | `layout-grid` |
| layoutList | boolean \| undefined | no | | `layout-list` |
| leaf | boolean \| undefined | no | | `leaf` |
| library | boolean \| undefined | no | | `library` |
| lifeBuoy | boolean \| undefined | no | | `life-buoy` |
| lightbulb | boolean \| undefined | no | | `lightbulb` |
| link | boolean \| undefined | no | | `link` |
| link2 | boolean \| undefined | no | | `link-2` |
| list | boolean \| undefined | no | | `list` |
| listChecks | boolean \| undefined | no | | `list-checks` |
| listOrdered | boolean \| undefined | no | | `list-ordered` |
| listTree | boolean \| undefined | no | | `list-tree` |
| loader | boolean \| undefined | no | | `loader` |
| loaderCircle | boolean \| undefined | no | | `loader-circle` |
| locate | boolean \| undefined | no | | `locate` |
| lock | boolean \| undefined | no | | `lock` |
| lockOpen | boolean \| undefined | no | | `lock-open` |
| logIn | boolean \| undefined | no | | `log-in` |
| logOut | boolean \| undefined | no | | `log-out` |
| mail | boolean \| undefined | no | | `mail` |
| mailOpen | boolean \| undefined | no | | `mail-open` |
| mailPlus | boolean \| undefined | no | | `mail-plus` |
| map | boolean \| undefined | no | | `map` |
| mapPin | boolean \| undefined | no | | `map-pin` |
| maximize | boolean \| undefined | no | | `maximize` |
| maximize2 | boolean \| undefined | no | | `maximize-2` |
| medal | boolean \| undefined | no | | `medal` |
| megaphone | boolean \| undefined | no | | `megaphone` |
| meh | boolean \| undefined | no | | `meh` |
| memoryStick | boolean \| undefined | no | | `memory-stick` |
| menu | boolean \| undefined | no | | `menu` |
| messageCircle | boolean \| undefined | no | | `message-circle` |
| messageSquare | boolean \| undefined | no | | `message-square` |
| messageSquareText | boolean \| undefined | no | | `message-square-text` |
| messageSquareWarning | boolean \| undefined | no | | `message-square-warning` |
| messagesSquare | boolean \| undefined | no | | `messages-square` |
| mic | boolean \| undefined | no | | `mic` |
| micOff | boolean \| undefined | no | | `mic-off` |
| milestone | boolean \| undefined | no | | `milestone` |
| minimize | boolean \| undefined | no | | `minimize` |
| minimize2 | boolean \| undefined | no | | `minimize-2` |
| minus | boolean \| undefined | no | | `minus` |
| monitor | boolean \| undefined | no | | `monitor` |
| moon | boolean \| undefined | no | | `moon` |
| moreHorizontal | boolean \| undefined | no | | `ellipsis` |
| mouse | boolean \| undefined | no | | `mouse` |
| mousePointerClick | boolean \| undefined | no | | `mouse-pointer-click` |
| move | boolean \| undefined | no | | `move` |
| move3d | boolean \| undefined | no | | `move-3d` |
| moveHorizontal | boolean \| undefined | no | | `move-horizontal` |
| moveVertical | boolean \| undefined | no | | `move-vertical` |
| music | boolean \| undefined | no | | `music` |
| navigation | boolean \| undefined | no | | `navigation` |
| newspaper | boolean \| undefined | no | | `newspaper` |
| notebook | boolean \| undefined | no | | `notebook` |
| octagon | boolean \| undefined | no | | `octagon` |
| paintbrush | boolean \| undefined | no | | `paintbrush` |
| palette | boolean \| undefined | no | | `palette` |
| panelRight | boolean \| undefined | no | | `panel-right` |
| paperclip | boolean \| undefined | no | | `paperclip` |
| pause | boolean \| undefined | no | | `pause` |
| pen | boolean \| undefined | no | | `pen` |
| pencil | boolean \| undefined | no | | `pencil` |
| pencilRuler | boolean \| undefined | no | | `pencil-ruler` |
| pentagon | boolean \| undefined | no | | `pentagon` |
| penTool | boolean \| undefined | no | | `pen-tool` |
| percent | boolean \| undefined | no | | `percent` |
| phone | boolean \| undefined | no | | `phone` |
| phoneCall | boolean \| undefined | no | | `phone-call` |
| phoneOff | boolean \| undefined | no | | `phone-off` |
| piggyBank | boolean \| undefined | no | | `piggy-bank` |
| pill | boolean \| undefined | no | | `pill` |
| pipette | boolean \| undefined | no | | `pipette` |
| plane | boolean \| undefined | no | | `plane` |
| play | boolean \| undefined | no | | `play` |
| plug | boolean \| undefined | no | | `plug` |
| plugZap | boolean \| undefined | no | | `plug-zap` |
| plus | boolean \| undefined | no | | `plus` |
| podcast | boolean \| undefined | no | | `podcast` |
| pointer | boolean \| undefined | no | | `pointer` |
| power | boolean \| undefined | no | | `power` |
| printer | boolean \| undefined | no | | `printer` |
| qrCode | boolean \| undefined | no | | `qr-code` |
| radio | boolean \| undefined | no | | `radio` |
| rainbow | boolean \| undefined | no | | `rainbow` |
| receipt | boolean \| undefined | no | | `receipt` |
| redo | boolean \| undefined | no | | `redo` |
| redo2 | boolean \| undefined | no | | `redo-2` |
| refreshCcw | boolean \| undefined | no | | `refresh-ccw` |
| refreshCw | boolean \| undefined | no | | `refresh-cw` |
| repeat | boolean \| undefined | no | | `repeat` |
| reply | boolean \| undefined | no | | `reply` |
| rewind | boolean \| undefined | no | | `rewind` |
| rocket | boolean \| undefined | no | | `rocket` |
| rotateCcw | boolean \| undefined | no | | `rotate-ccw` |
| rotateCw | boolean \| undefined | no | | `rotate-cw` |
| route | boolean \| undefined | no | | `route` |
| rss | boolean \| undefined | no | | `rss` |
| ruler | boolean \| undefined | no | | `ruler` |
| save | boolean \| undefined | no | | `save` |
| scale | boolean \| undefined | no | | `scale` |
| scan | boolean \| undefined | no | | `scan` |
| scanLine | boolean \| undefined | no | | `scan-line` |
| scissors | boolean \| undefined | no | | `scissors` |
| scrollText | boolean \| undefined | no | | `scroll-text` |
| search | boolean \| undefined | no | | `search` |
| send | boolean \| undefined | no | | `send` |
| server | boolean \| undefined | no | | `server` |
| settings | boolean \| undefined | no | | `settings` |
| settings2 | boolean \| undefined | no | | `settings-2` |
| share | boolean \| undefined | no | | `share` |
| share2 | boolean \| undefined | no | | `share-2` |
| shield | boolean \| undefined | no | | `shield` |
| shieldAlert | boolean \| undefined | no | | `shield-alert` |
| shieldCheck | boolean \| undefined | no | | `shield-check` |
| ship | boolean \| undefined | no | | `ship` |
| shoppingBag | boolean \| undefined | no | | `shopping-bag` |
| shoppingCart | boolean \| undefined | no | | `shopping-cart` |
| shrink | boolean \| undefined | no | | `shrink` |
| sigma | boolean \| undefined | no | | `sigma` |
| skipBack | boolean \| undefined | no | | `skip-back` |
| skipForward | boolean \| undefined | no | | `skip-forward` |
| slidersHorizontal | boolean \| undefined | no | | `sliders-horizontal` |
| slidersVertical | boolean \| undefined | no | | `sliders-vertical` |
| smartphone | boolean \| undefined | no | | `smartphone` |
| smile | boolean \| undefined | no | | `smile` |
| snowflake | boolean \| undefined | no | | `snowflake` |
| sparkles | boolean \| undefined | no | | `sparkles` |
| speaker | boolean \| undefined | no | | `speaker` |
| sprout | boolean \| undefined | no | | `sprout` |
| square | boolean \| undefined | no | | `square` |
| squareArrowOutUpRight | boolean \| undefined | no | | `square-arrow-out-up-right` |
| squarePen | boolean \| undefined | no | | `square-pen` |
| squarePlay | boolean \| undefined | no | | `square-play` |
| squarePlus | boolean \| undefined | no | | `square-plus` |
| squareTerminal | boolean \| undefined | no | | `square-terminal` |
| squareX | boolean \| undefined | no | | `square-x` |
| star | boolean \| undefined | no | | `star` |
| starHalf | boolean \| undefined | no | | `star-half` |
| stethoscope | boolean \| undefined | no | | `stethoscope` |
| stickyNote | boolean \| undefined | no | | `sticky-note` |
| store | boolean \| undefined | no | | `store` |
| sun | boolean \| undefined | no | | `sun` |
| sunrise | boolean \| undefined | no | | `sunrise` |
| sunset | boolean \| undefined | no | | `sunset` |
| syringe | boolean \| undefined | no | | `syringe` |
| table | boolean \| undefined | no | | `table` |
| tablet | boolean \| undefined | no | | `tablet` |
| tag | boolean \| undefined | no | | `tag` |
| tags | boolean \| undefined | no | | `tags` |
| target | boolean \| undefined | no | | `target` |
| terminal | boolean \| undefined | no | | `terminal` |
| textAlignCenter | boolean \| undefined | no | | `text-align-center` |
| textAlignJustify | boolean \| undefined | no | | `text-align-justify` |
| textCursorInput | boolean \| undefined | no | | `text-cursor-input` |
| thermometer | boolean \| undefined | no | | `thermometer` |
| thumbsDown | boolean \| undefined | no | | `thumbs-down` |
| thumbsUp | boolean \| undefined | no | | `thumbs-up` |
| ticket | boolean \| undefined | no | | `ticket` |
| timer | boolean \| undefined | no | | `timer` |
| toggleLeft | boolean \| undefined | no | | `toggle-left` |
| toggleRight | boolean \| undefined | no | | `toggle-right` |
| trainFront | boolean \| undefined | no | | `train-front` |
| trash | boolean \| undefined | no | | `trash` |
| trash2 | boolean \| undefined | no | | `trash-2` |
| treePine | boolean \| undefined | no | | `tree-pine` |
| trees | boolean \| undefined | no | | `trees` |
| trendingDown | boolean \| undefined | no | | `trending-down` |
| trendingUp | boolean \| undefined | no | | `trending-up` |
| triangle | boolean \| undefined | no | | `triangle` |
| trophy | boolean \| undefined | no | | `trophy` |
| truck | boolean \| undefined | no | | `truck` |
| type | boolean \| undefined | no | | `type` |
| umbrella | boolean \| undefined | no | | `umbrella` |
| undo | boolean \| undefined | no | | `undo` |
| undo2 | boolean \| undefined | no | | `undo-2` |
| unlink | boolean \| undefined | no | | `unlink` |
| upload | boolean \| undefined | no | | `upload` |
| usb | boolean \| undefined | no | | `usb` |
| user | boolean \| undefined | no | | `user` |
| userCheck | boolean \| undefined | no | | `user-check` |
| userCog | boolean \| undefined | no | | `user-cog` |
| userPlus | boolean \| undefined | no | | `user-plus` |
| userRound | boolean \| undefined | no | | `user-round` |
| users | boolean \| undefined | no | | `users` |
| usersRound | boolean \| undefined | no | | `users-round` |
| userX | boolean \| undefined | no | | `user-x` |
| utensils | boolean \| undefined | no | | `utensils` |
| variable | boolean \| undefined | no | | `variable` |
| video | boolean \| undefined | no | | `video` |
| volume | boolean \| undefined | no | | `volume` |
| volume1 | boolean \| undefined | no | | `volume-1` |
| volume2 | boolean \| undefined | no | | `volume-2` |
| volumeX | boolean \| undefined | no | | `volume-x` |
| wallet | boolean \| undefined | no | | `wallet` |
| wand | boolean \| undefined | no | | `wand` |
| wandSparkles | boolean \| undefined | no | | `wand-sparkles` |
| warehouse | boolean \| undefined | no | | `warehouse` |
| watch | boolean \| undefined | no | | `watch` |
| wifi | boolean \| undefined | no | | `wifi` |
| wifiOff | boolean \| undefined | no | | `wifi-off` |
| wind | boolean \| undefined | no | | `wind` |
| wrench | boolean \| undefined | no | | `wrench` |
| x | boolean \| undefined | no | | `x` |
| zap | boolean \| undefined | no | | `zap` |
| zoomIn | boolean \| undefined | no | | `zoom-in` |
| zoomOut | boolean \| undefined | no | | `zoom-out` |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Image
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/image/image.md`
Human reference: https://canvas.nannier.com/components/image
## Public imports
```tsx
import { Image } 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.
Displays a local or remote image. Set the `source` (`{ uri }`) and a size, then choose how it fills its box with a fit prop: `cover` (the default) fills the box and crops the overflow, while `contain` fits the whole image inside and letterboxes the spare space.
`stretch`, `center`, `repeat`, and `none` cover the rarer fits. `repeat` tiles a bundled asset (or a `{ uri, width, height }` source) at its point size on every platform, drawing the density variant that matches the screen (`grain@2x.png` on a 2x screen) into each tile; a box with no size of its own is one tile. A remote image with no stated size keeps React Native's own repeat. Remote images load over the network; bundle local assets with `require`. Name an image that carries content with `alt`, and it is announced as an image with that name on every platform; an image with no name is decorative, and assistive tech skips it. The v4 hand-off rounds a standalone photo at 12 (`radius="xl"`); Image draws no corner of its own, so a photo inside a Card or a Video keeps its host's corner. For a circular identity photo with an initials fallback, reach for ` ` rather than rounding a bare Image.
## Usage
```tsx
```
## Variants
### Contain
```tsx
```
### Cover
```tsx
```
## Do & Don't
### Fitting a photo
**Do**: Fill a box of another shape with `cover`: the photo keeps its proportions and crops what falls outside the box.
```tsx
```
**Don't**: Stretch a photo to a box of another shape; `stretch` scales each axis on its own, so the picture squashes out of proportion.
```tsx
```
## Supported props
### ImageProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| source | ImageSourcePropType \| undefined | no | | The image to display: a remote `{ uri }`, or a bundled asset from `require(...)`. |
| contain | boolean \| undefined | no | false | Fit: fit the whole image inside the box, letterboxing the spare space (pick one). |
| cover | boolean \| undefined | no | false | Fit: fill the box, cropping any overflow. The default when no fit is set (pick one). |
| stretch | boolean \| undefined | no | false | Fit: fill the box by distorting the aspect ratio (pick one). |
| center | boolean \| undefined | no | false | Fit: center at intrinsic size, scaling down only when larger than the box (pick one). |
| repeat | boolean \| undefined | no | false | Fit: tile the image at its intrinsic size to fill the box (pick one). |
| none | boolean \| undefined | no | false | Fit: show at intrinsic size with no scaling (pick one). |
| width | DimensionValue \| undefined | no | | Width of the image box (px, or a percent string like `"100%"`). Images have no intrinsic layout size. |
| height | DimensionValue \| undefined | no | | Height of the image box (px, or a percent string). |
| radius | ImageRadius \| undefined | no | | Corner radius from the kit's radius scale (pick one): none, sm, md, lg, xl, 2xl, 3xl, full. |
| style | StyleProp | no | | Composition only (aspectRatio, layout within a parent). Size comes from `width` / `height`, rounding from `radius`. |
| accessibilityLabel | string \| undefined | no | | Accessible name for the image, the same name `alt` gives (and it wins when both are set). On iOS and Android React Native makes an image its own accessibility element for `alt` (or `accessible`), so prefer `alt` for an image that stands on its own. |
| alt | string \| undefined | no | | The image's alt text: a named image is announced as an image with this name on every platform (the web's , the image role natively). With no name here or in `accessibilityLabel` the image is decorative, and assistive tech skips it. |
| testID | string \| undefined | no | | E2E hook forwarded to the underlying image. |
| accessible | boolean \| undefined | no | false | When true, indicates that the view is an accessibility element. By default, all the touchable elements are accessible. |
| accessibilityActions | readonly Readonly<{ name: AccessibilityActionName \| string; label?: string \| undefined; }>[] \| undefined | no | | Provides an array of custom actions available for accessibility. |
| aria-label | string \| undefined | no | | Alias for accessibilityLabel https://reactnative.dev/docs/view#accessibilitylabel https://github.com/facebook/react-native/issues/34424 |
| blurRadius | number \| undefined | no | | blurRadius: the blur radius of the blur filter added to the image |
| accessibilityRole | AccessibilityRole \| undefined | no | | Accessibility Role tells a person using either VoiceOver on iOS or TalkBack on Android the type of element that is focused on. |
| capInsets | Insets \| undefined | no | | When the image is resized, the corners of the size specified by capInsets will stay a fixed size, but the center content and borders of the image will be stretched. This is useful for creating resizable rounded buttons, shadows, and other resizable assets. More info on Apple documentation |
| accessibilityState | AccessibilityState \| undefined | no | | Accessibility State tells a person using either VoiceOver on iOS or TalkBack on Android the state of the element currently focused on. |
| onProgress | ((event: ImageProgressEventIOS) => void) \| undefined | no | | Invoked on download progress with {nativeEvent: {loaded, total}} |
| aria-busy | boolean \| undefined | no | false | alias for accessibilityState see https://reactnative.dev/docs/accessibility#accessibilitystate |
| aria-checked | boolean \| "mixed" \| undefined | no | | |
| aria-disabled | boolean \| undefined | no | false | |
| aria-expanded | boolean \| undefined | no | false | |
| aria-selected | boolean \| undefined | no | false | |
| onPartialLoad | (() => void) \| undefined | no | | Invoked when a partial load of the image is complete. The definition of what constitutes a "partial load" is loader specific though this is meant for progressive JPEG loads. |
| accessibilityHint | string \| undefined | no | | An accessibility hint helps users understand what will happen when they perform an action on the accessibility element when that result is not obvious from the accessibility label. |
| accessibilityValue | AccessibilityValue \| undefined | no | | Represents the current value of a component. It can be a textual description of a component's value, or for range-based components, such as sliders and progress bars, it contains range information (minimum, current, and maximum). |
| aria-valuemax | number \| undefined | no | | |
| aria-valuemin | number \| undefined | no | | |
| aria-valuenow | number \| undefined | no | | |
| aria-valuetext | string \| undefined | no | | |
| onAccessibilityAction | ((event: AccessibilityActionEvent) => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs an accessibility custom action. |
| importantForAccessibility | "auto" \| "yes" \| "no" \| "no-hide-descendants" \| undefined | no | | [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services. |
| resizeMethod | "scale" \| "none" \| "auto" \| "resize" \| undefined | no | | The mechanism that should be used to resize the image when the image's dimensions differ from the image view's dimensions. Defaults to `auto`. - `auto`: Use heuristics to pick between `resize` and `scale`. - `resize`: A software operation which changes the encoded image in memory before it gets decoded. This should be used instead of `scale` when the image is much larger than the view. - `scale`: The image gets drawn downscaled or upscaled. Compared to `resize`, `scale` is faster (usually hardware accelerated) and produces higher quality images. This should be used if the image is smaller than the view. It should also be used if the image is slightly bigger than the view. - `none`: No sampling is performed and the image is displayed in its full resolution. This should only be used in rare circumstances because it is considered unsafe as Android will throw a runtime exception when trying to render images that consume too much memory. More details about `resize` and `scale` can be found at http://frescolib.org/docs/resizing-rotating.html. |
| aria-hidden | boolean \| undefined | no | false | A value indicating whether the accessibility elements contained within this accessibility element are hidden. |
| aria-modal | boolean \| undefined | no | false | |
| fadeDuration | number \| undefined | no | | Duration of fade in animation in ms. Defaults to 300 |
| role | Role \| undefined | no | | Indicates to accessibility services to treat UI component like a specific role. |
| id | string \| undefined | no | | Used to reference react managed images from native code. |
| onLayout | ((event: LayoutChangeEvent) => void) \| undefined | no | | onLayout function Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height} }}. |
| onError | ((error: ImageErrorEvent) => void) \| undefined | no | | Invoked on load error with {nativeEvent: {error}} |
| onLoad | ((event: ImageLoadEvent) => void) \| undefined | no | | Invoked when load completes successfully { source: { uri, height, width } }. |
| onLoadEnd | (() => void) \| undefined | no | | Invoked when load either succeeds or fails |
| onLoadStart | (() => void) \| undefined | no | | Invoked on load start |
| progressiveRenderingEnabled | boolean \| undefined | no | false | |
| borderRadius | number \| undefined | no | | |
| borderTopLeftRadius | number \| undefined | no | | |
| borderTopRightRadius | number \| undefined | no | | |
| borderBottomLeftRadius | number \| undefined | no | | |
| borderBottomRightRadius | number \| undefined | no | | |
| accessibilityLabelledBy | string \| string[] \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| aria-labelledby | string \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| accessibilityLiveRegion | "none" \| "polite" \| "assertive" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| src | string \| undefined | no | | A string representing the resource identifier for the image. Similar to src from HTML. See https://reactnative.dev/docs/image#src |
| srcSet | string \| undefined | no | | Similar to srcset from HTML. See https://reactnative.dev/docs/image#srcset |
| aria-live | "polite" \| "assertive" \| "off" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| loadingIndicatorSource | ImageURISource \| undefined | no | | similarly to `source`, this property represents the resource used to render the loading indicator for the image, displayed until image is ready to be displayed, typically after when it got downloaded from network. |
| nativeID | string \| undefined | no | | Used to reference react managed images from native code. |
| defaultSource | number \| ImageURISource \| undefined | no | | A static image to display while downloading the final image off the network. |
| screenReaderFocusable | boolean \| undefined | no | false | Enables the view to be screen reader focusable, not keyboard focusable. |
| accessibilityElementsHidden | boolean \| undefined | no | false | A Boolean value indicating whether the accessibility elements contained within this accessibility element are hidden to the screen reader. |
| crossOrigin | "anonymous" \| "use-credentials" \| undefined | no | | Adds the CORS related header to the request. Similar to crossorigin from HTML. See https://reactnative.dev/docs/image#crossorigin |
| accessibilityViewIsModal | boolean \| undefined | no | false | A Boolean value indicating whether VoiceOver should ignore the elements within views that are siblings of the receiver. |
| tintColor | ColorValue \| undefined | no | | Changes the color of all the non-transparent pixels to the tintColor. See https://reactnative.dev/docs/image#tintcolor |
| onAccessibilityEscape | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the escape gesture (scrub with two fingers). |
| referrerPolicy | "origin" \| "no-referrer" \| "no-referrer-when-downgrade" \| "origin-when-cross-origin" \| "same-origin" \| "strict-origin" \| "strict-origin-when-cross-origin" \| "unsafe-url" \| undefined | no | | A string indicating which referrer to use when fetching the resource. Similar to referrerpolicy from HTML. See https://reactnative.dev/docs/image#referrerpolicy |
| onAccessibilityTap | (() => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs accessibility tap gesture. |
| onMagicTap | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the magic tap gesture. |
| accessibilityIgnoresInvertColors | boolean \| undefined | no | false | https://reactnative.dev/docs/accessibility#accessibilityignoresinvertcolorsios |
| accessibilityLanguage | string \| undefined | no | | By using the accessibilityLanguage property, the screen reader will understand which language to use while reading the element's label, value and hint. The provided string value must follow the BCP 47 specification (https://www.rfc-editor.org/info/bcp47). https://reactnative.dev/docs/accessibility#accessibilitylanguage-ios |
| accessibilityShowsLargeContentViewer | boolean \| undefined | no | false | A Boolean value that indicates whether or not to show the item in the large content viewer. Available on iOS 13.0+ https://reactnative.dev/docs/accessibility#accessibilityshowslargecontentviewer |
| accessibilityLargeContentTitle | string \| undefined | no | | When `accessibilityShowsLargeContentViewer` is set, this string will be used as title for the large content viewer. https://reactnative.dev/docs/accessibility#accessibilitylargecontenttitle |
| accessibilityRespondsToUserInteraction | boolean \| undefined | no | false | Blocks the user from interacting with the component through keyboard while still allowing screen reader to interact with it if this View is still accessible. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# InputOTP
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/input-otp/input-otp.md`
Human reference: https://canvas.nannier.com/components/input-otp
## Public imports
```tsx
import { InputOTP } 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 segmented one-time-code field: `length` cells display the typed characters while one underlying text input captures the keystrokes, so native SMS autofill, the one-time-code keyboard suggestion, and paste all flow into a single value.
Because that one input spans the whole row, the caret is pinned to the end of the code: tap any cell and the next character still lands in the first unfilled one, so a keystroke can never drop into the middle of a partly-entered code. It works controlled (`value` + `onChangeText`) or uncontrolled (`defaultValue`, or a bare ` ` that is typeable out of the box); `onComplete` fires once the code reaches `length` characters. Style and shape it with semantic props: `groups` splits the run into dash-separated chunks, `alphanumeric` accepts letters as well as digits, and `small`, `large`, `masked`, `disabled` and `autoFocus` do what they say.
## Usage
```tsx
```
## Variants
### Length
```tsx
```
### Grouped
```tsx
```
### Alphanumeric
```tsx
```
### Masked
```tsx
```
### Sizes
```tsx
```
### Disabled
```tsx
```
## Do & Don't
### Readable codes
**Do** — Size the field to the real code length with `length`, and split a long code with `groups` so the eye can chunk it the way the sender wrote it.
```tsx
```
**Don't** — Use `masked` for a code the user is meant to read back from an SMS; the bullets hide whether they typed it correctly.
```tsx
```
## Supported props
### InputOTPProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| length | number \| undefined | no | 6 | Number of segment cells (and the max code length). Default 6. |
| value | string \| undefined | no | | Current code (controlled). Only the typed characters, e.g. "123". Omit for uncontrolled use; a bare is typeable out of the box. |
| defaultValue | string \| undefined | no | "" | Starting code for the uncontrolled field. Defaults to empty. |
| onChangeText | ((code: string) => void) \| undefined | no | | Called with the new code (cleaned, sliced to `length`) on each change (both modes). |
| onComplete | ((code: string) => void) \| undefined | no | | Fired once when the code reaches `length` digits. |
| groups | number \| undefined | no | | Split the run into groups of this many cells, separated by a dash: `length={6}` with `groups={3}` reads 123-456. Omit for one unbroken run. |
| alphanumeric | boolean \| undefined | no | false | Accept letters as well as digits, and ask for the text keyboard instead of the number pad. Off by default: a one-time code is digits only. |
| autoFocus | boolean \| undefined | no | false | Focus the field on mount. |
| disabled | boolean \| undefined | no | false | Disable input and dim the cells. |
| masked | boolean \| undefined | no | false | Render a bullet (●) instead of the character, for passcode entry. |
| small | boolean \| undefined | no | false | Size (pick one; default is the medium cell). |
| large | boolean \| undefined | no | false | Size (pick one; default is the medium cell). |
| 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. |
| ref | React.Ref | no | | Ref to the underlying React Native host. Supports object refs and callback refs; native methods follow the platform's behavior. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Input
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/input/input.md`
Human reference: https://canvas.nannier.com/components/input
## Public imports
```tsx
import { Input } 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 Input component is a React Native text field with semantic boolean props
(`error`, `small`, `large`, `compact`, `disabled`), plus prefix/suffix addons, overlaid
icons (`leadingIcon` / `trailingIcon` with any Canvas glyph as `icon`), a
`passwordToggle` eye for a `secureTextEntry` value, a `clearable` clear
button, and a `shortcut` keycap. Input is single-line; for multi-line entry use the
dedicated Textarea.
On the web the eye shows what pressing it does: the open eye while the value is masked,
the struck-through eye once it is revealed. A field held
focused by an `InteractionOverride` (a docs specimen) draws the web's 1.5px accent caret,
blinking once a second, just after its value, since no real caret shows there.
`shortcut` shows a keyboard shortcut hint at the field's end, the key that focuses a
search field: a compact flat keycap (``, the 22 px cap; `tiny`, the 20 px
cap, on a `small` field) after the value and any inline suffix and before the trailing
glyphs, 8 px from the field's end and 10 px from the value (8 on a small field). It takes
the keys as Kbd's `keys` does: `"⌘K"` is one cap, `"⌘ K"` two. It is a hint only; bind
the key yourself.
Pass `label` (and `required`) to name the field:
iOS and web render the label
above the control, while Android floats the Material 3 in-container label. On
Android the field grows with the user's text size so the floated label never
meets the value, and a label longer than the field truncates on one line inside
it 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 ("Full name, required") on iOS and Android.
A 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.
Select and the search field share its look, and Field and Form compose that
label with helper and error text.
On iOS the field is the native text field in
Canvas's colors: a card-colored box with a hairline, a muted title above, a glyph
that tints with focus and error, and in solid mode an error wash. Under glass a
wash would cost the field's ink its contrast over the material, so the iOS field
takes none (the web's field a trace in dark only), and the error border, the tinted
glyph and a Field's message carry the state.
A prefix or suffix addon's fill is held to
the same bar: under glass it is only as dense as keeps its text at 4.5:1 over the field,
so in the browser's light palettes and on the iOS field in dark it is a faint trace.
Inside an overlay, Escape follows the overlay's cancellation policy. A supplied
`onKeyPress` runs first and can call `preventDefault()` to handle Escape locally.
Cancelling an IME candidate keeps the overlay open.
On web, the field is a sunken well: 40 pixels tall (32 small, 48 large) at the
10-pixel field corner, a hairline edge, a 12-pixel inset, a regular 14-pixel value
(13 small) and a medium 13-pixel muted label above. The edge darkens under the
pointer, takes the accent with a soft accent halo on focus, and turns red with a
red halo for an error; a read-only field is a dashed edge on no fill, and a
disabled one dims. A prefix sits in a muted box behind a divider, a passive
suffix is inline monospace text before the clear button (a small raised disc with
an x), and an action suffix is a button box at the end.
On iOS the clear button is the system's gray disc with an x, inside the boxed
suffix segment, and the action suffix's label is a semibold 15-point brand label.
On Android the suffix is inline text before the circled-x clear glyph, an errored
field shows a red alert glyph at its end (in place of a clear or eye button), and
a read-only field drops its fill under a dashed frame with a subtle underline.
`eyebrow` uses the original dense uppercase web field label, preserving its
9.5/12.35 metrics and tracking. Other platform label treatments retain their skin.
`soft` uses the brand’s entry-well fill where the skin provides it.
`systemFocus` preserves the platform’s system focus outline. An explicit
`accessibilityLabel` takes precedence over the visible label’s accessible name.
Pass `search` for the toolbar search: a magnifier, a borderless editor, and a
uppercase prompt while empty on web. The prompt names the editor unless
`accessibilityLabel` or `label` supplies its purpose.
iOS draws its search field
(a 36-point muted well) and Android the Material 3 search bar (a 56-dp muted
stadium).
`systemFocus` preserves the system
focus outline for the borderless editor.
## Usage
```tsx
```
## Variants
### Compact
`compact` is an outlined well with a 12px inset and the platform's field corner.
Its default is 40px high with a 14px value; `small` is 32px/13px and `large` is
48px/16px. Labels sit above the well on every platform.
```tsx
```
### Toolbar search
```tsx
```
### Label
```tsx
```
### Required
```tsx
```
### Prefix
```tsx
```
### Action
```tsx
```
### Icon
```tsx
```
### Email
```tsx
```
### Password
```tsx
```
### Username
```tsx
```
### Date of birth
```tsx
```
### Currency
```tsx
```
### Search
```tsx
```
### Shortcut
```tsx
```
### Card number
```tsx
```
### Error
```tsx
```
### Disabled
```tsx
```
### Read only
```tsx
```
### Measure
```tsx
```
## Do & Don't
### text
**Do** — Pass `label` so every field carries a persistent, programmatically-linked name.
```tsx
```
**Don't** — A placeholder is not a label; it vanishes the moment the user types and screen readers may skip it.
```tsx
```
### number
**Do** — Park the unit in a suffix addon so the value stays purely numeric.
```tsx
```
**Don't** — A plain text field lets users type the unit into the value, breaking parsing and validation.
```tsx
```
### Read only or disabled
**Do**: Pass `readOnly` for a value people still need to reach. On the web it stays in the tab order and its text stays selectable.
On iOS and Android, React Native maps a read-only field to a disabled native view, so VoiceOver and TalkBack announce it as dimmed and Android cannot select its text.
```tsx
```
**Don't**: Disable a field whose value people must read or copy. `disabled` takes it out of the tab order and announces it as unavailable on every platform.
```tsx
```
## Supported props
### InputProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string \| undefined | no | | Current text value (controlled). Omit and use `defaultValue` for uncontrolled use. |
| onChangeText | ((text: string) => void) \| undefined | no | | Called with the new text on each keystroke. |
| placeholder | string \| undefined | no | | Placeholder shown while the field is empty. |
| label | string \| undefined | no | | The field's persistent label. Its placement is platform-adaptive: iOS and web render it ABOVE the field (the static title the Field/Form composers produce today); Android renders the Material 3 in-container FLOATING label (centered like a placeholder at rest, floating to the top once the field is focused or filled). The label is the field's programmatic name (wired via both accessibilityLabel and aria-labelledby), so a placeholder is no longer needed for the accessible name. |
| search | boolean \| undefined | no | false | A toolbar search field with a magnifier and a tracked uppercase empty prompt on web. |
| eyebrow | boolean \| undefined | no | false | Dense uppercase eyebrow label where the skin offers it. |
| systemFocus | boolean \| undefined | no | false | Preserve the system focus outline on a framed field. |
| soft | boolean \| undefined | no | false | Use the brand’s softer entry-well fill where the skin provides it. |
| 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 ("Full name, required") on iOS and Android. The star takes effect only alongside `label`. |
| error | boolean \| undefined | no | false | State (orthogonal). `error` (alias `invalid`) flags a validation problem. |
| invalid | boolean \| undefined | no | false | State (orthogonal). `error` (alias `invalid`) flags a validation problem. |
| disabled | boolean \| undefined | no | false | State (orthogonal). `error` (alias `invalid`) flags a validation problem. |
| readOnly | boolean \| undefined | no | false | Read-only: shows the value but blocks editing without the dimmed look. |
| small | boolean \| undefined | no | false | Size (pick one; default is the medium field). |
| large | boolean \| undefined | no | false | Size (pick one; default is the medium field). |
| compact | boolean \| undefined | no | false | A compact outlined well: 40 high with a 14px value by default; small is 32/13, large is 48/16. Keeps the platform's field corner and places labels above. |
| prefix | string \| undefined | no | | Leading addon content (e.g. "https://", "$", an icon glyph). |
| suffix | string \| undefined | no | | Trailing addon content (e.g. "@canvas.dev", "USD", "Copy"). |
| leadingIcon | boolean \| undefined | no | | Render `icon` as a passive glyph inside the left of the field. |
| trailingIcon | boolean \| undefined | no | false | Render `icon` as a passive glyph inside the right of the field. |
| icon | keyof IconGlyphProps \| undefined | no | | Glyph name for leadingIcon/trailingIcon: any Canvas glyph (e.g. "search", "mail", "lock", "phone", "calendar", "creditCard"). |
| action | boolean \| undefined | no | false | Render the suffix as a pressable action button rather than a passive label. |
| onActionPress | ((event: GestureResponderEvent) => void) \| undefined | no | | Called when the action suffix is pressed (action only). |
| passwordToggle | boolean \| undefined | no | false | A trailing eye button that reveals and re-masks a `secureTextEntry` value (the password field's show/hide toggle). The field starts masked; the button is announced as "Show password" / "Hide password". Takes effect only with `secureTextEntry`. |
| clearable | boolean \| undefined | no | false | A trailing clear button (a circled x) that empties the field, shown while the field holds text (the search field's clear affordance). Works in both the controlled and the uncontrolled mode: it clears the native field and reports "" through `onChangeText`. |
| shortcut | string \| undefined | no | | A keyboard shortcut hint at the field's end: a compact flat keycap (``, the 20 px `tiny` cap on a `small` field) after the value and any inline suffix, before the trailing glyphs, the hint a search field shows for the key that focuses it (`shortcut="⌘K"`). Pass the keys as Kbd's `keys` takes them: `"⌘K"` is one cap, `"⌘ K"` two. A hint only: bind the key yourself. |
| accessibilityLabel | string \| undefined | no | | Accessible name for the field when there is no programmatically-associated visible label. Maps to accessibilityLabel + the aria-label web alias on the underlying TextInput, so a Form/Field control is announced with its name. |
| aria-labelledby | string \| undefined | no | | ID of the visible label element that names this field (its `nativeID`). react-native-web forwards aria-labelledby to the DOM input; prefer this over accessibilityLabel when a visible label is present so the label and field are programmatically linked. |
| aria-describedby | string \| undefined | no | | ID of the helper/error text that describes this field (its `nativeID`), so a screen reader reads the hint after the field name. |
| style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. |
| textContentType | "none" \| "name" \| "URL" \| "addressCity" \| "addressCityAndState" \| "addressState" \| "countryName" \| "creditCardNumber" \| "creditCardExpiration" \| "creditCardExpirationMonth" \| "creditCardExpirationYear" \| "creditCardSecurityCode" \| "creditCardType" \| "creditCardName" \| "creditCardGivenName" \| "creditCardMiddleName" \| "creditCardFamilyName" \| "emailAddress" \| "familyName" \| "fullStreetAddress" \| "givenName" \| "jobTitle" \| "location" \| "middleName" \| "namePrefix" \| "nameSuffix" \| "nickname" \| "organizationName" \| "postalCode" \| "streetAddressLine1" \| "streetAddressLine2" \| "sublocality" \| "telephoneNumber" \| "username" \| "password" \| "newPassword" \| "oneTimeCode" \| "birthdate" \| "birthdateDay" \| "birthdateMonth" \| "birthdateYear" \| "cellularEID" \| "cellularIMEI" \| "dateTime" \| "flightNumber" \| "shipmentTrackingNumber" \| undefined | no | | Give the keyboard and the system information about the expected semantic meaning for the content that users enter. To disable autofill, set textContentType to `none`. Possible values for `textContentType` are: - `'none'` - `'URL'` - `'addressCity'` - `'addressCityAndState'` - `'addressState'` - `'countryName'` - `'creditCardNumber'` - `'creditCardExpiration'` (iOS 17+) - `'creditCardExpirationMonth'` (iOS 17+) - `'creditCardExpirationYear'` (iOS 17+) - `'creditCardSecurityCode'` (iOS 17+) - `'creditCardType'` (iOS 17+) - `'creditCardName'` (iOS 17+) - `'creditCardGivenName'` (iOS 17+) - `'creditCardMiddleName'` (iOS 17+) - `'creditCardFamilyName'` (iOS 17+) - `'emailAddress'` - `'familyName'` - `'fullStreetAddress'` - `'givenName'` - `'jobTitle'` - `'location'` - `'middleName'` - `'name'` - `'namePrefix'` - `'nameSuffix'` - `'nickname'` - `'organizationName'` - `'postalCode'` - `'streetAddressLine1'` - `'streetAddressLine2'` - `'sublocality'` - `'telephoneNumber'` - `'username'` - `'password'` - `'newPassword'` - `'oneTimeCode'` - `'birthdate'` (iOS 17+) - `'birthdateDay'` (iOS 17+) - `'birthdateMonth'` (iOS 17+) - `'birthdateYear'` (iOS 17+) - `'cellularEID'` (iOS 17.4+) - `'cellularIMEI'` (iOS 17.4+) - `'dateTime'` (iOS 15+) - `'flightNumber'` (iOS 15+) - `'shipmentTrackingNumber'` (iOS 15+) |
| autoCapitalize | "none" \| "sentences" \| "words" \| "characters" \| undefined | no | | Can tell TextInput to automatically capitalize certain characters. characters: all characters, words: first letter of each word sentences: first letter of each sentence (default) none: don't auto capitalize anything https://reactnative.dev/docs/textinput#autocapitalize |
| autoComplete | "country" \| "name" \| "off" \| "nickname" \| "username" \| "password" \| "2fa-app-otp" \| "additional-name" \| "address-line1" \| "address-line2" \| "birthdate-day" \| "birthdate-full" \| "birthdate-month" \| "birthdate-year" \| "cc-csc" \| "cc-exp" \| "cc-exp-day" \| "cc-exp-month" \| "cc-exp-year" \| "cc-number" \| "cc-name" \| "cc-given-name" \| "cc-middle-name" \| "cc-family-name" \| "cc-type" \| "current-password" \| "email" \| "email-otp" \| "flight-confirmation-code" \| "flight-number" \| "family-name" \| "gender" \| "gift-card-number" \| "gift-card-pin" \| "given-name" \| "honorific-prefix" \| "honorific-suffix" \| "loyalty-account-number" \| "name-family" \| "name-given" \| "name-middle" \| "name-middle-initial" \| "name-prefix" \| "name-suffix" \| "new-password" \| "one-time-code" \| "organization" \| "organization-title" \| "password-new" \| "postal-address" \| "postal-address-country" \| "postal-address-dependent-locality" \| "postal-address-extended" \| "postal-address-extended-postal-code" \| "postal-address-locality" \| "postal-address-region" \| "postal-address-unit" \| "postal-code" \| "promo-code" \| "street-address" \| "sms-otp" \| "tel" \| "tel-country-code" \| "tel-national" \| "tel-device" \| "upi-vpa" \| "url" \| "wifi-password" \| "username-new" \| undefined | no | | Specifies autocomplete hints for the system, so it can provide autofill. On Android, the system will always attempt to offer autofill by using heuristics to identify the type of content. To disable autocomplete, set autoComplete to off. The following values work across platforms: - `additional-name` - `address-line1` - `address-line2` - `cc-number` - `country` - `current-password` - `email` - `family-name` - `given-name` - `honorific-prefix` - `honorific-suffix` - `name` - `new-password` - `off` - `one-time-code` - `postal-code` - `street-address` - `tel` - `username` The following values work on iOS only: - `nickname` - `organization` - `organization-title` - `url` The following values work on Android only: - `birthdate-day` - `birthdate-full` - `birthdate-month` - `birthdate-year` - `cc-csc` - `cc-exp` - `cc-exp-day` - `cc-exp-month` - `cc-exp-year` - `2fa-app-otp` - `email-otp` - `flight-confirmation-code` - `flight-number` - `gender` - `gift-card-number` - `gift-card-pin` - `loyalty-account-number` - `name-family` - `name-given` - `name-middle` - `name-middle-initial` - `name-prefix` - `name-suffix` - `password` - `password-new` - `postal-address` - `postal-address-country` - `postal-address-dependent-locality` - `postal-address-extended` - `postal-address-extended-postal-code` - `postal-address-locality` - `postal-address-region` - `postal-address-unit` - `promo-code` - `sms-otp` - `tel-country-code` - `tel-national` - `tel-device` - `upi-vpa` - `wifi-password` - `username-new` |
| autoCorrect | boolean \| undefined | no | false | If false, disables auto-correct. The default value is true. |
| autoFocus | boolean \| undefined | no | false | If true, focuses the input on componentDidMount. The default value is false. |
| defaultValue | string \| undefined | no | | Provides an initial value that will change when the user starts typing. Useful for simple use-cases where you don't want to deal with listening to events and updating the value prop to keep the controlled state in sync. |
| keyboardType | KeyboardTypeOptions \| undefined | no | | enum("default", 'numeric', 'email-address', "ascii-capable", 'numbers-and-punctuation', 'url', 'number-pad', 'phone-pad', 'name-phone-pad', 'decimal-pad', 'twitter', 'web-search', 'visible-password') Determines which keyboard to open, e.g.numeric. The following values work across platforms: - default - numeric - email-address - phone-pad The following values work on iOS: - ascii-capable - numbers-and-punctuation - url - number-pad - name-phone-pad - decimal-pad - twitter - web-search The following values work on Android: - visible-password |
| inputMode | InputModeOptions \| undefined | no | | Works like the inputmode attribute in HTML, it determines which keyboard to open, e.g. numeric and has precedence over keyboardType. |
| maxLength | number \| undefined | no | | Limits the maximum number of characters that can be entered. Use this instead of implementing the logic in JS to avoid flicker. |
| onBlur | ((e: BlurEvent) => void) \| undefined | no | | Callback that is called when the text input is blurred Note: If you are trying to find the last value of TextInput, you can use the `onEndEditing` event, which is fired upon completion of editing. |
| onFocus | ((e: FocusEvent) => void) \| undefined | no | | Callback that is called when the text input is focused |
| onSubmitEditing | ((e: TextInputSubmitEditingEvent) => void) \| undefined | no | | Callback that is called when the text input's submit button is pressed. |
| onKeyPress | ((e: TextInputKeyPressEvent) => void) \| undefined | no | | Callback that is called when a key is pressed. This will be called with `{ nativeEvent: { key: keyValue } }` where keyValue is 'Enter' or 'Backspace' for respective keys and the typed-in character otherwise including ' ' for space. Fires before onChange callbacks. Note: on Android only the inputs from soft keyboard are handled, not the hardware keyboard inputs. |
| returnKeyType | ReturnKeyTypeOptions \| undefined | no | | enum('default', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', 'emergency-call') Determines how the return key should look. |
| secureTextEntry | boolean \| undefined | no | false | If true, the text input obscures the text entered so that sensitive text like passwords stay secure. The default value is false. |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests |
| 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.
---
# Kbd
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/kbd/kbd.md`
Human reference: https://canvas.nannier.com/components/kbd
## Public imports
```tsx
import { Kbd } 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.
Keyboard shortcut indicator: a raised 24 px key cap with a mono label. `keys`
composes a whole shortcut, one cap per key; `sequence` says "then" between the
caps of keys pressed one after another. A shortcut reads as one accessible name
("⌘+K", "G then C"). `comfortable` pads the cap 3 px above and below its label and
8 px at the sides (25 px tall) for a key set beside what it does, as a reference's
keyboard table lists it; omit it for the 24 px cap with 6 px sides.
Two compact sizes take the cap down for a hint beside a field or a menu: `small`, the
22 px cap with 6 px sides at a 5 px corner and a 500 11 px label, and `tiny`, the 20 px cap
with 5 px sides at a 4 px corner and a 500 10 px label. Size precedence is `small`, then
`tiny`, and a size step wins over `comfortable`. `flat` is the hint cap's edge: one pixel
all round instead of the raised 2 px bottom, and the label in the secondary ink. An
`Input` draws its `shortcut` hint as `` (`tiny` on a small field).
## Usage
```tsx
```
## Variants
### Single
```tsx
Esc
```
### Combo
```tsx
```
### Sequence
```tsx
```
### Comfortable
```tsx
Enter
Activates
```
### Compact hint
```tsx
⌘K
⌘K
esc
```
### In a button
```tsx
}>Search…
```
### In a sentence
```tsx
Press
to search.
```
## Do & Don't
### Single
**Do**: Use `children` for one real key; give each cap exactly one key.
```tsx
Esc
```
**Don't**: Packing a whole shortcut into one key cap reads as a single keystroke that does not exist.
```tsx
⌘K
```
### Combo
**Do**: Pass every key to `keys`; Kbd lays out the caps and reads as one shortcut to a screen reader.
```tsx
```
**Don't**: Hand-assembling caps and separators is verbose and drops the combined accessible name; let `keys` compose the chord.
```tsx
⌘
+
⇧
+
P
```
### In a sentence
**Do**: Drop a `keys` Kbd inline so the shortcut reads as physical keys.
```tsx
Press
to search.
```
**Don't**: Plain-text shortcuts blend into the prose and are easy to miss.
```tsx
Press Ctrl+K to search.
```
## Supported props
### KbdProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no | | A single key label, rendered as one cap. Ignored when `keys` is set. |
| keys | string \| string[] \| undefined | no | | A multi-key shortcut (a combo, pressed together), rendered as one cap per key. Pass a whitespace-separated string (`"⌘ K"`) or an array (`["⌘", "K"]`). A single entry renders one cap, identical to passing that key as `children`. When set, `keys` takes precedence over `children`. |
| sequence | boolean \| undefined | no | false | Render a key SEQUENCE (press one, then the next): the caps are separated by "then". Only affects the `keys` form. |
| comfortable | boolean \| undefined | no | false | The comfortable cap: padded 3 px above and below its label and 8 px at the sides (25 px tall), the key the hand-off's component page sets beside a key's effect. Omit for the 24 px cap with 6 px sides. |
| small | boolean \| undefined | no | false | The compact 22 px cap: 6 px sides on the small key corner, a 500 11 px label on a 20 px line (the hint a search field or a command palette shows, ``). Size precedence: `small` > `tiny`; a size step wins over `comfortable`. |
| tiny | boolean \| undefined | no | false | The compact 20 px cap: 5 px sides on the tiny key corner, a 500 10 px label on an 18 px line. |
| flat | boolean \| undefined | no | false | The hint cap's edge: one pixel all round (no raised 2 px bottom) and the label in the secondary ink, the keycap a field or a menu shows beside what it opens. |
| 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.
---
# LineChart
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/line-chart/line-chart.md`
Human reference: https://canvas.nannier.com/components/line-chart
## Public imports
```tsx
import { LineChart } 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 lines with nice y ticks, gridlines, and a legend for multiple series.
`curved` draws a monotone cubic that never overshoots the data, `dots` marks each datum, and `baseline` + `fade` give the trading-app price idiom (dashed previous close, gain/loss auto tone, gradient fill). A series that means success or failure carries its own `success` / `destructive` tone instead of a ramp color. Press or scrub the plot to inspect a category.
## Usage
```tsx
```
## Variants
### Curved
```tsx
```
### Dots
```tsx
```
### Compact
```tsx
```
### Success vs failure
```tsx
```
### Gradient fade, multi-series
```tsx
```
### Price vs previous close
```tsx
```
### Formatted values
```tsx
`$${v}k`}
/>
```
### Press to inspect
```tsx
```
### Dashed series
A series marked `dashed` draws its line dashed with flat ends, and its legend swatch is a dashed rule: a target or a forecast beside the measured line.
```tsx
```
## Do & Don't
### Line
**Do** — Compare series that share one scale, and let the legend plus the fixed series colors carry identity.
```tsx
```
**Don't** — Mix measures of different scales on one axis: the smaller series flatlines against the baseline and reads as noise. Normalize, or use two charts.
```tsx
```
## Supported props
### LineChartProps
Includes `CartesianSeriesProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| dots | boolean \| undefined | no | false | Mark each datum with a dot. |
| baseline | number \| undefined | no | | Reference value (e.g. previous close) drawn as a dashed line. A single series with no explicit tone auto-tones success/destructive by whether its last value sits above or below the baseline - the trading-app gain/loss idiom. |
| fade | boolean \| undefined | no | false | Soft gradient fade under the lead (first) series' line (the price-chart look). |
| 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.
---
# Listbox
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/listbox/listbox.md`
Human reference: https://canvas.nannier.com/components/listbox
## Public imports
```tsx
import { Listbox } 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 custom select whose options stay on screen: single or multi-select, an optional detail line under each option, and a checkmark on the chosen items. Reach for it when the options need rich rows; prefer `Select` for a simple short list.
The list 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.
Neither iOS nor Material 3 ships a listbox, so every platform draws the Canvas v4 list, in its own face:
a chosen row sits on the accent's soft wash with a 16px check at its end in the accent ink, in single and multi select alike, and the check's slot is kept on every row so a choice never reflows a label.
The rows are 10px in with a 500 14/18.2px label (13px small) over a 400 12/16.2px detail in the subtle ink, rounded concentric inside a bordered list, which is the `popover` fill behind the `field-border` hairline at the tile corner with a 4px inset. A pressed row takes the `muted` wash, and on the web a row under the pointer takes the hover wash at once.
Single-select exposes a list of selectable options. With `multi`, it exposes a checkbox group: each row owns its label, checked state, and tap target. Give either mode a meaningful `accessibilityLabel`; the fallback is "Options". Arrow keys and Home/End move focus within one tab stop. Single-select follows focus, while multi-select keeps the current selection until Enter or Space toggles the focused row.
## Usage
```tsx
```
## Variants
### Multi
```tsx
```
### Small
```tsx
```
### Large
```tsx
```
### Disabled
```tsx
```
### Detail line
```tsx
```
### Measure
```tsx
```
## Do & Don't
### Prefer a native select for simple lists
**Do**: For short, plain lists a native select is lighter, accessible, and uses the platform picker on mobile.
```tsx
```
**Don't**: A custom listbox for two short options is heavier than it needs to be and worse on mobile.
```tsx
```
### single
**Do**: Show exactly one checkmark, mirror it in the trigger value, and close the panel on pick.
```tsx
```
**Don't**: Single-select with two checkmarks lies about state: only one option can be the value.
```tsx
```
### multi
**Do**: Keep the panel open, toggle each option's own checkmark, and summarize the count in the trigger.
```tsx
```
**Don't**: Don't close on each pick or echo only the last choice: multi-select needs to keep all selections visible.
```tsx
Backend
▾
```
## Supported props
### ListboxProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | ListboxItem[] | yes | | The options to render, top to bottom. |
| accessibilityLabel | string \| undefined | no | | Accessible name of the option list or multi-select checkbox group. Defaults to "Options". |
| multi | boolean \| undefined | no | false | Multi-select: each row is a checkbox (a leading indicator on the web and Android, a trailing ✓ on iOS) instead of a single choice. |
| bordered | boolean \| undefined | no | false | Wrap the list in a rounded, bordered content card with static glass in glass mode. |
| small | boolean \| undefined | no | false | Tighter rows with smaller text. |
| large | boolean \| undefined | no | false | Taller rows. |
| disabled | boolean \| undefined | no | false | Dim the list and block selection. |
| selected | number \| number[] \| undefined | no | | Selected option index(es) (CONTROLLED): a single index in single-select, an array of indices in multi-select. Omit for uncontrolled use. |
| defaultSelected | number \| number[] \| undefined | no | | Initial selection for uncontrolled use (a bare listbox selects on press). Falls back to the `selected` flags on `items` when omitted. |
| onChange | ((selected: number \| number[]) => void) \| undefined | no | | Fired with the full new selection (an index in single-select, an index array in multi). |
| onSelect | ((index: number) => void) \| undefined | no | | Fired with the pressed option's index. |
| testID | string \| undefined | no | | E2E hook forwarded to the root list view. |
| 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.
---
# MediaObject
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/molecules/media-objects/media-objects.md`
Human reference: https://canvas.nannier.com/components/media-objects
## Public imports
```tsx
import { MediaObject } 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.
Image or icon paired with text content. The fundamental building block for list items, notifications, and comment layouts.
## Usage
```tsx
```
## Variants
### Compact
```tsx
```
### Bare row with meta
```tsx
```
### Icon
```tsx
} />
```
### Action
The trailing `action` slot takes any control; the button's `onPress` is yours to
wire.
```tsx
Invite} />
```
### Numbered step
`bareIcon` draws the `icon` node as it is, with no tinted box around it, for a lead that is its own mark: a numbered `` numbers a list of steps.
```tsx
1 } title="Intrinsic · no JavaScript" description="Fill or hug inside the parent's bounds, a minWidth floor, wrapping." />
```
### Tappable
Passing `onPress` makes the whole row a single tap target; wire it to your own
handler and every tap runs it.
```tsx
{}} bordered avatar="RC" title="Rachel Chen" description="Engineering Lead" />
```
## Do & Don't
### Avatar
**Do** — Top-align with `start` so the avatar anchors to the first line of the title.
```tsx
```
**Don't** — Centering the avatar against a multi-line body leaves it floating beside the middle of the text.
```tsx
```
### Icon
**Do** — Keep the lead icon box compact with an 18px glyph so it reads as a tidy lead affordance.
```tsx
} />
```
**Don't** — An oversized icon box throws off the optical balance with the two-line text.
```tsx
Security first
End-to-end encryption with automatic key rotation.
```
### Action
**Do** — Pass `truncate` so long text clips in place and the trailing action stays pinned right.
```tsx
Invite} />
```
**Don't** — Without truncation a long email wraps and pushes the trailing button out of alignment.
```tsx
Invite} />
```
## Supported props
### MediaObjectProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| title | string \| undefined | no | | Primary line: the bold heading (e.g. a person's name). |
| description | string \| undefined | no | | Secondary line: muted supporting text under the title (e.g. a role or email). |
| body | ReactNode | no | | Optional longer body paragraph rendered below the description. |
| meta | string \| undefined | no | | Trailing metadata text pinned to the right (e.g. "2h ago", "admin"). |
| avatar | string \| undefined | no | | Initials for the leading avatar (e.g. "RC"); rendered as {avatar} . |
| src | string \| number \| undefined | no | | Photo for the leading avatar (a URI string or a bundled `require(...)` image); takes precedence over initials. |
| icon | ReactNode | no | | A leading icon glyph rendered in a tinted square box (stands in for an SVG icon). |
| bareIcon | boolean \| undefined | no | false | Draw the `icon` node as it is, with no tinted box around it: a lead that is its own mark, such as a numbered `` (the Layout page's "try in this order" list). |
| action | ReactNode | no | | A trailing action node (e.g. a ), pinned to the right edge. |
| onPress | (() => void) \| undefined | no | | Make the whole row a tappable target (navigate to a detail, select a row). When set, the row renders as a Pressable with a button role and a pressed affordance, so you never hand-roll a Pressable for a tappable media row. |
| center | boolean \| undefined | no | false | Alignment (pick one; default top-aligns with items-start). |
| start | boolean \| undefined | no | false | Alignment (pick one; default top-aligns with items-start). |
| reversed | boolean \| undefined | no | false | Direction (pick one; default leads with the media on the left). |
| leading | boolean \| undefined | no | false | Direction (pick one; default leads with the media on the left). |
| bordered | boolean \| undefined | no | false | Surface. |
| compact | boolean \| undefined | no | false | Menu-header density: a smaller leading avatar (28px) and title/description type stepped down one size, for a compact identity row (e.g. a menu or dropdown header). Backward-compatible; without it the row keeps its default 40px avatar and type. |
| truncate | boolean \| undefined | no | false | Layout. |
| 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.
---
# MetricBreakdown
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/metric-breakdown/metric-breakdown.md`
Human reference: https://canvas.nannier.com/components/metric-breakdown
## Public imports
```tsx
import { MetricBreakdown } 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 decomposed-metric dashboard card: headline value, tone-aware rate, Sparkline trend, per-category share rows, and a chip footer.
It holds a preformatted headline `value` with its caption, an optional secondary `rate` readout under the headline (toned by `rateSuccess`, `rateWarning`, or `rateDestructive`), an optional trend strip drawn by the kit Sparkline at the header's end with a latest-value caption above it, per-category `breakdown` rows with proportional share meters and Stats-style deltas, and a `chips` footer for recent notable codes. Every section is independently optional, so one layout backs OAuth token issuance, API request volume, sign-up sources, and any metric that needs decomposition plus trend in one card.
`detailed` emphasizes the value without repeating its caption, shows a trend arrow beside the rate and places the share rows under a divider. Its spark values are percentages from zero to one hundred; `rateLabel` supplies the period comparison.
## Usage
```tsx
```
## Variants
### Rate and trend
```tsx
```
### Plain, inside a card
```tsx
```
### Trend strip without its tag
`hideSparkTag` drops the latest-value tag over the trend strip, when the headline already carries the figure.
```tsx
```
## Do & Don't
### MetricBreakdown
**Do** - Pass preformatted strings and semantic tone booleans; the card owns the header, trend, rows, and chip anatomy.
```tsx
```
**Don't** - Rebuilding the header and trend from Text and Sparkline at the call site splits the anatomy the card owns and loses the rows' accessible shares.
```tsx
12.4k
REQUESTS
```
## Supported props
### MetricBreakdownProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string | yes | | The headline figure, preformatted (e.g. "3,771"). |
| label | string | yes | | Small caption over the headline (e.g. "Tokens issued"). |
| rate | string \| undefined | no | | Secondary metric, under the headline (e.g. "1.39%"). |
| rateLabel | string \| undefined | no | | Caption after the rate (e.g. "Error rate"). |
| rateSuccess | boolean \| undefined | no | false | Rate tone (slot-scoped; pick one; default muted). Precedence: rateSuccess > rateWarning > rateDestructive (first match wins). |
| rateWarning | boolean \| undefined | no | false | Rate tone (slot-scoped; pick one; default muted). Precedence: rateSuccess > rateWarning > rateDestructive (first match wins). |
| rateDestructive | boolean \| undefined | no | false | Rate tone (slot-scoped; pick one; default muted). Precedence: rateSuccess > rateWarning > rateDestructive (first match wins). |
| spark | number[] \| undefined | no | | Trend series rendered as a Sparkline bar strip beside the headline. Needs at least two points to draw. |
| sparkUnit | string \| undefined | no | | Unit suffix on the latest-value caption (e.g. "req/s"). |
| hideSparkTag | boolean \| undefined | no | false | Hide the latest-value tag over the trend strip (the strip alone, when the headline already carries the figure). |
| breakdown | BreakdownRow[] \| undefined | no | | Per-category rows with proportional share bars. |
| chips | MetricBreakdownChip[] \| undefined | no | | Footer chips rendered with the kit Chip (e.g. top error codes). |
| chipsLabel | string \| undefined | no | | Leading caption on the chip row (e.g. "Errors"). Omit to hide. |
| formatValue | ((v: number) => string) \| undefined | no | | Formats row values and the spark tag (data formatting, not styling). |
| compact | boolean \| undefined | no | false | Density (omit for the default spacing). |
| detailed | boolean \| undefined | no | false | Expanded metric summary with a divided share breakdown and trend annotation. |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| 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.
---
# Navbar
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/navbars/navbars.md`
Human reference: https://canvas.nannier.com/components/navbars
## Public imports
```tsx
import { Navbar } 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.
Topbars with a brand, navigation links, and an action button. Used as the
primary app-level navigation.
The bar measures its own width: at and below the `sm` breakpoint (640) the links
row automatically collapses into a menu button opening a dropdown of the same
links (the active one checkmarked), so links never clip off a phone screen;
`active` and `onSelect` keep their contract in both renderings.
The menu button
leads the bar, ahead of the logo and brand, where Material 3's top app bar puts
its navigation icon.
Pass `scrolls` to keep every link at every width instead: the bar's content row
then pans sideways inside the bar, with no scroll indicator, and the links never
fold into the menu. `onMenu` still replaces the links when both are passed.
On the web the bar is the v4 hand-off's: 56px tall with a 16px gutter, 32px
capsule links in the interface face, the active one (and a hovered one) on the
`secondary` fill. `floating` lifts it as a pill on the stronger hairline with the
md shade.
On iOS and Android the bar keeps the platform's own navigation bar and
top app bar shape.
In glass mode the active link paints a control-layer pane
(iOS keeps its
inactive capsules and carries the brand fill as glass, web and Android carry
their tinted tile),
and the collapsed menu is Dropdown's dense-layer glass card
under the hamburger. Solid mode keeps the skin's own active tile.
Either cluster takes a caller-supplied element beside its built-in parts:
`brandContent` leads the left one with a logo mark (beside or instead of the
`brand` wordmark) and `actions` leads the right one with free-form trailing
controls (a search button, icon buttons), ahead of `actionLabel` and `avatar`.
`links` is optional, so a console topbar with no middle nav renders neither the
links row nor the menu button that stands in for it.
## Usage
```tsx
```
## Variants
### Bordered
```tsx
```
### Floating
```tsx
```
### Compact
`compact` is the density axis's one member: the tight bar, 44px with a 5px inset
and 2px between every item on the web (a shorter bar with tighter gaps natively).
```tsx
```
### Scrolls
With `scrolls` the links never fold into the narrow menu: the content row scrolls
sideways inside the bar once it no longer fits. With `floating` and `compact` it is
the hand-off's docs pill.
```tsx
```
### Brand divider
`brandDivider` sets the brand apart from the links with a short vertical rule (the kit
`Divider vertical inset strong`), as the hand-off's floating docs pill does. It shows
only while the links row is on the bar.
```tsx
```
### Links with destinations
`hrefs` gives the links their destinations, by index. On the web each becomes a real
anchor, so middle-click and open-in-new-tab work, while a plain click still selects
the link and calls `onSelect` with the press event (call `event.preventDefault()`
there to route on the client). A modifier or middle click is left to the browser.
Natively `hrefs` is ignored and `onSelect` does the navigating.
```tsx
```
### Bare
A bare bar paints no surface and no hairline, so it sits clear on what is behind it.
Pass it at the scroll edge, while nothing has scrolled under the bar, and drop it once
content does: the bar's own surface then returns to keep its labels legible,
as iOS's
navigation bar and Material 3's top app bar do.
A framed bar (`bordered`, `floating`)
keeps its frame over `bare`.
```tsx
```
### App menu
An app with more destinations than a bar can hold (a component catalog, a settings
tree) passes `onMenu`: the bar leads with a menu button at every width and the app
opens its own navigation, such as a Drawer of grouped sections. It replaces `links`.
```tsx
{(open, setOpen) => (
setOpen(true)} />
Components
setOpen(false)}>Button
setOpen(false)}>Card
)}
```
### Console topbar
```tsx
}
actions={ } />}
avatar="RC"
/>
```
## Do & Don't
### Brand element
**Do**: Pass the mark through `brandContent`, so the bar owns the whole brand group and the wordmark keeps its own type.
```tsx
} brand="Console" links={["Overview", "Access", "Audit"]} avatar="RC" />
```
**Don't**: Folding the mark into the wordmark string leaves it as text, stuck in the title's size and color.
```tsx
```
### Standard topbar
**Do** — Keep a few primary links inline and fold the rest behind a More menu.
```tsx
```
**Don't** — Cramming every destination into the bar wraps the row and buries the primary links.
```tsx
```
### With search bar
**Do** — Use a button that opens the command palette and advertise the ⌘K shortcut.
```tsx
Canvas
} iconRight={⌘K }>Search…
```
**Don't** — A live text field in the bar reads as a form input and offers no keyboard affordance.
```tsx
Canvas
```
### Mobile
**Do** — Collapse the links into a hamburger and keep only the logo and avatar in the bar.
```tsx
} accessibilityLabel="Open menu" />
Canvas
```
**Don't** — A full horizontal nav at phone width wraps onto a second row and crowds out the logo.
```tsx
Canvas
[{ borderRadius: 6, backgroundColor: tokens.accent, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
Dashboard
[{ borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
Users
[{ borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
Settings
[{ borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
Billing
```
## Supported props
### NavbarProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| brand | string \| undefined | no | | Brand or product name shown at the left, in a semibold face. |
| brandContent | ReactNode | no | | A brand ELEMENT for the left cluster (a logo mark, a workspace switcher). It LEADS the cluster, so passing it beside `brand` renders the mark first and keeps the wordmark beside it; passing it alone makes the mark the whole brand. It takes the left cluster's own gap, so it needs no wrapper. |
| brandHref | string \| undefined | no | | Where the brand leads (an app's home, `/`). With it, or with `onBrandPress`, the brand lockup is one link: a real anchor on the web (an ``, so middle-click and modifier-click open it in a new tab), named by `brand`, with the kit's focus ring. |
| onBrandPress | ((event: GestureResponderEvent) => void) \| undefined | no | | Called when the brand is pressed; on the web a modifier-click on a `brandHref` stays the browser's. |
| brandDivider | boolean \| undefined | no | false | Draw a short vertical separator between the brand and the links (the kit ` `), as the hand-off's floating docs pill does. It shows only while both the brand and the links row are on the bar, so a collapsed bar or one without links never ends its brand on a stray rule. |
| links | string[] \| undefined | no | | Ordered navigation link labels rendered in the middle row. Omit it (or pass an empty array) for a bar with no middle nav: neither the links row nor the narrow menu button that stands in for it renders. |
| hrefs | string[] \| undefined | no | | Destinations for `links`, by index (parallel to it). A link with an href renders as a real anchor on the web (an ` `, so middle-click, modifier-click and open-in-new-tab work); natively it is ignored. A plain click still selects the link and calls `onSelect` with the press event, so an app that routes on the client calls `event.preventDefault()` there; a modifier or middle click is left to the browser and selects nothing. Omit it (or an entry) for a link without a destination. |
| active | number \| undefined | no | | Index of the active link (CONTROLLED; its label reads in the foreground color). Omit for uncontrolled use. |
| defaultActive | number \| undefined | no | | Initial active link for uncontrolled use (a bare navbar moves the active link on press). |
| actions | ReactNode | no | | Free-form trailing controls for the right cluster: a ghost search button, icon buttons, a notification dropdown, an account menu. They LEAD the cluster, ahead of the built-in `actionLabel` button and `avatar`, and take the cluster's own gap, so they need no wrapper. Keep them few and compact: the bar is a fixed height and the slot is rendered as given at every width. |
| actionLabel | string \| undefined | no | | Optional primary action; renders a on the right. |
| onAction | (() => void) \| undefined | no | | Called when the action button is pressed. |
| avatar | string \| undefined | no | | Optional account initials/name; renders an on the right. |
| onSelect | ((index: number, event?: GestureResponderEvent) => void) \| undefined | no | | Called when a nav link is pressed, with its index, and the press event when it came from the link itself (not from the narrow menu). |
| onMenu | (() => void) \| undefined | no | | The app's own navigation menu. When set, the bar leads with a menu button at every width and calls this on press, so the app can open navigation of its own (a Drawer of grouped sections). It replaces the `links` row and its narrow menu: pass one or the other. |
| bordered | boolean \| undefined | no | false | Surface (pick one; default is the flush bottom-hairline bar). |
| floating | boolean \| undefined | no | false | Surface (pick one; default is the flush bottom-hairline bar). |
| bare | boolean \| undefined | no | false | No surface and no hairline: the bar sits clear on what is behind it. Pass it while nothing has scrolled under the bar (the scroll edge) and drop it once content does. |
| compact | boolean \| undefined | no | false | The tight bar: 44px with a 5px inset and 2px between items on the web (the docs pill). |
| scrolls | boolean \| undefined | no | false | Keep every link at every width: the bar's content row scrolls sideways inside the bar (no scroll indicator) instead of folding the links into the narrow menu button. Pair it with `floating` and `compact` for the hand-off's docs pill. |
| 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.
---
# Pagination
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/pagination/pagination.md`
Human reference: https://canvas.nannier.com/components/pagination
## Public imports
```tsx
import { Pagination } 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.
Page-of-N navigation for tables and lists.
Neither iOS nor Android has a numbered pagination control, so every platform shows the
same pager:
rounded hairline cells at 28, 34 or 40 (small, default, large), the page
numbers in 13px mono numerals, and the current page in the accent fill with a soft glow
beneath it. Previous and Next carry a chevron and a "Prev" / "Next" label beside the
numbers (the chevron alone at the small size and in the compact row), the truncation gap
is a quiet text ellipsis (…), and the rows-per-page trigger sits on the field edge. A resting page
or arrow takes the hover wash at once; a disabled pager dims as one, and in an enabled
pager the arrow at either end dims on its own.
In glass mode the current page is a brand-tinted control-layer puck behind its number;
the other pages and the hairline arrows paint no surface, so they stay bare. Beyond
seven pages a page change also shifts the window (an ellipsis moves, a number appears or
drops out). Solid mode keeps the accent fill.
## Usage
```tsx
```
## Variants
### With item range
```tsx
```
### With size
```tsx
```
### Sizes
```tsx
```
### First and last page
```tsx
```
## Do & Don't
### compact
**Do** — Pass `itemCount` so the buttons carry a "Showing X-Y of N" range and position and total are always visible.
```tsx
```
**Don't** — Bare Previous/Next with no range label leaves the user unable to tell where they are or how much is left.
```tsx
```
### numbered
**Do** — Truncate the middle with an ellipsis; keep first, last, and a window around the current page.
```tsx
```
**Don't** — Rendering every page number overflows and stops being scannable past a handful.
```tsx
1
2
3
4
5
6
7
8
9
10
11
12
```
### with-size
**Do** — Show "Page X of N" beside the size selector and reset to page 1 when the size changes.
```tsx
```
**Don't** — Offering a page-size selector without a page indicator hides which page the new size landed on.
```tsx
Rows per page
10
```
## Supported props
### PaginationProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| page | number \| undefined | no | | Current page, 1-based (CONTROLLED). Clamped into the 1..total range before rendering. Omit for uncontrolled use. |
| defaultPage | number \| undefined | no | | Initial page for uncontrolled use (a bare pagination navigates on press). |
| total | number \| undefined | no | | Total number of pages. |
| onChange | ((page: number) => void) \| undefined | no | | Fired with the next 1-based page when a control or number is pressed. |
| itemCount | number \| undefined | no | | Total number of items across all pages. When set, the compact and with-size indicator reads "Showing X-Y of N" (the item range on the current page) instead of "Page X of N", computed from the current page and the page size. |
| compact | boolean \| undefined | no | false | Collapse to Prev/Next plus a "Page X of N" label, no number buttons. |
| withSize | boolean \| undefined | no | false | Prepend a "Rows per page" size selector to the compact Prev/Next + "Page X of N" layout. Takes precedence over `compact`. |
| pageSize | number \| undefined | no | | Currently selected rows-per-page value shown in the selector (CONTROLLED). Omit for uncontrolled use. |
| defaultPageSize | number \| undefined | no | | Initial rows-per-page value for uncontrolled use (the selector cycles on press). |
| pageSizes | number[] \| undefined | no | | Selectable rows-per-page values; pressing the selector advances through them. |
| onPageSizeChange | ((size: number) => void) \| undefined | no | | Fired with the next rows-per-page value when the selector is pressed. |
| 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). |
| disabled | boolean \| undefined | no | false | Size (pick one; default is the medium size). |
| 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.
---
# PhoneInput
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/molecules/phone-input/phone-input.md`
Human reference: https://canvas.nannier.com/components/phone-input
## Public imports
```tsx
import { PHONE_COUNTRIES, PhoneInput, flagOf } 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 phone number field: the Input's box with a country segment at its start (the
chosen country and a caret, which open a list of countries with their names and
dial codes) and that country's dial code inline before the number. On the web the
segment is a small chip inside the field naming the country by its ISO code, and
the dial code and the number are set in the mono face, and in the country list each
row is a 16px flag 10px from the name with the dial code a 12px mono note at its end;
on iOS and Android it is the
country's flag.
The number and the country are each controlled (`value` / `country`) or
self-managed (`defaultValue` / `defaultCountry`), the segment defaults to the
kit's curated `PHONE_COUNTRIES` list (pass `countries` for a full or localized
one), and the number field asks for the phone keypad. Pass `label` (and
`required`) to name it; `error` paints the whole box, segment included, and
Field delegates its label, required mark and error into it. The required star is
decorative: a screen reader hears the number field as required,
from
`aria-required` on the web and from its name ("Phone number, required") on iOS and
Android.
On iOS the field is
drawn to the iOS input-field reference: the white box, the flag and gray caret
segment with its state-coloured divider, the dial code in the placeholder gray.
In glass mode the country list is a dense-layer glass card under the box; the
box, its segment and its number stay in place. A pick commits the country and
returns focus to the number at once, and a field that becomes disabled or
read-only while the list is open closes it. Solid mode paints the skin's own list.
## Usage
```tsx
```
## Variants
### Pre-filled
```tsx
```
### Another country
```tsx
```
### Error
```tsx
```
### Required
```tsx
```
### Controlled country
```tsx
{(country, setCountry) => (
Selected: {country}
)}
```
### Custom country list
```tsx
```
### Disabled
```tsx
```
### Sizes
```tsx
```
## Do & Don't
### The country segment
**Do** — Let the segment carry the country so the number stays national and the dial code is never typed twice.
```tsx
```
**Don't** — A plain Input makes the user type the prefix, and nothing tells the app which country the digits belong to.
```tsx
```
### Errors
**Do** — Wrap the field in a Field so the message sits under the box and the box, segment included, paints the error border.
```tsx
```
**Don't** — A loose red caption beside a neutral box reads as unfinished, and a screen reader never hears it with the field.
```tsx
Invalid phone number.
```
## Supported props
### PhoneEntryProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| autoFocus | boolean \| undefined | no | false | If true, focuses the input on componentDidMount. The default value is false. |
| defaultValue | string \| undefined | no | | Provides an initial value that will change when the user starts typing. Useful for simple use-cases where you don't want to deal with listening to events and updating the value prop to keep the controlled state in sync. |
| maxLength | number \| undefined | no | | Limits the maximum number of characters that can be entered. Use this instead of implementing the logic in JS to avoid flicker. |
| onBlur | ((e: BlurEvent) => void) \| undefined | no | | Callback that is called when the text input is blurred Note: If you are trying to find the last value of TextInput, you can use the `onEndEditing` event, which is fired upon completion of editing. |
| onFocus | ((e: FocusEvent) => void) \| undefined | no | | Callback that is called when the text input is focused |
| onSubmitEditing | ((e: TextInputSubmitEditingEvent) => void) \| undefined | no | | Callback that is called when the text input's submit button is pressed. |
| onKeyPress | ((e: TextInputKeyPressEvent) => void) \| undefined | no | | Callback that is called when a key is pressed. This will be called with `{ nativeEvent: { key: keyValue } }` where keyValue is 'Enter' or 'Backspace' for respective keys and the typed-in character otherwise including ' ' for space. Fires before onChange callbacks. Note: on Android only the inputs from soft keyboard are handled, not the hardware keyboard inputs. |
| returnKeyType | ReturnKeyTypeOptions \| undefined | no | | enum('default', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', 'emergency-call') Determines how the return key should look. |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests |
### PhoneInputProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string \| undefined | no | | The national number typed so far (controlled). Omit and use `defaultValue` for uncontrolled use. |
| onChangeText | ((text: string) => void) \| undefined | no | | Called with the new number on each keystroke. |
| countries | readonly PhoneCountry[] \| undefined | no | | The countries the segment offers, in the order shown. Defaults to the kit's curated `PHONE_COUNTRIES` list; pass a full, localized, or reordered list. |
| country | string \| undefined | no | | The selected country's ISO alpha-2 code (controlled). Omit and use `defaultCountry`. |
| defaultCountry | string \| undefined | no | | Initial country code for uncontrolled use. Defaults to "US" when the list has it, else the first row. |
| onCountryChange | ((code: string) => void) \| undefined | no | | Called with the chosen country's code when a row is picked (both modes). |
| placeholder | string \| undefined | no | | Placeholder shown while the number is empty. |
| label | string \| undefined | no | | The field's persistent label, rendered above the box on every platform (a grouped field never floats its label). It is the number field's programmatic name, and Field delegates its label into it. |
| required | boolean \| undefined | no | false | Marks the field as required: a destructive "*" after the label (decorative, never in the accessible name), and the number field announced as required, through aria-required on the web and with the word in its name ("Phone number, required") on iOS and Android. |
| error | boolean \| undefined | no | false | State (orthogonal). `error` (alias `invalid`) flags a validation problem. |
| invalid | boolean \| undefined | no | false | State (orthogonal). `error` (alias `invalid`) flags a validation problem. |
| disabled | boolean \| undefined | no | false | State (orthogonal). `error` (alias `invalid`) flags a validation problem. |
| readOnly | boolean \| undefined | no | false | Read-only: shows the number but blocks editing and the country list. |
| small | boolean \| undefined | no | false | Size (pick one; default is the medium field). |
| large | boolean \| undefined | no | false | Size (pick one; default is the medium field). |
| accessibilityLabel | string \| undefined | no | | Accessible name for the number field when there is no `label`. |
| aria-describedby | string \| undefined | no | | ID of the helper/error text that describes the field (its `nativeID`). |
| style | LayoutStyle | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. |
| autoFocus | boolean \| undefined | no | false | If true, focuses the input on componentDidMount. The default value is false. |
| defaultValue | string \| undefined | no | | Provides an initial value that will change when the user starts typing. Useful for simple use-cases where you don't want to deal with listening to events and updating the value prop to keep the controlled state in sync. |
| maxLength | number \| undefined | no | | Limits the maximum number of characters that can be entered. Use this instead of implementing the logic in JS to avoid flicker. |
| onBlur | ((e: BlurEvent) => void) \| undefined | no | | Callback that is called when the text input is blurred Note: If you are trying to find the last value of TextInput, you can use the `onEndEditing` event, which is fired upon completion of editing. |
| onFocus | ((e: FocusEvent) => void) \| undefined | no | | Callback that is called when the text input is focused |
| onSubmitEditing | ((e: TextInputSubmitEditingEvent) => void) \| undefined | no | | Callback that is called when the text input's submit button is pressed. |
| onKeyPress | ((e: TextInputKeyPressEvent) => void) \| undefined | no | | Callback that is called when a key is pressed. This will be called with `{ nativeEvent: { key: keyValue } }` where keyValue is 'Enter' or 'Backspace' for respective keys and the typed-in character otherwise including ' ' for space. Fires before onChange callbacks. Note: on Android only the inputs from soft keyboard are handled, not the hardware keyboard inputs. |
| returnKeyType | ReturnKeyTypeOptions \| undefined | no | | enum('default', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', 'emergency-call') Determines how the return key should look. |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests |
| 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.
---
# PieChart
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/pie-chart/pie-chart.md`
Human reference: https://canvas.nannier.com/components/pie-chart
## Public imports
```tsx
import { PieChart } 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.
Proportional composition as arc slices in the `chart-1`..`chart-8` token colors, with a percent legend. `donut` centers the total (or the selected slice while inspecting); pressing a slice dims the others.
## Usage
```tsx
```
## Variants
### Donut
```tsx
```
### Detailed composition
```tsx
```
## Do & Don't
### Pie
**Do** — Keep slices to a handful and fold the tail into an "Other" slice; the legend carries exact shares.
```tsx
```
**Don't** — A dozen sliver slices cycle the palette and become unreadable; nothing is comparable at a glance.
```tsx
```
## Supported props
### PieChartProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| slices | StackedSegment[] | yes | | The slices, in order; each takes its share of the circle. |
| label | string \| undefined | no | | What the pie measures (e.g. "Traffic sources"); leads the accessible name. |
| donut | boolean \| undefined | no | false | Draw an annular ring with the total and label centered inside. |
| hideLegend | boolean \| undefined | no | false | Hide the legend (the labelled dots beside percentages). |
| detailed | boolean \| undefined | no | false | Spread names and shares into a value column beside a compact composition disc. |
| compact | boolean \| undefined | no | false | Smaller diameter. |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| selected | number \| null \| undefined | no | | Press-to-inspect: the selected slice index (controlled). Pass null for none. |
| defaultSelected | number \| undefined | no | | Press-to-inspect: the initially selected slice (uncontrolled). |
| onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press selects a slice (or clears it with null). |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For sizing/composition only. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Popover
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/popover/popover.md`
Human reference: https://canvas.nannier.com/components/popover
## Public imports
```tsx
import { Popover } 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 panel with a title, supporting text, rich content such as form fields, and a single follow-up action, anchored to its trigger. Pass children to host custom content in the panel body, between the description and the action.
On the web and Android the panel is the v4 card:
280 wide at the menu corner, the field
hairline and the large shadow, a 15px display heading over its 14px description in the
muted ink, and in solid mode a small diamond beak on the edge facing its trigger.
iOS
keeps the iPad popover, a rounder borderless card with a tapered beak toward its
trigger.
The triggered card opens below its button by default and above it with `top`; either
way it flips to the other side when its own lacks room, and the beak follows the
side it took. At phone width a triggered card that can close is presented as a sheet
on the bottom edge of the window, over the tab bar and a dimming scrim that glass on the
web also blurs,
its content kept clear of the home indicator: Dark Factory's sheet on the web, the iOS sheet on iPhone and
the Material 3 bottom sheet on Android.
In glass mode the triggered card is a functional-layer glass panel beside its
button, and the sheet a dense-layer one, legible over its scrim; focus enters once the
card's placement has committed, and the `inline` card is a static in-flow panel. Solid
mode paints the skin's own panel.
An existing control can own the trigger: pass its `anchorRef` and drive `open`
with `onOpenChange`. The popover then adds no trigger or layout wrapper. Name
custom content with `accessibilityLabel`. `compact` uses the original Argus panel
inset and outside hairline on web, with scrolling contents; `comfortable` gives
its filter panel more room. `panelWidth` and `maxHeight` bound the overlay.
`anchored` keeps a floating card at narrow widths, and `alignEnd` follows the
trigger’s trailing edge.
## Usage
```tsx
```
## Variants
### Compact panel
```tsx
Choose the roles and pods to include.
```
### Inline
```tsx
```
### Above
```tsx
```
### Inline with a beak
An inline panel draws its beak when it is given a side: with `top` the panel sits above
what it belongs to and points down at it, with `bottom` below it and points up, and
`alignEnd` moves the beak to the panel's end, over a trigger that sits there.
```tsx
```
### Description only
```tsx
```
### Controlled
```tsx
{(open, setOpen) => (
{open ? "The share popover is open." : "The share popover is closed."}
)}
```
## Do & Don't
### Compact content
**Do** — Keep popovers compact: a focused prompt with one input and a clear action.
```tsx
```
**Don't** — A full form belongs in a dialog; in a floating popover it is cramped and easy to dismiss by accident.
```tsx
Role
Cancel
Save
```
### Triggered
**Do** — Wrap the trigger in a relative anchor and dismiss on outside click so the panel positions and closes predictably.
```tsx
```
**Don't** — A trigger with no relative anchor and no way to dismiss leaves the panel floating loose and stuck open.
```tsx
Open popover
No anchor, no dismiss, no Close.
```
### Inline
**Do** — Reserve the static panel for a brief always-on message with a single follow-up action.
```tsx
```
**Don't** — An always-visible panel that scrolls internally is doing a card's or section's job; use the panel chrome only for short content.
```tsx
```
## Accessibility
The trigger is a button that announces its popup as a dialog and whether it is expanded. Opening the card moves keyboard focus into the panel, once its placement has settled: a dialog named by its title (by the trigger's label when it has none) and described by its supporting line. A card pinned open (a controlled `open` with no change handler) is part of the page and takes no focus as it appears.
With a keyboard, Tab moves through the panel's controls as if the card sat right after its trigger, then on to the control after the trigger, and the card stays open: the anchored popover is non-modal on the web. Shift+Tab from the panel returns to the trigger, and Tab from the trigger re-enters the card. Escape closes it, and focus goes back to whatever opened it.
The sheet on phones is modal, as it covers and dims the page: its dialog is announced as modal, Tab and Shift+Tab keep to its controls and wrap at their ends, and a Tab from anywhere on the page behind it goes into it. Escape, the action or a tap on the scrim closes it, and focus goes back to whatever opened it.
On iOS and Android the open card is modal to VoiceOver and TalkBack: the page behind it is hidden from them, their focus moves to the card's first line, and when the card closes it returns to the trigger. The escape gesture and the Android back button close the card.
Touch closes it with a tap anywhere outside it, and on phones a tap on the scrim above the sheet.
## Supported props
### PopoverProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| anchorRef | RefObject \| undefined | no | | An existing trigger to anchor against; omits the built-in trigger and wrapper. |
| accessibilityLabel | string \| undefined | no | | Explicit name for a panel with custom content. |
| compact | boolean \| undefined | no | false | Compact web application panel with an outside hairline and scrolling content. |
| comfortable | boolean \| undefined | no | false | Roomier compact-panel content inset. |
| anchored | boolean \| undefined | no | false | Keep the card anchored on narrow windows instead of adapting to a sheet. |
| alignEnd | boolean \| undefined | no | false | Align the trailing edge of the card to its trigger. |
| panelWidth | number \| undefined | no | | Floating card width, clamped inside the overlay outlet. |
| maxHeight | number \| undefined | no | | Maximum height of the floating card; its compact contents scroll within it. |
| trigger | string \| undefined | no | | Label for the trigger button. |
| title | string \| undefined | no | | Heading shown at the top of the floating card. |
| description | string \| undefined | no | | Supporting line beneath the title. |
| children | ReactNode | no | | Custom panel content (an input, a form row, any node), rendered in the card body between the description and the action row. Composes with the data-driven props. |
| actionLabel | string \| undefined | no | | When set, renders a primary action button at the bottom of the card. |
| top | boolean \| undefined | no | false | Anchor the card ABOVE the trigger (it flips below when the room above is short). Placement axis: `top` > `bottom`. |
| bottom | boolean \| undefined | no | false | Anchor the card BELOW the trigger, the default (it flips above when the room below is short). |
| inline | boolean \| undefined | no | false | Static mode: render the card on its own with no trigger button (an always-visible inline panel). When set, the card is always shown and `open` is ignored. With `top` or `bottom` it points its beak at what it belongs to (below it with `top`, above it with `bottom`), from the end edge with `alignEnd`. |
| open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (the trigger toggles it). |
| onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes. |
| 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.
---
# Pressable
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/pressable/pressable.md`
Human reference: https://canvas.nannier.com/components/pressable
## Public imports
```tsx
import { InteractionOverride, Pressable } 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 touchable primitive: wraps content, fires `onPress`, and exposes the press state to its style for feedback.
The kit's interactive components (Button, plus the tappable Card, Stats, GridList, MediaObject, and StackedList rows) cover the common cases and keep branding consistent, so prefer them; Pressable stays for full flexibility when you need a custom interaction the kit doesn't provide. Its style prop accepts a function of the press state, `({ pressed }) => style`, so you can show press feedback with no extra wrapper; style it with tokens to stay on-brand.
Pass `focusable={false}` for a surface only a pointer should reach, such as a row whose keyboard path is a button inside it: it still presses, and it leaves the tab order on the web too, where react-native-web's own Pressable would keep it a tab stop. A Pressable with the `link` role presses on Enter (and not on Space), as a link does, on the web too: react-native-web's own Pressable leaves that key to the browser, which activates only a link that has an `href`.
## Usage
Wire `onPress` to your own handler. The background dims while the button is held, straight from the `({ pressed }) => style` function.
```tsx
({ padding: 12, borderRadius: 8, backgroundColor: pressed ? alpha(tokens.primary, 0.8) : tokens.primary })}>
Press and hold
```
## Variants
### Opacity
The style function dims the whole surface to 50% opacity while pressed.
```tsx
({ opacity: pressed ? 0.5 : 1 })}>
Press and hold
```
### Disabled
A disabled Pressable ignores presses.
```tsx
Disabled
```
## Do & Don't
### Press feedback
**Do**: Answer the press with a style function: `({ pressed }) => style` changes the surface while the finger is down, so the tap visibly lands.
```tsx
({ padding: 12, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: pressed ? tokens.accent : tokens.card })}>
Open the weekly report
```
**Don't**: Give the surface a fixed style; nothing changes under the finger, so a tap feels dropped and a slow response reads as a missed press.
```tsx
Open the weekly report
```
### Kit controls first
**Do**: Use the kit's control when one fits the job: a Button already carries the brand fill, press feedback, focus ring, role and disabled state on every platform.
```tsx
{}}>Save changes
```
**Don't**: Rebuild a button from Pressable and a style object; it drifts from the brand the moment the kit's Button changes, and it misses every state the kit draws for you.
```tsx
({ alignSelf: "flex-start", paddingHorizontal: 16, paddingVertical: 10, borderRadius: 8, backgroundColor: pressed ? alpha(tokens.primary, 0.8) : tokens.primary })}>
Save changes
```
## Pinned states
`InteractionOverride` pins hover, keyboard focus or the pressed state for every kit control inside it, so a docs specimen or a visual test shows that state without a pointer or a key. Pass `hovered`, `focused` or `pressed` (its `InteractionOverrideProps`); they combine, and passing none gives the subtree back its real states. The kit's Pressable draws its focus ring and reports `pressed` to its style and children functions, the hover primitive reports a hover, and a field shows its focused look, so Button, Input, Checkbox, Switch, Pagination and every other kit control honour it without a prop of their own. It pins the look only: no handler fires, assistive technology hears the control as it is, a disabled control stays at rest, and natively a pinned focus draws no ring, as a real one draws none there. Portaled overlay content keeps its real states.
```tsx
Rest
Hover
Focus
Pressed
```
## Supported props
### InteractionOverrideProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| hovered | boolean \| undefined | no | | Every kit control in the subtree renders as if a pointer hovered it. |
| focused | boolean \| undefined | no | | Every kit control in the subtree renders its keyboard focus (the focus ring, or the field's own focused look). |
| pressed | boolean \| undefined | no | | Every kit control in the subtree renders as if it were held down. |
| children | ReactNode | no | | The subtree that resolves the pinned states. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# ProgressRing
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/progress-ring/progress-ring.md`
Human reference: https://canvas.nannier.com/components/progress-ring
## Public imports
```tsx
import { ProgressRing } 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-circle completion ring, the Gauge's sibling: a muted track ring, a tone-colored value arc revealed clockwise from 12 o'clock with rounded caps, the whole-percent readout centered inside, and an optional label below the graphic. The same 0-100 value contract, tone axis, and rounding rules as Gauge, in the circular anatomy that reads as "completion" rather than "level".
## Usage
```tsx
```
## Variants
### Success tone
```tsx
```
### Warning tone
```tsx
```
### Compact
```tsx
```
### Stacked label
`stacked` sets the label under the ring, centred, for a row of rings each named underneath.
```tsx
```
## Do & Don't
### ProgressRing
**Do** - Put a muted track behind the value arc, the percent readout in the ring's center, and the label below the graphic.
```tsx
```
**Don't** - A bordered circle with no track and no readout gives no baseline to read the fill against and no exact value.
```tsx
```
## Supported props
### ProgressRingProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | number | yes | | The value to display, 0-100. |
| label | string \| undefined | no | | A short caption beneath the number (e.g. "Complete"). |
| primary | boolean \| undefined | no | false | Tone of the value arc (pick one; default primary). Precedence when several are passed: success > warning > destructive (first match wins). |
| success | boolean \| undefined | no | false | Tone of the value arc (pick one; default primary). Precedence when several are passed: success > warning > destructive (first match wins). |
| warning | boolean \| undefined | no | false | Tone of the value arc (pick one; default primary). Precedence when several are passed: success > warning > destructive (first match wins). |
| destructive | boolean \| undefined | no | false | Tone of the value arc (pick one; default primary). Precedence when several are passed: success > warning > destructive (first match wins). |
| compact | boolean \| undefined | no | false | Density (omit for the default 168 ring; compact is 106). |
| stacked | boolean \| undefined | no | false | Set the label under the ring, centred, instead of beside it (a row of rings, each named underneath). |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For sizing/composition only. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Progress
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/progress/progress.md`
Human reference: https://canvas.nannier.com/components/progress
## Public imports
```tsx
import { Progress } 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.
Determinate and indeterminate progress bars: a rounded bar that reports how far a task has gotten, or that work is underway.
The bar 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.
Name the task with string `children`, or pass `accessibilityLabel` when the visible title is omitted or contains rich content.
`segments` displays a fixed number of discrete 13×5 dashes with three pixels
between them. It uses the same 0–1 `value`, intent fill and accessible percentage.
Indeterminate wins over discrete segments; a labelled segmented meter retains its header.
## Usage
```tsx
```
## Variants
### Labeled
```tsx
Uploading files
```
### Status
`status` puts a short word on the title line, in the percent's place and type: what the
bar means now, where `description` is a sentence under the title. It shows while
indeterminate too, and wins the slot over `showValue`.
```tsx
Indexing components
```
### Indeterminate
```tsx
```
### Small
```tsx
```
### Large
```tsx
```
### Warning
The tone axis recolors the fill when a metric crosses a soft threshold. Pass `warning` for an
amber bar; the track stays neutral.
```tsx
Storage used
```
### Danger
Pass `danger` for a red bar when a hard limit is exceeded, e.g. a work-in-progress cap. Reinforce
the state with copy, since the tone carries no new accessible value on its own.
```tsx
Over the WIP limit
```
### Live
The determinate fill eases to each new `value` instead of jumping, so a bar wired to real
progress fills smoothly. (Reduce Motion snaps instead.)
```tsx
{(value) => (
Uploading…
)}
```
### Measure
```tsx
xs step, pinned to the start (320)
lg step, pinned to the start (512)
Bare, in an lg Container: fills it (512)
```
## Do & Don't
### Determinate
**Do** — Use a determinate bar when you know the share of work done, and pass `showValue` so the control renders the percent on the label line; the number and the bar always agree.
```tsx
Uploading…
```
**Don't** — Don't park a determinate bar at a hard-coded value as a decorative divider; a frozen fill reads as a stalled task.
```tsx
Section
```
### Indeterminate
**Do** — Reach for `indeterminate` only when the duration is genuinely unknown. Pass the label as children;
on the web and Android a short bar sweeps the track; on iOS the control renders the kit Spinner, the platform's unknown-duration idiom (iOS has no linear indeterminate bar).
```tsx
Connecting…
```
**Don't** — Don't fake indeterminate progress when you do have a measurable value;
hiding a known value behind a sweep (or a spinner on iOS) hides information you could show.
```tsx
Step 3 of 4
```
### Context
**Do** — Give the bar a label through its own `children`; it fills the parent it is given, and a Container step sets its measure when the layout calls for it.
```tsx
Importing contacts
```
**Don't** — Don't hand-roll a bar from raw views; the kit's `Progress` already adapts its thickness, ends, and track anatomy per platform and stays themed.
```tsx
```
## Supported props
### ProgressProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | number \| undefined | no | | Completion as a 0..1 fraction (clamped). Ignored when `indeterminate`. Defaults to 0. |
| segments | number \| undefined | no | | A fixed number of discrete dashes, filled by the same fractional value. |
| indeterminate | boolean \| undefined | no | false | Indeterminate mode: ignore `value` and animate a sliding bar (a spinner on iOS). |
| children | ReactNode | no | | Optional title line rendered ABOVE the bar. Supplying it (with or without a `description`/`showValue`) builds the stacked header inside the control, so the caller never hand-composes a Row/Column + Typography around the bar. This is the "components own their label anatomy" contract, mirroring Checkbox/Radio/Switch's `description` slot. |
| description | ReactNode | no | | Optional muted secondary line rendered under the title. |
| showValue | boolean \| undefined | no | false | Render the percent (derived from `value`) as a right-aligned readout on the title line. Indeterminate has no measurable value, so this renders nothing then. |
| status | ReactNode | no | | A short status word on the title line, in the percent readout's place and type ("indeterminate", "strong", "paused"): what the bar means right now, where `description` is a sentence under the title. It shows while indeterminate too, and when both are passed the status wins the readout slot over `showValue`. |
| warning | boolean \| undefined | no | false | Tone axis (the active fill color; pick one, default is the call-to-action fill). `warning` paints the bar in the warning color, `danger` in the destructive color, for a metric over a soft threshold or a hard limit (a WIP meter over its cap); the track stays neutral. Pair with copy: the tone adds no accessible value. |
| danger | boolean \| undefined | no | false | Tone axis (the active fill color; pick one, default is the call-to-action fill). `warning` paints the bar in the warning color, `danger` in the destructive color, for a metric over a soft threshold or a hard limit (a WIP meter over its cap); the track stays neutral. Pair with copy: the tone adds no accessible value. |
| small | boolean \| undefined | no | false | Size axis (track thickness; pick one, default is the medium track). |
| large | boolean \| undefined | no | false | Size axis (track thickness; pick one, default is the medium track). |
| accessibilityLabel | string \| undefined | no | | Accessible description of what is progressing. |
| 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.
---
# QRCode
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/qrcode/qrcode.md`
Human reference: https://canvas.nannier.com/components/qrcode
## Public imports
```tsx
import { QRCode } 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.
Encodes a string as a scannable QR code. Built on react-native-svg,
so it renders the same on iOS, Android, and the web.
The code stays a fixed dark-on-white card so any camera can read it, whatever the app theme.
## Usage
```tsx
```
## Variants
### Small
```tsx
```
### Large
```tsx
```
## Do & Don't
### Keep it scannable
**Do** — Leave the fixed dark-on-white card in place so the code stays high-contrast for any camera.
```tsx
```
**Don't** — Shrinking a data-dense value to the small size drops the modules below a reliably scannable density.
```tsx
```
## Supported props
### QRCodeProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string | yes | | The string to encode: a URL, plain text, a token, etc. |
| small | boolean \| undefined | no | false | A compact 96px code. |
| large | boolean \| undefined | no | false | A large 200px code. |
| accessibilityLabel | string \| undefined | no | | Spoken equivalent for screen readers. A QR code is camera-only content, so without a label assistive tech announces nothing. Defaults to `QR code encoding ${value}`; pass a generic label (e.g. "QR code") when surfacing the raw value is undesirable. |
| 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.
---
# RadarChart
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/radar-chart/radar-chart.md`
Human reference: https://canvas.nannier.com/components/radar-chart
## Public imports
```tsx
import { RadarChart } 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 polygonal multi-axis comparison: concentric rings at nice tick fractions, a spoke per axis, and one closed polygon per series (ramp stroke over a soft matching wash), with the spoke labels just beyond the outer ring. Values align to `axes` by index and the accessible name folds every axis and value; press-to-inspect is deferred scope for this chart.
## Usage
```tsx
```
## Variants
### Single series, toned
```tsx
```
### Compact, no grid
```tsx
```
### Compact with spokes
```tsx
```
## Do & Don't
### RadarChart
**Do** - Compare a small number of profiles over the same axes, on one shared scale.
```tsx
```
**Don't** - Radar axes are unordered categories; a time series belongs on a LineChart, where the x axis carries the order.
```tsx
```
## Supported props
### RadarChartProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| axes | string[] | yes | | Spoke labels, clockwise from 12 o'clock. |
| series | ChartSeries[] | yes | | One polygon per series; values aligned to `axes` by index. |
| title | string \| undefined | no | | Optional heading shown above the plot. |
| max | number \| undefined | no | | Outer-ring maximum. Defaults to a nice bound above the data max. |
| success | boolean \| undefined | no | false | Tone (single-series only; pick one; default primary). Precedence: success > destructive. |
| destructive | boolean \| undefined | no | false | Tone (single-series only; 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). |
| hideLegend | boolean \| undefined | no | false | Hide the series legend (multi-series charts show one by default). |
| hideGrid | boolean \| undefined | no | false | Hide the rings and spokes (the labels always render). |
| spokes | boolean \| undefined | no | false | Join each outer vertex to the center with a radial guide. |
| formatValue | ((v: number) => string) \| undefined | no | | Formats 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.
---
# RadialBarChart
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/radial-bar-chart/radial-bar-chart.md`
Human reference: https://canvas.nannier.com/components/radial-bar-chart
## Public imports
```tsx
import { RadialBarChart } 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.
Concentric arc rings, one per category, outermost first: each pairs a muted full-circle track with a ramp-colored value arc revealed clockwise from 12 o'clock, and a column legend carries the formatted values. Every ring sweeps against the same `max` (the largest entry by default), so the chart compares attainment, not shares. Press a ring to select it (the others dim); press elsewhere to clear.
## Usage
```tsx
```
## Variants
### Compact, no legend
```tsx
```
### Inspected
```tsx
```
### Detailed attainment
```tsx
`${v}k`} data={[{label: "Phone", value: 64}, {label: "Tablet", value: 41}, {label: "Web", value: 23}]} />
```
## Do & Don't
### RadialBarChart
**Do** - Compare attainment on rings that share one sweep maximum, with the values in the legend.
```tsx
```
**Don't** - Rings encoding shares of a whole belong on PieChart; nested rings without a shared maximum cannot be compared by eye.
```tsx
```
## Supported props
### RadialBarChartProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| data | StackedSegment[] | yes | | One ring per entry, outer to inner. |
| label | string \| undefined | no | | What the rings measure (e.g. "Platform activation"); leads the accessible name. |
| max | number \| undefined | no | | The value of a full sweep. Defaults to the largest entry. |
| compact | boolean \| undefined | no | false | Density (omit for the default 160 disc; compact is 120). |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| hideLegend | boolean \| undefined | no | false | Hide the value legend (the accessible name still carries every value). |
| detailed | boolean \| undefined | no | false | A compact disc with a separate value column and shared maximum caption. |
| formatValue | ((v: number) => string) \| undefined | no | | Formats legend values (data formatting, not styling). |
| selected | number \| null \| undefined | no | | Press-to-inspect: the selected ring index (controlled). Pass null for none. |
| defaultSelected | number \| undefined | no | | Press-to-inspect: the initially selected ring (uncontrolled). |
| onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press selects a ring (or clears it with null). |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For sizing/composition only. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Radio
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/radio/radio.md`
Human reference: https://canvas.nannier.com/components/radio
## Public imports
```tsx
import { Radio, RadioGroup } 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.
Single-pick selection: stacked, inline, card-style.
One job, a platform's own control: iOS has no radio button, so there a single choice is a checkmark list, the inline picker of a grouped form. Each option's label leads and the chosen option carries a trailing check, and a RadioGroup's options form one inset-grouped section with a hairline between two rows. The web and Android keep the ring, and the role is `radio` on every platform: on the web an 18 px ring (16 `small`, 22 `large`) whose edge thickens in the accent around a hollow center when chosen, on Android Material 3's 20dp ring around an accent dot.
In glass mode the ring keeps its solid look on every platform: a ring is a small mark inside content, not a surface, so it draws with no pane and no rim. A `card` option is a content-layer pane, as a selectable Card is.
On iOS the list stays vertical when the group asks for a `row` (a list has no row form), and `card` options stay tiles that mark the choice with the same trailing check.
Pass `ref` to access the interactive radio row, preserving group navigation. 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. RadioGroup arrow keys continue to move focus and selection.
## Usage
```tsx
Hobby
Pro
Enterprise
```
## Variants
### Inline
`row` lays the options out in a wrapping row. The group fills its parent's width (the parent provides the bounds, as for any list or field), so the row wraps onto another line when the options run out of room instead of running past its container.
```tsx
Hobby
Pro
Enterprise
```
### Card
```tsx
Hobby
Pro
Enterprise
```
### Card icon and meta
A `card` radio takes an `icon` over its title, which turns the accent when it is chosen, and `equal` on a `row` group gives every option the same share of the row. Any radio takes `meta`, a short trailing note on the title's line (a version, a price, a badge).
```tsx
} meta="v4.0" description="Tested releases.">Stable
} meta="v4.1" description="Early features.">Beta
} meta="nightly" description="Every commit.">Canary
```
### Inline label
`inlineLabel` sets a `row` group's `label` at the start of the row, before the options, in place of the heading above them.
```tsx
Daily
default }>Weekly
Never
```
### With description
```tsx
Hobby
Pro
Enterprise
```
## Do & Don't
### Default selection
**Do**: Pre-select a sensible default so the common path needs no clicks, and name the set with the group's own `label`.
```tsx
Hobby
Pro
Enterprise
```
**Don't**: Leaving a radio group with nothing selected forces an extra decision and can submit empty.
```tsx
Plan
Hobby
Pro
Enterprise
```
### Stacked
**Do**: Pass a `description` and the control stacks the title over its secondary line for you, the mark aligned to the title
(the ring to its first line on the web and Android, the check centered on the row on iOS).
```tsx
Pro
```
**Don't**: Center-aligning the control floats it to the vertical middle of a two-line label, leaving it visually unattached to the title it controls.
```tsx
Pro
For growing teams that need more control.
```
### Inline
**Do**: Keep generous spacing between options and tighter spacing inside each so every label clearly pairs with its own control.
```tsx
Hobby
Pro
Enterprise
```
**Don't**: Cramped spacing between options makes each label blur into the next radio, so it is hard to tell which dot belongs to which choice.
```tsx
Hobby
Pro
Enterprise
```
### Card
**Do**: Give the selected card a primary border and tinted fill so the whole tile reads as chosen, not just the dot. Pass `card` and the control derives that treatment from its checked state automatically.
```tsx
Pro
Enterprise
```
**Don't**: When the selected card keeps the same plain border, only the tiny native dot signals the choice and the active card is easy to miss.
```tsx
Pro
For growing teams.
Enterprise
Advanced security.
```
## Supported props
### RadioGroupProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string \| number \| undefined | no | | The selected option's value (CONTROLLED). Omit for uncontrolled use. |
| defaultValue | string \| number \| undefined | no | | Initial selected value for uncontrolled use (a bare group selects on press). |
| onChange | ((value: string \| number) => void) \| undefined | no | | Fired with the newly selected option's value. |
| disabled | boolean \| undefined | no | false | Disable every radio in the group. |
| row | boolean \| undefined | no | false | Lay the options out in a wrapping row instead of the default column. iOS keeps its checkmark list vertical (a list has no row form); `card` options still sit in a row there. |
| equal | boolean \| undefined | no | false | With `row`: the options share the row equally and never wrap, a tile picker's even columns (card tiles 10 apart, plain options 20). |
| inlineLabel | boolean \| undefined | no | false | With `row` and a `label`: the label sits at the start of the row, before the options and centred on them, in the 500 13 px muted ink (a short setting's inline choice: "Digest ( ) Daily (o) Weekly"), instead of as a heading above. The `description` is not shown inline. |
| label | string \| undefined | no | | The group's name, rendered as a heading above the options. It also becomes the radiogroup's accessible name, so a screen reader announces what the set of options is about before reading them. 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. |
| children | ReactNode | no | | The `` options that make up the group. |
| testID | string \| undefined | no | | E2E hook forwarded to the group container. |
| style | StyleProp | no | | Outer layout composition only (width/flex within a parent), never a restyle hook. |
### RadioProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string \| number \| undefined | no | | This option's value within a RadioGroup. When the radio sits inside a ``, its checked state and selection are driven by the group matching this value; ignored for a standalone radio. |
| checked | boolean \| undefined | no | false | Whether this control is the selected option (controlled; standalone use). |
| selected | boolean \| undefined | no | false | Alias for `checked`, for callers that think in terms of "selected". |
| onChange | ((checked: boolean, event: GestureResponderEvent) => void) \| undefined | no | | Fired on press with the next checked value (always true for a radio). Web keyboard activation supports Space on release and Enter; RadioGroup also handles arrows. |
| testID | string \| undefined | no | | E2E hook forwarded to the pressable row. |
| children | ReactNode | no | | Label text (the option's title) shown beside the control. |
| description | ReactNode | no | | Optional muted secondary line rendered under the label, for the common title-plus-description option (a plan picker, a settings choice). Supplying it builds the stacked title/description layout inside the control, so the caller never hand-composes a Row + Column + two Typography nodes per option. |
| card | boolean \| undefined | no | false | Render the whole control as a selectable option CARD: the mark (the ring, or the trailing check on iOS) plus the title and description sit inside Card chrome (bordered, padded), and the ENTIRE card is the tap target. When this radio is the chosen option, the card takes Card's own `selected` treatment (a primary border and a soft primary tint), so the whole tile reads as chosen, not just the mark. Pairs with `description` for a plan/tier picker, and is still group-wired through `value` inside a ``. |
| meta | ReactNode | no | | A trailing detail on the title's line (a version, a price, a status pill): a string is set in the small mono meta type in the subtle ink, a node (a `Badge`) as it is. The title takes the row, so in a full-width option the detail sits at its end; in a `row` group's hugging option it follows the title. |
| icon | ReactNode | no | | A `card` tile's icon (a kit `Icon` with no tone of its own): the tile stacks the icon and the mark on one line above the title and description. The icon takes the accent text ink while the option is chosen and the subtle ink otherwise. Ignored without `card`. |
| small | boolean \| undefined | no | false | Size (pick one; default is the 18px control on the web). |
| large | boolean \| undefined | no | false | Size (pick one; default is the 18px control on the web). |
| disabled | boolean \| undefined | no | false | Dim the control and block presses. |
| 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 radio row, preserving group navigation. 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.
---
# RangeAreaChart
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/range-area-chart/range-area-chart.md`
Human reference: https://canvas.nannier.com/components/range-area-chart
## Public imports
```tsx
import { RangeAreaChart } 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 min/max envelope: a translucent band between each label's `low` and `high`, an optional solid `mid` line through it, and the standard scrub-to-inspect flag showing High, Mid, and Low. The y domain hugs the data rather than anchoring at zero, since an envelope is a range idiom, not a magnitude bar. Forecast bands, error envelopes, and daily temperature ranges all read this way.
## Usage
```tsx
```
## Variants
### Band only
```tsx
```
### Success tone, inspected
```tsx
```
## Do & Don't
### RangeAreaChart
**Do** - Encode uncertainty as the band and the estimate as the mid line, in one mark.
```tsx
```
**Don't** - Two separate lines for low and high leave the envelope unreadable as a region and double the legend.
```tsx
```
## Supported props
### RangeAreaChartProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| labels | string[] | yes | | Category labels along the x axis, one per data column. |
| data | RangePoint[] | yes | | One range per label, aligned by index. |
| label | string \| undefined | no | | Names the envelope (e.g. "Forecast"); leads the accessible name and the flag. |
| 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. |
| 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). |
| 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.
---
# Reveal
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/reveal/reveal.md`
Human reference: https://canvas.nannier.com/components/reveal
## Public imports
```tsx
import { Reveal, RevealGroup } 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 entrance for page content: what it wraps starts slightly offset and transparent, then travels into place and fades in when it reaches the viewport. It fires once and stays, so content that has arrived never leaves again.
Direction chooses the axis it travels from, with `fromBelow` the default and `fromAbove`, `fromLeft`, `fromRight` the alternatives (that is also the precedence order if more than one is passed). `pronounced` travels further, `brisk` arrives faster, and `deepInView` holds the entrance until the element is properly inside the viewport instead of firing as it enters.
Stagger is structural rather than numeric: wrap a set in `RevealGroup` and each child arrives after the one before it, so no call site ever computes a delay. `RevealGroup` renders no box of its own, so it can sit between a grid and the items the grid lays out.
The trigger is measured against the window rather than the nearest scrolling parent, so content sitting inside a short inner scroller can arrive before that scroller has been scrolled to it; reveal on the page scroll, which is what this is for. Under Reduce Motion the content renders its final frame immediately, with no measuring and no timers, and the stagger is skipped along with the motion.
## Usage
```tsx
Rises into place
```
## Variants
### From above
```tsx
Descends into place
```
### From the left
```tsx
Slides in from the left
```
### From the right
```tsx
Slides in from the right
```
### Pronounced
```tsx
Travels further before settling
```
### Brisk
```tsx
Arrives faster
```
### Deep in view
```tsx
Waits until it is properly in view
```
### Staggered group
```tsx
Measure
Reveal
Settle
```
### Replay
```tsx
{(pass) => (
Arriving again
)}
```
## Do & Don't
### stagger
**Do** - Wrap the set in a `RevealGroup` and let each item's position set its own rhythm.
```tsx
First
Second
Third
```
**Don't** - Leave a set ungrouped: every card fires at the same instant, so the row lands as one slab instead of a cascade.
```tsx
First
Second
Third
```
### direction
**Do** - Give a set one direction, so it arrives as a single gesture.
```tsx
Rises
Rises
```
**Don't** - Mix a direction per item; the set reads as scattered rather than as one movement.
```tsx
Left
Right
```
### scope
**Do** - Reveal the block, so its contents arrive together as one piece of content.
```tsx
One entrance
The card and its lines arrive as a single object.
```
**Don't** - Reveal each leaf inside one card; the card assembles itself in pieces in front of the reader.
```tsx
Three entrances
Every line arrives on its own schedule.
```
### the real thing
**Do** - Use the atom, so the entrance is one prop, plays on scroll, and honors Reduce Motion.
```tsx
Reveals itself
```
**Don't** - Hand-roll it with a wrapper and a hard-coded opacity: it never arrives, never watches the scroll position, and ignores Reduce Motion.
```tsx
Stuck half-arrived
```
## Supported props
### RevealGroupProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no | | The Reveals to stagger. Each direct child receives the next ordinal in document order, so a mapped list arrives one item after another with no per-item props. |
### RevealProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| fromBelow | boolean \| undefined | no | false | Rise into place from below. The default. |
| fromAbove | boolean \| undefined | no | false | Descend into place from above. |
| fromLeft | boolean \| undefined | no | false | Slide in from the left. Physical, and does not mirror under RTL (see above). |
| fromRight | boolean \| undefined | no | false | Slide in from the right. Physical, and does not mirror under RTL (see above). |
| pronounced | boolean \| undefined | no | false | Travel further before settling, for a hero or a section header. Omit for the default, shorter travel. |
| brisk | boolean \| undefined | no | false | Arrive faster, for dense sets where the eye should not wait. Omit for the default duration. |
| deepInView | boolean \| undefined | no | false | Wait until the element is properly inside the viewport before starting, instead of firing as it enters. |
| children | ReactNode | no | | The content to reveal. |
| style | LayoutStyle | no | | Outer layout composition ONLY: how this wrapper sits in its parent (a `minWidth` of 0 so a grid child may shrink below its content, a `height` of 100% so cards in a row stay even, a flex share). It is NOT a restyle hook: the entrance itself is chosen entirely with the boolean props above, and the revealed content styles itself. A wrapper that cannot be told how to sit in its parent forces callers to add a second wrapper around it, which is worse. |
| testID | string \| undefined | no | | E2E hook forwarded to the wrapper. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Row & Column
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/layout/layout.md`
Human reference: https://canvas.nannier.com/components/row-column
## Public imports
```tsx
import { Column, Row } 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 layout primitives. `Row` lays children out horizontally, `Column`
vertically. Both own arrangement through semantic boolean axes, a gap scale
(`flush` / `tight` / `snug` / `cozy` / `relaxed` / `loose`), main-axis
distribution (`center`, `between`, …), cross-axis alignment (`alignCenter`,
`baseline`, …), and `wrap` / `fill` / `grow` / `shrink`, so a call site never
hand-rolls `flexDirection`, `gap`, or `alignItems`.
The Argus reference adds intermediate spacing steps without rounding its layout:
`fine` 3, `dense` 5, `compact` 6, `close` 7, `nested` 9, `comfortable` 10,
`balanced` 11, `roomy` 14, `airy` 18, `spacious` 20, `generous` 22,
`section` 26, `panel` 28, `page` 30, `apart` 32 (the space between the side-by-side
panels of one composition), `ample` 40 (between a section page's hero and the body under
it, and between the hero's halves) and `broad` 48 (between the two halves of a page-wide split). Existing named steps keep their values.
Larger active steps take precedence.
`ample` (40) is the step between a section page's hero and the body under it, and between
the hero's halves (the Canvas v4 section pages). `chapter` (64) is the widest step: the
space between a page's chapters, the major sections of a long page (a component detail
page's Playground, Props and Examples). It wins over every other gap step, so the
precedence runs `chapter` > `broad` > `ample` > `apart` > `page` > `panel` > `section` >
`loose` and on down the scale to `flush`. Row, Column and Grid all take them.
`rowsTight`, `rowsComfortable`, `rowsSnug`, `rowsCozy`, `rowsSection` and `rowsPanel` set a
wrapped or stacked row's vertical gap to 4, 10, 8, 12, 26 and 28 independently of its main
gap (a stacked `Row stacks` lays its children down that vertical gap, so a hero whose
halves sit 40 apart beside each other and 28 apart stacked is `ample rowsPanel`). Application inset
roles preserve identity headers (24 top/horizontal, 18 bottom), action footers
(16 vertical, 24 horizontal), theme controls (14 top, 26 horizontal), status bands
(10 vertical, 28 horizontal), and navigation close controls (12 top/horizontal):
`insetIdentity`, `insetActions`, `insetTheme`, `insetStatus`, and `insetTabs`.
## Usage
```tsx
Save
Cancel
```
## Variants
### Vertical stack
```tsx
Save
Cancel
```
### Chapters
The widest gap, between the major sections of a long page.
```tsx
Playground
Props
Examples
```
### Centered row
```tsx
schema
role
```
### Space between
```tsx
Request ID
req_8f2c10ab
```
### Wrap
```tsx
alpha
beta
gamma
delta
epsilon
zeta
```
### Shrink a child to the row
React Native gives every box `flexShrink: 0`, the opposite of the web's flex
default, so a Row child sized by a long sentence keeps that sentence's full
single-line width and spills past the row's edge, where the nearest clipping
ancestor cuts it mid-word. `shrink` hands the row's width back to the text: the
child gives way and the copy wraps. Reach for it on the copy in a
heading-beside-actions row, or any Row child whose width should follow the row
rather than its own longest line. It is not `fill`: `fill` also zeroes the flex
basis, so in a `wrap` row every child then fits on one line and the actions stop
wrapping below the copy.
```tsx
Dashboard
Identity platform overview. Each widget reports its own window.
Export
```
### Padded surround
```tsx
Padded surround
```
### Stacks at narrow widths
`stacks` renders the Row as a Column when the row's own container is at or
below `stackBreakpoint` (default `sm` = 640): container-measured, so it stacks
inside a narrow desktop column too, and the same gap, distribution, alignment,
and padding props apply to the new axes. Children keep their own sizing, which
makes `stacks` the tool for content-sized rows (a toolbar, a label beside its
actions); equal-width tiles that should renumber columns belong to `Grid`. A
Column never needs the inverse: a Column that must become a Row is a Row that
stacks.
```tsx
New run
```
### Spans (twelfths of the Row)
A direct child's `span` is its width in twelfths of the Row, Bootstrap's
`col-n`: `span={6}` is half, `span={4}` a third, `span={8}` two thirds. The Row
measures its own width and hands each spanning child a px cell with the gaps
accounted for, so two `span={6}` children and the gap between them fill the row
exactly, and spans past twelve wrap onto the next line. Fill components inside a
span (a field, a card) fill the cell.
```tsx
Eight of twelve
Four
```
### Spans that stack
`stacks` and spans compose: below the breakpoint the Row becomes a Column and
every child is full width, the `col-12 col-md-6` idiom without a second prop.
Crossing the breakpoint changes the layout only: every child stays mounted, so
a field keeps its text and focus when a window is resized across it or a phone
switches from the server's desktop layout after hydration. This is why a
two-pane screen is a `Row stacks` of spans rather than a Row on one side of a
`useFormFactor()` branch and a Column on the other, which remounts everything
inside.
```tsx
Left
Right
```
### Weights (a share of the free width)
A direct child's `weight` is its share of the Row's width after the gaps, CSS
grid's `fr`: `weight={1.7}` beside `weight={1}` splits the row 1.7 to 1 whatever
either side holds, for the ratios twelfths cannot name. A weighted box has no
content floor, so text inside it wraps rather than pushing its neighbour. It
replaces `fill` and `grow` on the same box, a box takes a `span` or a `weight`,
not both, and once a `stacks` Row has stacked every weighted child is the whole
line.
```tsx
The figure
Its legend
```
## Do & Don't
### Gap
**Do** — Use the gap scale so spacing tracks the kit's spacing tokens.
```tsx
Save
Cancel
```
**Don't** — Hand-roll `flexDirection` and a raw `gap`; that is a styling escape hatch.
```tsx
Save
Cancel
```
### Alignment
**Do** — Reach for the main- and cross-axis booleans (`between`, `alignCenter`).
```tsx
Total
$1,240.00
```
**Don't** — Push content around with raw `justifyContent` and `marginLeft`.
```tsx
Total
$1,240.00
```
### Overflow
**Do** — Mark the child that may give way with `shrink`, so its text wraps to the row.
```tsx
Sessions
Every session Kratos is holding open, newest first.
Revoke all
```
**Don't** — Reach into the raw view to unstick it; that is a styling escape hatch.
```tsx
Sessions
Every session Kratos is holding open, newest first.
Revoke all
```
## Supported props
### FlexProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no | | |
| rowsTight | boolean \| undefined | no | false | |
| rowsComfortable | boolean \| undefined | no | false | |
| rowsSnug | boolean \| undefined | no | false | |
| rowsCozy | boolean \| undefined | no | false | |
| rowsSection | boolean \| undefined | no | false | |
| rowsPanel | boolean \| undefined | no | false | A wrapped or stacked row's vertical gap of 28, apart from its main gap: the hand-off's section hero (`gap:28px 40px`), whose halves sit 40 apart beside each other and 28 apart once stacked. |
| insetIdentity | boolean \| undefined | no | false | Application identity header inset. |
| insetActions | boolean \| undefined | no | false | Application action footer inset. |
| insetTheme | boolean \| undefined | no | false | Theme controls inset. |
| insetStatus | boolean \| undefined | no | false | Status band inset. |
| insetTabs | boolean \| undefined | no | false | Navigation close controls inset. |
| fine | boolean \| undefined | no | false | |
| dense | boolean \| undefined | no | false | |
| compact | boolean \| undefined | no | false | |
| close | boolean \| undefined | no | false | |
| nested | boolean \| undefined | no | false | |
| comfortable | boolean \| undefined | no | false | |
| balanced | boolean \| undefined | no | false | |
| roomy | boolean \| undefined | no | false | |
| airy | boolean \| undefined | no | false | |
| spacious | boolean \| undefined | no | false | |
| generous | boolean \| undefined | no | false | |
| section | boolean \| undefined | no | false | |
| panel | boolean \| undefined | no | false | |
| page | boolean \| undefined | no | false | |
| apart | boolean \| undefined | no | false | The step between side-by-side panels of one composition (32), the hand-off's widest grid gap. |
| ample | boolean \| undefined | no | false | The step between a section page's hero and the body under it, and between the hero's halves (40): the hand-off's section pages (`gap:28px 40px` on the hero, `margin-bottom:40px` under it). |
| broad | boolean \| undefined | no | false | The gap between the two halves of a page-wide split (48): the hand-off's hero grid. |
| chapter | boolean \| undefined | no | false | The widest gap step (64): the space between a page's chapters, the major sections of a long docs page (a component detail page's Playground, Props and Examples), wider than `page` so each chapter reads as its own block. Wins over every other gap step. |
| flush | boolean \| undefined | no | false | Gap scale (pick one; default `snug`). Maps to the shared spacing tokens. |
| tight | boolean \| undefined | no | false | Gap scale (pick one; default `snug`). Maps to the shared spacing tokens. |
| snug | boolean \| undefined | no | false | Gap scale (pick one; default `snug`). Maps to the shared spacing tokens. |
| cozy | boolean \| undefined | no | false | Gap scale (pick one; default `snug`). Maps to the shared spacing tokens. |
| relaxed | boolean \| undefined | no | false | Gap scale (pick one; default `snug`). Maps to the shared spacing tokens. |
| loose | boolean \| undefined | no | false | Gap scale (pick one; default `snug`). Maps to the shared spacing tokens. |
| start | boolean \| undefined | no | false | Main-axis distribution / justifyContent (pick one; default `start`). |
| center | boolean \| undefined | no | false | Main-axis distribution / justifyContent (pick one; default `start`). |
| end | boolean \| undefined | no | false | Main-axis distribution / justifyContent (pick one; default `start`). |
| between | boolean \| undefined | no | false | Main-axis distribution / justifyContent (pick one; default `start`). |
| around | boolean \| undefined | no | false | Main-axis distribution / justifyContent (pick one; default `start`). |
| evenly | boolean \| undefined | no | false | Main-axis distribution / justifyContent (pick one; default `start`). |
| alignStart | boolean \| undefined | no | false | Cross-axis alignment / alignItems (pick one; default `stretch`, RN's native default). |
| alignCenter | boolean \| undefined | no | false | Cross-axis alignment / alignItems (pick one; default `stretch`, RN's native default). |
| alignEnd | boolean \| undefined | no | false | Cross-axis alignment / alignItems (pick one; default `stretch`, RN's native default). |
| baseline | boolean \| undefined | no | false | Cross-axis alignment / alignItems (pick one; default `stretch`, RN's native default). |
| stretch | boolean \| undefined | no | false | Cross-axis alignment / alignItems (pick one; default `stretch`, RN's native default). |
| wrap | boolean \| undefined | no | false | Orthogonal modifiers (stack freely). |
| fill | boolean \| undefined | no | false | Orthogonal modifiers (stack freely). |
| grow | boolean \| undefined | no | false | Orthogonal modifiers (stack freely). |
| shrink | boolean \| undefined | no | false | Let this box shrink below its own content width when the line runs out of room (`flexShrink: 1`), so a long text child wraps instead of overflowing. React Native gives every box `flexShrink: 0`, which is the opposite of the web's flex default: inside a Row, a Column holding a sentence keeps its MAX-CONTENT width and spills past the row's edge, where the nearest clipping ancestor cuts it mid-word. `shrink` is the opt-in that hands the row's width back to the text. Reach for it on the copy in a heading-beside-actions row, a label beside a control, any Row child whose width should follow the row rather than its own longest line. It is not `fill`. `fill` (flex: 1) also zeroes the flex BASIS, so in a `wrap` row every child then fits on one line and the actions stop wrapping below the copy; `shrink` leaves the basis at the content size, so the row still breaks where it did and only the over-wide child gives way. Redundant alongside `fill`, which already shrinks. |
| stacks | boolean \| undefined | no | false | Responsive (Row only): render as a Column when the row's own CONTAINER is at or below `stackBreakpoint` (default `sm` = 640). Container-measured with a viewport seed for the first frame, so a Row inside a narrow desktop column stacks too, not just on phones. When stacked, the Row IS the Column with the same props: gap, justify, align, and padding apply to the new axes, the default `stretch` cross-axis makes children full width, and `wrap` is inert. Ignored (with a DEV warning) on Column: a Column that must become a Row is a Row that stacks. |
| stackBreakpoint | BreakpointKey \| undefined | no | | The breakpoint at and below which `stacks` flips to a column (default `"sm"`). Only meaningful with `stacks` (DEV warns without it). |
| span | number \| undefined | no | | Width in twelfths of the parent Row (Bootstrap `.col-n`): `span={6}` is half the row, `span={4}` a third, `span={12}` the full width. The Row measures its own width and gives this box a px cell, gaps included in the arithmetic, so two `span={6}` children plus the gap between them fill the row exactly. Spans over twelve wrap onto the next line. A Row with `stacks` ignores spans once it has stacked (every child is then full width). Only meaningful on a direct child of a Row (DEV warns elsewhere); in a Column, a width is a Container step. A child without a span, `fill`, or `grow` hugs its content (`.col-auto`): a fill component inside it collapses, so give it one of those. |
| weight | number \| undefined | no | | A share of the parent Row's free width, CSS grid's `minmax(0, Nfr)`: siblings with `weight={1.7}` and `weight={1}` split the row 1.7 to 1 after the gap, whatever their content, and neither holds a content floor. It is the split a ratio of twelfths cannot name (the Canvas v4 component page's anatomy is 1.7 to 1, where `span` gives 7 to 5). A positive number; it replaces `fill` and `grow` on the same box, and a box takes a `span` or a `weight`, not both. Only meaningful on a direct child of a Row (DEV warns elsewhere); once a `stacks` Row has stacked, the box is the whole line and the weight is ignored. |
| indent | boolean \| undefined | no | false | Indent the whole stack by one control gutter (24: a control box plus the row gap), so a nested option group lines up under its parent control's label instead of its box. For nesting checkboxes/radios under a "select all" parent. |
| padTight | boolean \| undefined | no | false | Padding scale (pick one; omit for none). |
| pad | boolean \| undefined | no | false | Padding scale (pick one; omit for none). |
| padLoose | boolean \| undefined | no | false | Padding scale (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 sizing and composition only (a `maxWidth` on a shell, a `flex` inside an app frame), never for styling or spacing. Gap, margin, padding, and flex layout come from the props above, and the codegen guardrail rejects those keys in `style`. Prefer a Container step or a `span` where one fits. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# RowMenu
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/row-menu/row-menu.md`
Human reference: https://canvas.nannier.com/components/row-menu
## Public imports
```tsx
import { RowMenu } 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 action menu items and navigation links.
On the web the ··· trigger is a 32px square on the field hairline, washed while its
menu is open or under the pointer, and it opens a card menu 220 wide: rows 34 tall
with a muted leading glyph beside the label, and a destructive row in red.
iOS and
Android keep their platform context menus in the theme's colours.
In glass mode the menu is a dense-layer glass card beside its glyph; a row's
callback fires the moment it is pressed. Solid mode paints the skin's own menu.
Board's card menus are RowMenus and behave the same.
## Usage
```tsx
```
## Variants
### Links
```tsx
```
### Section label
```tsx
```
### Leading icons
```tsx
```
### Disabled item
Mark an item `disabled` when its action is unavailable in the current context; the row dims,
does not fire `onSelect`, keeps the menu open, and is announced as disabled.
```tsx
```
### Trailing hints
A row can end in a keyboard `shortcut` (the mono face in the third ink; a destructive
row's in its own ink, dimmed), a `submenu` chevron, a soft accent `badge` or a quiet mono
`meta`.
```tsx
```
### Inline
`inline` draws the menu in place, with no ··· trigger and no overlay: the rows sit in
the menu's own panel, filling the width the parent gives them. With `links` it is a
grouped link list instead (below).
```tsx
```
### Inline links
`inline links` is a grouped link list: rows split by hairlines, each ending in its
`badge` or `meta` and a chevron.
```tsx
```
## Do & Don't
### When to use
**Do** — Collapse per-row actions behind a ··· trigger; keep Delete separated and danger-colored.
```tsx
```
**Don't** — Splaying every row action inline multiplies visual noise across every table row.
```tsx
Edit
Duplicate
Delete
```
### Actions
**Do** — Click an item: place destructive actions last, color them, and split them off with a divider.
```tsx
```
**Don't** — Click an item: a destructive action sandwiched between routine ones invites a costly misclick.
```tsx
```
### Links
**Do** — Pass `links` so rows render as real navigation links a browser can open in a new tab or bookmark.
```tsx
```
**Don't** — Buttons can't be opened in a new tab, bookmarked, or middle-clicked, navigation needs real links.
```tsx
```
## Supported props
### RowMenuProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | RowMenuItem[] | yes | | The menu rows, top to bottom. |
| open | boolean \| undefined | no | false | Controlled open state. Omit for uncontrolled (the trigger toggles it). |
| onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes. |
| links | boolean \| undefined | no | false | Render rows as navigation links rather than action buttons. |
| inline | boolean \| undefined | no | false | The menu drawn in place, with no ⋯ trigger and no overlay: the action rows in the menu's own panel, filling the width the parent gives it. With `links` it is a grouped link list instead: hairline-split rows, each ending in its `badge` or `meta` and a chevron. `open`, `onOpenChange` and `triggerLabel` do nothing here. |
| sectionLabel | string \| undefined | no | | Show a muted section label heading the menu. |
| onSelect | ((item: RowMenuItem, index: number) => void) \| undefined | no | | Fired with the selected item and its index when a row is pressed. |
| triggerLabel | string \| undefined | no | "More options" | Accessible name for the icon-only ⋯ trigger. Defaults to "More options". |
| 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.
---
# ScatterPlot
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/scatter-plot/scatter-plot.md`
Human reference: https://canvas.nannier.com/components/scatter-plot
## Public imports
```tsx
import { ScatterPlot } 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.
Numeric x/y point clouds with nice ticks on both axes and horizontal gridlines. Multi-series clouds use the series tokens; pressing near a point rings it and flags its coordinates.
## Usage
```tsx
```
## Variants
### Compact single series
```tsx
```
## Do & Don't
### Series per group
**Do**: Split the points into one series per group; each takes its own series color and a legend entry, so the clusters can be told apart.
```tsx
```
**Don't**: Pour every group into one series; the points share one color and no legend, and the clusters run together.
```tsx
```
## Supported props
### ScatterPlotProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| series | ScatterSeries[] | yes | | The point clouds; colors follow the chart-1..8 tokens in fixed order. |
| title | string \| undefined | no | | Optional heading shown above the plot. |
| 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). |
| 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 | ScatterSelection \| null \| undefined | no | | Press-to-inspect: the selected point (controlled). Pass null for none. |
| defaultSelected | ScatterSelection \| undefined | no | | Press-to-inspect: the initially selected point (uncontrolled). |
| onSelect | ((selection: ScatterSelection \| null) => void) \| undefined | no | | Fired when a press selects a point (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.
---
# ScrollView
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/scroll-view/scroll-view.md`
Human reference: https://canvas.nannier.com/components/scroll-view
## Public imports
```tsx
import { ScrollView } 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 scrollable container for content larger than its bounds: vertical by default, `horizontal` for a row.
Unlike a plain View, a ScrollView lets its children exceed its size and scroll. Style the frame with `style` and the inner content with `contentContainerStyle`. In Chromium and Firefox a scroller with nothing focusable inside it is a keyboard stop of its own, so arrow keys can scroll it; the browser draws its focus ring in the theme's `ring` colour, 2 px off the frame, like every kit control.
## Usage
```tsx
```
## Variants
### Horizontal
```tsx
```
## Do & Don't
### Bounded height
**Do**: Give the ScrollView a bounded height, its own or its parent's; the content past that edge scrolls inside it.
```tsx
Deploy started
Dependencies restored from cache
Tests passed
Image pushed to the registry
Rollout reached half the fleet
Rollout complete
```
**Don't**: Leave the height to the content; an unbounded ScrollView grows to fit all of it, so nothing scrolls and the page grows instead.
```tsx
Deploy started
Dependencies restored from cache
Tests passed
Image pushed to the registry
Rollout reached half the fleet
Rollout complete
```
## Supported props
### ScrollView host props
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| contentContainerStyle | StyleProp | no | | These styles will be applied to the scroll view content container which wraps all of the child views. Example: return ( ); ... const styles = StyleSheet.create({ contentContainer: { paddingVertical: 20 } }); |
| innerViewRef | React.RefObject \| undefined | no | | A ref to the inner View element of the ScrollView. This should be used instead of calling `getInnerViewRef`. |
| scrollViewRef | React.RefObject \| undefined | no | | A ref to the Native ScrollView component. This ref can be used to call all of ScrollView's public methods, in addition to native methods like measure, measureLayout, etc. |
| decelerationRate | number \| "normal" \| "fast" \| undefined | no | | A floating-point number that determines how quickly the scroll view decelerates after the user lifts their finger. You may also use string shortcuts `"normal"` and `"fast"` which match the underlying iOS settings for `UIScrollViewDecelerationRateNormal` and `UIScrollViewDecelerationRateFast` respectively. - `'normal'`: 0.998 on iOS, 0.985 on Android (the default) - `'fast'`: 0.99 on iOS, 0.9 on Android |
| horizontal | boolean \| null \| undefined | no | | When true the scroll view's children are arranged horizontally in a row instead of vertically in a column. The default value is false. |
| invertStickyHeaders | boolean \| undefined | no | | If sticky headers should stick at the bottom instead of the top of the ScrollView. This is usually used with inverted ScrollViews. |
| keyboardDismissMode | "none" \| "interactive" \| "on-drag" \| undefined | no | | Determines whether the keyboard gets dismissed in response to a drag. - 'none' (the default) drags do not dismiss the keyboard. - 'onDrag' the keyboard is dismissed when a drag begins. - 'interactive' the keyboard is dismissed interactively with the drag and moves in synchrony with the touch; dragging upwards cancels the dismissal. |
| keyboardShouldPersistTaps | boolean \| "handled" \| "always" \| "never" \| undefined | no | | Determines when the keyboard should stay visible after a tap. - 'never' (the default), tapping outside of the focused text input when the keyboard is up dismisses the keyboard. When this happens, children won't receive the tap. - 'always', the keyboard will not dismiss automatically, and the scroll view will not catch taps, but children of the scroll view can catch taps. - 'handled', the keyboard will not dismiss automatically when the tap was handled by a children, (or captured by an ancestor). - false, deprecated, use 'never' instead - true, deprecated, use 'always' instead |
| onContentSizeChange | ((contentWidth: number, contentHeight: number) => void) \| undefined | no | | Called when scrollable content view of the ScrollView changes. Handler function is passed the content width and content height as parameters: (contentWidth, contentHeight) It's implemented using onLayout handler attached to the content container which this ScrollView renders. |
| onScroll | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Fires at most once per frame during scrolling. |
| onScrollBeginDrag | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Fires if a user initiates a scroll gesture. |
| onScrollEndDrag | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Fires when a user has finished scrolling. |
| onMomentumScrollEnd | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Fires when scroll view has finished moving |
| onMomentumScrollBegin | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Fires when scroll view has begun moving |
| pagingEnabled | boolean \| undefined | no | | When true the scroll view stops on multiples of the scroll view's size when scrolling. This can be used for horizontal pagination. The default value is false. |
| scrollEnabled | boolean \| undefined | no | | When false, the content does not scroll. The default value is true |
| removeClippedSubviews | boolean \| undefined | no | | Experimental: When true offscreen child views (whose `overflow` value is `hidden`) are removed from their native backing superview when offscreen. This can improve scrolling performance on long lists. The default value is false. |
| showsHorizontalScrollIndicator | boolean \| undefined | no | | When true, shows a horizontal scroll indicator. |
| showsVerticalScrollIndicator | boolean \| undefined | no | | When true, shows a vertical scroll indicator. |
| stickyHeaderHiddenOnScroll | boolean \| undefined | no | | When true, Sticky header is hidden when scrolling down, and dock at the top when scrolling up. |
| style | StyleProp | no | | Style |
| refreshControl | React.ReactElement> \| undefined | no | | A RefreshControl component, used to provide pull-to-refresh functionality for the ScrollView. |
| snapToInterval | number \| undefined | no | | When set, causes the scroll view to stop at multiples of the value of `snapToInterval`. This can be used for paginating through children that have lengths smaller than the scroll view. Used in combination with `snapToAlignment` and `decelerationRate="fast"`. Overrides less configurable `pagingEnabled` prop. |
| snapToOffsets | number[] \| undefined | no | | When set, causes the scroll view to stop at the defined offsets. This can be used for paginating through variously sized children that have lengths smaller than the scroll view. Typically used in combination with `decelerationRate="fast"`. Overrides less configurable `pagingEnabled` and `snapToInterval` props. |
| snapToStart | boolean \| undefined | no | | Use in conjunction with `snapToOffsets`. By default, the beginning of the list counts as a snap offset. Set `snapToStart` to false to disable this behavior and allow the list to scroll freely between its start and the first `snapToOffsets` offset. The default value is true. |
| snapToEnd | boolean \| undefined | no | | Use in conjunction with `snapToOffsets`. By default, the end of the list counts as a snap offset. Set `snapToEnd` to false to disable this behavior and allow the list to scroll freely between its end and the last `snapToOffsets` offset. The default value is true. |
| stickyHeaderIndices | number[] \| undefined | no | | An array of child indices determining which children get docked to the top of the screen when scrolling. For example passing `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the top of the scroll view. This property is not supported in conjunction with `horizontal={true}`. |
| disableIntervalMomentum | boolean \| undefined | no | | When true, the scroll view stops on the next index (in relation to scroll position at release) regardless of how fast the gesture is. This can be used for horizontal pagination when the page is less than the width of the ScrollView. The default value is false. |
| disableScrollViewPanResponder | boolean \| undefined | no | | When true, the default JS pan responder on the ScrollView is disabled, and full control over touches inside the ScrollView is left to its child components. This is particularly useful if `snapToInterval` is enabled, since it does not follow typical touch patterns. Do not use this on regular ScrollView use cases without `snapToInterval` as it may cause unexpected touches to occur while scrolling. The default value is false. |
| StickyHeaderComponent | React.ComponentType \| undefined | no | | A React Component that will be used to render sticky headers, should be used together with stickyHeaderIndices. You may need to set this component if your sticky header uses custom transforms, for example, when you want your list to have an animated and hidable header. If component have not been provided, the default ScrollViewStickyHeader component will be used. |
| children | React.ReactNode | no | | |
| hitSlop | number \| Insets \| null \| undefined | no | | This defines how far a touch event can start away from the view. Typical interface guidelines recommend touch targets that are at least 30 - 40 points/density-independent pixels. If a Touchable view has a height of 20 the touchable height can be extended to 40 with hitSlop={{top: 10, bottom: 10, left: 0, right: 0}} NOTE The touch area never extends past the parent view bounds and the Z-index of sibling views always takes precedence if a touch hits two overlapping views. |
| id | string \| undefined | no | | Used to reference react managed views from native code. |
| needsOffscreenAlphaCompositing | boolean \| undefined | no | | Whether this view needs to rendered offscreen and composited with an alpha in order to preserve 100% correct colors and blending behavior. The default (false) falls back to drawing the component and its children with an alpha applied to the paint used to draw each element instead of rendering the full component offscreen and compositing it back with an alpha value. This default may be noticeable and undesired in the case where the View you are setting an opacity on has multiple overlapping elements (e.g. multiple overlapping Views, or text and a background). Rendering offscreen to preserve correct alpha behavior is extremely expensive and hard to debug for non-native developers, which is why it is not turned on by default. If you do need to enable this property for an animation, consider combining it with renderToHardwareTextureAndroid if the view contents are static (i.e. it doesn't need to be redrawn each frame). If that property is enabled, this View will be rendered off-screen once, saved in a hardware texture, and then composited onto the screen with an alpha each frame without having to switch rendering targets on the GPU. |
| onLayout | ((event: LayoutChangeEvent) => void) \| undefined | no | | Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}. |
| pointerEvents | "box-none" \| "none" \| "box-only" \| "auto" \| undefined | no | | In the absence of auto property, none is much like CSS's none value. box-none is as if you had applied the CSS class: .box-none { pointer-events: none; } .box-none * { pointer-events: all; } box-only is the equivalent of .box-only { pointer-events: all; } .box-only * { pointer-events: none; } But since pointerEvents does not affect layout/appearance, and we are already deviating from the spec by adding additional modes, we opt to not include pointerEvents on style. On some platforms, we would need to implement it as a className anyways. Using style or not is an implementation detail of the platform. |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests. |
| nativeID | string \| undefined | no | | Used to reference react managed views from native code. |
| collapsable | boolean \| undefined | no | | Views that are only used to layout their children or otherwise don't draw anything may be automatically removed from the native hierarchy as an optimization. Set this property to false to disable this optimization and ensure that this View exists in the native view hierarchy. |
| collapsableChildren | boolean \| undefined | no | | Setting to false prevents direct children of the view from being removed from the native view hierarchy, similar to the effect of setting `collapsable={false}` on each child. |
| onBlur | ((e: BlurEvent) => void) \| null \| undefined | no | | Callback that is called when the view is blurred. Note: This will only be called if the view is focusable. |
| onFocus | ((e: FocusEvent) => void) \| null \| undefined | no | | Callback that is called when the view is focused. Note: This will only be called if the view is focusable. |
| renderToHardwareTextureAndroid | boolean \| undefined | no | | Whether this view should render itself (and all of its children) into a single hardware texture on the GPU. On Android, this is useful for animations and interactions that only modify opacity, rotation, translation, and/or scale: in those cases, the view doesn't have to be redrawn and display lists don't need to be re-executed. The texture can just be re-used and re-composited with different parameters. The downside is that this can use up limited video memory, so this prop should be set back to false at the end of the interaction/animation. |
| focusable | boolean \| undefined | no | | Whether this `View` should be focusable with a non-touch input device, eg. receive focus with a hardware keyboard. |
| tabIndex | 0 \| -1 \| undefined | no | | Indicates whether this `View` should be focusable with a non-touch input device, eg. receive focus with a hardware keyboard. See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex for more details. Supports the following values: - 0 (View is focusable) - -1 (View is not focusable) |
| shouldRasterizeIOS | boolean \| undefined | no | | Whether this view should be rendered as a bitmap before compositing. On iOS, this is useful for animations and interactions that do not modify this component's dimensions nor its children; for example, when translating the position of a static view, rasterization allows the renderer to reuse a cached bitmap of a static view and quickly composite it during each frame. Rasterization incurs an off-screen drawing pass and the bitmap consumes memory. Test and measure when using this property. |
| isTVSelectable | boolean \| undefined | no | | *(Apple TV only)* When set to true, this view will be focusable and navigable using the Apple TV remote. |
| hasTVPreferredFocus | boolean \| undefined | no | | *(Apple TV only)* May be set to true to force the Apple TV focus engine to move focus to this view. |
| tvParallaxShiftDistanceX | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. |
| tvParallaxShiftDistanceY | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. |
| tvParallaxTiltAngle | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 0.05. |
| tvParallaxMagnification | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 1.0. |
| onStartShouldSetResponder | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Does this view want to become responder on the start of a touch? |
| onMoveShouldSetResponder | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Called for every touch move on the View when it is not the responder: does this view want to "claim" touch responsiveness? |
| onResponderEnd | ((event: GestureResponderEvent) => void) \| undefined | no | | If the View returns true and attempts to become the responder, one of the following will happen: |
| onResponderGrant | ((event: GestureResponderEvent) => void) \| undefined | no | | The View is now responding for touch events. This is the time to highlight and show the user what is happening |
| onResponderReject | ((event: GestureResponderEvent) => void) \| undefined | no | | Something else is the responder right now and will not release it |
| onResponderMove | ((event: GestureResponderEvent) => void) \| undefined | no | | The user is moving their finger |
| onResponderRelease | ((event: GestureResponderEvent) => void) \| undefined | no | | Fired at the end of the touch, ie "touchUp" |
| onResponderStart | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onResponderTerminationRequest | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Something else wants to become responder. Should this view release the responder? Returning true allows release |
| onResponderTerminate | ((event: GestureResponderEvent) => void) \| undefined | no | | The responder has been taken from the View. Might be taken by other views after a call to onResponderTerminationRequest, or might be taken by the OS without asking (happens with control center/ notification center on iOS) |
| onStartShouldSetResponderCapture | ((event: GestureResponderEvent) => boolean) \| undefined | no | | onStartShouldSetResponder and onMoveShouldSetResponder are called with a bubbling pattern, where the deepest node is called first. That means that the deepest component will become responder when multiple Views return true for *ShouldSetResponder handlers. This is desirable in most cases, because it makes sure all controls and buttons are usable. However, sometimes a parent will want to make sure that it becomes responder. This can be handled by using the capture phase. Before the responder system bubbles up from the deepest component, it will do a capture phase, firing on*ShouldSetResponderCapture. So if a parent View wants to prevent the child from becoming responder on a touch start, it should have a onStartShouldSetResponderCapture handler which returns true. |
| onMoveShouldSetResponderCapture | ((event: GestureResponderEvent) => boolean) \| undefined | no | | onStartShouldSetResponder and onMoveShouldSetResponder are called with a bubbling pattern, where the deepest node is called first. That means that the deepest component will become responder when multiple Views return true for *ShouldSetResponder handlers. This is desirable in most cases, because it makes sure all controls and buttons are usable. However, sometimes a parent will want to make sure that it becomes responder. This can be handled by using the capture phase. Before the responder system bubbles up from the deepest component, it will do a capture phase, firing on*ShouldSetResponderCapture. So if a parent View wants to prevent the child from becoming responder on a touch start, it should have a onStartShouldSetResponderCapture handler which returns true. |
| onTouchStart | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchMove | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchEnd | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchCancel | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchEndCapture | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onPointerEnter | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerEnterCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerLeave | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerLeaveCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerMove | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerMoveCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerCancel | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerCancelCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerDown | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerDownCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerUp | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerUpCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| accessible | boolean \| undefined | no | | When true, indicates that the view is an accessibility element. By default, all the touchable elements are accessible. |
| accessibilityActions | readonly Readonly<{ name: AccessibilityActionName \| string; label?: string \| undefined; }>[] \| undefined | no | | Provides an array of custom actions available for accessibility. |
| accessibilityLabel | string \| undefined | no | | Overrides the text that's read by the screen reader when the user interacts with the element. By default, the label is constructed by traversing all the children and accumulating all the Text nodes separated by space. |
| aria-label | string \| undefined | no | | Alias for accessibilityLabel https://reactnative.dev/docs/view#accessibilitylabel https://github.com/facebook/react-native/issues/34424 |
| accessibilityRole | AccessibilityRole \| undefined | no | | Accessibility Role tells a person using either VoiceOver on iOS or TalkBack on Android the type of element that is focused on. |
| accessibilityState | AccessibilityState \| undefined | no | | Accessibility State tells a person using either VoiceOver on iOS or TalkBack on Android the state of the element currently focused on. |
| aria-busy | boolean \| undefined | no | | alias for accessibilityState see https://reactnative.dev/docs/accessibility#accessibilitystate |
| aria-checked | boolean \| "mixed" \| undefined | no | | |
| aria-disabled | boolean \| undefined | no | | |
| aria-expanded | boolean \| undefined | no | | |
| aria-selected | boolean \| undefined | no | | |
| accessibilityHint | string \| undefined | no | | An accessibility hint helps users understand what will happen when they perform an action on the accessibility element when that result is not obvious from the accessibility label. |
| accessibilityValue | AccessibilityValue \| undefined | no | | Represents the current value of a component. It can be a textual description of a component's value, or for range-based components, such as sliders and progress bars, it contains range information (minimum, current, and maximum). |
| aria-valuemax | number \| undefined | no | | |
| aria-valuemin | number \| undefined | no | | |
| aria-valuenow | number \| undefined | no | | |
| aria-valuetext | string \| undefined | no | | |
| onAccessibilityAction | ((event: AccessibilityActionEvent) => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs an accessibility custom action. |
| importantForAccessibility | "auto" \| "yes" \| "no" \| "no-hide-descendants" \| undefined | no | | [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services. |
| aria-hidden | boolean \| undefined | no | | A value indicating whether the accessibility elements contained within this accessibility element are hidden. |
| aria-modal | boolean \| undefined | no | | |
| role | Role \| undefined | no | | Indicates to accessibility services to treat UI component like a specific role. |
| accessibilityLabelledBy | string \| string[] \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| aria-labelledby | string \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| accessibilityLiveRegion | "none" \| "polite" \| "assertive" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| aria-live | "polite" \| "assertive" \| "off" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| screenReaderFocusable | boolean \| undefined | no | | Enables the view to be screen reader focusable, not keyboard focusable. |
| accessibilityElementsHidden | boolean \| undefined | no | | A Boolean value indicating whether the accessibility elements contained within this accessibility element are hidden to the screen reader. |
| accessibilityViewIsModal | boolean \| undefined | no | | A Boolean value indicating whether VoiceOver should ignore the elements within views that are siblings of the receiver. |
| onAccessibilityEscape | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the escape gesture (scrub with two fingers). |
| onAccessibilityTap | (() => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs accessibility tap gesture. |
| onMagicTap | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the magic tap gesture. |
| accessibilityIgnoresInvertColors | boolean \| undefined | no | | https://reactnative.dev/docs/accessibility#accessibilityignoresinvertcolorsios |
| accessibilityLanguage | string \| undefined | no | | By using the accessibilityLanguage property, the screen reader will understand which language to use while reading the element's label, value and hint. The provided string value must follow the BCP 47 specification (https://www.rfc-editor.org/info/bcp47). https://reactnative.dev/docs/accessibility#accessibilitylanguage-ios |
| accessibilityShowsLargeContentViewer | boolean \| undefined | no | | A Boolean value that indicates whether or not to show the item in the large content viewer. Available on iOS 13.0+ https://reactnative.dev/docs/accessibility#accessibilityshowslargecontentviewer |
| accessibilityLargeContentTitle | string \| undefined | no | | When `accessibilityShowsLargeContentViewer` is set, this string will be used as title for the large content viewer. https://reactnative.dev/docs/accessibility#accessibilitylargecontenttitle |
| accessibilityRespondsToUserInteraction | boolean \| undefined | no | | Blocks the user from interacting with the component through keyboard while still allowing screen reader to interact with it if this View is still accessible. |
| alwaysBounceHorizontal | boolean \| undefined | no | | When true the scroll view bounces horizontally when it reaches the end even if the content is smaller than the scroll view itself. The default value is true when `horizontal={true}` and false otherwise. |
| alwaysBounceVertical | boolean \| undefined | no | | When true the scroll view bounces vertically when it reaches the end even if the content is smaller than the scroll view itself. The default value is false when `horizontal={true}` and true otherwise. |
| automaticallyAdjustContentInsets | boolean \| undefined | no | | Controls whether iOS should automatically adjust the content inset for scroll views that are placed behind a navigation bar or tab bar/ toolbar. The default value is true. |
| automaticallyAdjustKeyboardInsets | boolean \| undefined | no | | Controls whether the ScrollView should automatically adjust its contentInset and scrollViewInsets when the Keyboard changes its size. The default value is false. |
| automaticallyAdjustsScrollIndicatorInsets | boolean \| undefined | no | | Controls whether iOS should automatically adjust the scroll indicator insets. The default value is true. Available on iOS 13 and later. |
| bounces | boolean \| undefined | no | | When true the scroll view bounces when it reaches the end of the content if the content is larger than the scroll view along the axis of the scroll direction. When false it disables all bouncing even if the `alwaysBounce*` props are true. The default value is true. |
| bouncesZoom | boolean \| undefined | no | | When true gestures can drive zoom past min/max and the zoom will animate to the min/max value at gesture end otherwise the zoom will not exceed the limits. |
| canCancelContentTouches | boolean \| undefined | no | | When false once tracking starts won't try to drag if the touch moves. The default value is true. |
| centerContent | boolean \| undefined | no | | When true the scroll view automatically centers the content when the content is smaller than the scroll view bounds; when the content is larger than the scroll view this property has no effect. The default value is false. |
| contentInset | Insets \| undefined | no | | The amount by which the scroll view content is inset from the edges of the scroll view. Defaults to {0, 0, 0, 0}. |
| contentOffset | PointProp \| undefined | no | | Used to manually set the starting scroll offset. The default value is {x: 0, y: 0} |
| contentInsetAdjustmentBehavior | "always" \| "never" \| "automatic" \| "scrollableAxes" \| undefined | no | | This property specifies how the safe area insets are used to modify the content area of the scroll view. The default value of this property is "never". |
| directionalLockEnabled | boolean \| undefined | no | | When true the ScrollView will try to lock to only vertical or horizontal scrolling while dragging. The default value is false. |
| indicatorStyle | "default" \| "black" \| "white" \| undefined | no | | The style of the scroll indicators. - default (the default), same as black. - black, scroll indicator is black. This style is good against a white content background. - white, scroll indicator is white. This style is good against a black content background. |
| maintainVisibleContentPosition | { autoscrollToTopThreshold?: number \| null \| undefined; minIndexForVisible: number; } \| null \| undefined | no | | When set, the scroll view will adjust the scroll position so that the first child that is currently visible and at or beyond minIndexForVisible will not change position. This is useful for lists that are loading content in both directions, e.g. a chat thread, where new messages coming in might otherwise cause the scroll position to jump. A value of 0 is common, but other values such as 1 can be used to skip loading spinners or other content that should not maintain position. The optional autoscrollToTopThreshold can be used to make the content automatically scroll to the top after making the adjustment if the user was within the threshold of the top before the adjustment was made. This is also useful for chat-like applications where you want to see new messages scroll into place, but not if the user has scrolled up a ways and it would be disruptive to scroll a bunch. Caveat 1: Reordering elements in the scrollview with this enabled will probably cause jumpiness and jank. It can be fixed, but there are currently no plans to do so. For now, don't re-order the content of any ScrollViews or Lists that use this feature. Caveat 2: This uses contentOffset and frame.origin in native code to compute visibility. Occlusion, transforms, and other complexity won't be taken into account as to whether content is "visible" or not. |
| maximumZoomScale | number \| undefined | no | | The maximum allowed zoom scale. The default value is 1.0. |
| minimumZoomScale | number \| undefined | no | | The minimum allowed zoom scale. The default value is 1.0. |
| onScrollAnimationEnd | (() => void) \| undefined | no | | Called when a scrolling animation ends. |
| pinchGestureEnabled | boolean \| undefined | no | | When true, ScrollView allows use of pinch gestures to zoom in and out. The default value is true. |
| scrollEventThrottle | number \| undefined | no | | Limits how often scroll events will be fired while scrolling, specified as a time interval in ms. This may be useful when expensive work is performed in response to scrolling. Values <= `16` will disable throttling, regardless of the refresh rate of the device. |
| scrollIndicatorInsets | Insets \| undefined | no | | The amount by which the scroll view indicators are inset from the edges of the scroll view. This should normally be set to the same value as the contentInset. Defaults to {0, 0, 0, 0}. |
| scrollToOverflowEnabled | boolean \| undefined | no | | When true, the scroll view can be programmatically scrolled beyond its content size. The default value is false. |
| scrollsToTop | boolean \| undefined | no | | When true the scroll view scrolls to top when the status bar is tapped. The default value is true. |
| snapToAlignment | "start" \| "end" \| "center" \| undefined | no | | When `snapToInterval` is set, `snapToAlignment` will define the relationship of the snapping to the scroll view. - `start` (the default) will align the snap at the left (horizontal) or top (vertical) - `center` will align the snap in the center - `end` will align the snap at the right (horizontal) or bottom (vertical) |
| onScrollToTop | ((event: NativeSyntheticEvent) => void) \| undefined | no | | Fires when the scroll view scrolls to top after the status bar has been tapped |
| zoomScale | number \| undefined | no | | The current scale of the scroll view content. The default value is 1.0. |
| endFillColor | ColorValue \| undefined | no | | Sometimes a scrollview takes up more space than its content fills. When this is the case, this prop will fill the rest of the scrollview with a color to avoid setting a background and creating unnecessary overdraw. This is an advanced optimization that is not needed in the general case. |
| scrollPerfTag | string \| undefined | no | | Tag used to log scroll performance on this scroll view. Will force momentum events to be turned on (see sendMomentumEvents). This doesn't do anything out of the box and you need to implement a custom native FpsListener for it to be useful. |
| overScrollMode | "auto" \| "always" \| "never" \| undefined | no | | Used to override default value of overScroll mode. Possible values: - 'auto' - Default value, allow a user to over-scroll this view only if the content is large enough to meaningfully scroll. - 'always' - Always allow a user to over-scroll this view. - 'never' - Never allow a user to over-scroll this view. |
| nestedScrollEnabled | boolean \| undefined | no | | Enables nested scrolling for Android API level 21+. Nested scrolling is supported by default on iOS. |
| fadingEdgeLength | number \| { start: number; end: number; } \| undefined | no | | Controls the fading effect at the edges of the scroll content. A value greater than 0 will apply the fading effect, indicating more content is available to scroll. You can specify a single number to apply the same fading length to both edges. Alternatively, use an object with `start` and `end` properties to set different fading lengths for the start and end of the scroll content. The default value is 0. |
| persistentScrollbar | boolean \| undefined | no | | Causes the scrollbars not to turn transparent when they are not in use. The default value is false. |
| scrollsChildToFocus | boolean \| undefined | no | | When false, the ScrollView will not automatically scroll to a focused child when the child requests focus. This can be useful when you want to control the scroll position programmatically. The default value is true. |
| ref | Ref \| undefined | no | | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). |
| key | Key \| null \| undefined | no | | |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Select
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/select/select.md`
Human reference: https://canvas.nannier.com/components/select
## Public imports
```tsx
import { Select } 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-choice dropdown field: a trigger showing the chosen value that opens a
panel of options, styled to match Canvas inputs.
Pass `label` (and `required`) to name the field:
iOS and web render the label
above the trigger, while Android floats the Material 3 in-container label once
the menu opens or a value is selected. On Android the trigger grows with the
user's text size, and a label longer than the trigger truncates on one line clear
of the chevron while the trigger's accessible name stays the whole label.
The trigger 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.
In glass mode the option panel is a dense-layer glass card under the trigger (or
above it when it fits there); the trigger stays in place with its value and
chevron, and the chosen value and expanded state commit immediately. Solid mode
paints the skin's own panel.
The label also names the option list. Use `accessibilityLabel` to provide an explicit purpose when the visible label or placeholder is insufficient; it overrides both accessible names. A required field announces "required" with the button name and marks the option list as required. The selected value never replaces the field's purpose.
Pass `ref` to access the interactive trigger, preserving overlay measurement. 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 web, the trigger is the Canvas v4 field: 40 pixels tall (32 small, 48 large)
on the sunken fill behind the `field-border` hairline, a 400 14-pixel value (13
small) and a 16-pixel chevron that turns over while the list is open. The pointer
darkens the edge to the subtle ink, and keyboard focus or the open list rings it in
the accent with a 4-pixel halo. The list is a card 6 pixels under the trigger with
a 4-pixel inset, 34-pixel rows, the chosen row on the `muted` wash with a trailing
check in the accent ink.
iOS keeps the pop-up button (a 44-point field with the
up-and-down chevrons) and its menu, which marks the chosen row with a leading check;
Android keeps the Material 3 exposed dropdown, whose menu marks the chosen row with
its state layer alone.
Use `dense eyebrow soft` for the compact application field on web: a 32-pixel
trigger with a 600 13-pixel value under a tracked uppercase eyebrow label at the
kit’s 10-pixel reading floor, and 30-pixel rows. `plainOptions` removes the check
and marks the chosen row with its wash alone; pointer hover uses the brand wash. `steadyPress` keeps the resting fill and
opacity while pressed. `menuWidth` bounds the floating menu independently of the
field. `combobox` exposes the selection-field role. Native skins keep their
platform field anatomy and minimum touch targets.
## Usage
```tsx
```
## Variants
### Dense field
```tsx
```
### Inline label
```tsx
```
### Required field
```tsx
```
### Small
```tsx
```
### Large
```tsx
```
### Country
```tsx
```
### With leading icon
```tsx
```
### Disabled
```tsx
```
### Measure
```tsx
```
## Do & Don't
### Placeholder
**Do** — Use the placeholder prop for the prompt so it can never be submitted as a value.
```tsx
```
**Don't** — A placeholder as a normal option can be submitted as a real value.
```tsx
```
### When to use
**Do** — Reserve a select for picking one of several mutually exclusive options; use a switch or radios for two.
```tsx
```
**Don't** — A select for a single on/off choice buries a one-tap decision behind a dropdown.
```tsx
```
### Small
**Do** — Keep the small select inline with a short label so it stays compact inside toolbars and table footers.
```tsx
```
**Don't** — A stacked block label towers over the small control and breaks the dense row it belongs in.
```tsx
```
### Default size
**Do** — Match the default select to sibling inputs at the same height so the form row lines up.
```tsx
```
**Don't** — A default select next to a taller lg input leaves the row baselines misaligned.
```tsx
City
State
```
### Large
**Do** — Scale the text up with the height so the large select reads as a deliberate, touch-friendly target.
```tsx
```
**Don't** — Tiny option text inside a tall control wastes the height and looks like an accidental mismatch.
```tsx
Plan
Starter
▾
```
## Supported props
### SelectProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string \| undefined | no | | Controlled selection; omit for uncontrolled use. Empty shows the placeholder. With plain string options this is the option itself; with `SelectOption` objects it is the option's `value`, and the trigger shows its `label`. |
| defaultValue | string \| undefined | no | | Initial selection for uncontrolled use (a bare picks on its own). |
| options | (string \| SelectOption)[] \| undefined | no | | The selectable options: bare strings, or `{ value, label }` objects when the stored value differs from the text shown for it. |
| label | string \| undefined | no | | The field's persistent label. Its placement is platform-adaptive: iOS and web render it ABOVE the trigger; Android renders the Material 3 in-container FLOATING label (centered like a placeholder at rest, floating to the top once the menu opens or a value is selected). The label names the field for a11y. |
| accessibilityLabel | string \| undefined | no | | Accessible purpose for the trigger and option list. Overrides the visible label for assistive technology; otherwise the label or placeholder names them. |
| inline | boolean \| undefined | no | false | Places the `label` as a LEADING cluster INSIDE the trigger row (a toolbar-style labeled select) instead of the persistent above/floating placement. Takes effect only alongside `label`. The inline label still names the trigger (accessibilityLabel / aria-labelledby) and shares its tap target, since it lives inside the trigger Pressable. Pairs with `fit` for a content-hugging toolbar cluster. |
| required | boolean \| undefined | no | false | Marks the field as required and announces "required" with the trigger name. A visible label also gets a decorative destructive "*". |
| icon | boolean \| undefined | no | false | Renders a leading globe glyph inside the trigger, indented so the value clears it. |
| placeholder | string \| undefined | no | "Select an option" | Prompt shown in the trigger when no value is selected. |
| open | boolean \| undefined | no | false | Controlled open state of the option list; omit for uncontrolled use. |
| defaultOpen | boolean \| undefined | no | false | Initial open state for uncontrolled use. |
| onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the open state changes (trigger press, select), in both modes. |
| disabled | boolean \| undefined | no | false | Dims the control and blocks interaction. |
| onSelect | ((option: string) => void) \| undefined | no | | Called with the chosen option's value when a row is pressed (both modes). |
| testID | string \| undefined | no | | E2E hook forwarded to the trigger pressable. |
| dense | boolean \| undefined | no | false | Use the compact content-sized web field with a drawn disclosure chevron. |
| eyebrow | boolean \| undefined | no | false | Use the compact application eyebrow above a web field. |
| soft | boolean \| undefined | no | false | Use the brand field fill instead of the card fill. |
| plainOptions | boolean \| undefined | no | false | Text-only options: selected ink and a pointer wash, without a check gutter. |
| steadyPress | boolean \| undefined | no | false | Keep the resting fill and opacity during a press. |
| combobox | boolean \| undefined | no | false | Announce a selection field as a combobox rather than a menu button. |
| menuWidth | number \| undefined | no | | Width of the floating menu, clamped within the overlay outlet. |
| 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 interactive trigger, preserving overlay measurement. 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.
---
# ServiceHealthList
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/service-health-list/service-health-list.md`
Human reference: https://canvas.nannier.com/components/service-health-list
## Public imports
```tsx
import { ServiceHealthList } 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.
Per-service status rows: a status dot (down > degraded > operational), the service name, an optional right-aligned `detail` such as an uptime percentage, and, when the item carries `periods`, an inline strip of its latest 14 periods beside the detail.
It shares its strip renderer with UptimeBar, so the two never drift. `onPressItem` turns rows into drill-in buttons; `compact` tightens the rows; `plain` strips the card surface for nesting.
`detailed` places the services in one bordered panel, spells out each status and shows its complete history in a separate full-width strip.
## Usage
```tsx
({})) },
{ label: "Dashboard", detail: "99.92%", periods: Array.from({ length: 45 }, (_, i) => (i === 30 ? { degraded: true } : {})), degraded: true },
{ label: "Webhooks", detail: "97.10%", periods: Array.from({ length: 45 }, (_, i) => (i > 40 ? { down: true } : {})), down: true }
]}
/>
```
## Variants
### Compact
```tsx
```
### Drill-in rows
```tsx
{}}
items={[
{ label: "API", detail: "99.98%" },
{ label: "Search", detail: "99.95%" },
{ label: "Exports", detail: "99.80%", degraded: true }
]}
/>
```
### Plain, inside a card
```tsx
```
## Do & Don't
### ServiceHealthList
**Do** - One row per service, with the status carried by the dot, the composed accessible name, and the embedded strip.
```tsx
```
**Don't** - A hand-rolled status row splits the tap target and loses the status from assistive tech.
```tsx
API
```
## Supported props
### ServiceHealthListProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | ServiceHealthItem[] | yes | | The services to render, in order. |
| title | string \| undefined | no | | Optional heading shown above the rows. |
| compact | boolean \| undefined | no | false | Density (omit for the default row insets). |
| detailed | boolean \| undefined | no | false | Bordered status panel with full-width history strips and explicit status labels. |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| onPressItem | ((index: number) => void) \| undefined | no | | Rows become tappable drill-in targets (open the service) 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.
---
# Sidebar
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/sidebar/sidebar.md`
Human reference: https://canvas.nannier.com/components/sidebar
## Public imports
```tsx
import { Sidebar } 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.
App navigation rail with collapsible sections and active highlighting. The sidebar on the left of this page is a thin adapter over this very `Sidebar` component: it feeds the docs' nav tree in and gets the same collapse, accordion, and active-highlight behavior back.
In glass mode the active row paints a control-layer pane behind its icon, label
and badge; the drill-down drawer keeps its own row treatment. Solid mode keeps
the skin's own row fill.
On the web a hovered row's wash (the palette's translucent `hover`) fades in over
150 ms and back out when the pointer leaves, in the rail and the drill-down alike;
the pressed and active fills still switch at once, and Reduce Motion makes the wash
instant.
Add `responsive` to make it adapt across breakpoints: it stays the accordion rail on desktop, and at and below the `lg` breakpoint (1024px) it becomes a start-edge (left, RTL-aware) **navigation drawer** that drills through the same `sections` one level at a time (tap a group to slide in to its rows, back to return). Drive the drawer's open state with `open` / `onOpenChange` from your own hamburger button; the `header` and `footer` slots pin above and below the drill-down, just as in the rail. The drawer slides from the start (left) edge by default; `drawerRight`, `drawerTop`, and `drawerBottom` change which edge it slides in from. (This page's own left nav does exactly this: an accordion rail here, a drill-down drawer on a phone.)
Pass `drawer` instead when the shell's hamburger is the only way into the tree at every width (a top app bar on a phone and a tablet alike): the sidebar is then the drill-down drawer at every width and never the inline rail, with the same `open` / `onOpenChange` wiring. `drawer` wins over `responsive`.
On the web the sidebar takes the Canvas v4 rail: a 232px column on the sunken page
tone, rows 36px tall at a 9px corner (30px at 8 with `compact`, the docs rail's
density), 500 13px labels in the secondary ink, the active row on the accent's soft
wash in the accent's text ink, and a mono uppercase eyebrow over each titled group.
iOS and Android keep their platform rows (the HIG capsule, the Material 3 pill) in the
v4 colors.
`markers` gives each row a trailing dot: the accent on the active row, and the field
hairline on a row of the reader's current place, either one item with `marked: true`
or every row of a section with `current: true`. A `current` section also sets its
eyebrow in the accent's text ink and its resting rows in the foreground ink, and a
section's `count` sits at the far end of its heading.
`embedded` renders the navigation groups inside an app-owned rail: it inherits the
rail's frame, fill, width and scroll, stacks its groups 16px apart, and renders the
`header` and `footer` slots as plain blocks above and below them, so a filter field or
a heading row fits there. A framed sidebar's header grows past its 56px floor when its
content is taller.
A sidebar is a navigation landmark on every platform. Name it with `accessibilityLabel`,
a short statement of what it navigates such as "Workspace", so a screen reader's
landmark list can tell it apart; a page that holds more than one navigation landmark (a
sidebar beside the app's top navigation, or two sidebars) needs a distinct name on each.
In the narrow `responsive` drawer the same name labels the drawer.
## Usage
The one `Sidebar` below is the whole story: on desktop it is the collapsible accordion rail (tap the header chevron to shrink it to icons); narrow the window to a phone and the same component
becomes a start-edge drill-down **Liquid Glass menu** opened by the top bar's hamburger,
exactly like this page's own left nav. `AppShell` is the docs' live app frame (the top bar and placeholder page); the `Sidebar` wiring is the part you copy.
```tsx
{({ open, setOpen }) => (
(collapsed ? : Acme )}
sections={[
{ items: [
{ label: "Dashboard", icon: "layoutGrid" },
{ label: "Inbox", icon: "inbox", badge: "3" }
] },
{ title: "Reports", icon: "barChart2", collapsible: true, items: [
{ label: "Analytics", icon: "barChart2" },
{ label: "Traffic", icon: "activity" }
] }
]}
/>
)}
```
## Variants
### Collapsed rail
```tsx
```
### Error badge
```tsx
```
### Accent tag and account
`badgeAccent` draws a row's `badge` as the accent's solid tag (18 px, a 10 px label) for
something new rather than a count; a row's `meta` is quiet mono text in the third ink.
`account` pins a filled account card to the foot of the column: a small avatar beside the
name and a detail line in the third ink (the collapsed rail keeps the avatar).
```tsx
```
### Markers and the current place
```tsx
```
### Embedded rail with a filter
```tsx
}
sections={[
{ title: "Controls", count: 3, items: [
{ id: "button", label: "Button" },
{ id: "switch", label: "Switch" },
{ id: "checkbox", label: "Checkbox" }
] }
]}
/>
```
### Floating
```tsx
```
## Do & Don't
### Sidebar
**Do** — Exactly one item carries the accent background; clicking moves it so the active page is always unambiguous.
```tsx
```
**Don't** — Click an item: two rows wearing the active background means the nav can't tell you which page you're on.
```tsx
Acme
Dashboard
Users
Settings
```
## Supported props
### SidebarProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| sections | SidebarSection[] \| undefined | no | | Titled sections of nav rows. Use this or the flat `items` array. |
| items | SidebarItem[] \| undefined | no | | Flat list of nav rows, wrapped into a single untitled section. |
| active | string \| number \| undefined | no | | The active row (CONTROLLED), by id, by label, or by flat index across all rows. Omit for uncontrolled use. |
| defaultActive | string \| number \| undefined | no | | Initial active row for uncontrolled use (a bare sidebar moves the highlight on press). |
| onSelect | ((item: SidebarItem, index: number, event: GestureResponderEvent) => void) \| undefined | no | | Fired with the selected row and its flat index across all sections. |
| compact | boolean \| undefined | no | false | Density (pick one; default is the comfortable row). |
| bordered | boolean \| undefined | no | false | A rounded card inside the field hairline. |
| floating | boolean \| undefined | no | false | A rounded card lifted on the medium shadow (the bordered card natively). |
| embedded | boolean \| undefined | no | false | Navigation groups embedded in an app-owned rail: no frame, fill or width of its own and no scroll body (the host rail scrolls); the `header` and `footer` slots stack above and below the groups as plain blocks. |
| markers | boolean \| undefined | no | false | Trailing marker dots: the accent on the active row, the field hairline on a row of the current place (an item's `marked`, or every row of a `current` section). |
| collapsed | boolean \| undefined | no | false | Collapsed to the mini icon-rail (CONTROLLED). Omit for uncontrolled use. |
| defaultCollapsed | boolean \| undefined | no | false | Initial collapsed state for uncontrolled use (default expanded). |
| collapsible | boolean \| undefined | no | false | Show the collapse/expand toggle in the header slot. |
| onToggleCollapse | (() => void) \| undefined | no | | Fired when the user taps the collapse/expand toggle. |
| openSections | string \| string[] \| undefined | no | | Open collapsible sections (CONTROLLED), by section id/title. A single key by default, an array when `independentSections`. Pair with `onOpenSectionsChange`. |
| defaultOpenSections | string \| string[] \| undefined | no | | Initial open sections for uncontrolled use. |
| onOpenSectionsChange | ((value: string \| string[]) => void) \| undefined | no | | Fired with the next open sections whenever a section header toggles. |
| independentSections | boolean \| undefined | no | false | Allow more than one collapsible section open at once (default: one-open-at-a-time). |
| responsive | boolean \| undefined | no | false | Opt in to the responsive rail->drawer behavior. Off by default: a bare sidebar is the inline rail at every width, byte-identical to before. |
| drawer | boolean \| undefined | no | false | Always the drill-down drawer, at every width, never the inline rail: for an app shell whose only entry to the site tree is its own hamburger (a top app bar). Takes the same `open` / `onOpenChange` wiring as `responsive`, and wins over it. |
| open | boolean \| undefined | no | false | The narrow drawer's open state (CONTROLLED). The consumer wires its hamburger to this. |
| defaultOpen | boolean \| undefined | no | false | Initial open state for the uncontrolled narrow drawer (default closed). |
| onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the narrow drawer opens or closes (scrim tap, back, or a leaf selection). |
| drawerBreakpoint | BreakpointKey \| undefined | no | | The width at and below which `responsive` switches to the drawer (default `lg` = 1024). |
| drawerWidth | number \| undefined | no | | The narrow drawer panel width in px (default 288). |
| drawerContentInsetBottom | number \| undefined | no | | Extra bottom padding for the drawer's scrolling content, so its last rows clear persistent chrome painting over the drawer (e.g. a native bottom tab bar on Android). |
| drawerRight | boolean \| undefined | no | false | Slide the drawer in from the end (right) edge instead of the start (left). |
| drawerTop | boolean \| undefined | no | false | Drop the drawer down from the top edge (a top sheet). |
| drawerBottom | boolean \| undefined | no | false | Raise the drawer up from the bottom edge (a bottom sheet). |
| header | ReactNode \| ((collapsed: boolean) => ReactNode) | no | | Optional top slot (a brand lockup / logo). A render function receives the collapsed state so it can show a compact mark in the rail. When `header` or `footer` is set, the panel becomes a pinned header + scrolling body (+ footer). |
| footer | ReactNode \| ((collapsed: boolean) => ReactNode) | no | | Optional bottom slot (pinned below the scrolling sections). A render function receives the collapsed state so it can show a compact (icon-only) footer in the rail. |
| account | SidebarAccount \| undefined | no | | The signed-in account, drawn as a filled card pinned to the foot of the column: a small avatar beside the name and a detail line in the third ink. The collapsed rail keeps the avatar alone. It sits under the groups (above a `footer` slot), and in the drawer under the drill-down. |
| accessibilityLabel | string \| undefined | no | | The name of the sidebar's navigation landmark, a short statement of what it navigates ("Workspace", "Controls components"). A sidebar is a navigation landmark on every platform, and a page holding two navigation landmarks needs a distinct name on each, so a landmark list can tell them apart. In the narrow `responsive` drawer it names the drawer. Omit it and the landmark stays unnamed, as before. |
| 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.
---
# Skeleton
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/skeleton/skeleton.md`
Human reference: https://canvas.nannier.com/components/skeleton
## Public imports
```tsx
import { Skeleton } 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.
Placeholders for loading content.
## Usage
```tsx
```
## Variants
### Avatar
```tsx
```
### Button
```tsx
```
### Card
```tsx
```
### List
```tsx
```
### Table
```tsx
```
## Do & Don't
### text
**Do** — Vary the line widths and shorten the last line so it reads like real wrapped text.
```tsx
```
**Don't** — Three full-width lines read as a solid block, not as a paragraph of prose.
```tsx
```
### avatar
**Do** — Match the avatar's circle exactly so the photo drops in with no shift.
```tsx
```
**Don't** — A square placeholder for a round avatar snaps shape the instant the image loads.
```tsx
```
### button
**Do** — Size the placeholder to the button's real height and width.
```tsx
```
**Don't** — An oversized bar overstates a button and the layout jumps when the real control mounts.
```tsx
```
### card
**Do** — Mirror the real layout (avatar circle, text lines) so the swap is seamless.
```tsx
```
**Don't** — A generic block that ignores the content's shape causes a jarring shift when it loads.
```tsx
```
### list
**Do** — Repeat a per-row placeholder so the avatar-and-text rhythm matches the loaded list.
```tsx
```
**Don't** — One tall block hides the row rhythm, so the list reflows when each item appears.
```tsx
```
### table
**Do** — Lay placeholders out on the real column grid so each cell stays put when it fills in.
```tsx
```
**Don't** — A single rectangle gives no column structure; cells shift sideways once data lands.
```tsx
```
## Supported props
### SkeletonProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| text | boolean \| undefined | no | false | Shape (pick one; default is a single text line). |
| avatar | boolean \| undefined | no | false | Shape (pick one; default is a single text line). |
| button | boolean \| undefined | no | false | Shape (pick one; default is a single text line). |
| card | boolean \| undefined | no | false | Shape (pick one; default is a single text line). |
| list | boolean \| undefined | no | false | Shape (pick one; default is a single text line). |
| table | boolean \| undefined | no | false | Shape (pick one; default is a single text line). |
| small | boolean \| undefined | no | false | Size (pick one). Scales the line height and the avatar/button footprint. |
| large | boolean \| undefined | no | false | Size (pick one). Scales the line height and the avatar/button footprint. |
| long | boolean \| undefined | no | false | A line that runs most of the way across (80%), a mid-paragraph line. |
| short | boolean \| undefined | no | false | A short trailing line (60%), the last line of a paragraph. |
| animate | boolean \| undefined | no | false | Subtle opacity pulse while content loads. |
| accessibilityLabel | string \| undefined | no | | Screen-reader label announced for the loading placeholder. Defaults to "Loading". The placeholder carries a `progressbar` role + busy state so assistive tech announces the pending content (matching Spinner / Progress). |
| 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.
---
# Slider
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/slider/slider.md`
Human reference: https://canvas.nannier.com/components/slider
## Public imports
```tsx
import { Slider } 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.
Drag (or tap) along a track to pick a value in a range; the keyboard and screen readers step it too. Controlled by `value`, bounded by `min` / `max`, and snapped to `step`.
Like the other input-like controls, a slider 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.
Pass `ref` to access the interactive adjustable track, including when a header is shown. 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.
Name the setting with string `children`, or pass `accessibilityLabel` when the visible title is omitted or contains rich content.
## Usage
```tsx
```
In glass mode the handle is a control-layer glass knob whose position is the
authoritative value at every frame (a controlled value or a release can never
lag behind the rail). Tapping the track puts the handle on the tapped value at
once. The rail, the filled range, the value label and the hit target stay
fixed. A disabled slider is inert; solid mode keeps the skin's own handle.
On iOS 26 the handle is a real Apple Liquid Glass control, with the material's edge-lensing and specular showing on a physical device. This is automatic from the glass surface, the platform default there, so there is no prop to set; under a solid surface, Reduce Transparency, or Increase Contrast the handle falls back to a solid knob.
The Android and web handles keep their own look: on the web a 20 px white thumb (16 `small`, 26 `large`) on a 6 px rail (4, 8) whose accent range glows, ringed in the accent's soft wash while a pointer hovers or presses it, under a 13 px title with the value in the mono face; on Android Material 3 Expressive's split 16dp track (24dp `large`) and 4dp bar handle.
## Variants
### With label
```tsx
Volume
```
### With description
```tsx
Volume
```
### Small
```tsx
```
### Large
```tsx
```
### Stepped
```tsx
```
### Percent and scale
`percent` reads the value as a percentage (the readout and the announced value), and `scale` draws the minimum, the midpoint and the maximum under the rail.
```tsx
Glass intensity
```
### Disabled
```tsx
```
### Measure
```tsx
xs step, pinned to the start (320)
lg step, pinned to the start (512)
Bare, in an lg Container: fills it (512)
```
## Do & Don't
### Range
**Do**: Give the track room to breathe so the thumb has a clear travel path and the value reads at a glance. Pass the label as `children`; the slider owns the title above the rail.
```tsx
Volume
```
**Don't**: Cramming the slider into a tiny width leaves no travel, so the thumb can barely move and the value is hard to set.
```tsx
```
### Bounds
**Do**: Pair the slider with its current value so the number is explicit, not just inferred from the thumb position. `showValue` renders the live readout above the track.
```tsx
Volume
```
**Don't**: A slider with no readout and no visible label leaves users guessing what the value is and what it controls.
```tsx
```
### State
**Do**: Use the disabled state for values the user cannot change yet; it dims clearly so it does not look interactive.
```tsx
```
**Don't**: Don't fake a disabled slider with a faint inline track; the real `disabled` prop also blocks the gesture and sets accessibility state.
```tsx
```
### Keyboard
**Do**: Name the slider and let it take keyboard focus. Tab lands on it and its thumb wears the kit's 2 px focus ring in the theme's `ring` colour, over the glass knob too; the arrows, Page Up, Page Down, Home and End then set the value. A click shows no ring.
```tsx
```
**Don't**: Leave the slider unnamed. It still takes focus and shows the ring, but a screen reader announces a bare number with nothing to say what it sets.
```tsx
```
## Supported props
### SliderProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | number \| undefined | no | | Controlled value; omit for uncontrolled use. The thumb sits at this value (clamped to [min, max]). |
| defaultValue | number \| undefined | no | | Initial value for uncontrolled use (a bare drags out of the box). Default `min`. |
| min | number \| undefined | no | 0 | Lower bound of the range. Default 0. |
| max | number \| undefined | no | 100 | Upper bound of the range. Default 100. |
| step | number \| undefined | no | 1 | Snap increment. Default 1. Set to a fraction for finer control. |
| onChange | ((value: number) => void) \| undefined | no | | Fired with the next (clamped, snapped) value as the thumb is dragged or the track tapped (both modes). |
| testID | string \| undefined | no | | E2E hook forwarded to the slider container. |
| children | ReactNode | no | | The title line, rendered ABOVE the track (a slider's label sits above the rail, not beside it). Supplying it builds the component-owned header so the caller never hand-composes a Column + Typography above a bare slider, and the visible title also names the control for assistive tech (see `accessibilityLabel`). |
| description | ReactNode | no | | Optional muted secondary line rendered under the title, for a hint or unit note. |
| showValue | boolean \| undefined | no | false | Render the live current value (the same value the thumb sits at) as a trailing readout on the title line, right-aligned. With no title it still renders the value as a standalone trailing readout above the track. The number tracks the drag in real time and uses tabular figures so it does not jitter. |
| percent | boolean \| undefined | no | false | The value is a percentage: the `showValue` readout prints it with a percent sign ("62%") and assistive tech hears it so. Pair with the default 0 to 100 range. |
| scale | boolean \| undefined | no | false | A scale under the track: the minimum, the middle and the maximum, spread across the track's width in the small mono meta type, 8 px below the rail. |
| small | boolean \| undefined | no | false | Size (pick one; default is the standard track + thumb). |
| large | boolean \| undefined | no | false | Size (pick one; default is the standard track + thumb). |
| disabled | boolean \| undefined | no | false | State. |
| accessibilityLabel | string \| undefined | no | | Accessible name for the slider (e.g. "Volume"). |
| style | LayoutStyle | no | | Extra style on the slider container, applied last. |
| ref | React.Ref | no | | Ref to the interactive adjustable track, including when a header is shown. 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.
---
# Sparkline
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/sparkline/sparkline.md`
Human reference: https://canvas.nannier.com/components/sparkline
## Public imports
```tsx
import { Sparkline } 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 compact trend strip: a row of thin bars whose heights track a series of values,
with the series in a soft wash of the tone and the latest bar in the full accent.
Pass `values` and it sizes each bar against the series max and paints the tone, so
no call site hand-composes a row of `flexGrow` + `height` + `backgroundColor`
Views to draw an inline trend on a stat card or dashboard.
## Usage
```tsx
```
## Variants
### Tones
```tsx
```
### Sizes
```tsx
```
### Line
```tsx
```
### Track
`track` paints the plot area with the muted track, so the strip keeps a visible
frame. A flat or all-zero series then reads as a chart sitting at zero rather
than as one that failed to load, and a row of strips scans evenly because every
one holds the same band whatever its data. Use it where strips sit side by side;
a lone sparkline inside running text is usually better without.
```tsx
```
## Do & Don't
### Pair with a value
**Do** — Anchor the sparkline to an explicit headline value and delta with the Stats molecule, which owns the metric-card anatomy and renders the trend strip from its `spark` field.
```tsx
```
**Don't** — Draw a bare trend strip with no current value; the reader has to decode the slope.
```tsx
```
## Supported props
### SparklineProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| values | number[] | yes | | The series to plot; each value becomes one bar, sized against the max. |
| primary | boolean \| undefined | no | false | Tone (pick one; default `primary`). Colors the marks. |
| success | boolean \| undefined | no | false | Tone (pick one; default `primary`). Colors the marks. |
| destructive | boolean \| undefined | no | false | Tone (pick one; default `primary`). Colors the marks. |
| muted | boolean \| undefined | no | false | Tone (pick one; default `primary`). Colors the marks. |
| compact | boolean \| undefined | no | false | Height (pick one; default medium). |
| tall | boolean \| undefined | no | false | Height (pick one; default medium). |
| line | boolean \| undefined | no | false | A continuous 2px trend line with an area wash and end marker, instead of the bar strip. |
| track | boolean \| undefined | no | false | Paint the plot area with the muted track, giving the strip a visible frame. Two things it buys: a flat or all-zero series reads as a chart sitting at zero rather than as one that failed to load, and a row of strips scans evenly because every one of them holds the same band whatever its data. Use it wherever strips sit side by side (a Stats row); a lone sparkline inside running text is usually better without. |
| accessibilityLabel | string \| undefined | no | | Accessible summary (e.g. "requests, last 11 days"). |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For sizing/composition only (e.g. maxWidth); not a styling escape hatch. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Spinner
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/spinner/spinner.md`
Human reference: https://canvas.nannier.com/components/spinner
## Public imports
```tsx
import { Spinner } 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.
Animated loading spinner in three sizes.
## Usage
```tsx
```
## Variants
### With label
```tsx
Loading…
```
### With description
```tsx
Deploying…
```
### Stacked
```tsx
Working…
```
### Small
```tsx
```
### Large
```tsx
```
### Primary
```tsx
```
## Do & Don't
### Label
**Do** — Pair longer waits with a short label so the spinner has context.
```tsx
Loading…
```
**Don't** — A bare spinner with no label leaves users guessing what is happening and for how long.
```tsx
```
### sm
**Do** — Use the small size inline: inside a button or beside a line of text where its scale matches the type.
```tsx
Saving…
```
**Don't** — The 4×4 spinner is too small to anchor a full panel; alone in open space it reads as a stray dot.
```tsx
```
### default
**Do** — Keep the default square and centered with a label for small content panels and cards.
```tsx
Loading…
```
**Don't** — Don't stretch it with conflicting width and height; a spinner must stay a perfect circle to spin cleanly.
```tsx
```
### lg
**Do** — Reserve the large size for section- or page-level loading, centered in the empty content area.
```tsx
Loading dashboard…
```
**Don't** — The 8×8 spinner overflows a small control; cramming the large size into a button breaks its height.
```tsx
```
## Supported props
### SpinnerProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no | | Optional label rendered beside (or below) the indicator in the spinner's 500 14 foreground type. Supplying it turns the bare indicator into a component-owned loading pairing, so the caller never hand-composes a Row/Column + Typography beside a . A bare (no children) is unchanged. |
| description | ReactNode | no | | Optional muted secondary line rendered under the label (a progress hint, an ETA). Stacks under the label inside the control. |
| stacked | boolean \| undefined | no | false | Layout axis for the label. Omitted/false: the label sits BESIDE the indicator in a snug row (the inline pairing). `stacked`: the indicator sits over a centered label column (the panel / section-loader form). |
| small | boolean \| undefined | no | false | Size (pick one; default sits between small and large). |
| large | boolean \| undefined | no | false | Size (pick one; default sits between small and large). |
| primary | boolean \| undefined | no | false | Tone (pick one; default is the accent). |
| muted | boolean \| undefined | no | false | Tone (pick one; default is the accent). |
| foreground | boolean \| undefined | no | false | Tone (pick one; default is the accent). |
| accessibilityLabel | string \| undefined | no | | Accessible description of what is loading. When absent, a string label (children) becomes the accessible name; when neither is present it defaults to "Loading". |
| 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.
---
# StackedBar
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/stacked-bar/stacked-bar.md`
Human reference: https://canvas.nannier.com/components/stacked-bar
## Public imports
```tsx
import { StackedBar } 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.
One proportional horizontal bar split into colored segments (the `chart-1`..`chart-8` tokens in fixed order), with a legend carrying each segment's label and share.
## Usage
```tsx
```
## Variants
### Track
`track` paints the unfilled remainder as a muted rail. A full bar looks the same;
a bar whose segments sum to zero draws the rail instead of nothing, so a
composition of nothing still reads as a bar. Use it wherever the strip has to
hold its space, such as inside a `Stats` tile.
```tsx
```
### Strip size and wash
`tall` uses a 24 px strip, or 14 px separated bands when a capacity is supplied. `subtle` washes
the segments for a bar that supports a headline rather than being one, where a
solid bar would outweigh everything around it.
```tsx
```
### Detailed capacity
```tsx
```
## Do & Don't
### Stacked bar
**Do** — Always ship a legend with a colored dot, label, and percentage per segment.
```tsx
```
**Don't** — Colored segments with no legend force the reader to guess which channel each band represents.
```tsx
```
## Supported props
### StackedBarProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| segments | StackedSegment[] | yes | | |
| label | string \| undefined | no | | What the bar measures (e.g. "Traffic sources"); leads the accessible name. |
| hideLegend | boolean \| undefined | no | false | Hide the legend (the labelled dots below the bar). |
| detailed | boolean \| undefined | no | false | A two-column legend with separate right-aligned share readouts. |
| legendSegments | StackedSegment[] \| undefined | no | | The series to identify in a shared comparison legend. Defaults to this strip. |
| track | boolean \| undefined | no | false | Paint the unfilled remainder as a muted rail, so a bar whose segments sum to zero still reads as a bar rather than as blank space. A composition of nothing (an empty queue, a metric at zero) is a real state to draw, and without the rail it draws as an absence. |
| tall | boolean \| undefined | no | false | A 24 px strip instead of the default 8 px pill; capacity strips use 14 px bands. |
| subtle | boolean \| undefined | no | false | Draw the segments as washes of their ramp colours rather than at full strength. For a bar that supports a headline rather than being one (the strip under a metric's value), where a solid bar outweighs everything around it. |
| max | number \| undefined | no | | The capacity the segments are shares of (100 GB of storage): each segment is its value over `max`, and the room left is drawn as one remainder segment on the raised surface fill, left out of the legend. Omit to split the strip by the segments' own total. A `max` under the segments' total is ignored. |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For sizing/composition only. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# StackedList
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/molecules/stacked-lists/stacked-lists.md`
Human reference: https://canvas.nannier.com/components/stacked-lists
## Public imports
```tsx
import { StackedList } 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 lists with avatar, two-line items, and trailing metadata. Used for
contacts, activity feeds, and data previews.
Rows can carry a per-item `trailing` slot for an inline control, and
`reorderable` adds a leading drag grip per row (keyboard- and
screen-reader-operable) that reports each drop through `onReorder` while the
order stays controlled by your `items` array.
The rows are a list, as in Tailwind UI, and each row is one of its items: a
screen reader announces the list with its number of rows and moves through it
row by row. A clickable row's button sits inside its item. The header `title`
names the list; a list without a title takes its name from `label`.
A `virtualized` list with a bounded height scrolls its rows in a list of its
own. On the web, while those rows overflow, that list is a keyboard tab stop
(after the header's action, and before the rows' own buttons when they have
any), the keyboard can scroll it, and while it has keyboard focus the list's
card, or a plain list's frame, draws the theme's focus ring. Only the rows near
its viewport are mounted, and each says where it sits in the whole list, so a
screen reader still counts every row. Give a windowed list a `title` or a
`label`: focused, a list with no name is named in Chromium from the text of
every row it has rendered, so the kit warns in development when it has neither.
## Usage
```tsx
```
## Variants
### Status tones
```tsx
```
### Clickable
```tsx
```
### Card
```tsx
```
### Custom header action
```tsx
Invite}
items={[
{ name: "Rachel Chen", detail: "Engineering Lead" },
{ name: "Ada Lovelace", detail: "Staff Engineer" }
]}
/>
```
### Reorderable
Each row gains a leading grip: drag it, or focus it and press Space to grab, the arrow keys to move, Space to drop, Escape to cancel. The order stays controlled: a drop only reports the move (`fromIndex`/`toIndex` plus the new `afterId`/`beforeId` neighbors), and the consumer applies it to its own array.
```tsx
{(people, setPeople) => (
{
const next = [...people];
next.splice(toIndex, 0, ...next.splice(fromIndex, 1));
setPeople(next);
}}
/>
)}
```
### Inline trailing control
A per-item `trailing` slot renders right-aligned before the badge/meta cluster, for an inline editor or affordance. In the `clickable` variant a row carrying the slot moves its press target to the content region, so the control never nests inside the row pressable.
```tsx
Owner },
{ name: "Ada Lovelace", detail: "ada@example.com", trailing: Owner , meta: "editor" },
]}
/>
```
## Do & Don't
### Two-line with avatar
**Do** — Primary line bold; secondary line smaller and muted, truncated so long values never wrap.
```tsx
```
**Don't** — Equal weight on both lines flattens the hierarchy; the email competes with the name.
```tsx
RC
Rachel Chen
rachel.chen@example.com
```
### Clickable
**Do** — Wrap the row in a link with a hover background and a trailing chevron to signal drilldown.
```tsx
```
**Don't** — A drilldown row with no hover state and no chevron gives no hint it is interactive.
```tsx
```
### Card surface group
**Do** — Separate the titled header with a rule and give each row a trailing action menu.
```tsx
```
**Don't** — A header with no rule blends into the rows, and dropping the per-row action removes the affordance.
```tsx
Team members
RC
Rachel Chen
Engineering Lead
```
### Reorderable rows
**Do** — Give every reorderable row a stable `id` and apply the reported move to your own array: identity is what keeps focus, press state, and the drag announcements attached to the right row.
```tsx
{(people, setPeople) => (
{
const next = [...people];
next.splice(toIndex, 0, ...next.splice(fromIndex, 1));
setPeople(next);
}}
/>
)}
```
**Don't** — Don't reorder id-less rows: with the array index as the only key, every drop re-keys the rows under assistive tech and remounts their avatars, and the reported `afterId`/`beforeId` degrade to transient indices no backend can rank by.
```tsx
{}}
/>
```
## Supported props
### StackedListProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | StackedListItem[] \| undefined | no | | Rows to render. |
| title | ReactNode | no | | Optional header title; shown above the rows, separated by a rule. It also names the list for assistive tech ("Team, list, 12 items"). |
| label | string \| undefined | no | | The list's accessible name when it has no `title` (a `title` names the list, and wins when both are set). A screen reader announces it with the list. A `title` that is not a string names the list on the web only, and `label` then names it on iOS and Android. A `virtualized` list needs a name: on the web its scroller is a keyboard stop while the rows overflow, and Chromium names an unnamed stop from the text of every row it has rendered. |
| action | ReactNode | no | | Trailing header content (e.g. an action button); only shown with a title. Takes precedence over `addAction` when both are supplied. |
| addAction | string \| undefined | no | | Convenience header action: renders a small outlined button with a leading plus icon and this label (e.g. "Add"). Only shown with a title and when `action` is not set. Serializable, so the playground can drive it. |
| rowMenu | boolean \| undefined | no | false | Appends a trailing ghost overflow ("...") action-menu button to every row. Press is reported through `onPressItemMenu`. |
| onPressItem | ((index: number, event: GestureResponderEvent) => void) \| undefined | no | | Press handler for a row, by index. Only used in the `clickable` variant. |
| onPressItemMenu | ((index: number, event: GestureResponderEvent) => void) \| undefined | no | | Press handler for a row's overflow menu, by index. Used with `rowMenu`. |
| clickable | boolean \| undefined | no | false | Variant (pick one; default is the plain two-line list). |
| card | boolean \| undefined | no | false | Variant (pick one; default is the plain two-line list). |
| flush | boolean \| undefined | no | false | Divider modifier: rows are ruled by default; `flush` removes the hairlines. |
| 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. Ignored (with a dev warning) when `reorderable` is set: windowing unmounts off-screen rows, which would leave them undraggable and unmeasurable mid-drag. On the web, while the rows overflow, the windowed list is a keyboard tab stop the keyboard can scroll, and the list's frame draws the focus ring while it has keyboard focus. |
| reorderable | boolean \| undefined | no | false | Make the rows reorderable: each row gains a leading drag grip (keyboard- and screen-reader-operable: Space grabs, the arrow keys move, Space drops, Escape cancels) and the list becomes one drop zone. The order stays CONTROLLED by the consumer's `items` array: a drop only reports the move through `onReorder`, never reorders internally. |
| onReorder | ((move: StackedListMove) => void) \| undefined | no | | Fired on drop with the computed move (row id, from/to index, new neighbors). |
| 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.
---
# Stats
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/molecules/stats/stats.md`
Human reference: https://canvas.nannier.com/components/stats
## Public imports
```tsx
import { Stats } 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.
Single value, grouped row, with sparkline, with comparison. Used for dashboards and overview pages.
Under the value each tile can draw one strip: its trend (`spark`) or its composition (`share`). Both reserve the same height, so a row that mixes them keeps one card height and no tile is left half empty. `framed` puts every strip on a muted track, which is what makes a ROW of them scan evenly.
The metrics sit edge to edge inside one bordered surface, a hairline between each: a 16px
inset, a 13px label, a 28px figure and a 12px delta led by its direction arrow. `framed`
separates them into tiles 12px apart. A trend draws as a small line beside the figure and
a composition as a thin bar over its legend. `compact` renders value-first rail tiles with
a 12px inset and 20px figures; `large` renders 36px dashboard headlines; compact wins if
both are set. Each item may include a muted `description`.
## Usage
```tsx
```
## Variants
### Group
```tsx
```
### Plain
```tsx
```
### Per-tile icon, control and accent
```tsx
, chart1: true },
{ label: "Sessions", value: "489", actions: 7d , chart2: true }
]}
/>
```
### Sparkline
```tsx
```
### Composition strip
A metric that has no trend to plot usually still decomposes. `share` draws that
composition over a muted rail, in the same slot and at the same height as the
sparkline, so it fills the tile with something true rather than with padding.
```tsx
```
### Framed strips
Without a frame each strip draws only its marks, so a quiet series shows a
near-empty band beside a full bar and the row's densities range from blank to
solid. `framed` gives every strip the same track whatever its data: a flat
series then reads as a chart sitting at zero, and the fill is the only thing
that varies.
```tsx
```
### Mixed strips in one row
```tsx
```
### Tappable
```tsx
{}}
items={[
{ label: "Active users", value: "71,897" },
{ label: "Revenue", value: "$48.2k" }
]}
/>
```
## Do & Don't
### Single
**Do** — Name the comparison and the period so the delta is unambiguous.
```tsx
```
**Don't** — A bare delta with no baseline leaves the reader asking: up against what, and over what window?
```tsx
```
### Group
**Do** — Use the auto-fit grid and round headline numbers so cards wrap and stay scannable.
```tsx
```
**Don't** — A fixed flex row of full-precision numbers overflows on narrow viewports and crowds the cards.
```tsx
```
### Plain (no border)
**Do** — On a parent surface drop the border and radius; let the number stacks stand on their own.
```tsx
```
**Don't** — Bordered cards inside a card surface double the chrome: a box drawn around boxes.
```tsx
```
### With sparkline
**Do** — Pair the sparkline with an explicit delta so the headline reads without decoding the curve.
```tsx
```
**Don't** — A trend line with no current delta makes you eyeball the slope to guess the direction.
```tsx
Requests
24.5k
```
### A strip on every tile in a row
**Do** — Give every tile in a row a strip: the trend where one exists, the composition where it does not, and frame them so the row reads as one finished set.
```tsx
```
**Don't** — Give one tile a strip and leave its neighbour without: cards stretch to the tallest sibling, so the bare tile ends up with a band of blank space that reads as unfinished.
```tsx
```
## Supported props
### StatsProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | StatItem[] | yes | | The metrics to render, in order. |
| plain | boolean \| undefined | no | false | Borderless metric stacks on a shared surface, for nesting inside a card. |
| compact | boolean \| undefined | no | false | Dense value-first tile for a navigation rail: a 12px inset and a 20px headline. |
| large | boolean \| undefined | no | false | Value-first dashboard tile with a 36px headline. |
| title | string \| undefined | no | | Optional heading shown above the metrics (mainly for the plain surface). |
| framed | boolean \| undefined | no | false | Draw every metric's strip on a muted track, in one bounded band. For a ROW of metrics, where the strips are read against each other. Without it each strip draws only its marks, so a metric with a quiet series shows a near-empty band beside one with a full bar and the row's densities range from blank to solid with nothing tying them together; the eye lands on the fullest tile rather than scanning the row. The track gives every strip the same frame whatever its data, so a flat series reads as a chart sitting at zero and the fill is the only thing that varies. |
| onPressItem | ((index: number) => void) \| undefined | no | | Make each metric a tappable target (drill into the underlying detail). When set, every metric renders as a Pressable with a button role and a pressed affordance, so a tappable stat needs no hand-rolled Pressable. |
| 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.
---
# Stepper
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/stepper/stepper.md`
Human reference: https://canvas.nannier.com/components/stepper
## Public imports
```tsx
import { Stepper } 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.
Increment, decrement, or type a number in a bounded range. A − button, an editable numeric center field, and a + button, controlled by `value` and clamped to `min` / `max` by `step`.
This is the ± numeric control (the iOS UIStepper idiom); for a multi-step progress indicator see Steps.
On the web the control is one 40 px box (32 `small`, 48 `large`) at the control corner with square ± cells and the value in the mono face; on iOS the capsule [ − | + ] pill with the value to its left; on Android Material 3's outlined circular icon buttons around the value.
Pass `label` to title the control from above, `description` for a secondary line under it, and `required` to mark it: the label takes a decorative destructive star, and a screen reader hears the control as required,
from `aria-required` on the web's numeric field and from its name ("Quantity, required") on iOS and Android.
## Usage
```tsx
```
## Variants
### With label
```tsx
```
### With description
```tsx
```
### With suffix
`suffix` sets a unit beside the control, in the subtle ink.
```tsx
```
### Sizes
```tsx
```
### With min / max
```tsx
```
### Stepped
```tsx
```
### Disabled
```tsx
```
## Do & Don't
### Bounds
**Do**: Set `min` and `max` so the buttons disable at the edges and the value can never leave the valid range.
```tsx
{}} />
```
**Don't**: Leaving the range unbounded lets the user push the count below zero or past what the form can accept.
```tsx
{}} />
```
### Step
**Do**: Match `step` to the real increment, so each tap moves the value by an amount that makes sense for the field.
```tsx
{}} />
```
**Don't**: A step of 1 on a field that only takes round numbers makes the user tap many times to reach a usable value.
```tsx
{}} />
```
## Supported props
### StepperProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | number \| undefined | no | | Controlled numeric value (clamped to [min, max] for display); omit for uncontrolled use. |
| defaultValue | number \| undefined | no | | Initial value for uncontrolled use (a bare steps out of the box). Default `min`. |
| onChange | ((next: number) => void) \| undefined | no | | Fired with the next clamped value when buttons, adjustment keys, or direct entry change it (both modes). |
| testID | string \| undefined | no | | E2E hook forwarded to the group container. |
| min | number \| undefined | no | 0 | Lower bound. Default 0. The − button disables when value <= min. |
| max | number \| undefined | no | | Upper bound. Default Number.MAX_SAFE_INTEGER. The + button disables when value >= max. |
| step | number \| undefined | no | 1 | Increment/decrement amount for the ± buttons and adjustment keys. Default 1. |
| label | string \| undefined | no | | The control's persistent, component-owned label. When set it renders as a VISIBLE title ABOVE the ± control (mirroring Input's above-field placement, per-OS type from the skin) and becomes the group's programmatic name (wired via accessibilityLabel + aria-labelledby). When omitted, the control keeps the invisible "Number" accessible-name fallback and renders no visible label, so a bare ` ` looks exactly as before. |
| description | ReactNode | no | | Optional muted secondary line rendered under the label (parity with Input). Takes effect only alongside `label`. |
| required | boolean \| undefined | no | false | Marks the control required: appends a destructive "*" to the label (decorative, never in the accessible name) and announces the control as required, through aria-required on the web's numeric field and with the word in its name ("Quantity, required") on iOS and Android. The star takes effect only alongside `label`. |
| suffix | ReactNode | no | | A unit or note beside the control, 16 px after it on the same line ("× 4px", "items"): a string is set in the 400 13 px subtle ink, a node as it is. Unlike `description`, it reads as part of the value's line. |
| small | boolean \| undefined | no | false | Size (pick one; default is the medium control). Precedence: large > small. |
| large | boolean \| undefined | no | false | Size (pick one; default is the medium control). Precedence: large > small. |
| 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 underlying React Native host. Supports object refs and callback refs; native methods follow the platform's behavior. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Steps
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/steps/steps.md`
Human reference: https://canvas.nannier.com/components/steps
## Public imports
```tsx
import { Steps } 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-step progress indicators: horizontal, vertical, with progress.
## Usage
```tsx
```
## Variants
### Compact sidebar
`compact` with `vertical` renders short 22px markers without connector lines, as a
workflow sidebar. The current row is announced as the current step. Pressable steps
retain their normal controlled and uncontrolled behavior.
```tsx
```
### Sidebar rows
`rows` with `vertical` draws each step as a 32 point row like a desktop app's sidebar
list: the current step's row is filled, done steps carry a check, and the row reads
", done" or ", current" to assistive technology. With `onStepPress` each row is a button.
```tsx
```
### Vertical
```tsx
```
### Step progress
A vertical step's title sits on an 18.2 px line over its 12 px description in the third
ink; the current step's description takes the accent's text ink. A step with `progress`
(0 to 100) draws its own 6 px bar 8 px under its text, the fill running from the accent
into the cyan series hue with the accent's glow; the other layouts ignore it.
```tsx
```
### Segmented
`segmented` lays the steps out as equal segments 8 px apart: each a 4 px bar (the
accent once reached, glowing on the current step, the raised surface ahead) over its
500 13 px label and, from its `description`, a status line inked by the step's state:
the success ink once done, the accent's text ink on the current step, the third ink
ahead. The layout precedence is `progress` > `segmented` > `vertical`.
```tsx
```
### Progress bar
```tsx
```
### Stacks at narrow widths
`stacks` renders the horizontal layout as the vertical one when the component's
own container is at or below `stackBreakpoint` (default `sm` = 640):
container-measured, like Row `stacks`, so a wizard header inside a narrow
column stacks too.
```tsx
```
## Do & Don't
### Horizontal
**Do** — Mark exactly one step active; show completed steps filled and the rest pending.
```tsx
```
**Don't** — Styling several steps as active at once hides which step the user is actually on.
```tsx
1
Account
2
Profile
3
Review
```
### Vertical
**Do** — Pair each vertical step with a one-line description so the extra width earns its place.
```tsx
```
**Don't** — A vertical step with only a title wastes the space the layout is built to use.
```tsx
```
### Progress bar
**Do** — Label the bar and show the exact percentage so progress is legible at a glance.
```tsx
```
**Don't** — A bare progress bar with no percentage leaves users guessing how far along they are.
```tsx
```
## Supported props
### StepsProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| steps | Step[] | yes | | Ordered steps to render. Each is a label with an optional one-line note. |
| current | number \| undefined | no | | Index of the active step (CONTROLLED). Earlier steps read completed, later ones muted. Omit for uncontrolled use. |
| defaultCurrent | number \| undefined | no | | Initial active step for uncontrolled use (pressing a step, with `onStepPress`, moves it). |
| vertical | boolean \| undefined | no | false | Layout (pick one; default is horizontal). Precedence: progress, segmented, vertical. |
| compact | boolean \| undefined | no | false | A short vertical list with 22px markers and no connector lines, for a workflow sidebar. |
| rows | boolean \| undefined | no | false | Vertical only: each step a 32 px row like a desktop app's sidebar list (the Claude desktop window's), the current step's row filled, done steps marked with a check. |
| segmented | boolean \| undefined | no | false | Equal segments in a row: each step a 4 px bar (the accent once reached, glowing on the current step) over its label and, from its `description`, a status line inked by the step's state (the success ink once done, the accent's text ink on the current step). |
| progress | boolean \| undefined | no | false | Render a labeled percentage progress bar instead of discrete steps. |
| stacks | boolean \| undefined | no | false | Responsive (horizontal layout only): render the EXISTING vertical layout when the component's own CONTAINER is at or below `stackBreakpoint` (default `sm` = 640). Container-measured with a viewport seed, mirroring Row `stacks`. `vertical` and `progress` are unaffected (a progress bar is already fluid). |
| stackBreakpoint | BreakpointKey \| undefined | no | | The breakpoint at and below which `stacks` goes vertical (default `"sm"`). Only meaningful with `stacks`. |
| value | number \| undefined | no | | Progress mode only: filled fraction, 0-100 (clamped). Defaults to 0. |
| label | string \| undefined | no | | Progress mode only: caption shown left of the percentage. |
| onStepPress | ((index: number) => void) \| undefined | no | | When set, each step circle is pressable, reporting the step index. |
| 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.
---
# Swatch
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/swatch/swatch.md`
Human reference: https://canvas.nannier.com/components/swatch
## Public imports
```tsx
import { Swatch } 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 color sample: a filled rounded block with the token's name and value beneath
it, the anatomy a design-system color sheet repeats down a page. Swatch owns the
whole lockup, so a sheet never sets a bare block next to a hand-built text
column and drifts row to row.
Pass a live theme token as `color` and the sample follows the active scheme; a
hairline edge keeps a white or near-black sample visible on the surface behind
it. The chip is 48px at a 12px corner (32 `small`, 72 `large`, and 20 `tiny`,
the disc of a token Chip), the name a 13px line and the value lines 11px mono
in the subtle ink; the lockup also comes as a `circle`, `inline` (the label
beside the block), or as a `block` (a full-width ramp bar). `ring` draws the
edge as a 1px inset ring in the field border instead of the hairline, the token
chip's swatch. `selected` marks the chosen sample with the selection ring, a
2px gap in the page color and then a 2px accent band, and adds "selected" to
its accessible name. `decorative` hides a sample from assistive tech when text
beside it already names it (a theme choice's disc before its label), so the
name is read once.
A palette picker takes one of the kit's five accent palettes, `violet`, `cyan`,
`mint`, `rose` or `amber` (ThemeProvider's precedence: cyan, mint, rose, amber,
violet), with `selected` and `onPress`. It is a 16px disc of the palette's hue,
the same in both schemes, and a radio named for its palette ("Violet"). The
palette in force wears the selection ring, a 2px gap in the card color and then
a 2px muted-foreground band; the others draw none. On the web a hovered disc
grows to 1.25 times its size. A size boolean sizes the disc to that sample edge
(`small` is a 32px disc, the size a form's hue choice takes). Put the pickers in
a `radiogroup` so the set reads as one choice. A plain sample still uses `color`.
The section page's samples come as forms and lockups of the same component. `tile` is
the labelled colour tile, the parent's width and 64 px tall (40 `small`), its name set
inside the fill on one line (ellipsized where it outgrows the tile, its accessible name
whole) in the 11 px code face at an 8 px inset, at the foot (centred `small`),
in the `ink` it is given. `bar` is a colour bar, the parent's width at 26 px and a 7 px
corner. `steps` takes a list of colours in place of `color` and draws one bar of them,
flush and clipped at a 10 px corner at the size's height; `segmented` stands them 4 px
apart in a 28 px bar, each at a 3 px corner but for the bar's two outer ends at 7. The
form precedence is `tile` > `bar` > `circle`, and a `steps` list wins over all three.
`fluid` makes the chip's edge the parent's width, held 1:1; `glow` lays the fill's own
light under the chip; `centered` centres the label column 10 px under the chip; `bare`
drops the resting edge from a fill that needs none; and `compact` is the mono lockup, the
name 500 12 px and the value 400 10 px in the code face, each on one line (ellipsized in a
narrow column), 6 px apart and 6 px under a 56 px `block`.
## Usage
```tsx
primary
```
## Variants
### Sizes
```tsx
info
primary
success
destructive
```
### Circle
```tsx
primary
```
### Ring
The token chip's swatch: a 20px disc edged by a 1px inset ring.
```tsx
```
### Inline
```tsx
primary
```
### Block
```tsx
primary
```
### Spectrum
```tsx
```
### Tiles
```tsx
primary
success
primary-soft
success-soft
```
### Bar
```tsx
```
### Steps
```tsx
```
### Compact
```tsx
ion
secondary
```
### Selected
```tsx
primary
success
```
### Detail line
```tsx
primary
```
### Palette picker
```tsx
{}} />
{}} />
{}} />
{}} />
{}} />
```
### Form-sized palette picker
```tsx
{}} />
{}} />
{}} />
```
## Do & Don't
### Anatomy
**Do**: Let Swatch carry the name and the value, so every row of a sheet keeps one lockup and one accessible name.
```tsx
primary
```
**Don't**: Set a bare block beside a hand-composed text column: the spacing and the type scale drift row to row, and the sample ships as an unnamed block.
```tsx
primary
--primary
```
### Color source
**Do**: Pass the live token, so the sample repaints when the scheme flips.
```tsx
card
```
**Don't**: Hard-code the value a token resolves to; the sample keeps showing the light color after the scheme flips to dark.
```tsx
card
```
## Supported props
### SwatchProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| color | string \| undefined | no | | The fill: any React Native color string. Pass a theme token, not a literal. |
| violet | boolean \| undefined | no | false | Palette picker (pick one): the sample becomes the 16 px hue disc of that accent palette, a radio named for it (a size boolean sizes the disc to that sample edge: `small` is 32 px). Precedence is ThemeProvider's: cyan, mint, rose, amber, violet. |
| cyan | boolean \| undefined | no | false | Palette picker (pick one): the sample becomes the 16 px hue disc of that accent palette, a radio named for it (a size boolean sizes the disc to that sample edge: `small` is 32 px). Precedence is ThemeProvider's: cyan, mint, rose, amber, violet. |
| mint | boolean \| undefined | no | false | Palette picker (pick one): the sample becomes the 16 px hue disc of that accent palette, a radio named for it (a size boolean sizes the disc to that sample edge: `small` is 32 px). Precedence is ThemeProvider's: cyan, mint, rose, amber, violet. |
| rose | boolean \| undefined | no | false | Palette picker (pick one): the sample becomes the 16 px hue disc of that accent palette, a radio named for it (a size boolean sizes the disc to that sample edge: `small` is 32 px). Precedence is ThemeProvider's: cyan, mint, rose, amber, violet. |
| amber | boolean \| undefined | no | false | Palette picker (pick one): the sample becomes the 16 px hue disc of that accent palette, a radio named for it (a size boolean sizes the disc to that sample edge: `small` is 32 px). Precedence is ThemeProvider's: cyan, mint, rose, amber, violet. |
| selected | boolean \| undefined | no | false | The sample is the chosen one: draws the selection ring (a 2 px gap in the page color, then a 2 px band). On a plain sample the band is the accent and the name says "selected"; on a palette picker it is the one in force and the radio is checked. |
| onPress | (() => void) \| undefined | no | | Palette picker: pick this palette. |
| children | ReactNode | no | | The sample's name (the token name). Swatch owns this label column. |
| value | string \| undefined | no | | Primary mono line under the name: what the name resolves to (a hex, a custom-property name, a ramp step). |
| detail | string \| undefined | no | | Secondary mono line under the value, for a second notation of the same color (an oklch value, a legacy alias). |
| tiny | boolean \| undefined | no | false | The 20 px disc of a token chip (` }>`). |
| small | boolean \| undefined | no | false | |
| large | boolean \| undefined | no | false | |
| circle | boolean \| undefined | no | false | Circle instead of the default rounded square. Keeps the 1:1 footprint, so it wins over `block`'s stretch. |
| ring | boolean \| undefined | no | false | Draws the token chip's hairline INSIDE the sample, a 1 px inset ring in the field border, in place of the resting hairline edge; a selected sample keeps its selection ring outside it. |
| inline | boolean \| undefined | no | false | Put the label column beside the block instead of under it. |
| block | boolean \| undefined | no | false | Stretch the block to the parent's width; the size then reads as its height (the full-width ramp bar). |
| tile | boolean \| undefined | no | false | The labelled colour tile: the parent's width, 64 px tall (40 `small`), its name set inside the fill on one line (ellipsized where it outgrows the tile) at an 8 px inset in the 11 px mono face, at the foot (centred `small`). `ink` paints the name; the value lines are read into the accessible name only. |
| bar | boolean \| undefined | no | false | A colour bar: the parent's width, 26 px tall at the swatch bar corner (a neutral role's sample). |
| steps | readonly string[] \| undefined | no | | One bar of colour steps in place of `color`, flush and clipped at the ramp corner at the size's height (`block small` is the 32 px accent ramp); `segmented` stands them apart. |
| segmented | boolean \| undefined | no | false | `steps` as separate segments: a 28 px bar, 4 apart, the step corner inside and the bar corner at its ends. |
| fluid | boolean \| undefined | no | false | The chip's edge is the parent's width, held 1:1 (a spectrum's discs filling their cells). |
| glow | boolean \| undefined | no | false | The fill's own ambient light under the chip (`0 12px 30px -8px`, the fill at .6). |
| centered | boolean \| undefined | no | false | The label column centred under the chip, 10 px below it. |
| bare | boolean \| undefined | no | false | No resting edge: the sample sits on its fill alone (a saturated fill that needs no hairline). |
| compact | boolean \| undefined | no | false | The mono lockup: the name 500 12 px and the value 400 10 px in the code face, each on one line (ellipsized where it outgrows a narrow column), 6 px apart and 6 px under the chip; a `block` chip is 56 px tall. |
| ink | string \| undefined | no | | The `tile` name's ink, a theme token like `color` (default the foreground). |
| accessibilityLabel | string \| undefined | no | | Accessible name override. By default the sample names itself from its own name, value, and detail (falling back to the color string), so pass this only when the name is a node rather than text and there is nothing to read. |
| decorative | boolean \| undefined | no | false | The sample is decoration beside text that already names it (a theme choice's disc before its label): hidden from assistive tech, so the text carries the name once. Pass it in place of `accessibilityLabel`. |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | LayoutStyle | no | | For layout composition only (not styling): the fill, radius, and size come from props. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Switch
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/switch/switch.md`
Human reference: https://canvas.nannier.com/components/switch
## Public imports
```tsx
import { Switch } 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.
On / off switch, isolated or grouped in a settings list.
In glass mode a switch keeps its solid look on every platform: it is a small
mark inside content, not a surface, so its track and knob draw as in solid mode,
with no pane and no rim, a checked track in its solid brand fill. The track, the
label, the description and the hit target never move, and the checked state flips
at once. A disabled switch is inert.
`small` and `large` scale the track on the web.
Pass `chip` for a compact toolbar setting: a 32 px bordered capsule with a 26×16 track, a 10 px knob and the label after the switch. The web thumb follows the reference spring and Reduce Motion; native skins keep their own switch.
On iOS and Android the switch is the
platform's own control, which comes in one size (51x31 on iOS, 52x32 on Android), so
the size props leave it unchanged there.
Pass `ref` to access the interactive switch 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
Available to chat
```
## Variants
### Off
```tsx
Available to chat
```
### With description
```tsx
Available to chat
```
### Disabled
```tsx
Available to chat
```
### Controlled
```tsx
{(on, setOn) => Notifications }
```
### Settings list
`listed` gives each row a settings list's 10 px above and below, and `divided` rules a hairline above every row after the first, so the stack reads as one list.
```tsx
Glass surfaces
Reduced motion
Haptics
```
## Do & Don't
### Off
**Do** — Keep the standard off track, the raised fill inside its `input` edge, so off stays clearly interactive and distinct from a disabled control.
```tsx
Two-factor auth
```
**Don't** — A washed-out off track reads as disabled, so users can't tell the switch is simply off versus locked.
```tsx
Two-factor auth
```
### On
**Do** — Reserve the on switch for instantly reversible settings; route irreversible actions through a button plus confirmation.
```tsx
Auto-save drafts
Delete account…
```
**Don't** — A switch applies instantly; wiring an on toggle to an irreversible action invites accidental, unconfirmed data loss.
```tsx
Permanently delete account
```
### Label the setting
**Do** — Label the setting, not the state; the switch communicates on or off.
```tsx
Notifications
```
**Don't** — An On/Off label duplicates what the switch position already shows.
```tsx
Notifications
Off
On
```
## Supported props
### SwitchProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| checked | boolean \| undefined | no | false | Controlled on/off state; omit for uncontrolled use. |
| defaultChecked | boolean \| undefined | no | false | Initial state for uncontrolled use (a bare is interactive). |
| onChange | ((next: boolean) => void) \| undefined | no | | Fired with the next checked value when the switch is toggled (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. |
| testID | string \| undefined | no | | E2E hook forwarded to the pressable row. |
| small | boolean \| undefined | no | false | Size (pick one; default is the standard track). The web track scales; iOS and Android draw their platform switch, which comes in one size. |
| large | boolean \| undefined | no | false | Size (pick one; default is the standard track). The web track scales; iOS and Android draw their platform switch, which comes in one size. |
| chip | boolean \| undefined | no | false | A compact bordered capsule with its label after the switch, for a toolbar setting. |
| listed | boolean \| undefined | no | false | A row of a settings list: 10 px above and below the row, so a stack of switches reads as a list rather than a form (stack them in a `Column tight`). Pair with `divided` on every row after the first. |
| divided | boolean \| undefined | no | false | A hairline above the row in the `border` ink: a settings list's separator from the row before. |
| disabled | boolean \| undefined | no | false | State. |
| children | ReactNode | no | | Optional label, rendered to the left of the track. |
| description | ReactNode | no | | Optional muted description line, rendered under the label. |
| accessibilityLabel | string \| undefined | no | | Accessible name for the switch when no visible `children` label is rendered (e.g. a switch embedded in an Action Panel whose label sits in sibling Text). Without it, a label-less switch announces only "switch, on/off". |
| 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 switch 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.
---
# TabBar
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/tab-bar/tab-bar.md`
Human reference: https://canvas.nannier.com/components/tab-bar
## Public imports
```tsx
import { TabBar } 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.
Bottom app navigation: a row of equal-width destinations, each an icon over a short label, with exactly one active.
The mobile idiom (the iOS 26 floating tab bar / Material 3 navigation bar), rendered through the glass functional layer.
iOS and web share the iOS 26 anatomy: a 62 px capsule that floats above the
content (which scrolls beneath it), inset from the sides, with the safe-area
inset kept under the capsule, and the selected destination washed in the accent
as a capsule covering its whole cell. Android docks the Material 3 bar
full-bleed, 80 tall under a hairline, with its 64 by 32 icon pill. In glass mode
the selection is a control-layer pane on the active destination: the cell
capsule on iOS and web, the icon pill on Android. Solid mode retains the
ordinary platform surface.
## Usage
```tsx
},
{ key: "search", label: "Search", icon: (active) => },
{ key: "profile", label: "Profile", icon: (active) => }
]}
/>
```
## Variants
### Controlled
```tsx
{(active, setActive) => (
},
{ key: "search", label: "Search", icon: (active) => },
{ key: "profile", label: "Profile", icon: (active) => }
]}
/>
)}
```
### Unread
An item with `unread` carries a small destructive dot over its glyph, and its accessible
name ends in ", unread". On the web the capsule floats 12 px off its container's sides
and bottom.
```tsx
},
{ key: "inbox", label: "Inbox", unread: true, icon: (active) => },
{ key: "profile", label: "Profile", icon: (active) => }
]}
/>
```
## Do & Don't
### Tab Bar
**Do** — Exactly one destination carries the primary tint on both its icon and label,
over the raised cell capsule on iOS and web or Material 3's active-indicator pill on Android;
the rest stay muted, so the current tab is always singular and unmistakable. On web the capsule is what lets a bar and a `Sidebar` in the same app shell mark the current destination the same way across the breakpoint that swaps them.
```tsx
{}}
items={[
{ key: "home", label: "Home", icon: (active) => },
{ key: "search", label: "Search", icon: (active) => },
{ key: "profile", label: "Profile", icon: (active) => }
]}
/>
```
**Don't** — Tinting every destination primary erases the active indicator: nothing tells you which tab is current.
```tsx
Home
Search
Profile
```
## Supported props
### TabBarProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | TabBarItem[] | yes | | |
| active | string \| undefined | no | | The active item's key; omit for uncontrolled use. |
| defaultActive | string \| undefined | no | | Initial active key for uncontrolled use (defaults to the first item's key). |
| onSelect | ((key: string) => void) \| undefined | no | | Called with the pressed destination's key (both modes). |
| testID | string \| undefined | no | | E2E hook forwarded to the tablist row. |
| bottomInset | number \| undefined | no | 0 | Safe-area bottom inset, e.g. `insets.bottom`. It is added BELOW the bar's own symmetric vertical padding (not in place of it), so the bar clears the home indicator without making the bottom margin heavier than the top. Defaults to 0 (web desktop / no inset). |
| style | StyleProp | no | | Outer layout composition only. Use bottomInset for the safe area. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Tabs
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/tabs/tabs.md`
Human reference: https://canvas.nannier.com/components/tabs
## Public imports
```tsx
import { Tabs } 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.
Underline, pill, vertical, with badges.
On the web the underline look marks the active tab with a glowing accent bar on
the row's hairline, pills sit on a rounded track with a raised thumb, and the
vertical rail washes its active row in the accent.
iOS draws its segmented
control (UISegmentedControl): underline and pills are a capsule track with a
raised thumb, and vertical is a grouped rail. Android draws Material 3's tabs.
In glass mode a filled selection is a
control-layer pane behind the selected label, in the platform skin's selected
tint with readable foreground ink. Solid mode keeps the complete platform
treatment.
## Usage
```tsx
```
## Variants
### Pill
```tsx
```
### Vertical
```tsx
```
### Responsive vertical
`responsive` renders a vertical rail as the horizontal underline look when the
component's own container is at or below `sm` (640): container-measured, so a
settings rail inside a narrow column flattens to a top tab bar instead of
starving the panel beside it.
```tsx
```
### Badge counts
On the web a count's numerals take their tab's own ink.
```tsx
```
### Disabled tab
```tsx
```
### Leading glyphs
A tab item takes an `icon`, a kit glyph before its label in the label's ink (15 px on
the web), as a settings rail names its sections.
```tsx
```
### Block
On the web a `block` underline row is equal columns with no gap between them, each
label centred in its own column under its share of the rule.
```tsx
```
### Scrollable overflow
A row longer than its container pans horizontally instead of clipping, at any
container width and with no prop: the scroller is inert while the row
fits,
and on Android it then takes no sideways drag either, so a tap that drifts
sideways still selects its tab and a swipe that starts across the row scrolls
the page (while TalkBack explores by touch the scroller stays enabled, so the
exploring finger reaches every tab).
Selecting a tab (press or arrow key)
scrolls it fully into view with a sliver of its neighbor left showing. `block` shares the row equally and never
overflows; a vertical rail stacks instead.
```tsx
```
### Wrapped overflow
`wrap` lays a row longer than its container out on further lines inside one
track instead of panning it, so every tab is on screen at once: the track fills
its container and grows taller, its corners staying concentric with the pills.
Reach for it where the strip is a page's own navigation on a phone (the docs
example rail); keep the scroller where the strip sits in a toolbar that must stay
one line tall. `block` never overflows and wins over `wrap`; a `responsive`
vertical rail honors `wrap` once it flattens.
```tsx
```
## Do & Don't
### Underline
**Do** — Underline and foreground-color only the active tab; leave the rest muted with no rule.
```tsx
```
**Don't** — Underlining every tab erases the active indicator: there is no way to tell which view is current.
```tsx
[{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 10 }, pressed ? { opacity: 0.9 } : null]}>
Overview
[{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 10 }, pressed ? { opacity: 0.9 } : null]}>
Activity
[{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 10 }, pressed ? { opacity: 0.9 } : null]}>
Settings
```
### Pill
**Do** — Exactly one pill gets the elevated background; the rest sit flat on the muted track.
```tsx
```
**Don't** — Giving every pill the raised background makes the group read as three buttons, not one selection.
```tsx
[{ flexDirection: "row", alignItems: "center", justifyContent: "center", borderRadius: 6, backgroundColor: tokens.background, boxShadow: "0px 16px 32px -22px rgba(121, 100, 214, 0.22)", paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
All
[{ flexDirection: "row", alignItems: "center", justifyContent: "center", borderRadius: 6, backgroundColor: tokens.background, boxShadow: "0px 16px 32px -22px rgba(121, 100, 214, 0.22)", paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
Active
[{ flexDirection: "row", alignItems: "center", justifyContent: "center", borderRadius: 6, backgroundColor: tokens.background, boxShadow: "0px 16px 32px -22px rgba(121, 100, 214, 0.22)", paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
Archived
```
### Vertical
**Do** — Wash the active rail item in the accent so the selected pane is unmistakable.
```tsx
```
**Don't** — With no filled active item the rail collapses into a plain link list and loses its current selection.
```tsx
[{ width: "100%", flexDirection: "row", alignItems: "center", borderRadius: 6, backgroundColor: "transparent", paddingHorizontal: 12, paddingVertical: 8 }, pressed ? { opacity: 0.9 } : null]}>
General
[{ width: "100%", flexDirection: "row", alignItems: "center", borderRadius: 6, backgroundColor: "transparent", paddingHorizontal: 12, paddingVertical: 8 }, pressed ? { opacity: 0.9 } : null]}>
Security
[{ width: "100%", flexDirection: "row", alignItems: "center", borderRadius: 6, backgroundColor: "transparent", paddingHorizontal: 12, paddingVertical: 8 }, pressed ? { opacity: 0.9 } : null]}>
Notifications
```
## Supported props
### TabsProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| tabs | TabItem[] \| undefined | no | | Triggers, left to right. Strings, or `{ label, badge }` for a count. |
| active | number \| undefined | no | | Index of the active trigger; omit for uncontrolled use. |
| defaultActive | number \| undefined | no | | Initial active index for uncontrolled use (a bare switches out of the box). |
| onSelect | ((index: number) => void) \| undefined | no | | Called with the pressed trigger's index (both modes). Matches TabBar and ButtonGroup, which also fire `onSelect` for the active-index change. |
| testID | string \| undefined | no | | E2E hook forwarded to the tablist row. |
| pills | boolean \| undefined | no | false | Look (pick one; default is the underline look). Precedence when more than one is passed: pills, then vertical, then underline. |
| vertical | boolean \| undefined | no | false | Look (pick one; default is the underline look). Precedence when more than one is passed: pills, then vertical, then underline. |
| underline | boolean \| undefined | no | false | Look (pick one; default is the underline look). Precedence when more than one is passed: pills, then vertical, then underline. |
| block | boolean \| undefined | no | false | Layout: equal full-width triggers vs. leading-aligned hugging triggers. |
| wrap | boolean \| undefined | no | false | Layout (the underline and pills rows): a row longer than its container wraps onto further lines inside one track instead of panning in the overflow scroller, so every tab stays on screen at once. The track fills its container and grows taller, its corners concentric with the pills. `block` never overflows and wins; a `responsive` vertical rail honors it once it flattens. |
| responsive | boolean \| undefined | no | false | Responsive (vertical look only): render the EXISTING horizontal underline look when the component's own CONTAINER is at or below `sm` (640), where a side rail would starve the panel beside it. Container-measured with a viewport seed. `pills` and `underline` are unaffected. |
| disabled | boolean \| undefined | no | false | |
| 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.
---
# TextInput
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/text-input/text-input.md`
Human reference: https://canvas.nannier.com/components/text-input
## Public imports
```tsx
import { TextInput } 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.
Single-line (or multiline) text entry. Control it with `value` + `onChangeText`, and style the box with the usual View style props. Common props: `placeholder`, `secureTextEntry` (passwords), `keyboardType`, and `multiline`.
## Usage
```tsx
```
## Variants
### Placeholder
```tsx
```
### Multiline
```tsx
```
### Disabled
```tsx
```
## Do & Don't
### Labelled fields
**Do**: Reach for `Input` for a form field: its `label` stays in view, names the field for assistive tech, and the field box is drawn for each platform.
```tsx
```
**Don't**: Let a bare TextInput's placeholder stand in for the label; it vanishes as soon as the user types, taking the field's only name with it.
```tsx
```
### Disabled
**Do**: Pair `editable={false}` with `aria-disabled`. The browser then disables the field and takes it out of the tab order, and VoiceOver and TalkBack announce it as dimmed.
```tsx
```
**Don't**: Stop at `editable={false}`. On the web that is only read-only: the field stays a Tab stop and is announced as read-only, not unavailable.
```tsx
```
### Focus ring
**Do**: Leave the focus ring to the primitive. It takes the theme's `ring` colour 2 px off the field, like every kit control. The browser draws a text field's ring on every focus, a click included, since the field takes typing however it was focused.
```tsx
```
**Don't**: Switch the outline off without painting a focus state of your own. A keyboard user then has no way to see which field takes their typing.
```tsx
```
## Supported props
### TextInput host props
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| systemFocus | boolean \| undefined | no | | Keep the platform’s system focus appearance instead of the themed ring. |
| allowFontScaling | boolean \| undefined | no | | Specifies whether fonts should scale to respect Text Size accessibility settings. The default is `true`. |
| autoCapitalize | "none" \| "sentences" \| "words" \| "characters" \| undefined | no | | Can tell TextInput to automatically capitalize certain characters. characters: all characters, words: first letter of each word sentences: first letter of each sentence (default) none: don't auto capitalize anything https://reactnative.dev/docs/textinput#autocapitalize |
| autoComplete | "country" \| "name" \| "off" \| "nickname" \| "username" \| "password" \| "2fa-app-otp" \| "additional-name" \| "address-line1" \| "address-line2" \| "birthdate-day" \| "birthdate-full" \| "birthdate-month" \| "birthdate-year" \| "cc-csc" \| "cc-exp" \| "cc-exp-day" \| "cc-exp-month" \| "cc-exp-year" \| "cc-number" \| "cc-name" \| "cc-given-name" \| "cc-middle-name" \| "cc-family-name" \| "cc-type" \| "current-password" \| "email" \| "email-otp" \| "flight-confirmation-code" \| "flight-number" \| "family-name" \| "gender" \| "gift-card-number" \| "gift-card-pin" \| "given-name" \| "honorific-prefix" \| "honorific-suffix" \| "loyalty-account-number" \| "name-family" \| "name-given" \| "name-middle" \| "name-middle-initial" \| "name-prefix" \| "name-suffix" \| "new-password" \| "one-time-code" \| "organization" \| "organization-title" \| "password-new" \| "postal-address" \| "postal-address-country" \| "postal-address-dependent-locality" \| "postal-address-extended" \| "postal-address-extended-postal-code" \| "postal-address-locality" \| "postal-address-region" \| "postal-address-unit" \| "postal-code" \| "promo-code" \| "street-address" \| "sms-otp" \| "tel" \| "tel-country-code" \| "tel-national" \| "tel-device" \| "upi-vpa" \| "url" \| "wifi-password" \| "username-new" \| undefined | no | | Specifies autocomplete hints for the system, so it can provide autofill. On Android, the system will always attempt to offer autofill by using heuristics to identify the type of content. To disable autocomplete, set autoComplete to off. The following values work across platforms: - `additional-name` - `address-line1` - `address-line2` - `cc-number` - `country` - `current-password` - `email` - `family-name` - `given-name` - `honorific-prefix` - `honorific-suffix` - `name` - `new-password` - `off` - `one-time-code` - `postal-code` - `street-address` - `tel` - `username` The following values work on iOS only: - `nickname` - `organization` - `organization-title` - `url` The following values work on Android only: - `birthdate-day` - `birthdate-full` - `birthdate-month` - `birthdate-year` - `cc-csc` - `cc-exp` - `cc-exp-day` - `cc-exp-month` - `cc-exp-year` - `2fa-app-otp` - `email-otp` - `flight-confirmation-code` - `flight-number` - `gender` - `gift-card-number` - `gift-card-pin` - `loyalty-account-number` - `name-family` - `name-given` - `name-middle` - `name-middle-initial` - `name-prefix` - `name-suffix` - `password` - `password-new` - `postal-address` - `postal-address-country` - `postal-address-dependent-locality` - `postal-address-extended` - `postal-address-extended-postal-code` - `postal-address-locality` - `postal-address-region` - `postal-address-unit` - `promo-code` - `sms-otp` - `tel-country-code` - `tel-national` - `tel-device` - `upi-vpa` - `wifi-password` - `username-new` |
| autoCorrect | boolean \| undefined | no | | If false, disables auto-correct. The default value is true. |
| autoFocus | boolean \| undefined | no | | If true, focuses the input on componentDidMount. The default value is false. |
| blurOnSubmit | boolean \| undefined | no | | If `true`, the text field will blur when submitted. The default value is true for single-line fields and false for multiline fields. Note that for multiline fields, setting `blurOnSubmit` to `true` means that pressing return will blur the field and trigger the `onSubmitEditing` event instead of inserting a newline into the field. |
| submitBehavior | SubmitBehavior \| undefined | no | | When the return key is pressed, For single line inputs: - `'newline`' defaults to `'blurAndSubmit'` - `undefined` defaults to `'blurAndSubmit'` For multiline inputs: - `'newline'` adds a newline - `undefined` defaults to `'newline'` For both single line and multiline inputs: - `'submit'` will only send a submit event and not blur the input - `'blurAndSubmit`' will both blur the input and send a submit event |
| caretHidden | boolean \| undefined | no | | If true, caret is hidden. The default value is false. |
| contextMenuHidden | boolean \| undefined | no | | If true, context menu is hidden. The default value is false. |
| defaultValue | string \| undefined | no | | Provides an initial value that will change when the user starts typing. Useful for simple use-cases where you don't want to deal with listening to events and updating the value prop to keep the controlled state in sync. |
| editable | boolean \| undefined | no | | If false, text is not editable. The default value is true. |
| keyboardType | KeyboardTypeOptions \| undefined | no | | enum("default", 'numeric', 'email-address', "ascii-capable", 'numbers-and-punctuation', 'url', 'number-pad', 'phone-pad', 'name-phone-pad', 'decimal-pad', 'twitter', 'web-search', 'visible-password') Determines which keyboard to open, e.g.numeric. The following values work across platforms: - default - numeric - email-address - phone-pad The following values work on iOS: - ascii-capable - numbers-and-punctuation - url - number-pad - name-phone-pad - decimal-pad - twitter - web-search The following values work on Android: - visible-password |
| inputMode | InputModeOptions \| undefined | no | | Works like the inputmode attribute in HTML, it determines which keyboard to open, e.g. numeric and has precedence over keyboardType. |
| maxLength | number \| undefined | no | | Limits the maximum number of characters that can be entered. Use this instead of implementing the logic in JS to avoid flicker. |
| multiline | boolean \| undefined | no | | If true, the text input can be multiple lines. The default value is false. |
| onBlur | ((e: BlurEvent) => void) \| undefined | no | | Callback that is called when the text input is blurred Note: If you are trying to find the last value of TextInput, you can use the `onEndEditing` event, which is fired upon completion of editing. |
| onChange | ((e: TextInputChangeEvent) => void) \| undefined | no | | Callback that is called when the text input's text changes. |
| onChangeText | ((text: string) => void) \| undefined | no | | Callback that is called when the text input's text changes. Changed text is passed as an argument to the callback handler. |
| onContentSizeChange | ((e: TextInputContentSizeChangeEvent) => void) \| undefined | no | | Callback that is called when the text input's content size changes. This will be called with `{ nativeEvent: { contentSize: { width, height } } }`. Only called for multiline text inputs. |
| onEndEditing | ((e: TextInputEndEditingEvent) => void) \| undefined | no | | Callback that is called when text input ends. |
| onPress | ((e: NativeSyntheticEvent) => void) \| undefined | no | | Called when a single tap gesture is detected. |
| onPressIn | ((e: NativeSyntheticEvent) => void) \| undefined | no | | Callback that is called when a touch is engaged. |
| onPressOut | ((e: NativeSyntheticEvent) => void) \| undefined | no | | Callback that is called when a touch is released. |
| onFocus | ((e: FocusEvent) => void) \| undefined | no | | Callback that is called when the text input is focused |
| onSelectionChange | ((e: TextInputSelectionChangeEvent) => void) \| undefined | no | | Callback that is called when the text input selection is changed. |
| onSubmitEditing | ((e: TextInputSubmitEditingEvent) => void) \| undefined | no | | Callback that is called when the text input's submit button is pressed. |
| onScroll | ((e: TextInputScrollEvent) => void) \| undefined | no | | Invoked on content scroll with `{ nativeEvent: { contentOffset: { x, y } } }`. May also contain other properties from ScrollEvent but on Android contentSize is not provided for performance reasons. |
| onKeyPress | ((e: TextInputKeyPressEvent) => void) \| undefined | no | | Callback that is called when a key is pressed. This will be called with `{ nativeEvent: { key: keyValue } }` where keyValue is 'Enter' or 'Backspace' for respective keys and the typed-in character otherwise including ' ' for space. Fires before onChange callbacks. Note: on Android only the inputs from soft keyboard are handled, not the hardware keyboard inputs. |
| placeholder | string \| undefined | no | | The string that will be rendered before text input has been entered |
| placeholderTextColor | ColorValue \| undefined | no | | The text color of the placeholder string |
| readOnly | boolean \| undefined | no | | If `true`, text is not editable. The default value is `false`. |
| returnKeyType | ReturnKeyTypeOptions \| undefined | no | | enum('default', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', 'emergency-call') Determines how the return key should look. |
| enterKeyHint | EnterKeyHintTypeOptions \| undefined | no | | Determines what text should be shown to the return key on virtual keyboards. Has precedence over the returnKeyType prop. |
| secureTextEntry | boolean \| undefined | no | | If true, the text input obscures the text entered so that sensitive text like passwords stay secure. The default value is false. |
| selectTextOnFocus | boolean \| undefined | no | | If true, all text will automatically be selected on focus |
| selection | { start: number; end?: number \| undefined; } \| undefined | no | | The start and end of the text input's selection. Set start and end to the same value to position the cursor. |
| selectionColor | ColorValue \| undefined | no | | The highlight (and cursor on ios) color of the text input |
| style | StyleProp | no | | Styles |
| textAlign | "left" \| "right" \| "center" \| undefined | no | | Align the input text to the left, center, or right sides of the input field. |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests |
| inputAccessoryViewID | string \| undefined | no | | Used to connect to an InputAccessoryView. Not part of react-natives documentation, but present in examples and code. See https://reactnative.dev/docs/inputaccessoryview for more information. |
| inputAccessoryViewButtonLabel | string \| undefined | no | | An optional label that overrides the default input accessory view button label. |
| value | string \| undefined | no | | The value to show for the text input. TextInput is a controlled component, which means the native value will be forced to match this value prop if provided. For most uses this works great, but in some cases this may cause flickering - one common cause is preventing edits by keeping value the same. In addition to simply setting the same value, either set editable={false}, or set/update maxLength to prevent unwanted edits without flicker. |
| maxFontSizeMultiplier | number \| null \| undefined | no | | Specifies largest possible scale a font can reach when allowFontScaling is enabled. Possible values: - null/undefined (default): inherit from the parent node or the global default (0) - 0: no max, ignore parent/global default - >= 1: sets the maxFontSizeMultiplier of this node to this value |
| children | React.ReactNode | no | | |
| hitSlop | number \| Insets \| null \| undefined | no | | This defines how far a touch event can start away from the view. Typical interface guidelines recommend touch targets that are at least 30 - 40 points/density-independent pixels. If a Touchable view has a height of 20 the touchable height can be extended to 40 with hitSlop={{top: 10, bottom: 10, left: 0, right: 0}} NOTE The touch area never extends past the parent view bounds and the Z-index of sibling views always takes precedence if a touch hits two overlapping views. |
| id | string \| undefined | no | | Used to reference react managed views from native code. |
| needsOffscreenAlphaCompositing | boolean \| undefined | no | | Whether this view needs to rendered offscreen and composited with an alpha in order to preserve 100% correct colors and blending behavior. The default (false) falls back to drawing the component and its children with an alpha applied to the paint used to draw each element instead of rendering the full component offscreen and compositing it back with an alpha value. This default may be noticeable and undesired in the case where the View you are setting an opacity on has multiple overlapping elements (e.g. multiple overlapping Views, or text and a background). Rendering offscreen to preserve correct alpha behavior is extremely expensive and hard to debug for non-native developers, which is why it is not turned on by default. If you do need to enable this property for an animation, consider combining it with renderToHardwareTextureAndroid if the view contents are static (i.e. it doesn't need to be redrawn each frame). If that property is enabled, this View will be rendered off-screen once, saved in a hardware texture, and then composited onto the screen with an alpha each frame without having to switch rendering targets on the GPU. |
| onLayout | ((event: LayoutChangeEvent) => void) \| undefined | no | | Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}. |
| pointerEvents | "box-none" \| "none" \| "box-only" \| "auto" \| undefined | no | | In the absence of auto property, none is much like CSS's none value. box-none is as if you had applied the CSS class: .box-none { pointer-events: none; } .box-none * { pointer-events: all; } box-only is the equivalent of .box-only { pointer-events: all; } .box-only * { pointer-events: none; } But since pointerEvents does not affect layout/appearance, and we are already deviating from the spec by adding additional modes, we opt to not include pointerEvents on style. On some platforms, we would need to implement it as a className anyways. Using style or not is an implementation detail of the platform. |
| removeClippedSubviews | boolean \| undefined | no | | This is a special performance property exposed by RCTView and is useful for scrolling content when there are many subviews, most of which are offscreen. For this property to be effective, it must be applied to a view that contains many subviews that extend outside its bound. The subviews must also have overflow: hidden, as should the containing view (or one of its superviews). |
| nativeID | string \| undefined | no | | Used to reference react managed views from native code. |
| collapsable | boolean \| undefined | no | | Views that are only used to layout their children or otherwise don't draw anything may be automatically removed from the native hierarchy as an optimization. Set this property to false to disable this optimization and ensure that this View exists in the native view hierarchy. |
| collapsableChildren | boolean \| undefined | no | | Setting to false prevents direct children of the view from being removed from the native view hierarchy, similar to the effect of setting `collapsable={false}` on each child. |
| renderToHardwareTextureAndroid | boolean \| undefined | no | | Whether this view should render itself (and all of its children) into a single hardware texture on the GPU. On Android, this is useful for animations and interactions that only modify opacity, rotation, translation, and/or scale: in those cases, the view doesn't have to be redrawn and display lists don't need to be re-executed. The texture can just be re-used and re-composited with different parameters. The downside is that this can use up limited video memory, so this prop should be set back to false at the end of the interaction/animation. |
| focusable | boolean \| undefined | no | | Whether this `View` should be focusable with a non-touch input device, eg. receive focus with a hardware keyboard. |
| tabIndex | 0 \| -1 \| undefined | no | | Indicates whether this `View` should be focusable with a non-touch input device, eg. receive focus with a hardware keyboard. See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex for more details. Supports the following values: - 0 (View is focusable) - -1 (View is not focusable) |
| shouldRasterizeIOS | boolean \| undefined | no | | Whether this view should be rendered as a bitmap before compositing. On iOS, this is useful for animations and interactions that do not modify this component's dimensions nor its children; for example, when translating the position of a static view, rasterization allows the renderer to reuse a cached bitmap of a static view and quickly composite it during each frame. Rasterization incurs an off-screen drawing pass and the bitmap consumes memory. Test and measure when using this property. |
| isTVSelectable | boolean \| undefined | no | | *(Apple TV only)* When set to true, this view will be focusable and navigable using the Apple TV remote. |
| hasTVPreferredFocus | boolean \| undefined | no | | *(Apple TV only)* May be set to true to force the Apple TV focus engine to move focus to this view. |
| tvParallaxShiftDistanceX | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. |
| tvParallaxShiftDistanceY | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. |
| tvParallaxTiltAngle | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 0.05. |
| tvParallaxMagnification | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 1.0. |
| onStartShouldSetResponder | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Does this view want to become responder on the start of a touch? |
| onMoveShouldSetResponder | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Called for every touch move on the View when it is not the responder: does this view want to "claim" touch responsiveness? |
| onResponderEnd | ((event: GestureResponderEvent) => void) \| undefined | no | | If the View returns true and attempts to become the responder, one of the following will happen: |
| onResponderGrant | ((event: GestureResponderEvent) => void) \| undefined | no | | The View is now responding for touch events. This is the time to highlight and show the user what is happening |
| onResponderReject | ((event: GestureResponderEvent) => void) \| undefined | no | | Something else is the responder right now and will not release it |
| onResponderMove | ((event: GestureResponderEvent) => void) \| undefined | no | | The user is moving their finger |
| onResponderRelease | ((event: GestureResponderEvent) => void) \| undefined | no | | Fired at the end of the touch, ie "touchUp" |
| onResponderStart | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onResponderTerminationRequest | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Something else wants to become responder. Should this view release the responder? Returning true allows release |
| onResponderTerminate | ((event: GestureResponderEvent) => void) \| undefined | no | | The responder has been taken from the View. Might be taken by other views after a call to onResponderTerminationRequest, or might be taken by the OS without asking (happens with control center/ notification center on iOS) |
| onStartShouldSetResponderCapture | ((event: GestureResponderEvent) => boolean) \| undefined | no | | onStartShouldSetResponder and onMoveShouldSetResponder are called with a bubbling pattern, where the deepest node is called first. That means that the deepest component will become responder when multiple Views return true for *ShouldSetResponder handlers. This is desirable in most cases, because it makes sure all controls and buttons are usable. However, sometimes a parent will want to make sure that it becomes responder. This can be handled by using the capture phase. Before the responder system bubbles up from the deepest component, it will do a capture phase, firing on*ShouldSetResponderCapture. So if a parent View wants to prevent the child from becoming responder on a touch start, it should have a onStartShouldSetResponderCapture handler which returns true. |
| onMoveShouldSetResponderCapture | ((event: GestureResponderEvent) => boolean) \| undefined | no | | onStartShouldSetResponder and onMoveShouldSetResponder are called with a bubbling pattern, where the deepest node is called first. That means that the deepest component will become responder when multiple Views return true for *ShouldSetResponder handlers. This is desirable in most cases, because it makes sure all controls and buttons are usable. However, sometimes a parent will want to make sure that it becomes responder. This can be handled by using the capture phase. Before the responder system bubbles up from the deepest component, it will do a capture phase, firing on*ShouldSetResponderCapture. So if a parent View wants to prevent the child from becoming responder on a touch start, it should have a onStartShouldSetResponderCapture handler which returns true. |
| onTouchStart | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchMove | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchEnd | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchCancel | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchEndCapture | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onPointerEnter | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerEnterCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerLeave | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerLeaveCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerMove | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerMoveCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerCancel | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerCancelCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerDown | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerDownCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerUp | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerUpCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| accessible | boolean \| undefined | no | | When true, indicates that the view is an accessibility element. By default, all the touchable elements are accessible. |
| accessibilityActions | readonly Readonly<{ name: AccessibilityActionName \| string; label?: string \| undefined; }>[] \| undefined | no | | Provides an array of custom actions available for accessibility. |
| accessibilityLabel | string \| undefined | no | | Overrides the text that's read by the screen reader when the user interacts with the element. By default, the label is constructed by traversing all the children and accumulating all the Text nodes separated by space. |
| aria-label | string \| undefined | no | | Alias for accessibilityLabel https://reactnative.dev/docs/view#accessibilitylabel https://github.com/facebook/react-native/issues/34424 |
| accessibilityRole | AccessibilityRole \| undefined | no | | Accessibility Role tells a person using either VoiceOver on iOS or TalkBack on Android the type of element that is focused on. |
| accessibilityState | AccessibilityState \| undefined | no | | Accessibility State tells a person using either VoiceOver on iOS or TalkBack on Android the state of the element currently focused on. |
| aria-busy | boolean \| undefined | no | | alias for accessibilityState see https://reactnative.dev/docs/accessibility#accessibilitystate |
| aria-checked | boolean \| "mixed" \| undefined | no | | |
| aria-disabled | boolean \| undefined | no | | |
| aria-expanded | boolean \| undefined | no | | |
| aria-selected | boolean \| undefined | no | | |
| accessibilityHint | string \| undefined | no | | An accessibility hint helps users understand what will happen when they perform an action on the accessibility element when that result is not obvious from the accessibility label. |
| accessibilityValue | AccessibilityValue \| undefined | no | | Represents the current value of a component. It can be a textual description of a component's value, or for range-based components, such as sliders and progress bars, it contains range information (minimum, current, and maximum). |
| aria-valuemax | number \| undefined | no | | |
| aria-valuemin | number \| undefined | no | | |
| aria-valuenow | number \| undefined | no | | |
| aria-valuetext | string \| undefined | no | | |
| onAccessibilityAction | ((event: AccessibilityActionEvent) => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs an accessibility custom action. |
| importantForAccessibility | "auto" \| "yes" \| "no" \| "no-hide-descendants" \| undefined | no | | [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services. |
| aria-hidden | boolean \| undefined | no | | A value indicating whether the accessibility elements contained within this accessibility element are hidden. |
| aria-modal | boolean \| undefined | no | | |
| role | Role \| undefined | no | | Indicates to accessibility services to treat UI component like a specific role. |
| accessibilityLabelledBy | string \| string[] \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| aria-labelledby | string \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| accessibilityLiveRegion | "none" \| "polite" \| "assertive" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| aria-live | "polite" \| "assertive" \| "off" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| screenReaderFocusable | boolean \| undefined | no | | Enables the view to be screen reader focusable, not keyboard focusable. |
| accessibilityElementsHidden | boolean \| undefined | no | | A Boolean value indicating whether the accessibility elements contained within this accessibility element are hidden to the screen reader. |
| accessibilityViewIsModal | boolean \| undefined | no | | A Boolean value indicating whether VoiceOver should ignore the elements within views that are siblings of the receiver. |
| onAccessibilityEscape | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the escape gesture (scrub with two fingers). |
| onAccessibilityTap | (() => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs accessibility tap gesture. |
| onMagicTap | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the magic tap gesture. |
| accessibilityIgnoresInvertColors | boolean \| undefined | no | | https://reactnative.dev/docs/accessibility#accessibilityignoresinvertcolorsios |
| accessibilityLanguage | string \| undefined | no | | By using the accessibilityLanguage property, the screen reader will understand which language to use while reading the element's label, value and hint. The provided string value must follow the BCP 47 specification (https://www.rfc-editor.org/info/bcp47). https://reactnative.dev/docs/accessibility#accessibilitylanguage-ios |
| accessibilityShowsLargeContentViewer | boolean \| undefined | no | | A Boolean value that indicates whether or not to show the item in the large content viewer. Available on iOS 13.0+ https://reactnative.dev/docs/accessibility#accessibilityshowslargecontentviewer |
| accessibilityLargeContentTitle | string \| undefined | no | | When `accessibilityShowsLargeContentViewer` is set, this string will be used as title for the large content viewer. https://reactnative.dev/docs/accessibility#accessibilitylargecontenttitle |
| accessibilityRespondsToUserInteraction | boolean \| undefined | no | | Blocks the user from interacting with the component through keyboard while still allowing screen reader to interact with it if this View is still accessible. |
| disableKeyboardShortcuts | boolean \| undefined | no | | If true, the keyboard shortcuts (undo/redo and copy buttons) are disabled. The default value is false. |
| clearButtonMode | "always" \| "never" \| "while-editing" \| "unless-editing" \| undefined | no | | enum('never', 'while-editing', 'unless-editing', 'always') When the clear button should appear on the right side of the text view |
| clearTextOnFocus | boolean \| undefined | no | | If true, clears the text field automatically when editing begins |
| dataDetectorTypes | DataDetectorTypes \| DataDetectorTypes[] \| undefined | no | | Determines the types of data converted to clickable URLs in the text input. Only valid if `multiline={true}` and `editable={false}`. By default no data types are detected. You can provide one type or an array of many types. Possible values for `dataDetectorTypes` are: - `'phoneNumber'` - `'link'` - `'address'` - `'calendarEvent'` - `'none'` - `'all'` |
| enablesReturnKeyAutomatically | boolean \| undefined | no | | If true, the keyboard disables the return key when there is no text and automatically enables it when there is text. The default value is false. |
| keyboardAppearance | "light" \| "dark" \| "default" \| undefined | no | | Determines the color of the keyboard. |
| passwordRules | string \| null \| undefined | no | | Provide rules for your password. For example, say you want to require a password with at least eight characters consisting of a mix of uppercase and lowercase letters, at least one number, and at most two consecutive characters. "required: upper; required: lower; required: digit; max-consecutive: 2; minlength: 8;" |
| rejectResponderTermination | boolean \| null \| undefined | no | | If `true`, allows TextInput to pass touch events to the parent component. This allows components to be swipeable from the TextInput on iOS, as is the case on Android by default. If `false`, TextInput always asks to handle the input (except when disabled). |
| selectionState | DocumentSelectionState \| undefined | no | | See DocumentSelectionState.js, some state that is responsible for maintaining selection information for a document |
| spellCheck | boolean \| undefined | no | | If false, disables spell-check style (i.e. red underlines). The default value is inherited from autoCorrect |
| textContentType | "none" \| "name" \| "URL" \| "addressCity" \| "addressCityAndState" \| "addressState" \| "countryName" \| "creditCardNumber" \| "creditCardExpiration" \| "creditCardExpirationMonth" \| "creditCardExpirationYear" \| "creditCardSecurityCode" \| "creditCardType" \| "creditCardName" \| "creditCardGivenName" \| "creditCardMiddleName" \| "creditCardFamilyName" \| "emailAddress" \| "familyName" \| "fullStreetAddress" \| "givenName" \| "jobTitle" \| "location" \| "middleName" \| "namePrefix" \| "nameSuffix" \| "nickname" \| "organizationName" \| "postalCode" \| "streetAddressLine1" \| "streetAddressLine2" \| "sublocality" \| "telephoneNumber" \| "username" \| "password" \| "newPassword" \| "oneTimeCode" \| "birthdate" \| "birthdateDay" \| "birthdateMonth" \| "birthdateYear" \| "cellularEID" \| "cellularIMEI" \| "dateTime" \| "flightNumber" \| "shipmentTrackingNumber" \| undefined | no | | Give the keyboard and the system information about the expected semantic meaning for the content that users enter. To disable autofill, set textContentType to `none`. Possible values for `textContentType` are: - `'none'` - `'URL'` - `'addressCity'` - `'addressCityAndState'` - `'addressState'` - `'countryName'` - `'creditCardNumber'` - `'creditCardExpiration'` (iOS 17+) - `'creditCardExpirationMonth'` (iOS 17+) - `'creditCardExpirationYear'` (iOS 17+) - `'creditCardSecurityCode'` (iOS 17+) - `'creditCardType'` (iOS 17+) - `'creditCardName'` (iOS 17+) - `'creditCardGivenName'` (iOS 17+) - `'creditCardMiddleName'` (iOS 17+) - `'creditCardFamilyName'` (iOS 17+) - `'emailAddress'` - `'familyName'` - `'fullStreetAddress'` - `'givenName'` - `'jobTitle'` - `'location'` - `'middleName'` - `'name'` - `'namePrefix'` - `'nameSuffix'` - `'nickname'` - `'organizationName'` - `'postalCode'` - `'streetAddressLine1'` - `'streetAddressLine2'` - `'sublocality'` - `'telephoneNumber'` - `'username'` - `'password'` - `'newPassword'` - `'oneTimeCode'` - `'birthdate'` (iOS 17+) - `'birthdateDay'` (iOS 17+) - `'birthdateMonth'` (iOS 17+) - `'birthdateYear'` (iOS 17+) - `'cellularEID'` (iOS 17.4+) - `'cellularIMEI'` (iOS 17.4+) - `'dateTime'` (iOS 15+) - `'flightNumber'` (iOS 15+) - `'shipmentTrackingNumber'` (iOS 15+) |
| scrollEnabled | boolean \| undefined | no | | If false, scrolling of the text view will be disabled. The default value is true. Only works with multiline={true} |
| lineBreakStrategyIOS | "none" \| "standard" \| "hangul-word" \| "push-out" \| undefined | no | | Set line break strategy on iOS. |
| lineBreakModeIOS | "middle" \| "head" \| "tail" \| "clip" \| "wordWrapping" \| "char" \| undefined | no | | Set line break mode on iOS. |
| smartInsertDelete | boolean \| undefined | no | | If `false`, the iOS system will not insert an extra space after a paste operation neither delete one or two spaces after a cut or delete operation. The default value is `true`. |
| cursorColor | ColorValue \| null \| undefined | no | | When provided it will set the color of the cursor (or "caret") in the component. Unlike the behavior of `selectionColor` the cursor color will be set independently from the color of the text selection box. |
| selectionHandleColor | ColorValue \| null \| undefined | no | | When provided it will set the color of the selection handles when highlighting text. Unlike the behavior of `selectionColor` the handle color will be set independently from the color of the text selection box. |
| importantForAutofill | "auto" \| "yes" \| "no" \| "noExcludeDescendants" \| "yesExcludeDescendants" \| undefined | no | | Determines whether the individual fields in your app should be included in a view structure for autofill purposes on Android API Level 26+. Defaults to auto. To disable auto complete, use `off`. *Android Only* The following values work on Android only: - `auto` - let Android decide - `no` - not important for autofill - `noExcludeDescendants` - this view and its children aren't important for autofill - `yes` - is important for autofill - `yesExcludeDescendants` - this view is important for autofill but its children aren't |
| disableFullscreenUI | boolean \| undefined | no | | When false, if there is a small amount of space available around a text input (e.g. landscape orientation on a phone), the OS may choose to have the user edit the text inside of a full screen text input mode. When true, this feature is disabled and users will always edit the text directly inside of the text input. Defaults to false. |
| inlineImageLeft | string \| undefined | no | | If defined, the provided image resource will be rendered on the left. |
| inlineImagePadding | number \| undefined | no | | Padding between the inline image, if any, and the text input itself. |
| numberOfLines | number \| undefined | no | | Sets the number of lines for a TextInput. Use it with multiline set to true to be able to fill the lines. |
| returnKeyLabel | string \| undefined | no | | Sets the return key to the label. Use it instead of `returnKeyType`. |
| textBreakStrategy | "simple" \| "highQuality" \| "balanced" \| undefined | no | | Set text break strategy on Android API Level 23+, possible values are simple, highQuality, balanced The default value is simple. |
| underlineColorAndroid | ColorValue \| undefined | no | | The color of the textInput underline. |
| textAlignVertical | "bottom" \| "top" \| "auto" \| "center" \| undefined | no | | Vertically align text when `multiline` is set to true |
| showSoftInputOnFocus | boolean \| undefined | no | | When false, it will prevent the soft keyboard from showing when the field is focused. The default value is true |
| verticalAlign | "bottom" \| "top" \| "auto" \| "middle" \| undefined | no | | Vertically align text when `multiline` is set to true |
| ref | Ref \| undefined | no | | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). |
| key | Key \| null \| undefined | no | | |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Text
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/text/text.md`
Human reference: https://canvas.nannier.com/components/text
## Public imports
```tsx
import { Text } 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.
Renders text. In React Native every string must live inside a Text element, so you cannot put a bare string in a View. For type scale and tone in app code, reach for `` (e.g. ``, ``, ``), which owns the kit's roles and colors; use the raw Text primitive for the primitive-level cases it does not cover: `numberOfLines` truncation and nested inline runs.
## Usage
```tsx
The quick brown fox jumps over the lazy dog.
```
## Variants
### Truncation
```tsx
This single line is clipped with an ellipsis when it overflows its container.
```
### Nested runs
```tsx
A run of text can carry an inline emphasis that inherits everything else from its parent Text.
```
## Do & Don't
### Styled type
**Do** — Reach for `Typography` for any styled type: it owns the kit's roles, sizes, and tones.
```tsx
Heading
Body text
Muted caption
```
**Don't** — Hand-styling the raw Text primitive with literal font sizes and weights reinvents the type scale and drifts from the tokens.
```tsx
Heading
Body text
Muted caption
```
## Supported props
### Text host props
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| allowFontScaling | boolean \| undefined | no | | Specifies whether fonts should scale to respect Text Size accessibility settings. The default is `true`. |
| children | React.ReactNode | no | | |
| ellipsizeMode | "middle" \| "head" \| "tail" \| "clip" \| undefined | no | | This can be one of the following values: - `head` - The line is displayed so that the end fits in the container and the missing text at the beginning of the line is indicated by an ellipsis glyph. e.g., "...wxyz" - `middle` - The line is displayed so that the beginning and end fit in the container and the missing text in the middle is indicated by an ellipsis glyph. "ab...yz" - `tail` - The line is displayed so that the beginning fits in the container and the missing text at the end of the line is indicated by an ellipsis glyph. e.g., "abcd..." - `clip` - Lines are not drawn past the edge of the text container. The default is `tail`. `numberOfLines` must be set in conjunction with this prop. > `clip` is working only for iOS |
| id | string \| undefined | no | | Used to reference react managed views from native code. |
| lineBreakMode | "middle" \| "head" \| "tail" \| "clip" \| undefined | no | | Line Break mode. Works only with numberOfLines. clip is working only for iOS |
| numberOfLines | number \| undefined | no | | Used to truncate the text with an ellipsis after computing the text layout, including line wrapping, such that the total number of lines does not exceed this number. This prop is commonly used with `ellipsizeMode`. |
| onLayout | ((event: LayoutChangeEvent) => void) \| undefined | no | | Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}. |
| onTextLayout | ((event: TextLayoutEvent) => void) \| undefined | no | | Invoked on Text layout |
| onPress | ((event: GestureResponderEvent) => void) \| undefined | no | | This function is called on press. Text intrinsically supports press handling with a default highlight state (which can be disabled with suppressHighlighting). |
| onPressIn | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onPressOut | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onLongPress | ((event: GestureResponderEvent) => void) \| undefined | no | | This function is called on long press. e.g., `onLongPress={this.increaseSize}>` |
| style | StyleProp | no | | |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests. |
| nativeID | string \| undefined | no | | Used to reference react managed views from native code. |
| maxFontSizeMultiplier | number \| null \| undefined | no | | Specifies largest possible scale a font can reach when allowFontScaling is enabled. Possible values: - null/undefined (default): inherit from the parent node or the global default (0) - 0: no max, ignore parent/global default - >= 1: sets the maxFontSizeMultiplier of this node to this value |
| minimumFontScale | number \| undefined | no | | Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0). |
| pointerEvents | "box-none" \| "none" \| "box-only" \| "auto" \| undefined | no | | Controls how touch events are handled. Similar to `View`'s `pointerEvents`. |
| pressRetentionOffset | { top: number; left: number; bottom: number; right: number; } \| undefined | no | | Defines how far your touch may move off of the button, before deactivating the button. |
| adjustsFontSizeToFit | boolean \| undefined | no | | Specifies whether font should be scaled down automatically to fit given style constraints. |
| dynamicTypeRamp | "callout" \| "body" \| "caption2" \| "caption1" \| "footnote" \| "subheadline" \| "headline" \| "title3" \| "title2" \| "title1" \| "largeTitle" \| undefined | no | | The Dynamic Type scale ramp to apply to this element on iOS. |
| suppressHighlighting | boolean \| undefined | no | | When `true`, no visual change is made when text is pressed down. By default, a gray oval highlights the text on press down. |
| lineBreakStrategyIOS | "none" \| "standard" \| "hangul-word" \| "push-out" \| undefined | no | | Set line break strategy on iOS. |
| disabled | boolean \| undefined | no | | Specifies the disabled state of the text view for testing purposes. |
| selectable | boolean \| undefined | no | | Lets the user select text, to use the native copy and paste functionality. |
| selectionColor | ColorValue \| undefined | no | | The highlight color of the text. |
| textBreakStrategy | "simple" \| "highQuality" \| "balanced" \| undefined | no | | Set text break strategy on Android API Level 23+ default is `highQuality`. |
| dataDetectorType | "link" \| "none" \| "all" \| "email" \| "phoneNumber" \| null \| undefined | no | | Determines the types of data converted to clickable URLs in the text element. By default no data types are detected. |
| android_hyphenationFrequency | "none" \| "normal" \| "full" \| undefined | no | | Hyphenation strategy |
| accessible | boolean \| undefined | no | | When true, indicates that the view is an accessibility element. By default, all the touchable elements are accessible. |
| accessibilityActions | readonly Readonly<{ name: AccessibilityActionName \| string; label?: string \| undefined; }>[] \| undefined | no | | Provides an array of custom actions available for accessibility. |
| accessibilityLabel | string \| undefined | no | | Overrides the text that's read by the screen reader when the user interacts with the element. By default, the label is constructed by traversing all the children and accumulating all the Text nodes separated by space. |
| aria-label | string \| undefined | no | | Alias for accessibilityLabel https://reactnative.dev/docs/view#accessibilitylabel https://github.com/facebook/react-native/issues/34424 |
| accessibilityRole | AccessibilityRole \| undefined | no | | Accessibility Role tells a person using either VoiceOver on iOS or TalkBack on Android the type of element that is focused on. |
| accessibilityState | AccessibilityState \| undefined | no | | Accessibility State tells a person using either VoiceOver on iOS or TalkBack on Android the state of the element currently focused on. |
| aria-busy | boolean \| undefined | no | | alias for accessibilityState see https://reactnative.dev/docs/accessibility#accessibilitystate |
| aria-checked | boolean \| "mixed" \| undefined | no | | |
| aria-disabled | boolean \| undefined | no | | |
| aria-expanded | boolean \| undefined | no | | |
| aria-selected | boolean \| undefined | no | | |
| accessibilityHint | string \| undefined | no | | An accessibility hint helps users understand what will happen when they perform an action on the accessibility element when that result is not obvious from the accessibility label. |
| accessibilityValue | AccessibilityValue \| undefined | no | | Represents the current value of a component. It can be a textual description of a component's value, or for range-based components, such as sliders and progress bars, it contains range information (minimum, current, and maximum). |
| aria-valuemax | number \| undefined | no | | |
| aria-valuemin | number \| undefined | no | | |
| aria-valuenow | number \| undefined | no | | |
| aria-valuetext | string \| undefined | no | | |
| onAccessibilityAction | ((event: AccessibilityActionEvent) => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs an accessibility custom action. |
| importantForAccessibility | "auto" \| "yes" \| "no" \| "no-hide-descendants" \| undefined | no | | [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services. |
| aria-hidden | boolean \| undefined | no | | A value indicating whether the accessibility elements contained within this accessibility element are hidden. |
| aria-modal | boolean \| undefined | no | | |
| role | Role \| undefined | no | | Indicates to accessibility services to treat UI component like a specific role. |
| accessibilityLabelledBy | string \| string[] \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| aria-labelledby | string \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| accessibilityLiveRegion | "none" \| "polite" \| "assertive" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| aria-live | "polite" \| "assertive" \| "off" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| screenReaderFocusable | boolean \| undefined | no | | Enables the view to be screen reader focusable, not keyboard focusable. |
| accessibilityElementsHidden | boolean \| undefined | no | | A Boolean value indicating whether the accessibility elements contained within this accessibility element are hidden to the screen reader. |
| accessibilityViewIsModal | boolean \| undefined | no | | A Boolean value indicating whether VoiceOver should ignore the elements within views that are siblings of the receiver. |
| onAccessibilityEscape | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the escape gesture (scrub with two fingers). |
| onAccessibilityTap | (() => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs accessibility tap gesture. |
| onMagicTap | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the magic tap gesture. |
| accessibilityIgnoresInvertColors | boolean \| undefined | no | | https://reactnative.dev/docs/accessibility#accessibilityignoresinvertcolorsios |
| accessibilityLanguage | string \| undefined | no | | By using the accessibilityLanguage property, the screen reader will understand which language to use while reading the element's label, value and hint. The provided string value must follow the BCP 47 specification (https://www.rfc-editor.org/info/bcp47). https://reactnative.dev/docs/accessibility#accessibilitylanguage-ios |
| accessibilityShowsLargeContentViewer | boolean \| undefined | no | | A Boolean value that indicates whether or not to show the item in the large content viewer. Available on iOS 13.0+ https://reactnative.dev/docs/accessibility#accessibilityshowslargecontentviewer |
| accessibilityLargeContentTitle | string \| undefined | no | | When `accessibilityShowsLargeContentViewer` is set, this string will be used as title for the large content viewer. https://reactnative.dev/docs/accessibility#accessibilitylargecontenttitle |
| accessibilityRespondsToUserInteraction | boolean \| undefined | no | | Blocks the user from interacting with the component through keyboard while still allowing screen reader to interact with it if this View is still accessible. |
| ref | Ref \| undefined | no | | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). |
| key | Key \| null \| undefined | no | | |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Textarea
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/textarea/textarea.md`
Human reference: https://canvas.nannier.com/components/textarea
## Public imports
```tsx
import { Textarea } 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-line input, with character count, with toolbar.
Pass `label` (and `required`) to name the field:
iOS and web render the label
above the control, while Android floats the Material 3 in-container label at the
top of the multiline box, making room for itself at the user's text size and
truncating on one line when it is longer than the box 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 ("Bio, required") on iOS and Android.
The box 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.
The box is the single-line Input's on each platform. On the web it is the sunken
well with a 1 px hairline that steps to the subtle ink on hover, takes the `ring`
edge and a 4 px accent halo on focus and the `destructive` edge on error, with a
400 value on a 1.55 line; the count is an 11 px mono caption ("12 / 280").
iOS keeps
its 8 pt `card` box with the error wash and a tabular count ("12/280").
Android uses
the same opaque muted surface as the other filled fields, and its count sits at the
end of the supporting-text row.
Its label uses `primary-text` when focused and
`destructive-text` on error. The over-limit character count also uses
`destructive-text` on every platform. Without `rows` the field starts four rows tall.
Inside an overlay, Escape follows the overlay's cancellation policy. A supplied
`onKeyPress` runs first and can call `preventDefault()` to handle Escape locally.
Cancelling an IME candidate keeps the overlay open.
`dense` preserves application paragraph fields with 13/19.5 semibold values,
the dense field inset and explicit visible rows. `eyebrow` supplies the matching
web label. `accessibilityLabel` overrides the visible label’s accessible name;
`readOnly` prevents edits without dimming or removing the field from focus.
`systemFocus` keeps the platform system focus outline.
`embedded` renders a bare editor inside a parent-owned field surface and focus frame, as used by Chat. It implies `flush` and removes the independent backing and focus outline; the parent must provide a visible focus treatment.
## Usage
```tsx
```
## Variants
### With label
```tsx
```
### Pre-filled
```tsx
```
### Required
```tsx
```
### Character counter
```tsx
```
### Formatting toolbar
```tsx
B
I
```
### Composer
`composer` sets a chat composer's metrics: 15 / 20 text 6 by 8 points in, one line by default (`rows` raises it), so one line sits
in a 44 point composer box beside 32 point controls. Pair it with `embedded`; Chat's
composer uses it.
```tsx
```
### Disabled
```tsx
```
### Measure
```tsx
```
## Do & Don't
### With label
**Do** — Set `rows` for a sensible starting height so users can see their text; the field grows with the content from there.
```tsx
```
**Don't** — A locked, single-line textarea hides long content with no way to expand.
```tsx
Description
```
### Character counter
**Do** — Show the live count against the cap and turn it destructive past the limit so the overage is precise. `showCount` treats `maxLength` as a soft cap and flips the count (and the field) destructive automatically once you run over.
```tsx
```
**Don't** — A vague "over limit" message gives no number, so users cannot tell how much to trim.
```tsx
Bio
over limit
```
### Formatting toolbar
**Do** — Make each control a real focusable button that toggles an active state when pressed.
```tsx
B
I
{">"}
```
**Don't** — Static, unclickable glyphs look like a toolbar but cannot be pressed or focused.
```tsx
B
I
{">"}
```
### Disabled
**Do**: Pass `disabled` so the field blocks editing and focus, matching its dimmed look: the browser takes it out of the tab order, and VoiceOver and TalkBack announce it as dimmed.
```tsx
```
**Don't** — Dimming a textarea while leaving it editable looks disabled but still accepts input.
```tsx
Description
```
## Supported props
### TextareaProps
Includes `MeasureProps`; their supported fields are expanded below.
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| value | string \| undefined | no | | Controlled text value. Omit and use `defaultValue` for uncontrolled use. |
| onChangeText | ((next: string) => void) \| undefined | no | | Fired with the next text value on each edit. |
| placeholder | string \| undefined | no | | Placeholder shown while the field is empty. |
| 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 — a MULTILINE float pinned to the first text line at rest, floating to the top once the field is focused or filled. The label names the field for a11y. |
| eyebrow | boolean \| undefined | no | false | |
| systemFocus | boolean \| undefined | no | false | Preserve the platform system focus outline. |
| dense | boolean \| undefined | no | false | Dense paragraph-entry metrics matching the application field family. |
| terminal | boolean \| undefined | no | false | Bare monospace prompt metrics for a terminal transcript composer. Implies embedded. |
| composer | boolean \| undefined | no | false | A chat composer's entry: 15 / 20 text at 6 by 8 in, so one line sits in a 44 px composer box beside 32 px controls (the Claude desktop composer). Pair with `embedded`. |
| accessibilityLabel | string \| undefined | no | | |
| readOnly | boolean \| undefined | no | false | Prevent edits while retaining focus and the resting appearance. |
| 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 ("Bio, required") on iOS and Android. The star and the spoken word take effect only alongside `label`. |
| rows | number \| undefined | no | | Visible rows the field sizes to (4 when omitted). Sets the min height (rows times the skin's line plus the field's padding); the field still grows with content past this floor. |
| showCount | boolean \| undefined | no | false | Show a live character-count line inside the field's own anatomy: an end-aligned muted caption under the control reading "N / max" (just "N" when no `maxLength` is set), where N is the current text length. When on, `maxLength` becomes a SOFT cap rather than a hard one: it is NOT forwarded to the native TextInput (a hard cap silently drops the extra keystroke, so the overage could never be seen), the user can type past it, and once N exceeds `maxLength` the count turns destructive AND the field enters its error state automatically (red border/underline). Leave `showCount` off to keep `maxLength` as a hard input cap. |
| error | boolean \| undefined | no | false | Error/validation state: a destructive cue. `invalid` is an alias. |
| invalid | boolean \| undefined | no | false | |
| small | boolean \| undefined | no | false | Size (pick one; default is the base text-sm field). |
| large | boolean \| undefined | no | false | Size (pick one; default is the base text-sm field). |
| disabled | boolean \| undefined | no | false | Blocks editing and focus, and dims the field. |
| flush | boolean \| undefined | no | false | Borderless: drop the field's own border and radius so it sits flush inside a framed container (e.g. a Card with a formatting toolbar above it). |
| embedded | boolean \| undefined | no | false | Bare editor inside a parent-owned field surface and focus frame. Implies flush. |
| style | StyleProp> | no | | Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds. |
| textContentType | "none" \| "name" \| "URL" \| "addressCity" \| "addressCityAndState" \| "addressState" \| "countryName" \| "creditCardNumber" \| "creditCardExpiration" \| "creditCardExpirationMonth" \| "creditCardExpirationYear" \| "creditCardSecurityCode" \| "creditCardType" \| "creditCardName" \| "creditCardGivenName" \| "creditCardMiddleName" \| "creditCardFamilyName" \| "emailAddress" \| "familyName" \| "fullStreetAddress" \| "givenName" \| "jobTitle" \| "location" \| "middleName" \| "namePrefix" \| "nameSuffix" \| "nickname" \| "organizationName" \| "postalCode" \| "streetAddressLine1" \| "streetAddressLine2" \| "sublocality" \| "telephoneNumber" \| "username" \| "password" \| "newPassword" \| "oneTimeCode" \| "birthdate" \| "birthdateDay" \| "birthdateMonth" \| "birthdateYear" \| "cellularEID" \| "cellularIMEI" \| "dateTime" \| "flightNumber" \| "shipmentTrackingNumber" \| undefined | no | | Give the keyboard and the system information about the expected semantic meaning for the content that users enter. To disable autofill, set textContentType to `none`. Possible values for `textContentType` are: - `'none'` - `'URL'` - `'addressCity'` - `'addressCityAndState'` - `'addressState'` - `'countryName'` - `'creditCardNumber'` - `'creditCardExpiration'` (iOS 17+) - `'creditCardExpirationMonth'` (iOS 17+) - `'creditCardExpirationYear'` (iOS 17+) - `'creditCardSecurityCode'` (iOS 17+) - `'creditCardType'` (iOS 17+) - `'creditCardName'` (iOS 17+) - `'creditCardGivenName'` (iOS 17+) - `'creditCardMiddleName'` (iOS 17+) - `'creditCardFamilyName'` (iOS 17+) - `'emailAddress'` - `'familyName'` - `'fullStreetAddress'` - `'givenName'` - `'jobTitle'` - `'location'` - `'middleName'` - `'name'` - `'namePrefix'` - `'nameSuffix'` - `'nickname'` - `'organizationName'` - `'postalCode'` - `'streetAddressLine1'` - `'streetAddressLine2'` - `'sublocality'` - `'telephoneNumber'` - `'username'` - `'password'` - `'newPassword'` - `'oneTimeCode'` - `'birthdate'` (iOS 17+) - `'birthdateDay'` (iOS 17+) - `'birthdateMonth'` (iOS 17+) - `'birthdateYear'` (iOS 17+) - `'cellularEID'` (iOS 17.4+) - `'cellularIMEI'` (iOS 17.4+) - `'dateTime'` (iOS 15+) - `'flightNumber'` (iOS 15+) - `'shipmentTrackingNumber'` (iOS 15+) |
| autoCapitalize | "none" \| "sentences" \| "words" \| "characters" \| undefined | no | | Can tell TextInput to automatically capitalize certain characters. characters: all characters, words: first letter of each word sentences: first letter of each sentence (default) none: don't auto capitalize anything https://reactnative.dev/docs/textinput#autocapitalize |
| autoComplete | "country" \| "name" \| "off" \| "nickname" \| "username" \| "password" \| "2fa-app-otp" \| "additional-name" \| "address-line1" \| "address-line2" \| "birthdate-day" \| "birthdate-full" \| "birthdate-month" \| "birthdate-year" \| "cc-csc" \| "cc-exp" \| "cc-exp-day" \| "cc-exp-month" \| "cc-exp-year" \| "cc-number" \| "cc-name" \| "cc-given-name" \| "cc-middle-name" \| "cc-family-name" \| "cc-type" \| "current-password" \| "email" \| "email-otp" \| "flight-confirmation-code" \| "flight-number" \| "family-name" \| "gender" \| "gift-card-number" \| "gift-card-pin" \| "given-name" \| "honorific-prefix" \| "honorific-suffix" \| "loyalty-account-number" \| "name-family" \| "name-given" \| "name-middle" \| "name-middle-initial" \| "name-prefix" \| "name-suffix" \| "new-password" \| "one-time-code" \| "organization" \| "organization-title" \| "password-new" \| "postal-address" \| "postal-address-country" \| "postal-address-dependent-locality" \| "postal-address-extended" \| "postal-address-extended-postal-code" \| "postal-address-locality" \| "postal-address-region" \| "postal-address-unit" \| "postal-code" \| "promo-code" \| "street-address" \| "sms-otp" \| "tel" \| "tel-country-code" \| "tel-national" \| "tel-device" \| "upi-vpa" \| "url" \| "wifi-password" \| "username-new" \| undefined | no | | Specifies autocomplete hints for the system, so it can provide autofill. On Android, the system will always attempt to offer autofill by using heuristics to identify the type of content. To disable autocomplete, set autoComplete to off. The following values work across platforms: - `additional-name` - `address-line1` - `address-line2` - `cc-number` - `country` - `current-password` - `email` - `family-name` - `given-name` - `honorific-prefix` - `honorific-suffix` - `name` - `new-password` - `off` - `one-time-code` - `postal-code` - `street-address` - `tel` - `username` The following values work on iOS only: - `nickname` - `organization` - `organization-title` - `url` The following values work on Android only: - `birthdate-day` - `birthdate-full` - `birthdate-month` - `birthdate-year` - `cc-csc` - `cc-exp` - `cc-exp-day` - `cc-exp-month` - `cc-exp-year` - `2fa-app-otp` - `email-otp` - `flight-confirmation-code` - `flight-number` - `gender` - `gift-card-number` - `gift-card-pin` - `loyalty-account-number` - `name-family` - `name-given` - `name-middle` - `name-middle-initial` - `name-prefix` - `name-suffix` - `password` - `password-new` - `postal-address` - `postal-address-country` - `postal-address-dependent-locality` - `postal-address-extended` - `postal-address-extended-postal-code` - `postal-address-locality` - `postal-address-region` - `postal-address-unit` - `promo-code` - `sms-otp` - `tel-country-code` - `tel-national` - `tel-device` - `upi-vpa` - `wifi-password` - `username-new` |
| autoCorrect | boolean \| undefined | no | false | If false, disables auto-correct. The default value is true. |
| autoFocus | boolean \| undefined | no | false | If true, focuses the input on componentDidMount. The default value is false. |
| defaultValue | string \| undefined | no | | Provides an initial value that will change when the user starts typing. Useful for simple use-cases where you don't want to deal with listening to events and updating the value prop to keep the controlled state in sync. |
| keyboardType | KeyboardTypeOptions \| undefined | no | | enum("default", 'numeric', 'email-address', "ascii-capable", 'numbers-and-punctuation', 'url', 'number-pad', 'phone-pad', 'name-phone-pad', 'decimal-pad', 'twitter', 'web-search', 'visible-password') Determines which keyboard to open, e.g.numeric. The following values work across platforms: - default - numeric - email-address - phone-pad The following values work on iOS: - ascii-capable - numbers-and-punctuation - url - number-pad - name-phone-pad - decimal-pad - twitter - web-search The following values work on Android: - visible-password |
| inputMode | InputModeOptions \| undefined | no | | Works like the inputmode attribute in HTML, it determines which keyboard to open, e.g. numeric and has precedence over keyboardType. |
| maxLength | number \| undefined | no | | Limits the maximum number of characters that can be entered. Use this instead of implementing the logic in JS to avoid flicker. |
| onBlur | ((e: BlurEvent) => void) \| undefined | no | | Callback that is called when the text input is blurred Note: If you are trying to find the last value of TextInput, you can use the `onEndEditing` event, which is fired upon completion of editing. |
| onFocus | ((e: FocusEvent) => void) \| undefined | no | | Callback that is called when the text input is focused |
| onSubmitEditing | ((e: TextInputSubmitEditingEvent) => void) \| undefined | no | | Callback that is called when the text input's submit button is pressed. |
| onKeyPress | ((e: TextInputKeyPressEvent) => void) \| undefined | no | | Callback that is called when a key is pressed. This will be called with `{ nativeEvent: { key: keyValue } }` where keyValue is 'Enter' or 'Backspace' for respective keys and the typed-in character otherwise including ' ' for space. Fires before onChange callbacks. Note: on Android only the inputs from soft keyboard are handled, not the hardware keyboard inputs. |
| returnKeyType | ReturnKeyTypeOptions \| undefined | no | | enum('default', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', 'emergency-call') Determines how the return key should look. |
| secureTextEntry | boolean \| undefined | no | false | If true, the text input obscures the text entered so that sensitive text like passwords stay secure. The default value is false. |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests |
| 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.
---
# Toast
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/toast/toast.md`
Human reference: https://canvas.nannier.com/components/toast
## Public imports
```tsx
import { Toast, ToastProvider, useToast } 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 transient notification card. Render a `` directly, or drive them imperatively: mount a `` near your app root and call `toast(...)` from the `useToast()` hook to enqueue auto-dismissing toasts that stack over the app.
On the web and iOS the toast is a floating card:
the overlay panel at the card corner, up to 380px wide, with the intent glyph in its status color (the accent's sparkle on a neutral toast), a medium message over a muted description, and the action in the accent. A `brief` toast hugs its one line.
Android keeps the Material 3 snackbar on the inverse fill.
For a single brief notification, use ``. New messages replace the current toast and
restart its lifetime. `safeArea` includes the device safe area. `brief` retains a
single line without the dismiss
control; Android retains its snackbar skin.
Tab reaches a toast's action and its dismiss, and Escape dismisses the toast that holds
focus, then returns focus to where it came from. A toast the user is not in never takes
Escape, so the key still reaches the page or the Dialog under it. A provider toast always
answers it (a `brief` one too), a directly rendered `` when it has `onDismiss`.
On
iOS the same dismiss answers the accessibility escape (the VoiceOver two-finger scrub,
and Escape under Full Keyboard Access).
Android delivers no Escape key to a view, and
its back gesture is never taken for a toast.
## Usage
Toast is usually driven imperatively: mount a `` near your app root, then call the `toast(...)` handle from `useToast()` to enqueue an auto-dismissing card that floats over the app. Press the button to fire one. (`AppScreen` is just this demo's stand-in for your app root, so the toast has a screen to float over.)
```tsx
{({ toast }) => toast({ message: "Profile updated" })}>Show toast }
```
## Variants
### Rendered directly
```tsx
```
### Success
```tsx
```
### Destructive
```tsx
```
### Warning
```tsx
```
### With an action
```tsx
{} }} />
```
### Dismissible, informational
```tsx
{}} />
```
## Do & Don't
### One short message
**Do** — Keep a toast to one short, plain message (with an optional one-line description), and pair a destructive or success intent with the matching message.
```tsx
```
**Don't** — Don't crowd a toast with long paragraphs or more than one action; a toast is a glance, not a dialog.
```tsx
```
## Supported props
### ToastProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| brief | boolean \| undefined | no | false | A single-line notification pill without extra chrome. |
| message | string | yes | | The headline line (required). |
| description | string \| undefined | no | | A secondary line under the message. |
| action | ToastAction \| undefined | no | | A trailing action button (e.g. Undo). |
| success | boolean \| undefined | no | false | Green success intent (icon + tint). |
| destructive | boolean \| undefined | no | false | Red danger intent (icon + tint). This is the name the intent axis uses across the kit (Button, AlertDialog, and every chart) and the one the design hand-off uses. |
| warning | boolean \| undefined | no | false | Amber warning intent (icon + tint). |
| info | boolean \| undefined | no | false | Brand-tinted informational intent (icon + tint). |
| icon | ReactNode | no | | Override the auto intent icon; pass any node, or omit to use the intent glyph. |
| onDismiss | (() => void) \| undefined | no | | When provided, render a trailing dismiss (x) that calls this. |
| 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.
---
# Tooltip
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/tooltip/tooltip.md`
Human reference: https://canvas.nannier.com/components/tooltip
## Public imports
```tsx
import { Tooltip } 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.
Small floating helper text on hover or focus.
The bubble floats over the page: it is drawn in the overlay layer of the nearest
`OverlayProvider`, placed against its trigger, and changes no layout, so a tip on a
table row's action never grows the cell. It centers on the trigger at the side you
pick, flips to the opposite side when the visible area has no room there, and slides
to stay inside it. Moving the pointer from the trigger onto the bubble keeps it up.
With no `OverlayProvider` mounted it still floats, on an absolute anchor beside the
trigger, but an ancestor that clips its content can then cut it off.
The bubble inverts against the page (dark in the light scheme, light in the dark one)
and sets its text in Dark Factory's label type;
iOS, which has no tooltip, shows the
same bubble, and Android keeps the Material 3 plain tooltip.
The trigger is a boolean axis of its own, resolved first match wins:
- `children`, an element you already have (an icon `Button`, a `Chip`, any
control that owns its own press and accessible name). The child renders as-is
and the tip hangs off it through a wrapper that only LISTENS: no accessibility
role, no tab stop, and no press of its own, so the child stays the single
interactive, labelled element, keeps its `onPress`, and the tip never toggles
on tap. Hover and focus on the child both raise it.
- `iconTrigger`, a ghost icon button carrying a settings glyph.
- `textTrigger`, the `trigger` string as a pressable inline word.
- Otherwise the default: the `trigger` string as an outline Button.
Hover and focus are pointer-and-keyboard affordances. Touch pointers are
skipped,
and iOS and Android never fire hover at all.
On touch the built-in
triggers toggle the tip on tap (tap again to close it; a tip is never modal, so a
tap elsewhere goes to the page). A long press is not taken: it belongs to the
platform and to the control. An element trigger has no tap toggle either, because
that press belongs to the child, so on native and on touch the controlled `open`
prop is the way to show its tip: drive it from whatever the platform does offer (a
long press the child handles, a selected row, an inline help toggle). Keyboard
focus, including a hardware keyboard on a tablet, raises the tip on every platform.
Escape hides an open tip and leaves focus where it is (WCAG 1.4.13: hover and focus
content is dismissable without moving the pointer or focus). It goes through the kit's
one Escape owner, so a tip inside a Dialog or Popover closes first and the next Escape
reaches its host; a closed tip takes no Escape at all. A controlled tip reports the
close through `onOpenChange(false)`.
On iOS the same close answers the accessibility
escape (the VoiceOver two-finger scrub, and Escape on a hardware keyboard under Full
Keyboard Access).
Android delivers no Escape key to a view, and its back gesture is
never taken for a tip, so there the tip closes with focus or the controlled `open`.
## Usage
The examples pinned open leave room on the bubble's side, as a page would: the
bubble floats and takes no room of its own.
```tsx
```
## Variants
### Right
```tsx
```
### Bottom
```tsx
```
### Left
```tsx
```
### Icon
```tsx
```
### Text
```tsx
```
### Element trigger
```tsx
} />
```
### On hover
```tsx
```
## Do & Don't
### Short labels
**Do** — Keep tooltips short and supplementary; put essential steps in visible copy.
```tsx
```
**Don't** — Long, essential instructions hidden in a tooltip are missed on touch and by screen readers.
```tsx
```
### Placement
**Do** — Pick the side with the most room; the tip flips and slides to stay on screen when it has none.
```tsx
```
**Don't** — Don't place a tip where it covers the control a user needs next; it floats over the page, so it masks whatever sits on that side.
```tsx
Clear all
```
## Supported props
### TooltipProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no | | An arbitrary element for the tip to hang off: an existing Button, Chip, or any other control that already owns its own press and accessible name. The highest-precedence trigger: children win over `iconTrigger`, `textTrigger`, and the default Button. The child renders as-is and stays the single interactive, labelled element; the root view Tooltip wraps it in takes no accessibility role and no tab stop, listens only for hover and focus, and never claims the press, so a Button child keeps its `onPress` and the tip does not toggle on tap. |
| label | string \| undefined | no | | The tip text shown in the bubble. |
| trigger | string \| undefined | no | | The element the tip describes. By default rendered as a text ``; set `iconTrigger` for a ghost icon button, `textTrigger` to hang the tip off an inline pressable word, or pass `children` to wrap an element you already have. Ignored when `children` is passed. |
| iconTrigger | boolean \| undefined | no | false | Render the trigger as a ghost icon button (a settings glyph) instead of the text Button. When set, `trigger` (the label string) is ignored. Ignored when `children` is passed, which takes precedence. |
| textTrigger | boolean \| undefined | no | false | Render the `trigger` string as a pressable inline word (an underlined hover-text affordance) rather than the default text Button, so the tip can hang off a run of body copy. Orthogonal to placement (top/left/right/bottom). Ignored when `children` or `iconTrigger` is set, both of which take precedence. |
| open | boolean \| undefined | no | false | Controlled visibility. Omit for uncontrolled: the tip shows while the trigger is hovered or focused, and a tap toggles it (the touch analogue of hover) on the built-in triggers. An element trigger (`children`) has no tap toggle (the child keeps its own press), and native platforms never fire hover, so `open` is the native and touch path for that trigger. |
| onOpenChange | ((open: boolean) => void) \| undefined | no | | Fired when the bubble is shown/hidden. |
| top | boolean \| undefined | no | false | Placement (pick one; default is top). |
| bottom | boolean \| undefined | no | false | Placement (pick one; default is top). |
| left | boolean \| undefined | no | false | Placement (pick one; default is top). |
| right | boolean \| undefined | no | false | Placement (pick one; default is top). |
| 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.
---
# Treemap
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/treemap/treemap.md`
Human reference: https://canvas.nannier.com/components/treemap
## Public imports
```tsx
import { Treemap } 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.
Squarified value tiles: each datum becomes a ramp-colored rectangle whose area is proportional to its value, packed largest-first with near-square aspect ratios, with the label and formatted value rendered inside only when the tile fits them.
Flat one-level data; nesting and drill-down are deferred scope. Press a tile to flag its value and share (the others dim); the accessible name carries every tile with its share regardless of which labels fit.
## Usage
```tsx
```
## Variants
### Compact
```tsx
```
### Inspected
```tsx
```
### Outlined partitions
```tsx
`${v}%`} data={[{label: "Web", value: 42}, {label: "Phone", value: 28}, {label: "Tablet", value: 18}, {label: "Desktop", value: 7}, {label: "CLI", value: 3}, {label: "Other", value: 2}]} />
```
## Do & Don't
### Treemap
**Do** - Reserve the treemap for part-of-whole data with real size contrast, where area comparison earns the space.
```tsx
```
**Don't** - A handful of near-equal shares reads better as a PieChart or a BarList; near-equal tiles defeat the area encoding.
```tsx
```
## Supported props
### TreemapProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| data | TreemapDatum[] | yes | | The tiles, laid out largest first by the squarified algorithm. |
| title | string \| undefined | no | | Optional heading shown above the tiles. |
| compact | boolean \| undefined | no | false | Density (omit for the default 240 plot height; compact is 160). |
| partitioned | boolean \| undefined | no | false | Slice successive values along alternating axes, keeping the data order. |
| outlined | boolean \| undefined | no | false | Show pale series washes with colored outlines and large readable values. |
| plain | boolean \| undefined | no | false | Strip the card surface for nesting inside an existing card (mirrors Stats). |
| formatValue | ((v: number) => string) \| undefined | no | | Formats tile values and the flag (data formatting, not styling). |
| selected | number \| null \| undefined | no | | Press-to-inspect: the selected tile index (controlled). Pass null for none. |
| defaultSelected | number \| undefined | no | | Press-to-inspect: the initially selected tile (uncontrolled). |
| onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press selects a tile (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.
---
# Typography
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/typography/typography.md`
Human reference: https://canvas.nannier.com/components/typography
## Public imports
```tsx
import { Typography } 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.
Type scale classes for headings, body text, and helper styles.
`terminal` selects the system terminal face while retaining the reading role, size, weight, and ink: pair it with `tiny`, `caption`, `small`, or a heading. The web uses the system monospace stack beginning with `ui-monospace`, SFMono-Regular, and Menlo; native platforms use Menlo or their monospace alias. Existing `mono` and code typography keep their current behavior.
Argus reading roles include `subheading`, `label`, `micro`, `tag`, `eyebrow`,
`eyebrowLg`, `stageLabel`, `statValue`, `metricValue`, and `formValue`. These extend
Typography itself. Semantic leading and tracking props preserve the reference's
fractional web metrics; for example `labelLeading` is the compact label line box
and `wideTracking` its uppercase eyebrow tracking. `extraBold` selects the 800 face.
The owner's exact-reference directive preserves the existing 9.5px eyebrows.
`foreground`, `inverse`, `action`, `actionForeground`, and `info` name the ink;
`hue` gives a stage its theme-aware identity text. `inverseHint`, `inverseSubtle`,
`inverseMuted`, `inverseSoft`, and `inverseFaint` express the reference's hierarchy
on branded surfaces. `numberOfLines` truncates a reading block, and accessible
labels and roles stay with the text. Existing roles and defaults keep working.
`section` lets a heading own its `count`, muted `description` and trailing `actions`.
They wrap along a shared baseline, preserving dense section anatomy. Pair it with
`h5 bold labelLeading` or `subheading bold labelLeading`; the latter uses the compact
metadata metrics. Actions move to the far edge and wrap below on narrow surfaces.
`level={1..6}` sets the heading level assistive tech hears without changing the
look. A heading role exposes its own level (`hero`, `pageTitle`, `display` and `h1` at 1, `sectionTitle` at 2, `cardTitle` at 3, `h2` to `h5` at
2 to 5, a `section` heading 6), so `` draws the h3 size
and is announced as a level-2 heading: a real `` on the web, the header trait
natively. On a non-heading role `level` makes the text a heading at that level.
`href` still wins: a link is never a heading. The level is a number because it is
the document outline, not a style (the same reason Row's `span` is one); the look
stays on the boolean role axis.
`hero` and `pageTitle` are the two fluid headlines, sized by the window rather than a
fixed step: `hero` is the landing headline (56 to 112 px at 7.2 percent of the window,
a .94 line, tracked -.045em) and `pageTitle` a page's own title (40 to 64 px at 5
percent, a 1.0 line, tracked -.04em). Both are medium-weight display face.
The page roles are the hand-off pages' own titles and leads, outside its type table:
`sectionTitle` (a page section's heading, display 500 28/1.1), `cardTitle` (a feature
card's title, display 500 22/1.15), `panelTitle` (a panel's title, display 500 20/1.2),
`tagline` (the line under a section's title, display 400 22/1.3), `pageLead` (a page's
summary, 18/1.55, held to the hand-off's 72ch measure), `sectionLead` (a section's introduction, 17/1.55), `cardBody` (a feature
card's description, 14/1.55), `sectionNote` (the note under a section's heading,
14/1.5), `pageBody` (a page's body paragraph, 15/1.6) and `pageNote` (a page's note paragraph,
14/1.6), the last six in the secondary ink and the last two held to an 80ch measure.
Four more are the component page's own: `pageLabel` (a panel's "Props", a rail's group
header, a table's header cell, a previous/next eyebrow: 500 11/1 tracked .08em in caps, in
the subtle ink; pass `mono` for the mono face, as the hand-off's page does), `knobLabel` (a
knob panel's label row, a knob's name and its type hint: 500 12/1 in the secondary ink, the
hint beside it in the `muted` tone, the third ink; pass `mono` for the mono face), `guideTitle` (a
guidance card's title, 600 16/1.3 in the interface face, a level-3 heading) and `guideBody`
(a guidance card's paragraph, 400 13.5/1.6 in the secondary ink). `pageLabel` is not the
`eyebrow`: that is the type table's accent-ink label tracked .1em. `pagerName` is the
previous or next card's name under its label: the display face, 500 18 on the face's own 22
line (the hand-off sets no ratio), in the foreground ink. Two are the reference sheet's cells
(the component page's props table): `specMeta` (a type or default cell, 400 12/1.55; pass
`mono` for the mono face and `subtle` or `muted` for the ink, as the sheet's cells take
them) and `specText` (a description cell or an inherited-props note, 400 13/1.55 in the
secondary ink).
line the same way: `pagerLabel` (the previous or next card's label over its name, the page
label's 500 11 tracked .08em in caps and subtle ink on a 14 line; pass `mono`), `legendLabel`
(a legend row's label beside its numbered disc, 500 14 on an 18 line in the foreground ink)
and `tileCaption` (a specimen tile's caption strip, 500 12 on a 16 line in the secondary ink;
pass `mono`). Three more are the component page's Variants and Tokens sections: `variantTitle`
(a variant card's title over its note and snippet, 500 14/1.3 in the foreground ink),
`linkName` (a link tile's name beside its arrow, 600 14 on the face's own 18 line in the
foreground ink) and `subsectionTitle` (a section's own sub-heading over a grid of tiles,
600 16 on the face's own 20 line in the interface face, a level-3 heading).
The section pages' card roles are the hand-off's section cards' own text, one role per
value set however many pages set it: `cardNote` (a card's foot caption or an in-card note,
400 12/1.6 in the subtle ink; pass `mono`), `codeLabel` (a code name on its own line, a
variant row's label or a token's name, 400 12 on a 16 line in the secondary ink; pass
`mono`), `inlineNote` (a note beside a specimen, 400 12 on a 15 line in the subtle ink),
`tileTitle` (a tile's title, display 500 15/1.3), `paneTitle` (a pane's title, display 500
16/1.2 tracked -.01em; pass `level` to announce it), `gridLabel` (a grid widget's label, 500
10 mono tracked .08em in caps on a 13 line, subtle ink), `tick` (a scale's tick label, 500 10
mono on a 13 line, secondary ink), `rowLabel` (a stat row's label, 500 11/1 mono tracked
.06em in caps, subtle ink), `rowValue` (the value beside it, display 500 22/1 tracked
-.02em), `headlineValue` (a card's headline figure, display 500 26/1 tracked -.02em),
`bigNumber` (a card's big number, display 500 40/1 tracked -.03em), `unit` (the unit after a
figure, 400 16 on a 20 line in the subtle ink, nested in the figure's Typography so it shares
its baseline), `sampleLabel` and `sampleMeta` (a sample's caption and meta line, 400 11 on 14
and 400 10 on 13 in the subtle ink; pass `mono`) and `base` (the 14 px body in the
foreground ink, 400 14 on an 18 line). The figures and titles rank with the titles, the
labels with the page label, and the notes, samples, unit and base with the reading roles.
`natural` sets the text on its face's own normal line at the role's size, the line the
hand-off's text takes where it sets a size with no ratio: JetBrains Mono 10, 11 and 12 px on
13, 14 and 16, Instrument Sans 12, 13 and 14 on 15, 16 and 18, Bricolage Grotesque 14 and
15 on 17 and 18 (measured in Chromium on the faces, 10 to 28 px). The face is the one the
text is set in: `mono`, a display role, or the interface face. It sits on the leading axis
after the fixed ratios and before `tightLeading`.
`cyan` and `mint` are two inks on the tone axis that name a fixed series hue whatever the
palette (the hand-off's `--cyan` and `--mint`, the `chart-2` and `chart-3` tokens), each
solved to 4.5:1 on the scheme's surfaces. Tone precedence: `destructive`, `warning`,
`success`, `cyan`, `mint`, `primary`, `subtle`, `muted`.
`code` is the standalone code pill (regular 13px mono on the muted fill, a 6 radius and 2
by 6 padding). `code inline` is the prose code chip, a word of code inside a sentence: the
hand-off's inline ``, regular weight at .88 of the surrounding reading role's size
(pass that role beside it, `lead`, `small`, `pageBody`, `pageNote`, `pageLead` or
`sectionLead`; body when none), a 5 radius, 1 by 5 padding, the surface fill and a 1px
hairline rim. The chip keeps its regular weight inside a strong run, as the hand-off
applies bold and code independently. Under glass the fill is the same ink tint the pill
takes. Natively a Text nested in a Text has no border and no vertical box, so there the
chip draws its fill and horizontal padding only; `inline` without `code` does nothing.
## Usage
```tsx
The quick brown fox
```
## Variants
### Hero
```tsx
Made of light.
```
### Page title
```tsx
Button
```
### Section title
```tsx
Playground
```
### Card title
```tsx
One charged accent
```
### Panel title
```tsx
Workspace
```
### Tagline
```tsx
Buttons, toggles and sliders.
```
### Page lead
```tsx
Six intents, three sizes and every state.
```
### Section lead
```tsx
Everything here is live. Press, drag and toggle.
```
### Card body
```tsx
Surfaces are lit glass.
```
### Section note
```tsx
Every control sets a real prop.
```
### Display
```tsx
The quick brown fox
```
### H1
```tsx
The quick brown fox
```
### H2
```tsx
The quick brown fox
```
### H3
```tsx
The quick brown fox
```
### H4
```tsx
The quick brown fox
```
### H5
```tsx
The quick brown fox
```
### Lead
```tsx
The quick brown fox
```
### Body
```tsx
The quick brown fox
```
### Small
```tsx
The quick brown fox
```
### Tiny
```tsx
The quick brown fox
```
### Muted
```tsx
The quick brown fox
```
### Caption
```tsx
The quick brown fox
```
### Code
```tsx
The quick brown fox
```
### Inline code
```tsx
Pass
secondary
for the next action down.
```
### Mono
```tsx
The quick brown fox
```
### Page label
```tsx
Props
```
### Knob label
```tsx
iconLeft
icon
```
### Guide title
```tsx
Real links (href)
```
### Guide body
```tsx
A button that navigates renders a real link on the web, so middle-click and open-in-new-tab work.
```
### Pager name
```tsx
ButtonGroup
```
### Spec meta
```tsx
ReactNode
```
### Spec text
```tsx
An icon element rendered before the label. It takes the label's ink and size.
```
### Pager label
```tsx
Previous
```
### Legend label
```tsx
Leading icon
```
### Tile caption
```tsx
small
```
### Variant title
```tsx
Attention required
```
### Link name
```tsx
Row & Column
```
### Subsection title
```tsx
Related components
```
### Card note
```tsx
<Button primary> · <Button outline>
```
### Card figures
```tsx
107 components
1,284
p95
212 ms
```
### Card labels
```tsx
Phone
Overview
Revenue
sm 640
primary
Keycaps read as one unit
285
#0a0a0f
Design · 24 members
```
### Natural leading
```tsx
container 1164 px
Hugs its content
```
### Hue tones
```tsx
≤ 640
641 – 1024
≥ 1025
```
### Semibold
```tsx
The quick brown fox
```
### Primary
```tsx
View invoices
```
### Success
```tsx
+12.4% this week
```
### Destructive
```tsx
Payment failed
```
### Underline
```tsx
View invoices
```
### Italic
`italic` sets the text in the face's italic, on any role.
```tsx
Draft, not yet shared
```
### Tight leading
```tsx
Canvas
design system
```
### Tabular
```tsx
Requests
1,204,118
Errors
11,407
```
### Heading level
```tsx
Playground
Examples
```
## Do & Don't
### display
**Do** — Use display once per hero, then drop to a muted line for the supporting copy.
```tsx
Welcome
Sign in to pick up where you left off.
```
**Don't** — Two display-size lines in one view fight for attention and leave no clear focal point.
```tsx
Welcome
Get started
```
### h1
**Do** — Give each page a single h1, then step down to h2 for the sections beneath it.
```tsx
Billing
Invoices
```
**Don't** — Two h1 titles on a page break the document outline and confuse assistive tech.
```tsx
Billing
Invoices
```
### h2
**Do** — Follow an h1 with h2 for its top-level sections; don't skip the scale.
```tsx
Settings
Profile
```
**Don't** — Jumping from h1 straight to h4 skips a level and flattens the visible hierarchy.
```tsx
Settings
Profile
```
### h3
**Do** — Reserve heading styles for titles; set running text in a small body utility.
```tsx
About Canvas
Canvas is a universal React Native UI kit for building consistent product interfaces.
```
**Don't** — Body copy set in a heading style is hard to read in bulk and flattens the hierarchy.
```tsx
Canvas is a universal React Native UI kit for building consistent product interfaces.
```
### h4
**Do** — Keep h4 to a short label and carry the explanation in a small supporting line.
```tsx
Notifications
Choose how and when we reach you.
```
**Don't** — h4 is a minor heading, not a place for full sentences; long text at this weight reads as a wall.
```tsx
Notifications
A long descriptive sentence that explains everything in detail.
```
### h5
**Do** — Use h5 only for the label; render the value in body so the pair stays scannable.
```tsx
Members
Aisha, Bao, Cleo, and 9 others have access.
```
**Don't** — Setting the value in h5 too makes the label and its data indistinguishable.
```tsx
Members
Aisha, Bao, Cleo, and 9 others have access.
```
### body
**Do** — Keep body copy in sentence case and let inline code carry the technical emphasis.
```tsx
Run
npm install
, then restart the dev server before you continue.
```
**Don't** — All-caps emphasis inside body copy shouts and undercuts the relaxed reading rhythm.
```tsx
npm install
THEN restart the dev server BEFORE you continue.
```
### small
**Do** — Use small for secondary captions on a plain surface, not for the primary label.
```tsx
Save changes
Last saved 2 minutes ago.
```
**Don't** — small is muted-foreground; on a colored button it loses contrast and looks disabled.
```tsx
Save changes
```
### tiny
**Do** — Reserve tiny for short metadata like timestamps and counts beside the main text.
```tsx
Deploy succeeded
3m ago
```
**Don't** — tiny is for metadata, not legal prose; long copy at 11px strains the eye.
```tsx
These terms govern your use of the service and your data; please read them carefully before you continue past this screen.
```
### muted
**Do** — Keep muted for de-emphasized context; give the actual action full foreground or primary color.
```tsx
Payment due May 31.
View invoices
```
**Don't** — A primary, clickable action in muted-foreground reads as disabled and is easy to miss.
```tsx
View your invoices
```
### caption
**Do** — Use caption as a short eyebrow label above a section, then explain in body.
```tsx
Billing
Your subscription renews automatically each month.
```
**Don't** — Uppercase, letter-spaced caption text is illegible for anything longer than a label.
```tsx
Your subscription renews automatically each month unless you cancel from the billing page.
```
### code
**Do** — Use code for inline tokens inside a sentence; reach for the code block component for multi-line snippets.
```tsx
Create a branch with
git checkout -b feature
before committing.
```
**Don't** — The inline code utility has tight padding and no scroll; multi-line blocks overflow and clip.
```tsx
git checkout -b feature
git add .
git commit -m "wip"
```
### mono
**Do**: Use mono for identifiers and hashes, where every character must line up; set figures in a column or a changing value in `tabular` instead.
```tsx
Request ID
req_8f2c10ab
```
**Don't** — Mono spacing makes prose sentences sparse and slow to read; it is meant for fixed-width data.
```tsx
We could not process your request because the upstream service returned an unexpected response.
```
### tabular
**Do**: Set figures read down a column or updated in place in `tabular`, so every digit takes the same width and the column lines up.
```tsx
Uptime
99.982%
Latency
118 ms
```
**Don't**: Tabular figures in running prose space the digits out of the sentence's rhythm; prose keeps the face's own figures.
```tsx
Your 14 invoices from 2024 total $1,180.
```
## Inline links (href)
Inline text that NAVIGATES should be a real link, not pressable text that sets `location`: pass `href` and the web render becomes a genuine browser link (middle-click, new tab, crawlers, and assistive tech all treat it as one). Native platforms ignore `href`, so pair it with an `onPress` that runs the same navigation through your router; `underline` supplies the conventional link look, and `hrefAttrs` forwards `target` / `rel` / `download` on the web.
```tsx
Read the Canvas docs for the full component list.
```
## Supported props
### TypographyProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| terminal | boolean \| undefined | no | false | Use the system terminal face while retaining the selected reading role and size. |
| section | boolean \| undefined | no | false | A section heading owning its count, metadata and trailing actions. |
| count | string \| number \| undefined | no | | |
| description | string \| undefined | no | | |
| actions | ReactNode | no | | |
| subheading | boolean \| undefined | no | false | Exact Argus reading roles; these preserve the original dense web type. |
| label | boolean \| undefined | no | false | |
| micro | boolean \| undefined | no | false | |
| tag | boolean \| undefined | no | false | |
| eyebrow | boolean \| undefined | no | false | |
| eyebrowLg | boolean \| undefined | no | false | |
| stageLabel | boolean \| undefined | no | false | |
| statValue | boolean \| undefined | no | false | |
| metricValue | boolean \| undefined | no | false | |
| formValue | boolean \| undefined | no | false | |
| extraBold | boolean \| undefined | no | false | |
| uppercase | boolean \| undefined | no | false | |
| foreground | boolean \| undefined | no | false | |
| inverse | boolean \| undefined | no | false | |
| action | boolean \| undefined | no | false | |
| actionForeground | boolean \| undefined | no | false | |
| info | boolean \| undefined | no | false | |
| hue | number \| undefined | no | | Hue-keyed stage text; deep in light schemes, pastel in dark. |
| inverseHint | boolean \| undefined | no | false | De-emphasized white ink over branded surfaces. |
| inverseSubtle | boolean \| undefined | no | false | |
| inverseMuted | boolean \| undefined | no | false | |
| inverseSoft | boolean \| undefined | no | false | |
| inverseFaint | boolean \| undefined | no | false | |
| right | boolean \| undefined | no | false | |
| center | boolean \| undefined | no | false | |
| numberOfLines | number \| undefined | no | | |
| onTextLayout | ((event: NativeSyntheticEvent) => void) \| undefined | no | | The text's laid-out lines, as React Native reports them (each line's `y`, `height`, `ascender` and `descender`, so its baseline is `y + height - descender`): for drawing something on the text's own lines, such as an illustration over a word. Fired by the native platforms; the web does not report it. |
| accessibilityRole | AccessibilityRole \| undefined | no | | |
| accessibilityLabel | string \| undefined | no | | |
| level | HeadingLevel \| undefined | no | | The heading level assistive tech hears, independent of the visual role: a heading role exposes its own level (display and h1 at 1, h2 to h5 at 2 to 5) unless this overrides it, so `` draws the h3 size and is announced as a level-2 heading (a real `` on the web). On a non-heading role it makes the text a heading at that level. Semantics only: it never changes the look. `href` still wins (a link is not a heading). |
| displayLeading | boolean \| undefined | no | false | |
| singleLeading | boolean \| undefined | no | false | |
| lockupLeading | boolean \| undefined | no | false | |
| denseLeading | boolean \| undefined | no | false | |
| compactLeading | boolean \| undefined | no | false | |
| labelLeading | boolean \| undefined | no | false | |
| captionLeading | boolean \| undefined | no | false | |
| smallLeading | boolean \| undefined | no | false | |
| strongLeading | boolean \| undefined | no | false | |
| bodyLeading | boolean \| undefined | no | false | |
| relaxedLeading | boolean \| undefined | no | false | |
| displayTracking | boolean \| undefined | no | false | |
| tightTracking | boolean \| undefined | no | false | |
| referenceTracking | boolean \| undefined | no | false | |
| labelTracking | boolean \| undefined | no | false | |
| eyebrowTracking | boolean \| undefined | no | false | |
| wideTracking | boolean \| undefined | no | false | |
| expandedTracking | boolean \| undefined | no | false | |
| children | ReactNode | no | | |
| hero | boolean \| undefined | no | false | The hand-off's hero headline: the display face sized by the window, `clamp(56px, 7.2vw, 112px)` at a .94 line and -.045em tracking (56 px on a phone, 103.68 at a 1440 window, 112 from 1556 up). One per page, its lead line. A level-1 heading. |
| pageTitle | boolean \| undefined | no | false | The hand-off's page title (a component page's name): the display face sized by the window, `clamp(40px, 5vw, 64px)` at a 1.0 line and -.04em tracking. A level-1 heading. |
| sectionTitle | boolean \| undefined | no | false | A page section's heading (a component page's Playground, Variants...): the display face, 500 28/1.1, -.02em. A level-2 heading. |
| cardTitle | boolean \| undefined | no | false | A feature card's title: the display face, 500 22/1.15, -.02em. A level-3 heading. |
| panelTitle | boolean \| undefined | no | false | A panel's or dialog's title: the display face, 500 20/1.2, -.02em. |
| tagline | boolean \| undefined | no | false | The one-line tagline under a section's title: the display face, 400 22/1.3, -.015em, in the foreground ink. |
| pageLead | boolean \| undefined | no | false | A page's summary under its title: 400 18/1.55 in the secondary ink. |
| sectionLead | boolean \| undefined | no | false | A section's introductory paragraph: 400 17/1.55 in the secondary ink. |
| cardBody | boolean \| undefined | no | false | A feature card's description: 400 14/1.55 in the secondary ink. |
| sectionNote | boolean \| undefined | no | false | The note under a page section's heading: 400 14/1.5 in the secondary ink. |
| pageBody | boolean \| undefined | no | false | A page's body paragraph (a component page's usage line): 400 15/1.6 in the secondary ink, held to an 80ch measure. |
| pageNote | boolean \| undefined | no | false | A page's note paragraph (a component page's overview and docs notes): 400 14/1.6 in the secondary ink, held to an 80ch measure. |
| pageLabel | boolean \| undefined | no | false | A page's label (a panel's "Props", a rail's group header, a table's header cell, a previous/next eyebrow): 500 11/1 tracked .08em in caps, in the subtle ink. Pass `mono` for the mono face, as the other page roles do. Not the `eyebrow`, which is the type table's accent-ink label tracked .1em. |
| knobLabel | boolean \| undefined | no | false | A knob panel's label row (a knob's name, and the type hint beside it with `muted`, the third ink): 500 12/1 in the secondary ink. Pass `mono` for the mono face, as the hand-off's page does. |
| guideTitle | boolean \| undefined | no | false | A guidance card's title: 600 16/1.3 in the interface face. A level-3 heading. |
| guideBody | boolean \| undefined | no | false | A guidance card's paragraph: 400 13.5/1.6 in the secondary ink. |
| pagerName | boolean \| undefined | no | false | A pager card's name (the previous or next page under its label): the display face, 500 18 on the face's own 22 line, in the foreground ink. |
| specMeta | boolean \| undefined | no | false | A reference sheet's meta cell (a props table's type or default): 400 12/1.55 in the foreground ink. Pass `mono` for the mono face and `subtle` or `muted` for the ink, as the sheet's cells take them. |
| specText | boolean \| undefined | no | false | A reference sheet's text cell (a props table's description, an inherited-props note): 400 13/1.55 in the secondary ink. |
| pagerLabel | boolean \| undefined | no | false | A pager card's label (Previous, Next): the page label's 500 11 tracked .08em in caps and subtle ink on the face's own 14 line (the hand-off sets no ratio there). Pass `mono` for the mono face, as the page label takes it. |
| legendLabel | boolean \| undefined | no | false | A legend row's label (an anatomy part's name beside its numbered disc): 500 14 on the face's own 18 line, in the foreground ink. |
| tileCaption | boolean \| undefined | no | false | A specimen tile's caption strip (a size or state name under its well): 500 12 on the face's own 16 line, in the secondary ink. Pass `mono` for the mono face. |
| variantTitle | boolean \| undefined | no | false | A variant card's title (an example's label over its note and snippet): 500 14/1.3 in the interface face and the foreground ink. |
| linkName | boolean \| undefined | no | false | A link tile's name (a related component beside its arrow): 600 14 on the face's own 18 line, in the foreground ink. |
| subsectionTitle | boolean \| undefined | no | false | A page section's own sub-heading (the "Related components" title over its tiles): 600 16 on the face's own 20 line in the interface face. A level-3 heading. |
| cardNote | boolean \| undefined | no | false | A section card's foot caption, or a note inside the card: 400 12/1.6 (19.2) in the subtle ink. Pass `mono` for the mono face, as the hand-off sets it. |
| codeLabel | boolean \| undefined | no | false | A code name on its own line (a variant row's label, a token's name): 400 12 on the mono face's 16 line, in the secondary ink. Pass `mono` for the mono face. |
| inlineNote | boolean \| undefined | no | false | A short note beside a specimen: 400 12 on the interface face's 15 line, in the subtle ink. |
| tileTitle | boolean \| undefined | no | false | A tile's title: the display face, 500 15/1.3 (19.5), in the foreground ink. |
| gridLabel | boolean \| undefined | no | false | A grid widget's label: 500 10 mono tracked .08em in caps on a 13 line, in the subtle ink. |
| tick | boolean \| undefined | no | false | A scale's tick label: 500 10 mono on a 13 line, in the secondary ink. |
| rowLabel | boolean \| undefined | no | false | A stat row's label: 500 11/1 mono tracked .06em in caps, in the subtle ink. |
| rowValue | boolean \| undefined | no | false | A stat row's value beside its label: the display face, 500 22/1, -.02em, in the foreground ink. |
| headlineValue | boolean \| undefined | no | false | A card's headline figure: the display face, 500 26/1, -.02em, in the foreground ink. |
| bigNumber | boolean \| undefined | no | false | A card's big number: the display face, 500 40/1, -.03em, in the foreground ink. |
| unit | boolean \| undefined | no | false | The unit after a figure: 400 16 on the interface face's 20 line, in the subtle ink. Nest it inside the figure's Typography so it shares the figure's baseline (`107 components `). |
| paneTitle | boolean \| undefined | no | false | A pane's title: the display face, 500 16/1.2, -.01em, in the foreground ink. Pass `level` to announce it as a heading. |
| sampleLabel | boolean \| undefined | no | false | A sample's caption (a hue number, a series name): 400 11 on the mono face's 14 line, in the subtle ink. Pass `mono`. |
| sampleMeta | boolean \| undefined | no | false | A sample's meta line (a hex value, a size): 400 10 on the mono face's 13 line, in the subtle ink. Pass `mono`. |
| base | boolean \| undefined | no | false | The 14 px body in the foreground ink: 400 14 on the interface face's 18 line (a description list's value). |
| display | boolean \| undefined | no | false | |
| h1 | boolean \| undefined | no | false | |
| h2 | boolean \| undefined | no | false | |
| h3 | boolean \| undefined | no | false | |
| h4 | boolean \| undefined | no | false | |
| h5 | boolean \| undefined | no | false | |
| lead | boolean \| undefined | no | false | |
| body | boolean \| undefined | no | false | |
| small | boolean \| undefined | no | false | |
| tiny | boolean \| undefined | no | false | |
| muted | boolean \| undefined | no | false | |
| caption | boolean \| undefined | no | false | |
| code | boolean \| undefined | no | false | |
| mono | boolean \| undefined | no | false | |
| inline | boolean \| undefined | no | false | With `code`: the prose code chip, a word of code inside a sentence. Regular weight at .88 of the surrounding reading role's size (pass that role beside it, `lead`, `small`, `pageBody`, `pageNote`, `pageLead` or `sectionLead`; body when none), the chip's own small corner, 1 by 5 padding, the surface fill and a hairline rim. Natively a Text nested in a Text draws the fill and horizontal padding only. Without `code` it does nothing. |
| subtle | boolean \| undefined | no | false | Tone (pick one; orthogonal to role). When omitted, the role's own color is used. |
| primary | boolean \| undefined | no | false | Tone (pick one; orthogonal to role). When omitted, the role's own color is used. |
| destructive | boolean \| undefined | no | false | Tone (pick one; orthogonal to role). When omitted, the role's own color is used. |
| success | boolean \| undefined | no | false | Tone (pick one; orthogonal to role). When omitted, the role's own color is used. |
| warning | boolean \| undefined | no | false | Tone (pick one; orthogonal to role). When omitted, the role's own color is used. |
| cyan | boolean \| undefined | no | false | The fixed cyan series hue (the hand-off's `--cyan`, `chart-2`) as an ink, whatever the palette, solved to 4.5:1 on the scheme's surfaces. |
| mint | boolean \| undefined | no | false | The fixed mint series hue (the hand-off's `--mint`, `chart-3`) as an ink, whatever the palette, solved like `cyan`. |
| regular | boolean \| undefined | no | false | Weight (pick one; orthogonal to role). When omitted, the role's own weight is used. |
| medium | boolean \| undefined | no | false | Weight (pick one; orthogonal to role). When omitted, the role's own weight is used. |
| semibold | boolean \| undefined | no | false | Weight (pick one; orthogonal to role). When omitted, the role's own weight is used. |
| bold | boolean \| undefined | no | false | Weight (pick one; orthogonal to role). When omitted, the role's own weight is used. |
| tightLeading | boolean \| undefined | no | false | Leading (orthogonal to role). Pulls the line box in to 1.25x the font size for a STACKED LOCKUP, where two lines read as one unit (a wordmark over its tagline, a title over its subtitle) and the roles reading leading would leave dead air between them. Only ever tightens, so it is safe on every role. Omit for prose: the role's own line height is the reading value. |
| natural | boolean \| undefined | no | false | Leading: the face's own normal line at the role's size, the line the hand-off's text takes where it sets a size with no ratio (JetBrains Mono 10, 11 and 12 px on 13, 14 and 16; Instrument Sans 12, 13 and 14 on 15, 16 and 18; Bricolage Grotesque 14 and 15 on 17 and 18). The face is the one the text is set in (`mono`, a display role, or the interface face). A size off the measured table (10 to 28 px) keeps the role's line. |
| underline | boolean \| undefined | no | false | Decoration (orthogonal to role / tone / weight). Underlines the text so an inline text link reads as a link, replacing the banned raw `textDecorationLine` style shim. Combines freely with every role, tone, and weight; omit for undecorated text. |
| italic | boolean \| undefined | no | false | Slant (orthogonal to role / tone / weight / decoration). Sets the text in the face's italic, for a word set off in prose or the italic mark of a formatting toolbar. Omit for upright text. |
| tabular | boolean \| undefined | no | false | Numerals (orthogonal to role / tone / weight). Tabular figures: every digit the same width, for numbers read down a column or updated in place (a table's figures, a live metric, a counter), so the column lines up and a changing value holds still. Never for prose, which keeps the face's own proportional figures. |
| href | string \| undefined | no | | Renders this text as a REAL inline link on the web: react-native-web gives the root element a browser link with this destination, so middle-click, cmd-click, and open-in-new-tab work and assistive tech hears a link. Native platforms have no browser links and ignore it, so pair `href` with an `onPress` running the same navigation through the host's router. Combine with `underline` for the conventional link look. When a heading role and `href` are both set, the link role wins. |
| hrefAttrs | { target?: "_blank" \| "_self"; rel?: string; download?: boolean \| string; } \| undefined | no | | Attributes forwarded with `href` on the web: `target`, `rel`, `download`. |
| onPress | ((event: GestureResponderEvent) => void) \| undefined | no | | Press handler; the native navigation path for `href` (fires on web too). |
| testID | string \| undefined | no | | E2E hook forwarded to the root element. |
| style | StyleProp | no | | For layout/positioning composition only (margins handled by Row/Column). Not a styling escape hatch: size, color, and weight come from the props above. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# UptimeBar
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/uptime-bar/uptime-bar.md`
Human reference: https://canvas.nannier.com/components/uptime-bar
## Public imports
```tsx
import { UptimeBar } 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 statuspage strip: per-period status pills (operational, degraded, down, unknown) with a summary caption and edge labels.
The pills run in a single row, oldest on the left, colored operational green, degraded amber, down red, or unknown muted (precedence down > degraded > unknown; an unmarked period is operational). An optional `caption` summarizes the strip above it, and `startLabel`/`endLabel` caption the physical edges below. The strip is a time axis, so it keeps left-to-right ordering in every locale, and its accessible name tallies every status.
`detailed` uses a display header, taller history marks and neutral unknown periods. Hovering a period expands it with an overshooting transition; Reduced Motion keeps it still.
## Usage
```tsx
(i === 61 ? { down: true } : i === 78 ? { degraded: true } : {}))}
/>
```
## Variants
### Compact
```tsx
(i === 40 ? { degraded: true } : {}))}
/>
```
### With unknown periods
```tsx
(i < 6 ? { unknown: true } : {}))}
/>
```
### Middle caption
`midLabel` captions the middle of the strip, centred between the edge captions.
```tsx
(i === 61 ? { down: true } : {}))}
/>
```
### Detailed
A display header and taller marks for a status overview. A resting pointer expands its period; Reduced Motion keeps the marks still.
```tsx
({
label: `Day ${i + 1}`,
degraded: i === 17 || i === 41,
down: i === 52,
unknown: i === 3
}))}
/>
```
## Do & Don't
### UptimeBar
**Do** - Let the strip carry the whole anatomy: pills, summary caption, and edge labels, with the status tally in its accessible name.
```tsx
(i === 61 ? { down: true } : {}))}
/>
```
**Don't** - A hand-rolled row of colored Views has no accessible tally and no edge-caption anatomy.
```tsx
{Array.from({ length: 90 }, (_, i) => (
))}
```
## Supported props
### UptimeBarProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| periods | UptimePeriod[] | yes | | The periods to render, oldest first; each becomes one pill. |
| label | string \| undefined | no | | What the strip measures (e.g. "API"); shown in the header and leading the accessible name. |
| caption | string \| undefined | no | | A summary at the header's right (e.g. "99.98% uptime"). |
| startLabel | string \| undefined | no | | Caption under the strip's left (oldest) edge (e.g. "90 days ago"). |
| endLabel | string \| undefined | no | | Caption under the strip's right (latest) edge (e.g. "Today"). |
| midLabel | string \| undefined | no | | Caption centred under the strip, between the edge captions (e.g. "1 incident · 2 degraded days"). |
| compact | boolean \| undefined | no | false | Density (omit for the default pill height). |
| detailed | boolean \| undefined | no | false | Display header and taller history marks for a status overview. |
| 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.
---
# Video
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/video/video.md`
Human reference: https://canvas.nannier.com/components/video
## Public imports
```tsx
import { Video } 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.
Plays a clip with an optional poster: tap to play inline, or full transport controls
(the platform's own on iOS and Android, the kit's bar on the web),
with boolean fit, `autoplay`, `loop` and `muted` props.
Give it a `source` (a `{ uri }`, or a bundled asset from `require`), an optional `poster` shown until the clip's first frame is on screen, and the clip's `aspectRatio` (width over height, 16 / 9 by default): the frame fills its parent's width at that ratio and letterboxes the picture on black, inside the v4 hand-off's frame: a 14 px corner on a hairline (`radius` picks a step of the kit's scale instead). Without `controls` it is an inline player: the whole picture is one play and pause control, a play emblem sits over the paused picture, and a spinner shows while the clip loads. With `controls` each platform shows transport its own way:
iOS and Android hand the frame to their own player controls, and the web draws
the kit's control bar under the picture, with play and pause, the elapsed time, a seek slider, the length, mute, and full screen.
`autoplay` starts a clip once it is ready unless the system asks to reduce motion; browsers autoplay only `muted` clips, and this site's own policy blocks autoplay, so the examples here play on tap. Name every clip with `accessibilityLabel`: its controls are announced as "Play", "Pause", "Seek" and "Mute" followed by that name. Playback runs through the optional `expo-video` peer; without it the frame keeps its size and poster in a labeled frame.
## Usage
```tsx
```
## Variants
### Controls
```tsx
```
### Cover
```tsx
```
### Loop and muted
```tsx
```
## Do & Don't
### Clips with sound
**Do**: Give a clip people watch with sound `controls`: they can seek, see how long it runs, and mute it.
```tsx
```
**Don't**: Leave a clip with sound as an inline player; a tap can only play or pause it, so there is no way to seek, see its length, or mute it.
```tsx
```
## Supported props
### VideoProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| source | VideoSource | yes | | The clip: a remote `{ uri }`, or a bundled asset from `require(...)`. |
| poster | ImageSourcePropType \| undefined | no | | A still shown until the clip's first frame is on screen: a remote `{ uri }` or a bundled asset. |
| contain | boolean \| undefined | no | false | Fit: show the whole picture, letterboxed on black. The default (pick one). |
| cover | boolean \| undefined | no | false | Fit: fill the frame, cropping the overflow (pick one). |
| stretch | boolean \| undefined | no | false | Fit: fill the frame by distorting the aspect ratio (pick one). |
| autoplay | boolean \| undefined | no | false | Start playing as soon as the clip is ready, unless the system asks to reduce motion. Browsers autoplay only muted clips, so pair it with `muted` on the web. |
| loop | boolean \| undefined | no | false | Play again from the start at the end. |
| muted | boolean \| undefined | no | false | Play without sound. |
| controls | boolean \| undefined | no | false | Transport controls: the platform's own player controls on iOS and Android, the kit's control bar on the web. |
| aspectRatio | number \| undefined | no | | Width over height of the clip; the frame fills its parent's width at this ratio (default 16 / 9). |
| radius | ImageRadius \| undefined | no | | Corner radius from the kit's radius scale (pick one): none, sm, md, lg, xl, 2xl, 3xl, full. |
| accessibilityLabel | string \| undefined | no | | The clip's accessible name, used in the names of its controls (default "video"). |
| onPlayingChange | ((playing: boolean) => void) \| undefined | no | | Called when playback starts or stops. |
| onEnd | (() => void) \| undefined | no | | Called when the clip plays to its end (each time round, when looping). |
| testID | string \| undefined | no | | E2E hook forwarded to the frame. |
| 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.
---
# View
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/atoms/view/view.md`
Human reference: https://canvas.nannier.com/components/view
## Public imports
```tsx
import { View } 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 layout primitive: a flex container that
runs identically on iOS, Android, and the web.
A View is a column by default.
Reach for it directly only when no semantic primitive covers what you need: arrangement (direction, gap, alignment, padding) belongs to `Row` and `Column`, which are Views with that axis exposed as boolean props, and a bordered filled surface belongs to `Card`. Numbers are density-independent pixels.
## Usage
```tsx
```
## Variants
### Column
```tsx
```
### Flex sizing
```tsx
```
### Padding
```tsx
A padded View
```
## Do & Don't
### Arrangement
**Do** — Row and Column carry direction, gap, alignment and padding as boolean props, so the arrangement reads as language and lands on the kit's spacing scale.
```tsx
Semantic arrangement
```
**Don't** — A hand-written flex style object restates what the primitives already name, drifts off the spacing scale, and has to be re-read to know what it does.
```tsx
Hand-rolled arrangement
```
### Surfaces
**Do** — A bordered, filled, padded surface is a Card; it carries the platform's own radius, hairline and elevation.
```tsx
A real surface
```
**Don't** — Rebuilding the same box out of a View and four style properties drifts from the platform skins the moment one of them changes.
```tsx
A look-alike surface
```
## Supported props
### View host props
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | React.ReactNode | no | | |
| hitSlop | number \| Insets \| null \| undefined | no | | This defines how far a touch event can start away from the view. Typical interface guidelines recommend touch targets that are at least 30 - 40 points/density-independent pixels. If a Touchable view has a height of 20 the touchable height can be extended to 40 with hitSlop={{top: 10, bottom: 10, left: 0, right: 0}} NOTE The touch area never extends past the parent view bounds and the Z-index of sibling views always takes precedence if a touch hits two overlapping views. |
| id | string \| undefined | no | | Used to reference react managed views from native code. |
| needsOffscreenAlphaCompositing | boolean \| undefined | no | | Whether this view needs to rendered offscreen and composited with an alpha in order to preserve 100% correct colors and blending behavior. The default (false) falls back to drawing the component and its children with an alpha applied to the paint used to draw each element instead of rendering the full component offscreen and compositing it back with an alpha value. This default may be noticeable and undesired in the case where the View you are setting an opacity on has multiple overlapping elements (e.g. multiple overlapping Views, or text and a background). Rendering offscreen to preserve correct alpha behavior is extremely expensive and hard to debug for non-native developers, which is why it is not turned on by default. If you do need to enable this property for an animation, consider combining it with renderToHardwareTextureAndroid if the view contents are static (i.e. it doesn't need to be redrawn each frame). If that property is enabled, this View will be rendered off-screen once, saved in a hardware texture, and then composited onto the screen with an alpha each frame without having to switch rendering targets on the GPU. |
| onLayout | ((event: LayoutChangeEvent) => void) \| undefined | no | | Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}. |
| pointerEvents | "box-none" \| "none" \| "box-only" \| "auto" \| undefined | no | | In the absence of auto property, none is much like CSS's none value. box-none is as if you had applied the CSS class: .box-none { pointer-events: none; } .box-none * { pointer-events: all; } box-only is the equivalent of .box-only { pointer-events: all; } .box-only * { pointer-events: none; } But since pointerEvents does not affect layout/appearance, and we are already deviating from the spec by adding additional modes, we opt to not include pointerEvents on style. On some platforms, we would need to implement it as a className anyways. Using style or not is an implementation detail of the platform. |
| removeClippedSubviews | boolean \| undefined | no | | This is a special performance property exposed by RCTView and is useful for scrolling content when there are many subviews, most of which are offscreen. For this property to be effective, it must be applied to a view that contains many subviews that extend outside its bound. The subviews must also have overflow: hidden, as should the containing view (or one of its superviews). |
| style | StyleProp | no | | |
| testID | string \| undefined | no | | Used to locate this view in end-to-end tests. |
| nativeID | string \| undefined | no | | Used to reference react managed views from native code. |
| collapsable | boolean \| undefined | no | | Views that are only used to layout their children or otherwise don't draw anything may be automatically removed from the native hierarchy as an optimization. Set this property to false to disable this optimization and ensure that this View exists in the native view hierarchy. |
| collapsableChildren | boolean \| undefined | no | | Setting to false prevents direct children of the view from being removed from the native view hierarchy, similar to the effect of setting `collapsable={false}` on each child. |
| onBlur | ((e: BlurEvent) => void) \| null \| undefined | no | | Callback that is called when the view is blurred. Note: This will only be called if the view is focusable. |
| onFocus | ((e: FocusEvent) => void) \| null \| undefined | no | | Callback that is called when the view is focused. Note: This will only be called if the view is focusable. |
| renderToHardwareTextureAndroid | boolean \| undefined | no | | Whether this view should render itself (and all of its children) into a single hardware texture on the GPU. On Android, this is useful for animations and interactions that only modify opacity, rotation, translation, and/or scale: in those cases, the view doesn't have to be redrawn and display lists don't need to be re-executed. The texture can just be re-used and re-composited with different parameters. The downside is that this can use up limited video memory, so this prop should be set back to false at the end of the interaction/animation. |
| focusable | boolean \| undefined | no | | Whether this `View` should be focusable with a non-touch input device, eg. receive focus with a hardware keyboard. |
| tabIndex | 0 \| -1 \| undefined | no | | Indicates whether this `View` should be focusable with a non-touch input device, eg. receive focus with a hardware keyboard. See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex for more details. Supports the following values: - 0 (View is focusable) - -1 (View is not focusable) |
| shouldRasterizeIOS | boolean \| undefined | no | | Whether this view should be rendered as a bitmap before compositing. On iOS, this is useful for animations and interactions that do not modify this component's dimensions nor its children; for example, when translating the position of a static view, rasterization allows the renderer to reuse a cached bitmap of a static view and quickly composite it during each frame. Rasterization incurs an off-screen drawing pass and the bitmap consumes memory. Test and measure when using this property. |
| isTVSelectable | boolean \| undefined | no | | *(Apple TV only)* When set to true, this view will be focusable and navigable using the Apple TV remote. |
| hasTVPreferredFocus | boolean \| undefined | no | | *(Apple TV only)* May be set to true to force the Apple TV focus engine to move focus to this view. |
| tvParallaxShiftDistanceX | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. |
| tvParallaxShiftDistanceY | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. |
| tvParallaxTiltAngle | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 0.05. |
| tvParallaxMagnification | number \| undefined | no | | *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 1.0. |
| onStartShouldSetResponder | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Does this view want to become responder on the start of a touch? |
| onMoveShouldSetResponder | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Called for every touch move on the View when it is not the responder: does this view want to "claim" touch responsiveness? |
| onResponderEnd | ((event: GestureResponderEvent) => void) \| undefined | no | | If the View returns true and attempts to become the responder, one of the following will happen: |
| onResponderGrant | ((event: GestureResponderEvent) => void) \| undefined | no | | The View is now responding for touch events. This is the time to highlight and show the user what is happening |
| onResponderReject | ((event: GestureResponderEvent) => void) \| undefined | no | | Something else is the responder right now and will not release it |
| onResponderMove | ((event: GestureResponderEvent) => void) \| undefined | no | | The user is moving their finger |
| onResponderRelease | ((event: GestureResponderEvent) => void) \| undefined | no | | Fired at the end of the touch, ie "touchUp" |
| onResponderStart | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onResponderTerminationRequest | ((event: GestureResponderEvent) => boolean) \| undefined | no | | Something else wants to become responder. Should this view release the responder? Returning true allows release |
| onResponderTerminate | ((event: GestureResponderEvent) => void) \| undefined | no | | The responder has been taken from the View. Might be taken by other views after a call to onResponderTerminationRequest, or might be taken by the OS without asking (happens with control center/ notification center on iOS) |
| onStartShouldSetResponderCapture | ((event: GestureResponderEvent) => boolean) \| undefined | no | | onStartShouldSetResponder and onMoveShouldSetResponder are called with a bubbling pattern, where the deepest node is called first. That means that the deepest component will become responder when multiple Views return true for *ShouldSetResponder handlers. This is desirable in most cases, because it makes sure all controls and buttons are usable. However, sometimes a parent will want to make sure that it becomes responder. This can be handled by using the capture phase. Before the responder system bubbles up from the deepest component, it will do a capture phase, firing on*ShouldSetResponderCapture. So if a parent View wants to prevent the child from becoming responder on a touch start, it should have a onStartShouldSetResponderCapture handler which returns true. |
| onMoveShouldSetResponderCapture | ((event: GestureResponderEvent) => boolean) \| undefined | no | | onStartShouldSetResponder and onMoveShouldSetResponder are called with a bubbling pattern, where the deepest node is called first. That means that the deepest component will become responder when multiple Views return true for *ShouldSetResponder handlers. This is desirable in most cases, because it makes sure all controls and buttons are usable. However, sometimes a parent will want to make sure that it becomes responder. This can be handled by using the capture phase. Before the responder system bubbles up from the deepest component, it will do a capture phase, firing on*ShouldSetResponderCapture. So if a parent View wants to prevent the child from becoming responder on a touch start, it should have a onStartShouldSetResponderCapture handler which returns true. |
| onTouchStart | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchMove | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchEnd | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchCancel | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onTouchEndCapture | ((event: GestureResponderEvent) => void) \| undefined | no | | |
| onPointerEnter | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerEnterCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerLeave | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerLeaveCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerMove | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerMoveCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerCancel | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerCancelCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerDown | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerDownCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerUp | ((event: PointerEvent) => void) \| undefined | no | | |
| onPointerUpCapture | ((event: PointerEvent) => void) \| undefined | no | | |
| accessible | boolean \| undefined | no | | When true, indicates that the view is an accessibility element. By default, all the touchable elements are accessible. |
| accessibilityActions | readonly Readonly<{ name: AccessibilityActionName \| string; label?: string \| undefined; }>[] \| undefined | no | | Provides an array of custom actions available for accessibility. |
| accessibilityLabel | string \| undefined | no | | Overrides the text that's read by the screen reader when the user interacts with the element. By default, the label is constructed by traversing all the children and accumulating all the Text nodes separated by space. |
| aria-label | string \| undefined | no | | Alias for accessibilityLabel https://reactnative.dev/docs/view#accessibilitylabel https://github.com/facebook/react-native/issues/34424 |
| accessibilityRole | AccessibilityRole \| undefined | no | | Accessibility Role tells a person using either VoiceOver on iOS or TalkBack on Android the type of element that is focused on. |
| accessibilityState | AccessibilityState \| undefined | no | | Accessibility State tells a person using either VoiceOver on iOS or TalkBack on Android the state of the element currently focused on. |
| aria-busy | boolean \| undefined | no | | alias for accessibilityState see https://reactnative.dev/docs/accessibility#accessibilitystate |
| aria-checked | boolean \| "mixed" \| undefined | no | | |
| aria-disabled | boolean \| undefined | no | | |
| aria-expanded | boolean \| undefined | no | | |
| aria-selected | boolean \| undefined | no | | |
| accessibilityHint | string \| undefined | no | | An accessibility hint helps users understand what will happen when they perform an action on the accessibility element when that result is not obvious from the accessibility label. |
| accessibilityValue | AccessibilityValue \| undefined | no | | Represents the current value of a component. It can be a textual description of a component's value, or for range-based components, such as sliders and progress bars, it contains range information (minimum, current, and maximum). |
| aria-valuemax | number \| undefined | no | | |
| aria-valuemin | number \| undefined | no | | |
| aria-valuenow | number \| undefined | no | | |
| aria-valuetext | string \| undefined | no | | |
| onAccessibilityAction | ((event: AccessibilityActionEvent) => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs an accessibility custom action. |
| importantForAccessibility | "auto" \| "yes" \| "no" \| "no-hide-descendants" \| undefined | no | | [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services. |
| aria-hidden | boolean \| undefined | no | | A value indicating whether the accessibility elements contained within this accessibility element are hidden. |
| aria-modal | boolean \| undefined | no | | |
| role | Role \| undefined | no | | Indicates to accessibility services to treat UI component like a specific role. |
| accessibilityLabelledBy | string \| string[] \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| aria-labelledby | string \| undefined | no | | Identifies the element that labels the element it is applied to. When the assistive technology focuses on the component with this props, the text is read aloud. The value should should match the nativeID of the related element. |
| accessibilityLiveRegion | "none" \| "polite" \| "assertive" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| aria-live | "polite" \| "assertive" \| "off" \| undefined | no | | Indicates to accessibility services whether the user should be notified when this view changes. Works for Android API >= 19 only. |
| screenReaderFocusable | boolean \| undefined | no | | Enables the view to be screen reader focusable, not keyboard focusable. |
| accessibilityElementsHidden | boolean \| undefined | no | | A Boolean value indicating whether the accessibility elements contained within this accessibility element are hidden to the screen reader. |
| accessibilityViewIsModal | boolean \| undefined | no | | A Boolean value indicating whether VoiceOver should ignore the elements within views that are siblings of the receiver. |
| onAccessibilityEscape | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the escape gesture (scrub with two fingers). |
| onAccessibilityTap | (() => void) \| undefined | no | | When `accessible` is true, the system will try to invoke this function when the user performs accessibility tap gesture. |
| onMagicTap | (() => void) \| undefined | no | | When accessible is true, the system will invoke this function when the user performs the magic tap gesture. |
| accessibilityIgnoresInvertColors | boolean \| undefined | no | | https://reactnative.dev/docs/accessibility#accessibilityignoresinvertcolorsios |
| accessibilityLanguage | string \| undefined | no | | By using the accessibilityLanguage property, the screen reader will understand which language to use while reading the element's label, value and hint. The provided string value must follow the BCP 47 specification (https://www.rfc-editor.org/info/bcp47). https://reactnative.dev/docs/accessibility#accessibilitylanguage-ios |
| accessibilityShowsLargeContentViewer | boolean \| undefined | no | | A Boolean value that indicates whether or not to show the item in the large content viewer. Available on iOS 13.0+ https://reactnative.dev/docs/accessibility#accessibilityshowslargecontentviewer |
| accessibilityLargeContentTitle | string \| undefined | no | | When `accessibilityShowsLargeContentViewer` is set, this string will be used as title for the large content viewer. https://reactnative.dev/docs/accessibility#accessibilitylargecontenttitle |
| accessibilityRespondsToUserInteraction | boolean \| undefined | no | | Blocks the user from interacting with the component through keyboard while still allowing screen reader to interact with it if this View is still accessible. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# WaterfallChart
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/charts/waterfall-chart/waterfall-chart.md`
Human reference: https://canvas.nannier.com/components/waterfall-chart
## Public imports
```tsx
import { WaterfallChart } 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 running-total bridge: each step floats from the running total by its signed `value`, and a `total` step snapshots the running total as an absolute bar from zero.
The coloring is fixed semantics rather than a prop: rises green, falls red, totals the brand primary, so every bridge reads the same way. Scrubbing a step flags its change and running total.
## Usage
```tsx
```
## Variants
### Signed steps only
```tsx
```
### Compact, inspected
```tsx
```
## Do & Don't
### WaterfallChart
**Do** - Bridge from one total to the next with signed steps; let the fixed coloring carry rise and fall.
```tsx
```
**Don't** - Plain bars of period totals hide the walk: what rose, what fell, and by how much.
```tsx
```
## Supported props
### WaterfallChartProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| steps | WaterfallStep[] | yes | | The bridge's steps, in order. |
| 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). |
| 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 step index (controlled). Pass null for none. |
| defaultSelected | number \| undefined | no | | Scrub-to-inspect: the initially selected step (uncontrolled). |
| onSelect | ((index: number \| null) => void) \| undefined | no | | Fired when a press/scrub selects a step (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.
---
# Wizard
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Source: `src/organisms/wizard/wizard.md`
Human reference: https://canvas.nannier.com/components/wizard
## Public imports
```tsx
import { Wizard } 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 workflow with a fixed step sidebar, a title and status header, and footer actions around a bounded, scrolling body. The application owns stage content, validation, saving and transitions; Wizard composes the existing Dialog, Steps and Button controls.
Add `embedded` to show an open wizard 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, trap Tab, set `aria-modal`, or close on Escape or
native back, and it renders in place in the page's flow instead of presenting into an
`OverlayProvider`.
## Usage
`ViewportOverlay` is docs-only scaffolding that presents the example above the docs page. In an application, mount Wizard beneath the app-root OverlayProvider.
```tsx
{(open, setOpen) =>
{(current, setCurrent) =>
{ setCurrent(0); setOpen(true); }}>Open project setup
0 ? { label: "Details", onPress: () => setCurrent(0) } : undefined}
status={`Step ${current + 1} of 2`}
actions={ current === 0 ? setCurrent(1) : setOpen(false)}>{current === 0 ? "Review project" : "Create project"} }>
{current === 0 ? "Project details" : "Ready to create"}
{current === 0 ? : Customer portal is ready to create. }
}
}
```
## Variants
### Team invitation
The status sits beside the title, with the previous action at the footer's start and the
application actions at its end. Compact steps occupy the fixed sidebar. When the panel is narrower than 520 points (a phone), the steps move under the title as segmented bars and the body takes the full width, so content such as a Chat is never squeezed beside the rail.
```tsx
{}} title="Create workspace"
steps={[{ label: "Name" }, { label: "Members" }, { label: "Theme" }, { label: "Review" }]}
current={1} status="Step 2 of 4" previous={{ label: "Back", onPress: () => {} }}
actions={Continue }>
Invite your team
```
### Assistant workspace
`assistant` lays the wizard out like a desktop assistant window: the steps as a full-height
sidebar of rows, a 44 point top bar with the title and the status, and `previous` and
`actions` in a bar under the conversation. The body is unpadded so an `assistant flush` Chat fills it; a scrolled body
centers its content in the conversation's column. Below 720 points of panel the sidebar
gives way to segmented steps under the title.
```tsx
{}} title="Plan a project" scrollBody={false}
steps={[{ label: "Plan project" }, { label: "Review project plan" }, { label: "Plan workforce" }]}
current={0} status="Autosaved" actions={Reset }>
{}} onSend={() => {}} />
```
### Child owns scrolling
Set `scrollBody={false}` for a bounded child such as Chat that already manages its own thread scroll. The header and footer retain their positions.
```tsx
{(open, setOpen) =>
setOpen(true)}>Open planning conversation
setOpen(false)}>Done }>
{}} onSend={() => {}} />
}
```
## Do & Don't
### Named backward navigation
**Do** — Name the previous stage so its destination is clear.
```tsx
{}} title="Project setup" steps={[{ label: "Details" }, { label: "Review" }]} current={1} previous={{ label: "Details", onPress: () => {} }} />
```
**Don't** — Hide the destination behind a generic label.
```tsx
{}} title="Project setup" steps={[{ label: "Details" }, { label: "Review" }]} current={1} previous={{ label: "Back", onPress: () => {} }} />
```
## Accessibility
Dialog supplies the accessible modal name, focus management, live close action, Escape and native back behavior. Embedded specimens keep the bare panel without a close action or modal focus ownership. The current sidebar stage carries `aria-current="step"`. The previous action carries its stage name; application actions provide their own labels and disabled states. Footer buttons stay on one row; use Button `shrink` on application actions so long labels wrap within their button rather than moving the operation to another row. A controlled `onOpenChange` lets the application decide whether a dismissal request can proceed.
## Supported props
### WizardProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| open | boolean | yes | | Controlled modal visibility. |
| title | string | yes | | Names the modal and its fixed header. |
| description | string \| undefined | no | | Supporting text below the title. |
| steps | Step[] | yes | | Ordered stages; the application owns their content and transitions. |
| current | number | yes | | Zero-based active stage; earlier stages read as complete. |
| onOpenChange | (open: boolean) => void | yes | | Called when the close action, Escape or native back requests dismissal. |
| panelWidth | number \| undefined | no | 620 | Maximum panel width; defaults to the hand-off's 620px and shrinks to its parent. |
| previous | { label: string; onPress: () => void; disabled?: boolean; } \| undefined | no | | Named previous stage action at the footer's start. |
| actions | ReactNode | no | | Application actions at the footer's end. |
| status | string \| undefined | no | | Save/progress status beside the title in the fixed header. |
| children | ReactNode | no | | |
| scrollBody | boolean \| undefined | no | true | Scroll the body (default true). Set false when a child, such as Chat, owns its scroll. |
| embedded | boolean \| undefined | no | false | Show the open wizard as a non-modal specimen, for a catalogue or a showcase: it keeps its look, its dialog role and its labelling, but does not take focus, trap Tab, claim `aria-modal`, or close on Escape or native back. It renders in place, in the flow of the page, as Dialog's contained form does, rather than presenting into an OverlayProvider. |
| assistant | boolean \| undefined | no | false | Lay the wizard out like a desktop assistant's window (the Claude desktop window, reference claude-desktop-chat): the steps as sidebar rows down the panel's full height, a 44 px top bar with the title and the status, and `previous` and `actions` in a bar under the conversation. The body is unpadded so an `assistant flush` Chat fills it; a scrolled body centers its content in the conversation's column. The description is not drawn. |
| testID | string \| undefined | no | | Prefix for modal, steps, body-scroll and footer test hooks. |
See [installation](https://canvas.nannier.com/ai/4.4.0/installation.md) for required peers and feature fallbacks.
---
# Canvas agent documentation
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
This generated source snapshot describes `@nannier/canvas` at repository package version `4.4.0`. Check the application's installed package version and its declarations before using an API. A source snapshot is not a claim that npm has published this version.
## Guides
- [installation](https://canvas.nannier.com/ai/4.4.0/installation.md)
- [theming](https://canvas.nannier.com/ai/4.4.0/theming.md)
- [layout](https://canvas.nannier.com/ai/4.4.0/layout.md)
- [overlays](https://canvas.nannier.com/ai/4.4.0/overlays.md)
- [Machine catalog](https://canvas.nannier.com/ai/4.4.0/catalog.json)
- [Content and source manifest](https://canvas.nannier.com/ai/4.4.0/manifest.json)
## Components
- [Accordion](https://canvas.nannier.com/ai/4.4.0/components/accordion.md): A vertically stacked group of disclosure rows: each `items` entry is a header (its `title` plus a chevron that rotates when the row opens) over a collapsible content panel.
- [ActionPanel](https://canvas.nannier.com/ai/4.4.0/components/action-panels.md): Section panel with headline, body text, and one action. Used to surface a single decision or call-to-action.
- [ActionSheet](https://canvas.nannier.com/ai/4.4.0/components/action-sheet.md): 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.
- [Alert](https://canvas.nannier.com/ai/4.4.0/components/alert.md): Inline notification banners: info, success, warning, and destructive, plus a full-width announcement bar. For a blocking confirmation prompt, see Alert Dialog.
- [AlertDialog](https://canvas.nannier.com/ai/4.4.0/components/alert-dialog.md): 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.
- [AppFrame](https://canvas.nannier.com/ai/4.4.0/components/app-frame.md): 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.
- [AreaChart](https://canvas.nannier.com/ai/4.4.0/components/area-chart.md): 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.
- [Autocomplete](https://canvas.nannier.com/ai/4.4.0/components/autocomplete.md): Text input + dropdown: searchable single-select.
- [Avatar](https://canvas.nannier.com/ai/4.4.0/components/avatar.md): 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.
- [Badge](https://canvas.nannier.com/ai/4.4.0/components/badge.md): 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.
- [BarList](https://canvas.nannier.com/ai/4.4.0/components/bar-list.md): 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.
- [Board](https://canvas.nannier.com/ai/4.4.0/components/board.md): 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.
- [BoxPlot](https://canvas.nannier.com/ai/4.4.0/components/box-plot.md): 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.
- [Breadcrumb](https://canvas.nannier.com/ai/4.4.0/components/breadcrumb.md): Hierarchical navigation showing where you are.
- [BulletChart](https://canvas.nannier.com/ai/4.4.0/components/bullet-chart.md): 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.
- [Button](https://canvas.nannier.com/ai/4.4.0/components/button.md): 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).
- [ButtonGroup](https://canvas.nannier.com/ai/4.4.0/components/button-group.md): Segmented controls, split buttons, attached groups.
- [Calendar](https://canvas.nannier.com/ai/4.4.0/components/calendar.md): 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.
- [CandlestickChart](https://canvas.nannier.com/ai/4.4.0/components/candlestick-chart.md): 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.
- [Card](https://canvas.nannier.com/ai/4.4.0/components/card.md): 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.
- [Carousel](https://canvas.nannier.com/ai/4.4.0/components/carousel.md): A horizontally paged slide viewer: swipe (or use the prev/next arrows and the dot indicators) to move one slide at a time.
- [Chart](https://canvas.nannier.com/ai/4.4.0/components/chart.md): A single- or multi-series bar chart: vertical columns (or horizontal rows) sized against the axis max, with per-category values and labels.
- [Chat](https://canvas.nannier.com/ai/4.4.0/components/chat.md): 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.
- [Checkbox](https://canvas.nannier.com/ai/4.4.0/components/checkbox.md): Multi-select option, single yes/no, grouped lists.
- [Chip](https://canvas.nannier.com/ai/4.4.0/components/chip.md): 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`.
- [CodeBlock](https://canvas.nannier.com/ai/4.4.0/components/code-block.md): Syntax-highlighted code display with clipboard copy, horizontal scrolling, line emphasis, diff rendering, collapsible folding, and tabbed alternatives.
- [Collapsible](https://canvas.nannier.com/ai/4.4.0/components/collapsible.md): A single disclosure: one header (its `title`, or a custom `trigger`, plus a chevron that rotates when open) over one collapsible content panel.
- [Command](https://canvas.nannier.com/ai/4.4.0/components/command.md): Cmd+K search: navigation, actions, recent items.
- [ComposedChart](https://canvas.nannier.com/ai/4.4.0/components/composed-chart.md): 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).
- [Container](https://canvas.nannier.com/ai/4.4.0/components/container.md): 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.
- [DashboardGrid](https://canvas.nannier.com/ai/4.4.0/components/dashboard-grid.md): 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.
- [DataTable](https://canvas.nannier.com/ai/4.4.0/components/data-table.md): A data table rendered from column and row data, with sorting, row selection, pagination, and the loading and empty states built in.
- [DepthChart](https://canvas.nannier.com/ai/4.4.0/components/depth-chart.md): 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.
- [DescriptionList](https://canvas.nannier.com/ai/4.4.0/components/description-lists.md): 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.
- [Dialog](https://canvas.nannier.com/ai/4.4.0/components/dialog.md): 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.
- [Divider](https://canvas.nannier.com/ai/4.4.0/components/divider.md): Horizontal, vertical, with label, with action.
- [Drag & drop](https://canvas.nannier.com/ai/4.4.0/components/drag-drop.md): 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.
- [Drawer](https://canvas.nannier.com/ai/4.4.0/components/drawer.md): 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.
- [Dropdown](https://canvas.nannier.com/ai/4.4.0/components/dropdown.md): Floating menus triggered by a button: actions, options, navigation.
- [Emblem](https://canvas.nannier.com/ai/4.4.0/components/emblem.md): 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.
- [EmptyState](https://canvas.nannier.com/ai/4.4.0/components/empty-state.md): Centered, calm, never blame the user. Always tell them what could be here, and ideally how to get there.
- [Feed](https://canvas.nannier.com/ai/4.4.0/components/feeds.md): Vertical activity streams with icons and timestamps. Used for audit logs, change history, and notification lists.
- [Field](https://canvas.nannier.com/ai/4.4.0/components/field.md): 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.
- [FilterPanel](https://canvas.nannier.com/ai/4.4.0/components/filter-panel.md): Sidebar filter rail: grouped checkbox options under mono group headings, each with its count, and a "Clear N" action in the header while any filter is on (a plain accent text action). On the web an option's label is the 400 14px row label, 10px from its box.
- [FlowEditor](https://canvas.nannier.com/ai/4.4.0/components/flow-editor.md): A node-graph editor: cards placed freely on a pannable, zoomable canvas, joined by directed edges. Use it to let people shape a process, such as a delivery workflow whose edges carry conditions. The application owns the graph; FlowEditor draws the `nodes` and `edges` it is given and reports what the user asked for (`onMoveNode`, `onConnect`, `onAddNode`, `onDelete`, `onSelectionChange`).
- [Form](https://canvas.nannier.com/ai/4.4.0/components/form.md): Stitch your own fields inside it; Form adds the stacked or two-column rhythm, titled sections, the submit and cancel actions row, and Enter to submit.
- [FunnelChart](https://canvas.nannier.com/ai/4.4.0/components/funnel-chart.md): Stage-by-stage conversion bars: one centered row per stage, labelled on the left and valued on the right.
- [Gauge](https://canvas.nannier.com/ai/4.4.0/components/gauge.md): A semicircular dial: a 180 degree top arc (muted track plus a tone-colored value arc) for a 0-100 value, the percent readout in the open center of the semicircle, and an optional label below the graphic.
- [GeoMap](https://canvas.nannier.com/ai/4.4.0/components/geo-map.md): A world map with a bubble per place, its area proportional to its count, over coastlines and country borders precomputed at build time. Optionally zoomable, merging crowded places into one bubble that splits as you zoom in.
- [Grid](https://canvas.nannier.com/ai/4.4.0/components/grid.md): The container-measured auto-fit tile grid. `minTileWidth` sets the floor (default 240): the grid fits as many equal-width columns of at least that width as its own measured container allows, and re-fits as the container changes, so the same grid renders three-up on a desktop, two-up on a tablet, and a single column on a phone with no breakpoint at the call site.
- [GridList](https://canvas.nannier.com/ai/4.4.0/components/grid-lists.md): Tiled card grids for people directories, item collections, and image galleries.
- [Heatmap](https://canvas.nannier.com/ai/4.4.0/components/heatmap.md): Density cells whose fill encodes each value, in seven-row columns inside the chart frame, titled by `label` (or `caption`) and closed by a less-to-more legend; `plain` strips the frame for nesting inside a card. Pass `calendar` for a GitHub-style contribution graph: each cell is a day, and a press or hover inspects its count and date. The calendar layout IS the kit's activity calendar, so a contribution graph needs no separate component.
- [Histogram](https://canvas.nannier.com/ai/4.4.0/components/histogram.md): An auto-binned frequency distribution: pass raw sample `values` and the chart bins them into nice-edged uniform buckets (Sturges' rule by default, `bins` to override) and draws contiguous bars on a numeric axis. Press or scrub a bar to inspect its range and count. The accessible name lists every bin with its bounds and tally.
- [Icon](https://canvas.nannier.com/ai/4.4.0/components/icon.md): Lucide-style outline. 1.75 stroke width, rounded caps. The stroke defaults to the theme foreground token and tracks light and dark automatically; pick other hues with the semantic color booleans (`primary`, `destructive`, `success`, `warning`, `muted`, `subtle`; that order is the precedence). `muted` is the third ink (`subtle-foreground`) and `subtle` the second (`muted-foreground`), the names Typography gives the same two inks. Inside a control that owns a label ink (a Button's `iconLeft` and `iconRight` slots) a glyph with no tone takes that ink instead, the way the hand-off's icons inherit `currentColor`: the primary's contrast ink on a primary button, the muted ink on a quiet one. The precedence is the explicit tone, then the host's ink, then the foreground, so a toned glyph keeps its tone anywhere.
- [Image](https://canvas.nannier.com/ai/4.4.0/components/image.md): Displays a local or remote image. Set the `source` (`{ uri }`) and a size, then choose how it fills its box with a fit prop: `cover` (the default) fills the box and crops the overflow, while `contain` fits the whole image inside and letterboxes the spare space.
- [Input](https://canvas.nannier.com/ai/4.4.0/components/input.md): The Input component is a React Native text field with semantic boolean props (`error`, `small`, `large`, `compact`, `disabled`), plus prefix/suffix addons, overlaid icons (`leadingIcon` / `trailingIcon` with any Canvas glyph as `icon`), a `passwordToggle` eye for a `secureTextEntry` value, a `clearable` clear button, and a `shortcut` keycap. Input is single-line; for multi-line entry use the dedicated Textarea.
- [InputOTP](https://canvas.nannier.com/ai/4.4.0/components/input-otp.md): A segmented one-time-code field: `length` cells display the typed characters while one underlying text input captures the keystrokes, so native SMS autofill, the one-time-code keyboard suggestion, and paste all flow into a single value.
- [Kbd](https://canvas.nannier.com/ai/4.4.0/components/kbd.md): Keyboard shortcut indicator: a raised 24 px key cap with a mono label. `keys` composes a whole shortcut, one cap per key; `sequence` says "then" between the caps of keys pressed one after another. A shortcut reads as one accessible name ("⌘+K", "G then C"). `comfortable` pads the cap 3 px above and below its label and 8 px at the sides (25 px tall) for a key set beside what it does, as a reference's keyboard table lists it; omit it for the 24 px cap with 6 px sides.
- [LineChart](https://canvas.nannier.com/ai/4.4.0/components/line-chart.md): Categorical-x series lines with nice y ticks, gridlines, and a legend for multiple series.
- [Listbox](https://canvas.nannier.com/ai/4.4.0/components/listbox.md): A custom select whose options stay on screen: single or multi-select, an optional detail line under each option, and a checkmark on the chosen items. Reach for it when the options need rich rows; prefer `Select` for a simple short list.
- [MediaObject](https://canvas.nannier.com/ai/4.4.0/components/media-objects.md): Image or icon paired with text content. The fundamental building block for list items, notifications, and comment layouts.
- [MetricBreakdown](https://canvas.nannier.com/ai/4.4.0/components/metric-breakdown.md): The decomposed-metric dashboard card: headline value, tone-aware rate, Sparkline trend, per-category share rows, and a chip footer.
- [Navbar](https://canvas.nannier.com/ai/4.4.0/components/navbars.md): Topbars with a brand, navigation links, and an action button. Used as the primary app-level navigation.
- [Pagination](https://canvas.nannier.com/ai/4.4.0/components/pagination.md): Page-of-N navigation for tables and lists.
- [PhoneInput](https://canvas.nannier.com/ai/4.4.0/components/phone-input.md): A phone number field: the Input's box with a country segment at its start (the chosen country and a caret, which open a list of countries with their names and dial codes) and that country's dial code inline before the number. On the web the segment is a small chip inside the field naming the country by its ISO code, and the dial code and the number are set in the mono face, and in the country list each row is a 16px flag 10px from the name with the dial code a 12px mono note at its end; on iOS and Android it is the country's flag.
- [PieChart](https://canvas.nannier.com/ai/4.4.0/components/pie-chart.md): Proportional composition as arc slices in the `chart-1`..`chart-8` token colors, with a percent legend. `donut` centers the total (or the selected slice while inspecting); pressing a slice dims the others.
- [Popover](https://canvas.nannier.com/ai/4.4.0/components/popover.md): Floating panel with a title, supporting text, rich content such as form fields, and a single follow-up action, anchored to its trigger. Pass children to host custom content in the panel body, between the description and the action.
- [Pressable](https://canvas.nannier.com/ai/4.4.0/components/pressable.md): The touchable primitive: wraps content, fires `onPress`, and exposes the press state to its style for feedback.
- [Progress](https://canvas.nannier.com/ai/4.4.0/components/progress.md): Determinate and indeterminate progress bars: a rounded bar that reports how far a task has gotten, or that work is underway.
- [ProgressRing](https://canvas.nannier.com/ai/4.4.0/components/progress-ring.md): A full-circle completion ring, the Gauge's sibling: a muted track ring, a tone-colored value arc revealed clockwise from 12 o'clock with rounded caps, the whole-percent readout centered inside, and an optional label below the graphic. The same 0-100 value contract, tone axis, and rounding rules as Gauge, in the circular anatomy that reads as "completion" rather than "level".
- [QRCode](https://canvas.nannier.com/ai/4.4.0/components/qrcode.md): Encodes a string as a scannable QR code. Built on react-native-svg, so it renders the same on iOS, Android, and the web. The code stays a fixed dark-on-white card so any camera can read it, whatever the app theme.
- [RadarChart](https://canvas.nannier.com/ai/4.4.0/components/radar-chart.md): A polygonal multi-axis comparison: concentric rings at nice tick fractions, a spoke per axis, and one closed polygon per series (ramp stroke over a soft matching wash), with the spoke labels just beyond the outer ring. Values align to `axes` by index and the accessible name folds every axis and value; press-to-inspect is deferred scope for this chart.
- [RadialBarChart](https://canvas.nannier.com/ai/4.4.0/components/radial-bar-chart.md): Concentric arc rings, one per category, outermost first: each pairs a muted full-circle track with a ramp-colored value arc revealed clockwise from 12 o'clock, and a column legend carries the formatted values. Every ring sweeps against the same `max` (the largest entry by default), so the chart compares attainment, not shares. Press a ring to select it (the others dim); press elsewhere to clear.
- [Radio](https://canvas.nannier.com/ai/4.4.0/components/radio.md): Single-pick selection: stacked, inline, card-style.
- [RangeAreaChart](https://canvas.nannier.com/ai/4.4.0/components/range-area-chart.md): A min/max envelope: a translucent band between each label's `low` and `high`, an optional solid `mid` line through it, and the standard scrub-to-inspect flag showing High, Mid, and Low. The y domain hugs the data rather than anchoring at zero, since an envelope is a range idiom, not a magnitude bar. Forecast bands, error envelopes, and daily temperature ranges all read this way.
- [Reveal](https://canvas.nannier.com/ai/4.4.0/components/reveal.md): An entrance for page content: what it wraps starts slightly offset and transparent, then travels into place and fades in when it reaches the viewport. It fires once and stays, so content that has arrived never leaves again.
- [Row & Column](https://canvas.nannier.com/ai/4.4.0/components/row-column.md): The layout primitives. `Row` lays children out horizontally, `Column` vertically. Both own arrangement through semantic boolean axes, a gap scale (`flush` / `tight` / `snug` / `cozy` / `relaxed` / `loose`), main-axis distribution (`center`, `between`, …), cross-axis alignment (`alignCenter`, `baseline`, …), and `wrap` / `fill` / `grow` / `shrink`, so a call site never hand-rolls `flexDirection`, `gap`, or `alignItems`.
- [RowMenu](https://canvas.nannier.com/ai/4.4.0/components/row-menu.md): Vertical action menu items and navigation links.
- [ScatterPlot](https://canvas.nannier.com/ai/4.4.0/components/scatter-plot.md): Numeric x/y point clouds with nice ticks on both axes and horizontal gridlines. Multi-series clouds use the series tokens; pressing near a point rings it and flags its coordinates.
- [ScrollView](https://canvas.nannier.com/ai/4.4.0/components/scroll-view.md): A scrollable container for content larger than its bounds: vertical by default, `horizontal` for a row.
- [Select](https://canvas.nannier.com/ai/4.4.0/components/select.md): A single-choice dropdown field: a trigger showing the chosen value that opens a panel of options, styled to match Canvas inputs.
- [ServiceHealthList](https://canvas.nannier.com/ai/4.4.0/components/service-health-list.md): Per-service status rows: a status dot (down > degraded > operational), the service name, an optional right-aligned `detail` such as an uptime percentage, and, when the item carries `periods`, an inline strip of its latest 14 periods beside the detail.
- [Sidebar](https://canvas.nannier.com/ai/4.4.0/components/sidebar.md): App navigation rail with collapsible sections and active highlighting. The sidebar on the left of this page is a thin adapter over this very `Sidebar` component: it feeds the docs' nav tree in and gets the same collapse, accordion, and active-highlight behavior back.
- [Skeleton](https://canvas.nannier.com/ai/4.4.0/components/skeleton.md): Placeholders for loading content.
- [Slider](https://canvas.nannier.com/ai/4.4.0/components/slider.md): Drag (or tap) along a track to pick a value in a range; the keyboard and screen readers step it too. Controlled by `value`, bounded by `min` / `max`, and snapped to `step`.
- [Sparkline](https://canvas.nannier.com/ai/4.4.0/components/sparkline.md): A compact trend strip: a row of thin bars whose heights track a series of values, with the series in a soft wash of the tone and the latest bar in the full accent. Pass `values` and it sizes each bar against the series max and paints the tone, so no call site hand-composes a row of `flexGrow` + `height` + `backgroundColor` Views to draw an inline trend on a stat card or dashboard.
- [Spinner](https://canvas.nannier.com/ai/4.4.0/components/spinner.md): Animated loading spinner in three sizes.
- [StackedBar](https://canvas.nannier.com/ai/4.4.0/components/stacked-bar.md): One proportional horizontal bar split into colored segments (the `chart-1`..`chart-8` tokens in fixed order), with a legend carrying each segment's label and share.
- [StackedList](https://canvas.nannier.com/ai/4.4.0/components/stacked-lists.md): Vertical lists with avatar, two-line items, and trailing metadata. Used for contacts, activity feeds, and data previews.
- [Stats](https://canvas.nannier.com/ai/4.4.0/components/stats.md): Single value, grouped row, with sparkline, with comparison. Used for dashboards and overview pages.
- [Stepper](https://canvas.nannier.com/ai/4.4.0/components/stepper.md): Increment, decrement, or type a number in a bounded range. A − button, an editable numeric center field, and a + button, controlled by `value` and clamped to `min` / `max` by `step`. This is the ± numeric control (the iOS UIStepper idiom); for a multi-step progress indicator see Steps.
- [Steps](https://canvas.nannier.com/ai/4.4.0/components/steps.md): Multi-step progress indicators: horizontal, vertical, with progress.
- [Swatch](https://canvas.nannier.com/ai/4.4.0/components/swatch.md): A color sample: a filled rounded block with the token's name and value beneath it, the anatomy a design-system color sheet repeats down a page. Swatch owns the whole lockup, so a sheet never sets a bare block next to a hand-built text column and drifts row to row.
- [Switch](https://canvas.nannier.com/ai/4.4.0/components/switch.md): On / off switch, isolated or grouped in a settings list.
- [TabBar](https://canvas.nannier.com/ai/4.4.0/components/tab-bar.md): Bottom app navigation: a row of equal-width destinations, each an icon over a short label, with exactly one active. The mobile idiom (the iOS 26 floating tab bar / Material 3 navigation bar), rendered through the glass functional layer.
- [Tabs](https://canvas.nannier.com/ai/4.4.0/components/tabs.md): Underline, pill, vertical, with badges.
- [Text](https://canvas.nannier.com/ai/4.4.0/components/text.md): Renders text. In React Native every string must live inside a Text element, so you cannot put a bare string in a View. For type scale and tone in app code, reach for `` (e.g. ``, ``, ``), which owns the kit's roles and colors; use the raw Text primitive for the primitive-level cases it does not cover: `numberOfLines` truncation and nested inline runs.
- [TextInput](https://canvas.nannier.com/ai/4.4.0/components/text-input.md): Single-line (or multiline) text entry. Control it with `value` + `onChangeText`, and style the box with the usual View style props. Common props: `placeholder`, `secureTextEntry` (passwords), `keyboardType`, and `multiline`.
- [Textarea](https://canvas.nannier.com/ai/4.4.0/components/textarea.md): Multi-line input, with character count, with toolbar.
- [Toast](https://canvas.nannier.com/ai/4.4.0/components/toast.md): A transient notification card. Render a `` directly, or drive them imperatively: mount a `` near your app root and call `toast(...)` from the `useToast()` hook to enqueue auto-dismissing toasts that stack over the app.
- [Tooltip](https://canvas.nannier.com/ai/4.4.0/components/tooltip.md): Small floating helper text on hover or focus.
- [Treemap](https://canvas.nannier.com/ai/4.4.0/components/treemap.md): Squarified value tiles: each datum becomes a ramp-colored rectangle whose area is proportional to its value, packed largest-first with near-square aspect ratios, with the label and formatted value rendered inside only when the tile fits them.
- [Typography](https://canvas.nannier.com/ai/4.4.0/components/typography.md): Type scale classes for headings, body text, and helper styles.
- [UptimeBar](https://canvas.nannier.com/ai/4.4.0/components/uptime-bar.md): The statuspage strip: per-period status pills (operational, degraded, down, unknown) with a summary caption and edge labels.
- [Video](https://canvas.nannier.com/ai/4.4.0/components/video.md): Plays a clip with an optional poster: tap to play inline, or full transport controls (the platform's own on iOS and Android, the kit's bar on the web), with boolean fit, `autoplay`, `loop` and `muted` props.
- [View](https://canvas.nannier.com/ai/4.4.0/components/view.md): The layout primitive: a flex container that runs identically on iOS, Android, and the web. A View is a column by default.
- [WaterfallChart](https://canvas.nannier.com/ai/4.4.0/components/waterfall-chart.md): The running-total bridge: each step floats from the running total by its signed `value`, and a `total` step snapshots the running total as an absolute bar from zero.
- [Wizard](https://canvas.nannier.com/ai/4.4.0/components/wizard.md): A modal workflow with a fixed step sidebar, a title and status header, and footer actions around a bounded, scrolling body. The application owns stage content, validation, saving and transitions; Wizard composes the existing Dialog, Steps and Button controls.
---
# Installation and runtime support
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Install Canvas into an existing Expo, React Native, or React Native Web application.
For an Expo app, let Expo select the SVG version compatible with its SDK:
```bash
npx expo install @nannier/canvas react-native-svg
```
Keep the React and React Native versions supplied by your Expo SDK. For web support,
also install Expo's web dependencies if the app does not already have them:
```bash
npx expo install react-dom react-native-web
```
In an existing bare React Native or React Native Web app:
```bash
npm install @nannier/canvas react-native-svg
```
Canvas ships compiled (no Canvas build step in your app). Its **required** peer
dependencies are React 18 or later, React Native 0.74 or later, and
react-native-svg 15.8.0 or later. Use a React version compatible with your React
Native version. Web applications also need React DOM, React Native Web, and their
bundler's RNW configuration; see [Platforms](https://canvas.nannier.com/ai/4.4.0/installation.md#platforms).
### Package and documentation versions
The published package is `@nannier/canvas`; use that name for both installation
and imports. The [packaged recipe onboarding](https://canvas.nannier.com/agents.md#start-a-project-with-an-available-cli) explains how to create an app when your installed release provides the CLI. Check the registry and your installed
version when following an example:
```bash
npm view @nannier/canvas version
npm ls @nannier/canvas
```
The repository's documentation describes its current source revision. A design
preview label or a change on `main` does not establish what npm has published.
For an installed release, its package declarations and changelog describe the
available API. The npm package and its packaged recipes are MIT-licensed. The private repository and its development-only starter retain separate [source terms](https://canvas.nannier.com/agents.md#package-and-source-terms).
### Optional peers
Install the optional peers needed by your app's features. The package remains usable without them, with the following fallbacks:
| Package | Install only if | Without it |
| --- | --- | --- |
| `react-native-qrcode-svg` | you render QR codes with `QRCode` | the accessible, sized frame remains empty and warns once in development |
| `expo-glass-effect` | you want native Liquid Glass on supported iOS 26+ devices | forced glass uses `expo-blur` when available, otherwise the skin's solid fill; automatic surface mode remains solid |
| `expo-blur` | you want static frost on iOS, including content beneath iOS 26 Liquid Glass, or supported legacy Android frost | surfaces use their solid fill when no other material is available; browser frost/lenses, native Liquid Glass and the Android capture module remain independent |
| `expo-modules-core` | never by hand: every Expo app already provides it | Canvas's Android capture module is not linked (a React Native app without Expo), and surfaces use another supported material or the complete solid appearance |
| `expo-video` | you play clips with `Video` | the frame keeps its size and poster, labeled, and warns once in development |
| `expo-clipboard` | you want `CodeBlock` to copy text on native | web can use `navigator.clipboard`; native copying needs a supplied `onCopy` handler |
| `react-native-safe-area-context` | you want safe-area insets in Canvas shells, with your app's `SafeAreaProvider` | safe-area wrappers render as plain views without insets |
```bash
# add any subset you actually use
npm install react-native-qrcode-svg expo-video expo-glass-effect expo-blur expo-clipboard react-native-safe-area-context
```
### Android capture module
Canvas includes a native Android module (`android/`) that captures the backdrop behind
glass surfaces and frosts it on Android 12 and later. Expo links it into your app's
Android build automatically: rebuild the native app after installing or upgrading
Canvas (Expo Go cannot load it). It is built and verified with Expo SDK 57 and React
Native 0.86. An app on an older SDK, or one that does not want it, leaves the module out
of its native build with one line in its `package.json`:
```json
{ "expo": { "autolinking": { "exclude": ["@nannier/canvas"] } } }
```
Excluding it removes only this module, the only native code Canvas ships. Surfaces then
use `expo-blur`'s frost or their complete solid appearance, exactly as on a device where
the module is unavailable. iOS and the web are unaffected either way.
## Quick start
Wrap your app once in `ThemeProvider`, then compose components imported from `@nannier/canvas`. The provider supplies the active color scheme, surface, and token map to every component below it.
```jsx
import { ThemeProvider, Card, CardHeader, CardTitle, CardContent, Button } from "@nannier/canvas";
export default function App() {
return (
Welcome to Canvas
console.log("saved")}>
Save
);
}
```
That exact tree renders natively on iOS and Android and, through React Native Web, in the browser. There is no separate web component set to learn.
For a runnable application, follow the [packaged recipe onboarding](https://canvas.nannier.com/agents.md#start-a-project-with-an-available-cli). Check CLI availability in your installed release, then choose settings, dashboard or booking. Packaged recipes follow the npm package's LICENSE.
## Platforms
Canvas targets all three platforms from one install. The only thing that changes is which peers your app already provides.
- **Expo** apps use the required peers and any optional modules needed by their features. The docs app in this repository is a working Expo example, including native glass and blur modules.
- **Bare React Native** works the same way once the required peers are installed and linked (`react-native-svg` needs the usual autolinking / pod install). Add the optional peers if you want QRCode or full-fidelity glass.
- **Web via React Native Web** needs one bundler step: install `react-native-web` and alias `react-native` to `react-native-web`, exactly as any RNW project does. Web bundlers use the default `dist/index.js` entry. On the web you can also flip glass at runtime with the exported `setSurface("glass")` / `setSurface("solid")` DOM helper.
Metro selects `dist/native/index.js` through the `react-native` export condition
or legacy field. That compiled output preserves Metro's iOS and Android module
selection, including native material helpers. No custom Canvas resolver is needed
in consumer apps. Public declarations and the web ESM build remain in `dist/`.
`bun run dev` watches both outputs and syncs them to local consumer overlays.
`bun run verify-native-consumer` packs the build and bundles an isolated React
Native consumer for both platforms with all optional peers omitted. CI also runs
this check on the exact sealed tarball before it can be published.
Use the declarations installed with your dependency as the final API authority; this snapshot follows the source package version, not a registry lookup.
---
# Layout and responsive bounds
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Parents provide bounds. Use `Container` measure steps, `Row` and `Column` spacing/alignment booleans, `span` for twelfths, and `Grid` for responsive tiles. A field fills its parent; a button hugs its contents unless `block` or a supported measure step changes it. Do not set fixed component widths or invent string variant props. Measure the component's own container for internal reflow; viewport breakpoints are for application chrome.
- [container](https://canvas.nannier.com/ai/4.4.0/components/container.md)
- [row-column](https://canvas.nannier.com/ai/4.4.0/components/row-column.md)
- [grid](https://canvas.nannier.com/ai/4.4.0/components/grid.md)
### MeasureProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| 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. |
---
# Overlay hosting
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Mount an `OverlayProvider` inside `ThemeProvider`, outside scrolling page content,
and let it fill the app window. Dropdowns, selects, autocompletes and popovers then
share measured viewport bounds, move above their triggers when needed, and scroll
long content within the available height. Opening a hosted menu preserves the
trigger's native view hierarchy and focus.
Nested providers can scope where cards are placed without restricting them to the
height of a short form. They inherit the root's visible bounds. A card that closes
on an outside tap still paints in the window's outermost provider, at the place
its nearest provider gives it, so a tap anywhere off it closes it, and the page
under it takes no touches until it closes. Use
`` for a deliberately bounded nested panel, whose size
must come from its viewport rather than its content.
Inside a custom React Native `Modal`, wrap the content in `GlassModalBlurTarget`,
then ``, in that order:
```jsx
{content}
```
The `separateWindow` provider gives the Modal's own cards a host in its window: its
measurements must not inherit bounds from a different native window, and without
it a card that closes on an outside tap paints in the main window's outermost
provider, behind the `Modal`. `GlassModalBlurTarget` hands the Modal's glass the
root provider's page on Android, so it frosts the page behind the Modal (see
Theming below); it renders no view and does nothing on iOS and the web. It belongs
only inside a `Modal`: in the main window, a react-native-screens or expo-router
modal screen included, its surfaces keep their solid skin. Canvas `Drawer` supplies
both automatically, and `ActionSheet` its bridge.
When page content scrolls under a header, declare the header's measured height as
`viewportInsets={{ top: headerHeight }}` on the root or a nested `viewport` host.
The optional `bottom` inset similarly reserves an overlapping footer. Insets are
relative to that host's own box and are intersected with the keyboard boundary;
changing them updates open cards without remounting their content.
Android keyboard avoidance supports full-screen edge-to-edge windows through
native keyboard events, and legacy resize-mode windows through their measured
root bounds. Legacy pan/nothing modes and multi-window coordinates are not
supported. iOS uses the keyboard frame; at the RN 0.74 support floor,
this requires a full-screen window because that RN version reports screen
coordinates. Current RN converts the frame to window coordinates. Web fitting
uses the layout viewport; RNW does not expose the mobile keyboard's visual
viewport. Clipping ancestors that are not viewport hosts do not contribute
additional bounds. With no provider, the legacy inline overlay remains available,
but has no viewport fitting or outside-tap backdrop.
## Dialog trigger focus
Keep the trigger mounted and focusable while the dialog opens so the focus manager can capture the return target. Avoid disabling that trigger on the same render that sets `open`; use the modal backdrop and focus isolation to prevent background interaction. On close, return focus to the trigger.
Component behavior and controlled/uncontrolled props: [Dialog](https://canvas.nannier.com/ai/4.4.0/components/dialog.md), [Popover](https://canvas.nannier.com/ai/4.4.0/components/popover.md), [Select](https://canvas.nannier.com/ai/4.4.0/components/select.md).
---
# Theming and semantic props
Package: @nannier/canvas
Version: 4.4.0
Status: source-snapshot (not proof of an npm release)
Source SHA-256: 35b0556b3cf4787d8b9cdf8a7e93b47c770a9726957905f5b8d3a812e5fea783
Wrap the application in `ThemeProvider`. Use flat boolean styling props; choose at most one prop per visual axis, such as `primary`, `large`, and `loading`.
The default accent is violet. The `cyan`, `mint`, `rose`, and `amber` palettes apply in both light and dark schemes. Omitted scheme follows the OS; omitted surface selects the platform default. Material capability and accessibility preferences can select a solid fallback.
```tsx
import { ThemeProvider, OverlayProvider, Button } from "@nannier/canvas";
export default function App() {
return Save ;
}
```
### ThemeProviderProps
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| dark | boolean \| undefined | no | false | Force the dark color scheme. |
| light | boolean \| undefined | no | false | Force the light color scheme. |
| scheme | ColorScheme \| undefined | no | | Legacy value form of the scheme axis ("light" \| "dark"), kept for back-compat and for config-driven code that already holds a `ColorScheme` value (a stored preference, an hook). The boolean axis above wins when both are passed. |
| ssrScheme | ColorScheme \| undefined | no | | The scheme the SERVER rendered, for SSR/SSG apps (Next.js and the like) whose client scheme can differ from it (a stored preference, the OS). Canvas resolves colors in JS and serializes them into the server HTML as literal inline styles, so when the hydration render disagrees with that HTML, React logs a mismatch and keeps the server's colors on any element that never re-renders again: components stay stuck in the server's scheme. With `ssrScheme` set, the provider renders it on the server AND for the hydration render (matching the server HTML exactly), then applies the requested scheme (the boolean axis or the legacy `scheme` prop) right after mount; that switch re-renders every consumer, which writes the real colors to the DOM. Pass the same value the server resolves (e.g. the next-themes `defaultTheme`). Omit in client-only apps and on native. |
| violet | boolean \| undefined | no | false | Paint the accent violet (hue 285), the default. |
| cyan | boolean \| undefined | no | false | Paint the accent cyan (hue 205). |
| mint | boolean \| undefined | no | false | Paint the accent mint (hue 165). |
| rose | boolean \| undefined | no | false | Paint the accent rose (hue 20). |
| amber | boolean \| undefined | no | false | Paint the accent amber (hue 75). |
| ssrPalette | Palette \| undefined | no | | The palette the SERVER rendered, for SSR/SSG apps whose client palette can differ from it (a stored preference: `getPalette()` reads one on the web). The `ssrScheme` contract applied to the palette axis: the server render and the hydration render paint this palette, matching the server HTML exactly, and the requested one (`cyan`, `mint` and so on) applies right after mount. Omit in client-only apps and on native. |
| ssrBreakpoint | BreakpointKey \| "base" \| undefined | no | | The breakpoint bucket the SERVER should assume, for SSR/SSG apps (the `ssrScheme` contract applied to the viewport axis). The server cannot measure a window, so `useBreakpoint`/`useResponsive`/`useFormFactor` resolve to the desktop `base` there by default; an app that knows it is serving a narrow client (UA hints) passes the bucket the server should render instead (e.g. "sm" for phones). The server render and the hydration render use it, then the real measured bucket re-renders every consumer right after hydration. Omit in client-only apps and on native. |
| glass | boolean \| undefined | no | false | Request role-appropriate glass: stable content material and functional Liquid Glass where supported. Semantic tokens remain opaque. |
| solid | boolean \| undefined | no | false | Request complete opaque surfaces without glass effects, even on iOS 26+. |
| surface | Surface \| undefined | no | | Legacy value form of the surface axis ("solid" \| "glass"), kept for back-compat and for config-driven code that already holds a `Surface` value. The boolean axis above wins when both are passed. |
| tokens | ThemeTokenOverrides \| undefined | no | | Brand token overrides, merged over the active palette's base tokens so a consumer can rebrand the kit (e.g. `tokens={{ primary: "#7c3aed" }}`) without forking the token files. Pass a flat `Partial` to apply the same overrides to both schemes, or `{ light, dark }` to override each scheme separately. These are the SEMANTIC tokens only; the glass material carries its own fill (`glassByScheme`) and is never rewritten by a rebrand, so the two are independent. Pass a stable reference (a module constant or a memoized object); an inline literal re-creates the theme value on every render. Unlike `fonts`, a nested ThemeProvider does not inherit a parent's `tokens`: a nested provider is how a subtree shows its own palette or brand, so it paints its palette unbranded unless it is given overrides of its own. Pass the same constant again to carry a rebrand into a nested provider. |
| fonts | ThemeFonts \| undefined | no | | The typefaces the app registered, so the kit can paint its labels in the brand faces (`typeface.sans` is Instrument Sans, `typeface.display` Bricolage Grotesque, `typeface.mono` JetBrains Mono). Each entry is either one family name that carries every weight (a variable font, an OS-installed family) or a map from weight to the face registered for that weight (expo-google-fonts style: `{ "400": "InstrumentSans_400Regular", "500": "InstrumentSans_500Medium" }`). Pass it once, on the app's root provider: the faces are what the app loaded, not a per-subtree choice, so a nested ThemeProvider that omits `fonts` keeps the nearest parent provider's, and one that passes it uses its own map in place of the parent's (the roles are not merged; a stable empty map, `{}`, returns that subtree to the system face). Omit it on the root and the kit renders in the platform's system face, as it always has. Pass a stable reference (a module constant); an inline literal re-creates the theme value on every render. |
| children | ReactNode | yes | | |