← Home
breakstuff

Design System

Theme:

The tokens, primitives, and rules that build every surface. Switch themes to preview.

Semantic Colors

--bg
#08080d
ref: {base.color.ink.950}
--surface
#11111a
ref: {base.color.ink.850}
--surface-raised
#1d1d28
ref: {base.color.ink.750}
--on-bg
#f0f0f8
ref: {base.color.ink.50}
--on-bg-muted
#8888a0
ref: {base.color.ink.300}
--on-bg-subtle
#44445a
ref: {base.color.ink.500}
--on-surface
#f0f0f8
ref: {base.color.ink.50}
--on-surface-muted
#8888a0
ref: {base.color.ink.300}
--on-surface-subtle
#44445a
ref: {base.color.ink.500}
--accent
#ff5500
ref: {base.color.orange.500}
--accent-hover
#ff7733
ref: {base.color.orange.400}
--accent-press
#cc4400
ref: {base.color.orange.600}
--accent-muted
rgba(255, 85, 0, 0.12)
ref: {base.color.orange.dim}
--on-accent
#ffffff
ref: #FFFFFF
--border
rgba(255, 255, 255, 0.08)
--border-strong
rgba(255, 255, 255, 0.16)
--focus-ring
#ff5500
ref: {base.color.orange.500}
--success
#00e5a0
ref: {base.state.success}
--success-muted
rgba(0, 229, 160, 0.12)
ref: {base.state.success-dim}
--on-success
#001a0d
ref: {base.state.on-success}
--warning
#ffb800
ref: {base.state.warning}
--warning-muted
rgba(255, 184, 0, 0.12)
ref: {base.state.warning-dim}
--on-warning
#1a1200
ref: {base.state.on-warning}
--error
#ff4d6d
ref: {base.state.error}
--error-muted
rgba(255, 77, 109, 0.12)
ref: {base.state.error-dim}
--on-error
#1a0008
ref: {base.state.on-error}
--info
#6366ff
ref: {base.state.info}
--info-muted
rgba(99, 102, 255, 0.12)
ref: {base.state.info-dim}
--on-info
#ffffff
ref: {base.state.on-info}
--achievement
#ffb800
ref: {base.color.gold.500}
--achievement-muted
rgba(255, 184, 0, 0.12)
ref: {base.color.gold.dim}
--on-achievement
#000000
--spark-glow
#6366ff
ref: {base.state.info}
--spark-core
#ffb800
ref: {base.color.gold.500}
--spark-hot
#f0f0f8
ref: {base.color.ink.50}
--spark-branch
#ff5500
ref: {base.color.orange.500}
--spark-flash
#f0f0f8
ref: {base.color.ink.50}
--chart-1
#ff5500
ref: {base.color.orange.500}
--chart-2
#6366ff
ref: {base.state.info}
--chart-3
#00e5a0
ref: {base.state.success}
--chart-4
#ffb800
ref: {base.color.gold.500}
--chart-5
#ff4d6d
ref: {base.state.error}

Spacing

--sp-1
4px
--sp-2
8px
--sp-3
12px
--sp-4
16px
--sp-5
20px
--sp-6
24px
--sp-8
32px
--sp-10
40px
--sp-12
48px
--sp-16
64px
--sp-20
80px

Radius

radius-badge 4px
radius-input 6px
radius-card 8px
radius-md 6px
radius-modal 12px
radius-pill 9999px

Typography

text-micro The quick brown fox 0.625rem
text-sm The quick brown fox 0.75rem
text-body The quick brown fox 0.875rem
text-base The quick brown fox 1rem
text-lead The quick brown fox 1.125rem
text-heading The quick brown fox 1.375rem
text-title The quick brown fox 1.75rem
text-display The quick brown fox 2.25rem
text-hero The quick brown fox 3rem
text-giant The quick brown fox 4rem

Motion

dur-fast · ease
dur-fast · ease-io
dur-base · ease
dur-base · ease-io
dur-slow · ease
dur-slow · ease-io

Rules for consumers

@breakstuff/tokens
---
description: How to use @breakstuff/tokens semantic CSS custom properties in apps
---

