| name | frontend-design |
| description | Use whenever building, restyling, or reviewing any in-house web UI — a new screen, a component, a marketing page, a dashboard, an empty state, anything with visual surface. Reach for this the moment you are about to pick a font, color, spacing value, shadow, or radius, so the output uses the house design system instead of generic defaults. Especially load this if the work is drifting toward the templated "AI-generated app" look (Inter + a purple/indigo gradient + heavy drop shadows + `#fff` on `#f9fafb`) — this skill exists to push that off the table and replace it with concrete house tokens. |
frontend-design
Overview
This is the house web design system. It exists for one reason: to make UI look intentional and ours, not like a generic component-library starter. Most defaults a model reaches for — the Inter typeface, a purple→blue gradient hero, border-radius: 0.5rem everywhere, soft gray-on-gray cards with big blurry shadows — are exactly the "AI template" signature this system avoids. Below are the actual tokens, type ramp, spacing scale, elevation, and component conventions. Use these values, not your instinct.
When to Use
Use this skill when you are:
- Creating any new screen, view, page, or component.
- Restyling or refactoring existing UI, or porting a mockup to code.
- Choosing typography, color, spacing, radius, shadow, or motion.
- Building empty states, error states, forms, tables, or marketing/landing surfaces.
- Reviewing a diff that adds or changes visual styling.
Do NOT use this for: pure logic/data-layer work with no visual surface, third-party embedded widgets you don't control, or contexts where a different brand's design system is explicitly mandated. If another design system is named for the task, that one wins — this is the in-house default.
Hard "don'ts" (the generic-AI look)
These are the tells. Avoid them on sight:
- No Inter, no Roboto, no system-ui as the brand face. See the type stack below.
- No purple/indigo/violet gradients (
#6366f1, #8b5cf6, from-purple-500 to-indigo-600, etc.). Gradients in general are reserved; see Color.
- No big soft drop shadows as the primary depth cue (
0 10px 25px rgba(0,0,0,.15) and friends). Elevation is mostly borders + tiny shadows.
- No
#ffffff cards floating on #f9fafb. Surfaces are differentiated by the token roles below, not by near-white-on-near-white.
- No emoji as UI iconography, no center-everything hero with a single CTA and a gradient blob.
Design tokens
Color roles (light)
Use roles, never raw hex inline. The palette is a warm-neutral base with a single confident teal accent — not blue, not purple.
| Role | Token | Hex | Use |
|---|
| Background | --bg | #FBFAF7 | App canvas (warm off-white, not gray). |
| Surface | --surface | #FFFFFF | Cards, sheets, raised panels. |
| Surface sunken | --surface-sunken | #F2F0EA | Wells, inset fields, code blocks. |
| Border | --border | #E4E0D6 | Default hairline; the primary depth cue. |
| Border strong | --border-strong | #CFC9BB | Dividers, focused field outline. |
| Text primary | --text | #1F1D18 | Body and headings (near-black, warm). |
| Text secondary | --text-muted | #6B665B | Captions, metadata, placeholder. |
| Accent | --accent | #0E7C6B | Primary actions, links, active state (deep teal). |
| Accent hover | --accent-hover | #0A6357 | Hover/pressed for accent. |
| Accent subtle | --accent-subtle | #DCEFEA | Accent backgrounds, selected rows, badges. |
| Success | --success | #2F7D32 | Positive status. |
| Warning | --warning | #B8860B | Caution (amber, not yellow). |
| Danger | --danger | #B23A2E | Destructive actions, errors. |
Color roles (dark)
Dark mode is a paired remap of the same roles, not an inversion. Every role above has a dark counterpart; never hardcode a light hex in a dark context.
| Role | Hex (dark) |
|---|
--bg | #16140F |
--surface | #211E18 |
--surface-sunken | #100E0A |
--border | #332F26 |
--border-strong | #4A4538 |
--text | #EDE9DF |
--text-muted | #A39D8E |
--accent | #3FB89F (lightened so it stays AA on dark surface) |
--accent-subtle | #16332D |
Accent must be lighter in dark mode — the light-mode #0E7C6B fails contrast on a dark surface. Pair every token; test both modes.
Typography
Type stack: "Söhne", "Georgia" is NOT the house face — use the system's two real families:
- Display / headings:
"Tiempos Headline", "Charter", Georgia, serif — a serif, deliberately, to break from the sans-default look.
- Body / UI:
"Söhne", "Untitled Sans", -apple-system, sans-serif — a grotesk with character, never Inter.
- Mono:
"Söhne Mono", "JetBrains Mono", ui-monospace, monospace.
If those licensed faces are unavailable in the environment, fall back to Georgia (headings) + a non-Inter grotesk like "Public Sans" (body) — still not Inter.
Type ramp (size / line-height / weight / tracking):
| Token | Size | Line height | Weight | Family |
|---|
display | 48px / 3rem | 1.05 | 500 | serif |
h1 | 32px / 2rem | 1.15 | 500 | serif |
h2 | 24px / 1.5rem | 1.2 | 500 | serif |
h3 | 19px / 1.1875rem | 1.3 | 600 | sans |
body-lg | 17px | 1.6 | 400 | sans |
body | 15px | 1.6 | 400 | sans |
caption | 13px | 1.4 | 500 | sans |
mono | 13.5px | 1.5 | 400 | mono |
Headings are serif and use weight 500 (not 700 bold). Body is 15px/1.6 — generous leading. Don't crank heading weights to 700/800; that reads as the template look.
Spacing scale
A 4px base, non-linear scale. Only use these steps — arbitrary 13px/22px margins are the fastest way to look unsystematic.
--space-0:0 · 1:4px · 2:8px · 3:12px · 4:16px · 5:24px · 6:32px · 7:48px · 8:64px · 9:96px
Component padding: buttons 8px 16px (space-2 space-4), cards 24px (space-5), section gaps 48–96px (space-7/space-9). Snap everything to the scale.
Radius
Restrained, consistent: --radius-sm:4px (inputs, badges) · --radius-md:8px (buttons, cards) · --radius-lg:14px (modals, sheets) · --radius-full:9999px (pills/avatars only). No 0.5rem-everywhere; pick by component role above.
Elevation
Depth comes from borders first, then a small shadow — never a big blur.
--elev-0: flat, 1px --border. Default for cards.
--elev-1: 1px border + 0 1px 2px rgba(31,29,24,.06). Hover/raised.
--elev-2: 1px border + 0 4px 12px rgba(31,29,24,.10). Popovers, dropdowns.
--elev-3: 1px border-strong + 0 12px 32px rgba(31,29,24,.16). Modals only.
A card is a border + --surface, not a shadow on white. Reserve elev-3 for true overlays.
Motion
Fast and quiet: 120ms for hover/press, 200ms for enter/leave, easing cubic-bezier(.2,0,0,1). No bounce, no long fades. Respect prefers-reduced-motion — drop transforms, keep opacity.
Component conventions
- Buttons: primary =
--accent bg, --surface text, radius-md, 8px 16px, weight 600. Secondary = --surface bg + 1px --border-strong + --text. Destructive = --danger. No gradients, no uppercase, no full-width unless mobile.
- Inputs:
--surface-sunken bg, 1px --border, radius-sm, focus = 1px --accent ring + --border-strong. Label above (caption token), help text below in --text-muted.
- Cards:
--surface, 1px --border, radius-md, 24px padding, --elev-0. Hover to --elev-1 only if interactive.
- Links:
--accent, underline on hover, never purple visited state — keep the role color.
- Tables: no zebra-stripe-on-white; header in
--text-muted caption, rows separated by --border hairlines, selected row --accent-subtle.
- Empty states: serif
h2 line + one body sentence + a single secondary button. No illustration blob, no emoji.
Gotchas
ALWAYS check these before calling UI "done" — each is a real way this system gets violated.
-
Contrast must hit WCAG AA (4.5:1 body text, 3:1 large text / UI borders). --text-muted (#6B665B) on --surface passes; it does NOT pass on --accent-subtle or as placeholder on a colored field — verify. --warning (#B8860B) is borderline as text; use it for icons/borders, put warning text in --text. Never ship a color pair you didn't check.
-
Dark mode is a token remap, not filter: invert or a hardcoded light hex in a .dark block. The #1 dark-mode bug here is reusing the light --accent (#0E7C6B) on a dark surface — it fails contrast. Every role has a dark value (see table); reference the token, never a literal hex, so both modes resolve correctly. Always preview both modes before finishing.
-
Spacing must snap to the scale — margin: 13px/padding: 22px is the tell that betrays the system. When a value "looks right" off-scale, round to the nearest step (12px or 16px). Mixed off-scale spacing is what makes UI feel generated rather than designed. Same for radius: pick sm/md/lg by component role, don't sprinkle 0.5rem.
-
The generic-AI look creeps back in through defaults, not decisions. A copied snippet brings Inter, a from-indigo-500 gradient, shadow-xl, and rounded-lg with it. After writing any UI, scan the diff specifically for: Inter/Roboto/system-ui as the face, any purple/indigo/violet, gradient backgrounds, shadow values with blur > 12px as the primary depth, and #fff-on-#f9fafb. Replace each with the house token. If the screen could be any AI demo, it's not done.
-
Headings are serif and weight 500 — not sans-bold-700. Reaching for font-weight:700 on a sans heading is the muscle-memory default this system rejects. Use the serif display/h1/h2 tokens; let the typeface, not the weight, carry the heading.
-
Accent is teal, singular. Don't introduce a second accent hue. Status colors (success/warning/danger) are not accents — don't use blue for links and teal for buttons. One accent role across links, primary actions, focus, and selection keeps it coherent.
Files
SKILL.md (this file) — the complete house design system: tokens, type ramp, spacing, elevation, component conventions, and the anti-patterns to avoid. All guidance is inline; there is no separate reference to load.