# 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 `