# helixui — full documentation Source: https://helixui.ai _helixui.llms-full/v1 · generated 2026-06-18 · build `444df7f` · canonical at https://helixui.ai_ This document is generated. It contains the helixui DESIGN.md, the DESIGN.md format spec, every documentation page, and the resolved design-token manifest — concatenated for use as one-shot context for an AI tool. --- # DESIGN.md URL: https://helixui.ai/DESIGN.md --- # This frontmatter is the canonical Google DESIGN.md spec (Apache-2.0, alpha). # Every token block below is *derived* from helixui's `wildtype` DNA via the # 8-gene express() function. Edit the DNA in `extensions.helixui` and regenerate # (`pnpm build:design-md` or `node scripts/build-helixui-design-md.mjs`); the # canonical blocks update consistently. version: alpha name: helixui description: "An AI-friendly React design system. Tokens are JSON in W3C DTCG; every component is one self-describing spec.md; static manifests ship with every build so an LLM can answer \"what tokens exist?\" or \"how do I use Button?\" from a single fetch." # === DERIVED FROM extensions.helixui.basePreset (wildtype) ===================== # accent=blue chroma=standard radius=standard density=comfortable # typography=sans surface=elevated motion=standard lightness=auto colors: brand-50: "oklch(96% 0.045 234)" brand-100: "oklch(93% 0.0788 234)" brand-200: "oklch(87% 0.1125 234)" brand-300: "oklch(78% 0.1462 234)" brand-400: "oklch(67% 0.1744 234)" brand-500: "oklch(56% 0.18 234)" brand-600: "oklch(47% 0.1744 234)" brand-700: "oklch(38% 0.1462 234)" brand-800: "oklch(28% 0.1125 234)" brand-900: "oklch(18% 0.0788 234)" neutral-50: "#f8fafc" neutral-100: "#f1f5f9" neutral-200: "#e2e8f0" neutral-300: "#cbd5e1" neutral-400: "#94a3b8" neutral-500: "#64748b" neutral-600: "#475569" neutral-700: "#334155" neutral-800: "#1e293b" neutral-900: "#0f172a" danger-50: "#fef2f2" danger-500: "#ef4444" danger-600: "#dc2626" danger-700: "#b91c1c" success-50: "#f0fdf4" success-500: "#22c55e" success-600: "#16a34a" success-700: "#15803d" warning-50: "#fffbeb" warning-500: "#f59e0b" warning-600: "#d97706" warning-700: "#b45309" primary: "{colors.brand-500}" secondary: "{colors.neutral-700}" tertiary: "{colors.brand-600}" background: "{colors.neutral-50}" surface: "#ffffff" text: "{colors.neutral-900}" muted: "{colors.neutral-500}" border: "{colors.neutral-200}" danger: "{colors.danger-500}" success: "{colors.success-500}" warning: "{colors.warning-500}" typography: display: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 36.00px, fontWeight: 700, lineHeight: 1.2 } h1: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 30.00px, fontWeight: 700, lineHeight: 1.2 } h2: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 24.00px, fontWeight: 600, lineHeight: 1.2 } h3: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 20.00px, fontWeight: 600, lineHeight: 1.2 } body-lg: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 18.00px, fontWeight: 400, lineHeight: 1.5 } body: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 16.00px, fontWeight: 400, lineHeight: 1.5 } body-sm: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 14.00px, fontWeight: 400, lineHeight: 1.5 } caption: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 12.00px, fontWeight: 500, lineHeight: 1.5 } button: { fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif", fontSize: 14.00px, fontWeight: 600, lineHeight: 1.2 } mono: { fontFamily: "ui-monospace, SFMono-Regular, 'Berkeley Mono', Menlo, Consolas, monospace", fontSize: 14.00px, fontWeight: 400, lineHeight: 1.5 } rounded: none: 0px full: 9999px sm: 4.00px md: 8.00px lg: 12.00px xl: 16.00px spacing: "0": 0px "1": 4.00px "2": 8.00px "3": 12.00px "4": 16.00px "5": 20.00px "6": 24.00px "8": 32.00px "10": 40.00px "12": 48.00px "16": 64.00px "20": 80.00px "24": 96.00px components: Button: { backgroundColor: "{colors.primary}", textColor: "#ffffff", typography: "{typography.button}", rounded: "{rounded.md}", padding: "12.00px 16.00px", height: 36px } IconButton: { backgroundColor: transparent, textColor: "{colors.text}", rounded: "{rounded.md}", size: 36px } Card: { backgroundColor: "{colors.surface}", textColor: "{colors.text}", rounded: "{rounded.lg}", padding: 20.00px } Dialog: { backgroundColor: "{colors.surface}", textColor: "{colors.text}", rounded: "{rounded.xl}", padding: 24.00px } Sheet: { backgroundColor: "{colors.surface}", rounded: "{rounded.xl}" } TextInput: { backgroundColor: "{colors.surface}", textColor: "{colors.text}", rounded: "{rounded.md}", padding: "8.00px 12.00px", height: 36px } Badge: { backgroundColor: "{colors.brand-50}", textColor: "{colors.brand-700}", typography: "{typography.caption}", rounded: "{rounded.full}", padding: "4.00px 8.00px" } # === HELIXUI EXTENSIONS — ignored by canonical-only parsers =================== extensions: helixui: package: "@helixui/core" designSystemKind: design-system flavorVersion: 1 generated: schema: helixui.design-md/v1 buildId: 444df7f at: 2026-06-18T04:22:54.839Z themes: [light, dark] mediums: [web, mobile-web] aiFriendly: true # The DNA shorthand. helixui-aware tools resolve this back into the # canonical token blocks above (which they then know are deterministic). basePreset: wildtype dna: accent: blue chroma: standard lightness: auto radius: standard density: comfortable typography: sans surface: elevated motion: standard mutations: [] manifests: tokens: /tokens-manifest.json components: /components-manifest.json llms_index: /llms.txt llms_full: /llms-full.txt artifacts: perComponent: /components/.md consolidated: /components.md spec: componentFormat: ./packages/core/src/components/SPEC_FORMAT.md promptDsl: ./packages/prompt/SPEC_DSL.md designMdFlavor: ./DESIGN_MD_FORMAT.md --- # helixui — design statement > An AI-friendly React design system. The system itself is shaped so an AI does not need a skill to use it correctly. This file follows the [DESIGN.md format from Google Labs](https://github.com/google-labs-code/design.md) (Apache-2.0, currently `alpha`). Any AI tool that reads `DESIGN.md` — Stitch, Cursor, Claude Code, Lovable, v0, Bolt — can understand helixui from this one file. **The frontmatter is DNA-first.** Every token block (`colors`, `typography`, `rounded`, `spacing`, `components`) is *derived* from the 8-gene helixui DNA expressed in `extensions.helixui` — not hand-authored. Change one gene, the whole frontmatter changes consistently. If you only read one file: read this one, then `/components.md`, then `/tokens-manifest.json`. That is enough to use helixui. ## Overview helixui is shaped around three design choices: 1. **Tokens are JSON, with descriptions.** `packages/tokens/tokens/*.json` is the source. Every token has `$value`, `$type`, `$description`. The build emits CSS, TS types, **and** a flat manifest at `/tokens-manifest.json`. 2. **Every component documents itself in one file.** `packages/core/src/components//spec.md` is the single source of truth — props, slots, tokens, accessibility, anatomy, layout, visual, composition graph, prompt examples. 3. **DNA compresses the design space into 8 genes.** Accent (hue), chroma (saturation), lightness (light/dark), radius (multiplier), density (multiplier), typography (family + scale), surface (shadow style), motion (duration). `express(dna)` produces this DESIGN.md frontmatter deterministically. Any tool that knows the DNA shorthand can re-derive the full canonical document on demand. There is no AST extractor and no runtime injection. To document a new component, you write that one file. To re-derive this DESIGN.md from DNA: `node scripts/build-helixui-design-md.mjs`. ## Colors The brand ramp is generated in **OKLCH** from `accent.hue` and `chroma.multiplier`. helixui's wildtype is `accent=blue` (hue 234) at `chroma=standard` (multiplier 1.0), so `brand-500` resolves to `oklch(56% 0.18 234)` — the perceptually-uniform equivalent of `#3b82f6`. Other ramps (`neutral`, `danger`, `success`, `warning`) are static — they don't shift with the DNA. Semantic aliases (`primary`, `text`, `background`, …) resolve via `{colors.}` references. Light vs dark themes pick different ramp stops: - **light**: `primary → brand-500`, `text → neutral-900`, `background → neutral-50`. - **dark**: `primary → brand-400` (lighter for contrast), `text → neutral-50`, `background → neutral-900`. Switching the DNA `accent` gene changes the brand hue family without touching the semantic structure. `accent: violet` makes `{colors.primary}` violet everywhere; nothing else moves. ## Typography `typography.family` (8 alleles: sans / grotesk / rounded / serif / mono) picks the family stack; `typography.scale` multiplies the font-size scale (1.0 default, 1.05 for serif, 0.98 for mono). The wildtype is `sans` at scale 1.0. Nine typography tokens cover everything: `display` / `h1` / `h2` / `h3` / `body-lg` / `body` / `body-sm` / `caption` / `button` / `mono`. Rules: - Headings always use `lineHeight: 1.2` (tight). - Body always uses `lineHeight: 1.5` (normal). - `button` uses size 14px / weight 600 / line-height 1.2 — distinct from `body-sm`. ## Layout Spacing is a 13-step scale (`0`, `1`, `2`, `3`, `4`, `5`, `6`, `8`, `10`, `12`, `16`, `20`, `24`) where the index is the canonical name and the resolved value is `index × 4px × density.spacing`. The wildtype `density=comfortable` (×1.0) gives `{spacing.4} = 16px`. The DNA `density` gene multiplies the entire scale — `compact` (×0.78) makes `{spacing.4} = 12.48px`, `spacious` (×1.25) makes it `20px`. Component layouts adapt automatically because they reference token indices, not pixels. `` resolves to whatever `{spacing.4}` currently is. ## Elevation & Depth Three shadow tokens — `sm` (cards on a page), `md` (popovers / dropdowns), `lg` (modals / sheets / FAB) — driven by the DNA `surface` gene: - `flat` — borders only, no shadows. - `elevated` (wildtype) — soft layered RGBA shadows. - `glassy` — shadow + thin top highlight, designed for use with `backdrop-blur` on overlays. Borders carry the rest of the depth: `border` (1px subtle) for separation, `border-strong` for emphasis, `border-focus` (2px brand) for `:focus-visible` only. ## Shapes Six radius tokens (`none`, `sm`, `md`, `lg`, `xl`, `full`). The DNA `radius` gene multiplies the four base values (sm 4, md 8, lg 12, xl 16): | Allele | factor | md resolves to | |--------|--------|----------------| | sharp | 0.35 | 2.80px | | subtle | 0.7 | 5.60px | | standard (wildtype) | 1.0 | 8.00px | | rounded | 1.4 | 11.20px | | extreme | 2.2 | 17.60px | Per-component conventions: - `sm` — Badge, chip, hairline indicator. - `md` — Button, IconButton, TextInput, Select, NumberField, Checkbox, RadioCard, Tabs, SegmentedControl, Tooltip. - `lg` — Card, ChatComposer, ToolCall, Callout, Banner, ChatMessage bubble, CodeBlock. - `xl` — Dialog, Sheet, ActionSheet, BottomNav (top corners), CollapsingHeader. - `full` — Avatar, AvatarGroup, FAB, PresenceDot, Switch knob. ## Components The frontmatter `components` block is also DNA-derived — Button's height, padding, and rounded values are computed from `density.spacing` and `radius.factor`. helixui-aware tools should fetch `/components-manifest.json` for the full surface (90 components across 11 categories: shell, navigation, layout, primitive, surface, overlay, form, feedback, data, media, chat). ## Do's and Don'ts **Do** - **Edit the DNA** in `extensions.helixui` and re-run `build-helixui-design-md`. The canonical blocks regenerate consistently. - Use semantic aliases (`{colors.primary}`) over ramp stops (`{colors.brand-500}`). Themes only swap the alias. - Use `Button` for actions, `Link` for navigation — even if they look the same. - Provide `aria-label` on icon-only controls. - Honor `prefers-reduced-motion`. helixui components already do; don't override. **Don't** - Don't hand-edit canonical token blocks to drift from the DNA. `helixui-prompt design --strict` warns; CI rejects. - Don't reference literal hex / px in component CSS — add a token if one is missing. - Don't put `onClick` on `Card` — wrap or render an `` / ` Lift on hover only
Scale on press only
``` `helixui-tap` = `helixui-hover-lift` + `helixui-press`. Drop it onto any tappable surface (cards, tiles, custom buttons). ## Reduced motion helixui respects `prefers-reduced-motion: reduce` automatically. The motion stylesheet collapses every animation duration and transition duration to `0.01ms`, so animations effectively snap to their end state but `transitionend` / `animationend` events still fire — useful for components that bind exit cleanup to those events. You don't need to add per-component reduced-motion guards. ## Theming motion Because all timings are CSS variables, a DNA theme can rebind motion at a scope: ```css [data-theme="dense"] { --helixui-motion-duration-fast: 80ms; --helixui-motion-duration-normal: 120ms; } ``` Every component inside `[data-theme="dense"]` becomes snappier without code changes. --- ## Tokens overview URL: https://helixui.ai/docs/tokens/overview --- title: Tokens overview description: How helixui design tokens are structured. --- helixui tokens are authored in **W3C DTCG** JSON. The source files live in [`packages/tokens/tokens/`](https://github.com/your-org/helixui/tree/main/packages/tokens/tokens): - `core.json` — primitive values (color ramps, spacing scale, radii, type, shadows). Not for direct use in components. - `semantic.light.json` — intent-based aliases. Components consume these. References primitives via `{color.brand.500}` syntax. - `semantic.dark.json` — same shape as light, different primitive references. ## Naming Token paths read as a sentence: `...`. | Path | Reads as | | ---- | -------- | | `color.bg.action.brand.default` | Background color, action role, brand context, default state. | | `color.bg.action.brand.hover` | …same, hover state. | | `color.text.on.brand` | Text color when placed on top of a brand fill. | | `color.border.focus` | Border color used for focus rings. | | `space.4` | The 4-step on the spacing scale (16px). | | `radius.md` | Medium corner radius (8px). | In CSS the same paths become `--helixui-` prefixed kebab-case variables: `color.bg.action.brand.default → --helixui-color-bg-action-brand-default`. ## Layers ``` primitive → semantic → component CSS core.json semantic.*.json button.css ``` A component never reaches into `core.json`. It composes semantic tokens. This means: - **Theming is alias swapping.** Dark mode rebinds the same semantic names to different primitives. - **Renaming a primitive does not break components.** Only the semantic mapping changes. ## Build outputs Run `pnpm build:tokens`. Outputs: - `dist/css/core.css` — primitive CSS variables on `:root`. - `dist/css/semantic.css` — semantic CSS variables, themed via `[data-theme="light"|"dark"]`. - `dist/css/all.css` — both, in order. - `dist/index.{js,d.ts}` — TypeScript types and a typed `cssVar(path)` helper. - `dist/manifest.json` — flat AI-readable manifest. One entry per token with value, type, description, layer, and per-theme resolutions. ## Why not Style Dictionary? Style Dictionary is great. We chose a 200-line script instead because the build chain is part of the public API of an AI-friendly system: an LLM that wants to reason about how a token becomes a CSS variable can read the script in one minute. We may swap to Style Dictionary later if scope demands it; the JSON source is portable either way. --- # DNA — theme engine ## DNA — the helixui theme engine URL: https://helixui.ai/docs/dna/overview --- title: DNA — the helixui theme engine description: A theme genome you can clone, mutate, breed, and evolve. Eight genes, dominant/recessive alleles, expressed into CSS custom properties at runtime. --- **DNA — Design Nucleotide Allele.** It's how helixui names its theme system. A `DNA` is a complete theme **genome** — eight genes, one allele each. You can clone DNAs, mutate them, breed two parents into a child, or run generations of breeding over a population. Dominant alleles win in crossover; recessive alleles still surface occasionally. That's where surprise looks come from across generations. Live demo: [`/showcase/dna-lab`](/showcase/dna-lab) — every component on the page renders under a DNA you can mutate live. ## Genes (8) | Gene | Trait | Sample alleles | | ------------ | ---------------------------------------------- | ------------------------------------------- | | `accent` | Brand hue (drives the OKLCH 50–900 ramp) | blue, violet, cyan, green, orange, crimson… | | `chroma` | Saturation multiplier across the brand ramp | muted, standard, vibrant | | `lightness` | Light / dark / auto | light, dark, auto | | `radius` | Corner-radius scale factor | sharp, subtle, standard, rounded, extreme | | `density` | Spacing scale factor | compact, comfortable, spacious | | `typography` | Font family + scale | sans, grotesk, rounded, serif, mono | | `surface` | Shadow / depth treatment | flat, elevated, glassy | | `motion` | Animation duration scale | subtle, standard, lively | Each allele has a `dominance` score (1–10) and an optional `recessive` flag. The full registry lives in [`@helixui/dna/src/alleles.ts`](https://github.com/your-org/helixui/blob/main/packages/dna/src/alleles.ts). ## API ```ts import { wildtype, PRESETS, clone, mutate, compose, evolve, express, type DNA, } from '@helixui/dna'; const a = wildtype(); // base DNA const b = PRESETS.botanic(); // a preset const c = mutate(a, { gene: 'accent', allele: 'crimson' }); const child = compose(a, b); // cross two DNAs const next = evolve([a, b, c, child]); // run a generation const { cssVars, dataTheme } = express(child); // → CSS custom properties ``` Apply a DNA to a subtree via `` (from `@helixui/core`): ```tsx import { HelixUIDNAProvider, Button } from '@helixui/core'; ``` ## Inheritance rules When two DNAs cross, for each gene: 1. **Higher dominance wins.** Ties are decided by coin flip. 2. **Recessive surfacing.** If the loser allele is flagged `recessive`, it takes the locus ~15% of the time anyway. That's how visual surprises emerge. 3. **Mutation.** A small per-gene mutation rate (default ~4%) replaces the winner with a random other allele. Tune all three per call (`compose(a, b, { mutationRate: 0.2 })` or `evolve(pop, { fitness: contrastScore })`). ## Why DNA changes are instant helixui tokens ship in two layers: 1. **Primitives** (`color.brand.500`, `space.4`, `radius.md`, `font.size.md`, `shadow.md`) — concrete CSS variable values. 2. **Semantic aliases** (`color.bg.action.brand.default = {color.brand.500}`) — emitted as `var(--helixui-color-brand-500)` in the built CSS. When DNA's `express()` overrides a primitive (e.g. `--helixui-color-brand-500`), every semantic that references it follows through the cascade automatically. **No re-render. No recomputation.** Just a style mutation on the DNA provider's wrapping div. ## Phenotype `express(dna)` produces: - A color ramp generated with `oklch(L% C H)` from the `accent` and `chroma` genes (10 shades, 50–900). - Spacing tokens scaled by `density.spacing`. - Radius tokens scaled by `radius.factor`. - Font family + size scale from `typography`. - Shadow set per `surface.style` (flat / elevated / glassy). - Motion duration tokens (`--helixui-motion-duration-*`). - A `data-theme` attribute when `lightness` is `light` or `dark`. ```ts { '--helixui-color-brand-500': 'oklch(56% 0.16 234)', '--helixui-color-brand-600': 'oklch(47% 0.155 234)', '--helixui-radius-md': '11.20px', '--helixui-space-4': '20.00px', '--helixui-font-family-sans': "'Geist', 'Space Grotesk', …", '--helixui-shadow-md': '0 4px 6px -1px rgb(0 0 0 / 0.1), …', '--helixui-motion-duration-normal': '200ms', /* …38 vars total… */ } ``` ## Persistence A `DNA` is a plain JSON-serialisable object. To persist a user's chosen theme, `JSON.stringify(dna)` to localStorage / your backend; `JSON.parse` to restore. Allele values are inert data — no functions or class instances. --- # Components ## ActionSheet URL: https://helixui.ai/docs/components/action-sheet --- title: ActionSheet status: stable since: 0.5.0 package: "@helixui/core" import: "import { ActionSheet, type ActionSheetAction } from '@helixui/core'" description: An iOS-style modal sheet that slides up from the bottom edge with a list of actions. Different from Sheet — Sheet is a side drawer, ActionSheet is a vertical action menu. props: - { name: title, type: ReactNode, default: "", description: "Top centered title." } - { name: description, type: ReactNode, default: "", description: "Sub-title under the title." } - { name: actions, type: "ActionSheetAction[]", default: "", description: "Required. List of actions; each has `id`, `label`, optional `description`, `icon`, `destructive`, `disabled`, and `onAction(close)`." } - { name: cancelLabel, type: ReactNode, default: Cancel, description: "Label for the bottom cancel button. Set null to hide." } - { name: isOpen, type: boolean, default: "", description: "Controlled open state." } - { name: defaultOpen, type: boolean, default: false, description: "Uncontrolled initial open." } - { name: onOpenChange, type: "(open: boolean) => void", default: "", description: "Open change handler." } slots: [] tokens: [color.bg.surface.default, color.bg.action.neutral.default, color.text.primary, color.text.secondary, color.text.muted, color.text.action.brand, color.text.action.danger, color.border.default, color.border.focus, radius.lg, space.1, space.2, space.3, space.4, font.family.sans, font.size.xs, font.size.sm, font.size.md, font.weight.medium, font.weight.semibold] a11y: role: dialog keyboard: - { key: Escape, action: Close. } notes: - Built on react-aria-components Modal — focus trap, restoration, dismiss on overlay click, all handled. - The cancel button is a button (not an action item) so it visually separates from the list — matches iOS pattern. related: [Sheet, Dialog, FAB] category: overlay tags: [mobile, modal, menu, bottom-sheet, actions, ios] anatomy: | [overlay (dimmed)] ┌── action sheet panel (slides up from bottom) ──┐ │ Title (optional) │ │ ───────────────────────────────────────────── │ n │ • Action 1 │ │ • Action 2 │ │ • Destructive action (red) │ │ ───────────────────────────────────────────── │ │ Cancel │ └────────────────────────────────────────────────┘ layout: display: portal width: "fill" height: "content" intrinsicSize: "pinned to bottom edge, max-h ~80vh, scrolls inside" stackable: false fullBleed: true visual: | A bottom-anchored panel with rounded top corners (radius.lg) that slides up from the bottom edge over a dimmed overlay. Each action is a full-width row separated by hairlines; destructive rows render in danger tone. A trailing Cancel row is separated by a small gap. Mounts with a translateY animation that respects prefers-reduced-motion. composes_with: - { component: Sheet, relation: alternative, note: "Use Sheet for general side drawers; ActionSheet is the iOS-style action menu only." } - { component: Menu, relation: alternative, note: "On desktop prefer Menu anchored to a trigger." } - { component: Button, relation: trigger, note: "Common opener." } - { component: Dialog, relation: sibling } - { component: FAB, relation: sibling } prompt_examples: - intent: "mobile destructive choice" prompt: "show a delete / cancel sheet on tap" snippet: | - intent: "photo source picker" prompt: "Take photo / Choose from library / Cancel sheet" snippet: | --- # ActionSheet ```tsx const [open, setOpen] = useState(false); <> setOpen(true)} aria-label="New" icon={} /> , onAction: (close) => { /* … */ close(); } }, { id: 'upload', label: 'Upload from device', icon: , onAction: (close) => { /* … */ close(); } }, { id: 'paste', label: 'Paste from clipboard', icon: , onAction: (close) => { /* … */ close(); } }, { id: 'delete', label: 'Delete library', destructive: true, onAction: (close) => { /* … */ close(); } }, ]} /> ``` **Install:** `@helixui/core` ```ts import { ActionSheet, type ActionSheetAction } from '@helixui/core' ``` status: `stable` · since: `0.5.0` **Tags:** `mobile`, `modal`, `menu`, `bottom-sheet`, `actions`, `ios` ## Anatomy ``` [overlay (dimmed)] ┌── action sheet panel (slides up from bottom) ──┐ │ Title (optional) │ │ ───────────────────────────────────────────── │ n │ • Action 1 │ │ • Action 2 │ │ • Destructive action (red) │ │ ───────────────────────────────────────────── │ │ Cancel │ └────────────────────────────────────────────────┘ ``` ## Layout - **display** — `portal` - **width** — `fill` - **height** — `content` - **intrinsicSize** — `pinned to bottom edge, max-h ~80vh, scrolls inside` - **stackable** — `false` - **fullBleed** — `true` ## Visual A bottom-anchored panel with rounded top corners (radius.lg) that slides up from the bottom edge over a dimmed overlay. Each action is a full-width row separated by hairlines; destructive rows render in danger tone. A trailing Cancel row is separated by a small gap. Mounts with a translateY animation that respects prefers-reduced-motion. ## Props | Name | Type | Default | Description | | --- | --- | --- | --- | | `title` | `ReactNode` | — | Top centered title. | | `description` | `ReactNode` | — | Sub-title under the title. | | `actions` | `ActionSheetAction[]` | — | Required. List of actions; each has `id`, `label`, optional `description`, `icon`, `destructive`, `disabled`, and `onAction(close)`. | | `cancelLabel` | `ReactNode` | `Cancel` | Label for the bottom cancel button. Set null to hide. | | `isOpen` | `boolean` | — | Controlled open state. | | `defaultOpen` | `boolean` | `false` | Uncontrolled initial open. | | `onOpenChange` | `(open: boolean) => void` | — | Open change handler. | ## Tokens used `color.bg.surface.default`, `color.bg.action.neutral.default`, `color.text.primary`, `color.text.secondary`, `color.text.muted`, `color.text.action.brand`, `color.text.action.danger`, `color.border.default`, `color.border.focus`, `radius.lg`, `space.1`, `space.2`, `space.3`, `space.4`, `font.family.sans`, `font.size.xs`, `font.size.sm`, `font.size.md`, `font.weight.medium`, `font.weight.semibold` ## Accessibility **Role:** `dialog` **Keyboard** | Key | Action | | --- | --- | | `Escape` | Close. | **Notes** - Built on react-aria-components Modal — focus trap, restoration, dismiss on overlay click, all handled. - The cancel button is a button (not an action item) so it visually separates from the list — matches iOS pattern. ## Composes with | Component | Relation | Note | | --- | --- | --- | | `Sheet` | `alternative` | Use Sheet for general side drawers; ActionSheet is the iOS-style action menu only. | | `Menu` | `alternative` | On desktop prefer Menu anchored to a trigger. | | `Button` | `trigger` | Common opener. | | `Dialog` | `sibling` | | | `FAB` | `sibling` | | ## Prompt examples These are the AI prompt → JSX mappings used by the helixui prompt DSL and integrations like Cursor / Claude Code. ### mobile destructive choice > _"show a delete / cancel sheet on tap"_ ```tsx ``` ### photo source picker > _"Take photo / Choose from library / Cancel sheet"_ ```tsx ``` ## Related - [Sheet](./sheet) - [Dialog](./dialog) - [FAB](./fab) --- ## AppShell URL: https://helixui.ai/docs/components/app-shell --- title: AppShell status: stable since: 0.2.0 package: "@helixui/core" import: "import { AppShell } from '@helixui/core'" description: A responsive application layout — sticky header, persistent sidebar on desktop, sidebar hidden on mobile (caller composes a Sheet drawer). props: - { name: sidebar, type: ReactNode, default: "", description: "Left rail content. Shown on desktop, hidden below the breakpoint." } - { name: header, type: ReactNode, default: "", description: "Sticky top bar." } - { name: sidebarWidth, type: "number | string", default: 240, description: "Sidebar width. Number → px." } - { name: collapseBreakpoint, type: number, default: 768, description: "Min viewport (px) for sidebar to be visible." } slots: - "children — main content area" tokens: [color.bg.surface.default, color.text.primary, color.border.default] a11y: notes: - Sidebar uses `