# @breakstuff/tokens — Consumer Rules

Import the package once in your app's entry point:

```ts
import '@breakstuff/tokens/tokens.css';
```

Set the theme on the `<html>` element in `index.html`:

```html
<html data-theme="dadbod">
```

Available themes: `dadbod`, `dark-orange`, `heartthrob-crimson`, `heartthrob-vitalscan`.

## Hard rules

- **Never use raw hex or px values in CSS.** Every color, spacing, radius, typography, and motion value must come from a CSS custom property.
- **Use semantic tokens only.** Base tokens are source inputs and are not emitted to app-facing CSS.
- If a semantic token doesn't exist for your use case, add one (see maintainer rules) — do not hardcode.

## Available CSS Custom Properties

### Color

| Variable | Use |
|----------|-----|
| `--bg` | Page/app background |
| `--surface` | Card, panel, sheet background |
| `--surface-raised` | Elevated surface (dropdown, popover) |
| `--on-bg` | Primary text/icon on page background |
| `--on-bg-muted` | Secondary text on page background |
| `--on-bg-subtle` | Tertiary/disabled text on page background |
| `--on-surface` | Primary text/icon on surface |
| `--on-surface-muted` | Secondary text on surface |
| `--on-surface-subtle` | Tertiary text on surface |
| `--accent` | Primary accent — interactive elements, highlights |
| `--accent-hover` | Accent on hover state |
| `--accent-press` | Accent on active/press state |
| `--accent-muted` | Low-emphasis accent tint (background fills) |
| `--on-accent` | Text/icon on accent-colored backgrounds |
| `--border` | Default border color |
| `--border-strong` | Emphasis border |
| `--focus-ring` | Focus indicator color |
| `--success` | Success state |
| `--success-muted` | Low-emphasis success tint |
| `--warning` | Warning state |
| `--warning-muted` | Low-emphasis warning tint |
| `--error` | Error/danger state |
| `--error-muted` | Low-emphasis error tint |
| `--info` | Informational state |
| `--info-muted` | Low-emphasis info tint |
| `--achievement` | Gold achievement/PR highlight |
| `--achievement-muted` | Low-emphasis achievement tint |
| `--spark-glow` | Lightning/spark effect — outer aura and mid-bolt glow |
| `--spark-core` | Lightning/spark effect — bright bolt core |
| `--spark-hot` | Lightning/spark effect — white-hot bolt center |
| `--spark-branch` | Lightning/spark effect — branch filaments / secondary accent |
| `--spark-flash` | Lightning/spark effect — full-screen impact flash |
| `--chart-1` through `--chart-5` | Chart series colors (in order) |
| `--overlay`, `--overlay-light` | Overlay backgrounds |
| `--shadow-raised`, `--shadow-overlay` | Elevation shadows |

### Typography

| Variable | Use |
|----------|-----|
| `--font` | Body/UI font family |
| `--font-display` | Display/heading font family |
| `--text-micro` | Smallest label text |
| `--text-sm` | Small supporting text |
| `--text-body` | Default body text |
| `--text-base` | Base size (1rem) |
| `--text-lead` | Lead/subheading text |
| `--text-heading` | Section heading |
| `--text-title` | Page title |
| `--text-display` | Large display number/stat |
| `--text-hero` | Hero-scale text |
| `--text-giant` | Hero display |
| `--weight-normal` | 400 |
| `--weight-medium` | 500 |
| `--weight-semibold` | 600 |
| `--weight-bold` | 700 |
| `--leading-tight` | Tight line height |
| `--leading-normal` | Default line height |
| `--tracking-tight` | Tight letter spacing |
| `--tracking-normal` | Normal letter spacing |
| `--tracking-wide` | Loose letter spacing |
| `--tracking-wider` | Extra-loose letter spacing |

### Spacing

| Variable | Use |
|----------|-----|
| `--sp-1` through `--sp-20` | Raw spacing scale (multiples of 4px) |
| `--inset-sm` | Compact component padding |
| `--inset-md` | Default component padding |
| `--inset-lg` | Generous component padding |
| `--component-gap` | Gap between items in a component group |
| `--item-gap` | Gap between items in a list |
| `--section-gap` | Gap between page sections |
| `--page-padding` | Horizontal page padding |

