hermes-hub/.agents/skills/frontend-design/components.md

420 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 23 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 | 1415px |
| `lg` | 48px | 0 20px | 16px |
| `xl` | 56px | 0 24px | 1718px |
### 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 23, max.
- ❌ Don't make buttons pills (`border-radius: 9999px`) by default. 68px 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
<button class="btn btn--primary">Get started</button>
<button class="btn btn--secondary">Read docs</button>
```
```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 (68px)
- **Padding:** `0 12px`
- **Font:** same as body, 1416px
- **Placeholder:** `--ink-subtle`, NOT `--ink-muted` — distinguish placeholders from real values
- **Label:** Above the input, 1314px, `--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 `<select>` is ugly but accessible. Three options:
1. **Style the native element** as much as possible — works in most cases
2. **Custom dropdown** with full keyboard accessibility (much more code)
3. **Use a library** (Radix, Headless UI, React Aria) for safety
Whichever path: keep the visible trigger simple — same height and border as inputs.
### Form layout
- Labels above inputs (most common, fastest to scan)
- One column by default. Two-column only when columns are independent (e.g., First Name / Last Name).
- Help text below the input, smaller and muted.
- Error messages: red, specific, actionable ("Enter a valid email" not "Invalid input").
- Required field marker: `*` or "(required)" — pick one, be consistent.
---
## Cards
A card groups related content. Use sparingly. The more cards on a page, the less each one matters.
### Anatomy
- Surface: `--surface-elevated` (or same as page if minimal)
- Border: `1px solid var(--hairline)` — preferred over shadow
- Radius: 812px (or 0 in Swiss style)
- Padding: 24px (compact) to 32px (generous)
- Optional: header, body, footer zones, separated by hairline or padding
### Variants
**Flat card** — hairline border, no shadow. Default for content cards.
```css
.card {
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: 12px;
padding: 24px;
}
```
**Elevated card** — subtle shadow, used for floating elements (popovers, modals). Rare for content cards.
```css
.card-elevated {
background: var(--surface-elevated);
border-radius: 12px;
padding: 24px;
box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
}
```
**Interactive card** — entire card is clickable. Cursor pointer, hover lifts the border color or background.
```css
.card-interactive {
background: var(--surface);
border: 1px solid var(--hairline);
border-radius: 12px;
padding: 24px;
cursor: pointer;
transition: border-color 150ms ease, background 150ms ease;
}
.card-interactive:hover {
border-color: var(--ink);
}
```
### Card content rules
- ❌ Don't put a card inside a card
- ❌ Don't make every card the same size if content varies wildly
- ❌ Don't add a small "category" tag to every card automatically
- ❌ Don't use cards as layout placeholders for non-card content (use proper sections)
---
## Navigation
### Top nav
- **Height:** 5672px
- **Background:** same as surface (or slight elevation if scroll-aware)
- **Logo:** left, 2432px tall
- **Links:** center or right, 1415px, medium weight
- **CTA:** right side, distinct button
- **Sticky:** optional, but if sticky, add backdrop or shadow on scroll
**Mobile:** Hamburger menu OR a horizontal scroll of categories. Don't hide navigation behind gestures users don't know.
### Side nav (for apps, dashboards)
- **Width:** 240280px (collapsible to 5664px)
- **Sections:** grouped by purpose, with section labels
- **Active state:** clear visual — background tint or accent border on left edge
- **Icons:** 1620px, single weight stroke, paired with labels
- ❌ Don't make icon-only navigation without tooltips
### Breadcrumbs
- Small, muted, 1314px
- Separator: `/` or ``, in `--ink-subtle`
- Last item: `--ink`, no link
- ❌ Don't make breadcrumbs interactive if the parent pages don't exist
### Footer
- **Layout:** can be 4-column (product / company / resources / legal) OR a single editorial line OR a technical mono footer with metadata
- **Tone:** smaller type (1314px), muted
- **Content:** links + small print + small brand mark + maybe a single line of brand voice
- ❌ Don't fill it with content just to fill it
- ❌ Don't use the footer as a primary navigation surface
---
## Tables
Tables are for data. If it's not data, don't use a table.
### Style
- **Header row:** slightly different background, `--ink-muted`, smaller text (1213px), often uppercase with tracking
- **Cells:** 1216px vertical padding
- **Borders:** bottom-only hairlines between rows, not full grid
- **Numbers:** monospace font, tabular figures, right-aligned
- **Hover row:** subtle background (`--surface-sunken`) for readability in long tables
- **Actions:** last column, icon buttons or text links
### Table anti-patterns
- ❌ Full grid of borders (looks like Excel)
- ❌ Centered text in data cells (left-align text, right-align numbers)
- ❌ Wrapping headers (use shorter labels)
- ❌ Inconsistent row heights (vary them carefully)
---
## Badges & Tags
### Badges
Small inline labels. Two types:
- **Status badges:** rounded pill (46px radius), small (1012px text), color-coded for state
- **Categorical badges:** rectangular or pill, neutral background, used for taxonomy
### Rules
- ❌ Don't use too many colors — limit to 23 states plus neutral
- ❌ Don't make badges too large (they're punctuation, not headlines)
- ❌ Don't make every list item have a badge — most shouldn't
```css
.badge {
display: inline-flex;
align-items: center;
height: 20px;
padding: 0 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
background: var(--surface-sunken);
color: var(--ink-muted);
}
.badge--success { background: #DCFCE7; color: #14532D; }
.badge--warning { background: #FEF3C7; color: #78350F; }
.badge--error { background: #FEE2E2; color: #7F1D1D; }
```
---
## Avatars
- **Sizes:** 24px (inline), 32px (list), 40px (comment), 64px (profile), 96px (hero)
- **Shape:** circle by default; rounded square OK in some contexts
- **Fallback:** initials on a muted background, in mono or display type
- **Image:** always set `alt` (use empty `alt=""` for decorative)
- **Border:** optional 1px hairline if on a similar-colored background
---
## Empty / Loading / Error States
These are where amateurs stop and pros begin. **Always design them.**
### Empty state
- Centered or left-aligned
- Single sentence explaining why it's empty
- One action to fix it ("Create your first project")
- Optional small illustration or icon — restrained
### Loading state
- Skeleton: same layout as loaded content, animated shimmer or pulse
- Spinner: only for short waits (<2s), centered
- Progress: for long operations, with meaningful stages
- Don't show a spinner for under 200ms it flashes and feels broken
### Error state
- What happened, in plain language
- What the user can do
- A way to retry or contact support
- Don't show raw error messages (`"TypeError: undefined is not a function"`)
- Don't use a sad emoji or stock illustration of someone frustrated
### 404 page
- A real, designed page not the default server one
- One clear explanation ("This page doesn't exist.")
- A way back (link to home, search bar, navigation)
- An opportunity for voice: a small editorial moment, a real photo, a piece of brand personality
- Don't use a 404 page as a place to be clever at the expense of utility
---
## Tooltips & Popovers
- Appear on hover (desktop) or tap (mobile)
- Disappear on escape, on click outside, on scroll
- Maximum 2 lines of text
- Background: `--ink` with white text OR `--surface-elevated` with a stronger shadow
- Animation: fade-in 100ms, no movement
- Always include an arrow pointing to the trigger (unless context makes it obvious)
- Don't put interactive content inside a tooltip (use a popover for that)
- Don't show tooltips on touch devices (they don't have hover)
---
## Modal / Dialog
- Centered, max-width 480560px for forms, larger for content
- Backdrop: `rgba(0, 0, 0, 0.40.6)` enough to focus, not so much it blacks out
- Surface: `--surface-elevated`
- Border-radius: 12px (or match cards)
- Padding: 2432px
- Close: visible X button (top-right) AND `Escape` key
- Focus trap: keyboard focus stays inside the modal
- Scroll: inside the modal if content overflows
- Animation: fade + slight scale (0.98 1), 150ms
---
## Component Checklist (before shipping)
For every component on the page, verify:
- [ ] Default state is designed
- [ ] Hover state is defined
- [ ] Focus-visible state is defined (and looks intentional)
- [ ] Active / pressed state is defined
- [ ] Disabled state is defined
- [ ] Loading state (if async)
- [ ] Empty state (if data-driven)
- [ ] Error state (if forms or data)
- [ ] Keyboard accessible (Tab, Enter, Escape)
- [ ] Screen reader labels present (`aria-label` where needed)
- [ ] Mobile breakpoint at 480px and 768px
- [ ] Touch targets at least 44×44px on mobile