Craft doctrine for building distinctive, polished frontend interfaces. Use when building or reviewing web UI — components, pages, layouts, styling, animation. Covers design direction, typography, surfaces, and motion. Defers to the repo's DESIGN.md for all project-specific choices.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of Frontend Design?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/maxtrigify-frontend-design)
---
name: frontend-design
description: Craft doctrine for building distinctive, polished frontend interfaces. Use when building or reviewing web UI — components, pages, layouts, styling, animation. Covers design direction, typography, surfaces, and motion. Defers to the repo's DESIGN.md for all project-specific choices.
---
<!-- generated by design-md v1.1.0 -->
Great interfaces are a collection of small, intentional details that compound. Apply this doctrine when building or reviewing any UI.
**DESIGN.md wins.** If this repo has a `DESIGN.md`, its fonts, tokens, budgets, and recipes override everything below — this skill covers the craft layer underneath any design language. Express every change in the styling system the project already uses (Tailwind, CSS Modules, styled-components, plain CSS); never introduce a second one for a fix.
## Direction
Before coding a new surface, commit to a clear aesthetic point of view — the repo's `DESIGN.md` if one exists, otherwise a deliberate choice for the context. Intentionality matters more than intensity: refined minimalism and bold maximalism both work; timid, evenly-distributed defaults do not. Never ship generic AI aesthetics: stock font stacks chosen by habit, purple-gradient-on-white, cookie-cutter card grids with no context-specific character. Match implementation effort to the vision — maximalist designs need elaborate effects, minimal ones need precision in spacing and type.
## Typography
Good typography is mostly restraint: a sensible scale, comfortable spacing, enough contrast.
- **Few fonts, few weights.** Rarely more than two or three families. Pair for contrast (display vs body), not similarity. Hierarchy comes from size and weight used sparingly.
- **Scale, not one-offs.** Sizes come from the project's type scale. Heading sizes descend with level — pick the tag from the document outline, control size with CSS, never skip levels.
- **Line-height by role.** Headings ~`1.1`, body `1.5`–`1.6`, always unitless.
- **Letter-spacing by size.** Slightly negative on large headings, slightly positive on small uppercase labels, neither on body.
- **Cap the measure.** Long-form text at 60–75 characters per line (`max-w-2xl` at 16px body, or `65ch`).
- **Wrap deliberately.** `text-wrap: balance` on headings, `text-wrap: pretty` on descriptions; neither in long-form text.
- **Tabular numbers on anything that changes.** `tabular-nums` on timers, counters, prices, table columns.
- **Natural case in copy.** Store text in sentence case; uppercase via `text-transform` only where the design language sanctions it.
- **Mechanics.** `.woff2` for web fonts. CSS properties over raw tags (`font-weight: 650`, `font-variant-numeric: tabular-nums` — not `font-variation-settings`/`font-feature-settings`). `font-synthesis: none` so missing weights fail visibly. `antialiased` once on the root layout. Inputs ≥ `16px` on mobile (`text-base sm:text-sm`) or iOS zooms. Body ≥ `16px`; contrast ≥ `4.5:1` (`3:1` for large text).
## Surfaces
- **Concentric radius.** When nesting rounded elements: `outer = inner + padding`. Matching radii on parent and child is the most common thing that makes UI feel subtly off. Derive radii from the project's radius token where one exists.
- **Shadows over borders.** Layered transparent `box-shadow` (1px ring + tight lift + soft ambient) adapts to any background and animates smoothly; flat borders do neither. Use the project's shadow utility if it has one; otherwise:
```
shadow-[0px_0px_0px_1px_rgba(0,0,0,0.06),0px_1px_2px_-1px_rgba(0,0,0,0.06),0px_2px_4px_0px_rgba(0,0,0,0.04)] dark:shadow-[0px_0px_0px_1px_rgba(255,255,255,0.06),0px_1px_2px_-1px_rgba(255,255,255,0.03),0px_2px_4px_0px_rgba(0,0,0,0.2)]
```
Strengthen the same layers for hover/selected. Extract a shared component after the third repetition, with `transition-shadow duration-200`.
- **Image outlines.** Inset `1px` outline on images/media for separation: pure black at 10% in light mode, pure white at 10% in dark — never a tinted neutral, which reads as dirt on the edge. `[outline:1px_solid_rgba(0,0,0,0.1)] [outline-offset:-1px]`.
- **Optical alignment.** When geometric centering looks off (icon+text buttons, play triangles), nudge until it *looks* centered; the best fix is the SVG viewBox itself.
- **Hit areas.** ≥ 44×44px for touch, ≥ 40×40px on desktop. Extend with a pseudo-element if the visible control is smaller; never overlap two hit areas.
## Motion
- **Interruptible by default.** CSS transitions and springs retarget mid-flight; keyframes don't. Transitions/springs for interactive states (hover, toggle, dismiss), keyframes only for one-shot sequences (page load). With a motion library, prefer `type: "spring", bounce: 0`.
- **Split and stagger enters.** Don't animate one container — break content into semantic chunks with ~60–100ms stagger, combining `opacity`, small `translateY`, and `blur`:
```css
@keyframes enter {
from { opacity: 0; transform: translateY(8px); filter: blur(4px); }
}
.animate-enter {
animation: enter 600ms cubic-bezier(0.2, 0, 0, 1) both;
animation-delay: calc(80ms * var(--stagger, 0));
}
```
- **Exits subtler than enters.** Small fixed `translateY` (~`-12px`) + blur + fade — never a full reversal of the enter path, never zero motion.
- **Icon swaps.** Cross-fade in place with opacity/scale/blur (scale `0.25→1`, blur `4px→0`); `AnimatePresence mode="popLayout"` with a spring, or an absolute-positioned CSS cross-fade without a library.
- **Press feedback.** `active:scale-[0.96]` on buttons — never below `0.95`.
- **No animation on first paint** for default-state elements: `initial={false}` on `AnimatePresence`.
- **Name transition properties.** Never `transition: all` — list exact properties. `will-change` only for `transform`/`opacity`/`filter`, only after observing first-frame stutter.
- **Respect `prefers-reduced-motion`** on every entrance and press effect.
## Review checklist
- [ ] Fonts, tokens, and budgets match `DESIGN.md` (if present)
- [ ] Sizes from the type scale; headings descend with level; line-height unitless by role
- [ ] `balance` on headings, `pretty` on descriptions, measure capped on long-form
- [ ] `tabular-nums` on changing numbers; `antialiased` on root; inputs ≥ 16px on mobile
- [ ] Concentric radii on nested surfaces
- [ ] Layered shadows (or the project's shadow utility) instead of flat borders
- [ ] Images have the 1px untinted outline
- [ ] Icon+text pairs optically aligned; hit areas ≥ 40/44px
- [ ] Enters split and staggered; exits subtler; interactive motion interruptible
- [ ] `active:scale-[0.96]` on primary press targets; reduced-motion honored
- [ ] No `transition: all`; `will-change` rare and specific