### Radius

| Variable | Use |
|----------|-----|
| `--radius-pill` | Pill-shaped elements |
| `--radius-input` | Input fields |
| `--radius-card` | Cards and panels |
| `--radius-md` | Generic medium radius |
| `--radius-modal` | Modals and dialogs |
| `--radius-badge` | Badge/chip elements |

### Control Size

| Variable | Use |
|----------|-----|
| `--height-control-sm` | Small control minimum height |
| `--height-control-md` | Default control minimum height |
| `--height-control-compact` | Compact form control minimum height |
| `--height-control-lg` | Large control minimum height |

### Motion

| Variable | Use |
|----------|-----|
| `--dur-fast` | Quick transitions (hover, toggle) |
| `--dur-base` | Default transition duration |
| `--dur-slow` | Slow transitions (modals, drawers) |
| `--ease` | Default easing curve |
| `--ease-io` | In/out easing curve |

## Machine-readable token list

`@breakstuff/tokens/semantic-vars.json` exports the public CSS custom property names. Stylelint should use that list when enforcing allowed design-system variables.

`@breakstuff/tokens/semantic-manifest.json` exports the public semantic token catalog with purpose descriptions. Agents should read this file before choosing a token so they select by intended use, not by color value or name similarity.

## iOS-specific rule

Interactive inputs (`<input>`, `<textarea>`, `<select>`) must have `font-size` ≥ 16px (`var(--text-body)` or larger). Anything smaller triggers iOS Safari auto-zoom on focus. Use `var(--text-body)` as the minimum.
@breakstuff/primitives-react
# @breakstuff/primitives-react — consumer rule

You are editing code that depends on `@breakstuff/primitives-react`. Follow these rules.

## Setup (one-time, at the app root)

```ts
import '@breakstuff/tokens/tokens.css';
import '@breakstuff/primitives-react/styles.css';
```

Both imports must happen exactly once each, and **tokens.css must come first**.

## Token selection

When writing or reviewing component CSS, read `@breakstuff/tokens/semantic-manifest.json` before choosing CSS custom properties. The manifest documents each public semantic token's intended purpose. Choose tokens by purpose, not by matching the current color value.

Use `@breakstuff/tokens/semantic-vars.json` only as the allowed-variable list for tooling; it does not explain when to use a token.

## Primitives available

