# Components — Build Them Once, Use Them Everywhere > Every interactive element on the page must have: default, hover, focus-visible, active, disabled. Skip one and the design breaks on the edges. --- ## Buttons ### Anatomy A button is a **promise to the user**: click me, this happens. It must look pressable. It must have a clear label. ### Variants (use 2–3 max) **Primary** - Background: `--ink` (or `--accent`) - Text: `--surface` - One per page, max. The thing the user should do. **Secondary** - Background: transparent - Border: `1px solid var(--hairline-strong)` (or `--ink` for emphasis) - Text: `--ink` - The second thing the user could do. **Tertiary / Ghost** - Background: transparent - Text: `--ink` - Optional underline or arrow - The third thing. Or a low-priority action. **Destructive** - Background: `--error` - Text: `--surface` - Use for irreversible actions. Always confirm before executing. ### Sizes | Token | Height | Padding | Font size | |---|---|---|---| | `sm` | 32px | 0 12px | 14px | | `md` (default) | 40px | 0 16px | 14–15px | | `lg` | 48px | 0 20px | 16px | | `xl` | 56px | 0 24px | 17–18px | ### States | State | Treatment | |---|---| | Default | As designed | | Hover | Slight darken of background, or border strengthens. Use `transition: background-color 120ms ease, border-color 120ms ease;` | | Focus-visible | 2px ring, accent color, 2px offset | | Active | Slight darken or scale(0.98). 80ms transition. | | Disabled | Reduced opacity (0.5), no hover effects, `cursor: not-allowed` | | Loading | Replace label with spinner, OR keep label and add small spinner before | ### Rules - ❌ Don't use 5 button variants. Pick 2–3, max. - ❌ Don't make buttons pills (`border-radius: 9999px`) by default. 6–8px is safer. - ❌ Don't put icons inside button labels without text (icon-only buttons need `aria-label`). - ❌ Don't stack a primary next to another primary. Primary is singular. - ❌ Don't make buttons too small to tap. Minimum 40px tall, 44px on mobile. - ❌ Don't use more than 2 buttons in a single CTA group. ### Sample HTML + CSS ```html ``` ```css .btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 40px; padding: 0 16px; border-radius: 8px; font-size: 14px; font-weight: 500; line-height: 1; cursor: pointer; transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease; border: 1px solid transparent; } .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .btn--primary { background: var(--ink); color: var(--surface); } .btn--primary:hover { background: #1F1F1F; } .btn--secondary { background: transparent; border-color: var(--hairline-strong); color: var(--ink); } .btn--secondary:hover { border-color: var(--ink); } ``` --- ## Forms ### Inputs - **Height:** 40px default. 36px for compact. - **Background:** `--surface-elevated` or `--surface-sunken` (slight contrast from page) - **Border:** `1px solid var(--hairline-strong)` - **Border-radius:** matches buttons (6–8px) - **Padding:** `0 12px` - **Font:** same as body, 14–16px - **Placeholder:** `--ink-subtle`, NOT `--ink-muted` — distinguish placeholders from real values - **Label:** Above the input, 13–14px, `--ink-muted`, margin-bottom 6px ### States | State | Border | |---|---| | Default | `--hairline-strong` | | Hover | `--ink` | | Focus | `--accent`, 2px | | Error | `--error` | | Disabled | `--hairline`, opacity 0.6, `cursor: not-allowed` | ### Inputs anti-patterns - ❌ Placeholder used as label (loses on focus) - ❌ Label inside input (accessibility disaster) - ❌ No label at all (placeholder isn't a label) - ❌ Border that disappears on focus with no replacement - ❌ Default browser styling (especially checkboxes, radios, selects) ### Custom checkboxes / radios ```css input[type="checkbox"] { appearance: none; width: 16px; height: 16px; border: 1.5px solid var(--hairline-strong); border-radius: 4px; background: var(--surface); cursor: pointer; position: relative; } input[type="checkbox"]:checked { background: var(--accent); border-color: var(--accent); } input[type="checkbox"]:checked::after { content: ''; position: absolute; left: 4px; top: 1px; width: 5px; height: 9px; border: solid var(--surface); border-width: 0 2px 2px 0; transform: rotate(45deg); } ``` ### Select dropdowns Native `