frontend-design
nolly-studio/cult-ui/.agents/skills/frontend-design/SKILL.md
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.
Skill6.4k starsChanged 8 days ago
What's in it
- Direction
- Typography
- Surfaces
- Motion
- Review checklist
---
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
More agent context in nolly-studio/cult-ui
5 other files this repository gives its agents.
Cursor rule
Skill
- components-build.agents/skills/components-build/SKILL.md
- fixing-motion-performance.claude/skills/fixing-motion-performance/SKILL.md
- fixing-motion-performance.cursor/skills/fixing-motion-performance/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
Reports can't be read right now.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