- `Button` (single component). Props: `variant: "primary" | "secondary" | "ghost"`, `size: "sm" | "md" | "lg"`, `tone: "accent" | "neutral" | "danger" | "info" | "warning" | "success"`, `asChild`, plus native `<button>` attributes. Use `tone="warning"` (amber/gold, `--warning` token) for AI-related actions. Use `tone="info"` (indigo/violet, `--info` token) for informational actions.
- `IconButton` (single component). Props: required `aria-label: string`, `variant: "primary" | "secondary" | "ghost"` (default `"ghost"`), `size: "sm" | "md" | "lg"` (default `"md"`), `tone: "accent" | "neutral" | "danger" | "info" | "warning" | "success"` (default `"neutral"`), plus native `<button>` attributes. Use for icon-only actions such as menus, settings, close/delete affordances, and compact toolbar actions. Children should be an icon or visually hidden content; the accessible name must come from `aria-label`.
- `ButtonGroup` (compound): `ButtonGroup.Root`, `ButtonGroup.Button`. `Root` props: `orientation: "horizontal" | "vertical"` (default `"horizontal"`), `equalWidth?: boolean` (default `true`), plus native `<div>` attributes. `Button` accepts `Button` props and defaults to `variant="secondary"`, `tone="neutral"`, `size="md"`.
- `Pill` (single component). Props: `size: "sm" | "md"` (default `"md"`), `trailingIcon?: ReactNode`, plus native `<button>` attributes. Use for compact filter chips and removable labels, not for primary actions.
- `Dialog` (compound): `Dialog.Root`, `Dialog.Trigger`, `Dialog.Content` (requires `title: string`, optional `description`, `titleLevel: "h2" | "h3"`, `titleSize: "default" | "compact"`, `variant: "modal" | "sheet"`), `Dialog.Footer`, `Dialog.Close`.
- `Tabs` (compound): `Tabs.Root`, `Tabs.List`, `Tabs.Trigger`, `Tabs.Panel`.
- `Input` (single component). Props: `label: string` (required), `hint?: string`, `state: "idle" | "ok" | "error" | "disabled"` (default `"idle"`), `size: "sm" | "md" | "lg"` (default `"md"`), `density: "normal" | "compact"` (default `"normal"`), `variant: "default" | "number"` (default `"default"`), `selectOnFocus?: boolean`, plus native `<input>` attributes except native `disabled` and `size`. Set `--input-label-bg` on the containing element when the input sits on `--surface` or `--surface-raised` (defaults to `--bg`). Match `size` to the `Button` size used alongside the input for visual symmetry.
- `Textarea` (single component). Props: `label: string` (required), `hint?: string`, `state: "idle" | "ok" | "error" | "disabled"` (default `"idle"`), `className?` (layout only), plus native `<textarea>` attributes except native `disabled`. Use for multi-line text entry; hints in error state render as alerts.
- `Select` (single component). Props: `label: string` (required), `options: Array<{ value: string; label: string; disabled?: boolean }>`, `placeholder?: string`, `hint?: string`, `state: "idle" | "ok" | "error" | "disabled"` (default `"idle"`), `density: "normal" | "compact"` (default `"normal"`), `size: "sm" | "md" | "lg"` (default `"md"`), `disabled?: boolean`, `className?` (layout only), `triggerClassName?` (layout only), `id?`, plus Radix Select root props except native disabled handling.
- `SearchableSelect` (single component). Use when an option set can grow beyond easy scanning. Props: `label: string` (required), `options: Array<{ value: string; label: string; description?: string; disabled?: boolean }>`, `value?: string`, `onValueChange?: (value: string) => void`, `placeholder?: string`, `searchPlaceholder?: string`, `emptyText?: string`, `hint?: string`, `state: "idle" | "ok" | "error" | "disabled"` (default `"idle"`), `size: "sm" | "md" | "lg"` (default `"md"`), `disabled?: boolean`, `className?` (layout only), `triggerClassName?` (layout only), `id?`, plus Radix Popover root open props. Prefer over `Select` for long prototype lists, large entity pickers, or any picker where users may know the item by name.
- `Slider` (single component). Wraps `@radix-ui/react-slider` for accessible single-value range controls. Props: `label: string` (required), `value?: number`, `defaultValue?: number`, `onValueChange?: (value: number) => void`, `onValueCommit?: (value: number) => void`, `valueLabel?: ReactNode`, `hint?: string`, `minLabel?: ReactNode`, `maxLabel?: ReactNode`, `state: "idle" | "disabled"` (default `"idle"`), `disabled?: boolean`, plus Radix Slider root props except array-based value/change props and disabled handling. Keep domain math in app code; pass the computed value/min/max/step into the primitive.
- `Stack` (single component). Props: `direction: "row" | "column"` (default `"column"`), `gap: "none" | "xs" | "sm" | "md" | "lg" | "xl"` (default `"md"`), `align: "start" | "center" | "end" | "stretch"` (default `"stretch"`), `justify: "start" | "center" | "end" | "between" | "around"` (default `"start"`), `wrap?: boolean`, `asChild`, plus native `<div>` attributes. Gap maps to `--sp-1/2/4/6/8`.
- `ReorderableStack` (single component). Props: `items`, `getKey`, `renderItem`, `onReorder`, `direction`, `gap`, `align`, `justify`, `activationDelay`, `disabled`, `itemClassName`, plus native `<div>` attributes except `children`. Use when app code needs keyboard- and drag-driven reordering; `renderItem` receives `{ isDragging, isDropTarget }` state.
- `Drawer` (compound): `Drawer.Root`, `Drawer.Trigger`, `Drawer.Content` (requires `title: string`, optional `description`), `Drawer.Close`. `Content` also supports `minimized`, `onMinimize`, `onExpand`, `outsideInteraction: "close" | "minimize" | "none"`, and `swipeDownAction: "close" | "minimize" | "none"`. Renders as a bottom sheet that slides up from the bottom edge. Has a drag-handle indicator and safe-area-inset-bottom padding built in. Use for contextual overlays that don't need to be full-screen (e.g. timers, quick-action panels). Stacks correctly on top of a full-screen `Dialog` sheet.
- `Card` (single component). Props: `asChild`, plus native `<div>` attributes. Renders a surface-raised container with `--border-strong` border, `--radius-card` radius, and `--inset-sm` padding. Use `asChild` to render as a `Link` or `button` for clickable cards. Apply layout-only `className` for width, flex children, or positional tweaks — never restyle the card surface.
- `MobileBottomNav` (single component). Props: native `<nav>` attributes. Renders a `position: fixed` bottom nav shell with safe-area-inset-bottom padding, surface background, and top border. Defines `--bottom-nav-height: 3.5rem` on `:root` — use `calc(var(--bottom-nav-height) + env(safe-area-inset-bottom))` anywhere content needs to clear the nav. Place app-specific tab items as children.
- `SearchInput` (single component). Props: `value: string` (required), `onChange: (value: string) => void` (required, receives the string directly — not a synthetic event), `placeholder?: string` (default `"Search…"`), plus all native `<input>` attributes except `type` and `onChange`. Always renders a magnifying-glass icon on the left and a × clear button on the right. The × is always visible but dimmed when the field is empty. Suppresses the native browser search-cancel button. Font size is clamped to ≥ 16px to prevent iOS Safari viewport zoom.
- `Checkbox` (single component). Wraps `@radix-ui/react-checkbox`. Props: `checked?: boolean | "indeterminate"`, `defaultChecked?: boolean`, `onCheckedChange?: (checked: boolean | "indeterminate") => void`, `disabled?`, `required?`, `name?`, `value?`, `id?`, `className?` (layout only), plus all Radix CheckboxProps. Renders as a square box (`--sp-5` × `--sp-5`) with an accent-filled background and check icon when checked. The indicator is a `fa-solid fa-check` icon — Font Awesome Kit must be loaded. Always associate with a `<label>` via `id`/`htmlFor` or wrap in a `<label>`.
- `SegmentedControl` (compound): `SegmentedControl.Root`, `SegmentedControl.Item`. Use for small mutually exclusive option sets. It wraps Radix RadioGroup, so `Root` takes RadioGroup root props such as `value`, `defaultValue`, `onValueChange`, and `aria-label`; `Item` takes RadioGroup item props such as `value` and `disabled`.

Always use the primitive from this package in preference to raw `@radix-ui/*` or hand-rolled HTML controls. If something is missing, **add it to this package** — do not one-off it in an app.

## Composition

- Use the **compound API** (`Dialog.Trigger`, `Tabs.Panel`). Never import sub-pieces by name from `@radix-ui/*` directly.
- `asChild` is the only supported way to change the rendered element:

  ```tsx
  <Button asChild>
    <a href="/go">Go</a>
  </Button>
  ```

  Never add an `as` prop.
- Disabled state uses the native `disabled` attribute. Never set `aria-disabled` manually.

## className

`className` is a **layout escape hatch only** — use it for margin, grid placement, or positional tweaks. Never use it to:
- Re-skin colors, backgrounds, borders, or text styles of the primitive.
- Change padding or font-size of the primitive.

If you need to restyle a primitive visually, add a new variant / tone / size to the primitive in this package.

## Focus

Focus styling is handled by the `.ds-app` class from `@breakstuff/tokens`. Do not add component-level focus styles.

Primitives

Button

ButtonGroup

Pill

Input

Looks good
Required

Textarea

SearchInput

Select

Checkbox

unchecked
checked
indeterminate
disabled

SegmentedControl

Card

Total Volume
12,400 kg
Sessions
47

Stack

row / gap sm / align center
A
B
C
column / gap md / align stretch
Set 1
Set 2
Set 3

Tabs

Today's session goes here.

Dialog

Drawer