UX/UI Design Engineering Skill

33 design engineering principles for building interfaces that feel excellent — from font rendering and OKLCH color to optical alignment, hit areas, and animation timing.

Most UI built by AI assistants today reads as "AI slop" — generic shapes, default radii, default shadows, default animations. The output works. It doesn't feel made.

A seasoned UX/UI Design Engineer would close that gap with dozens of small choices most teams (and most agents) never make. This skill packages 33 of those choices across six areas — typography, color, surfaces and depth, visual details, animations, and performance — into an instruction set Claude Code, Cursor, and other AI coding assistants load when you ask for UI work.

$ npx skills add ux-ui-design-engineering
Source on GitHubandrzejdelgado/ux-ui-design-engineering

How it works

Two modes for the agent, plus the architecture that keeps each call cheap.

Building from scratch

When you ask the agent to build a new component, page, or app, it applies all 33 principles directly — concentric radii on nested elements, two-layer elevation shadows, OKLCH color tokens, hit areas at the 40×40 floor, scroll-driven enter animations, compositor-only motion. The output reads like work from a seasoned UX/UI Design Engineer.

Auditing existing work

When you point the agent at existing code or a finished design, it walks the file against the same 33 principles and reports back: what's off, why it matters, and the smallest change that fixes it. Returns a Before/After diff per topic — no speculative refactors, no rewrites of things that already work.

The routing trick

Under the hood, the skill is split into a thin instruction file plus six topic-specific references. The agent reads only the references that match the task — a shadow question doesn't pull in animation timing, a gradient question doesn't pull in z-index conventions. Less context loaded, faster answers, lower cost per call.

Operating protocol

An operating discipline sits underneath, in the spirit of Andrej Karpathy's principles for AI-assisted work:

  • Don't assume, don't hide confusion, surface tradeoffs
  • Minimum code that solves the problem
  • Touch only what you must
  • Define success criteria, loop until verified, then stop

The agent applies this whether it's building or auditing — no scope creep, no speculative abstractions, no drive-by refactors.

Typography

01Typography

Font Rendering

The browser default subpixel-antialiased uses RGB channel hinting, making text appear heavier and less crisp — most visible on dark backgrounds. -webkit-font-smoothing: antialiased switches to grayscale antialiasing on macOS, producing thinner, sharper strokes. Add text-rendering: optimizeLegibility to enable kerning pairs and ligatures. These properties have no effect on Windows where ClearType handles rendering at the OS level.

Subpixel rendering

Browser default. RGB channel hinting fattens stroke edges, making weight feel heavier than intended against dark backgrounds.

The slow river bends past stones at dusk.

abcdefghijklmnopqrstuvwxyz · 0123456789

v2.4.1 · $1,249.99 · 0xff0080

Grayscale antialiasing

Refined treatment. Thinner, sharper strokes — the same weight reads as more deliberate against dark backgrounds.

The slow river bends past stones at dusk.

abcdefghijklmnopqrstuvwxyz · 0123456789

v2.4.1 · $1,249.99 · 0xff0080

02Typography

Typography System

Font size creates scale. line-height, letter-spacing, and font-weight create hierarchy. A heading and a body paragraph set to the same weight and line-height will read as the same importance regardless of their size difference.

Each context has different optical needs. Labels are read in a glance — they need tighter line-height and slightly open tracking. Body text needs breathing room. Display headings need compressed tracking to hold together visually at large sizes.

display

Type scale

heading

Section heading

body

Body text reads comfortably at 1.7 line-height with no tracking adjustment. The generous space between lines lets the eye move from the end of one line to the beginning of the next without effort. Tighter line-height — 1.3 or 1.4 — works for single-line labels but causes lines to feel stacked and compressed in paragraph form.

label

Label / caption context

03Typography

Line Length Constraint

The optimal range for comfortable reading is 45–90 characters per line. Below 45, the eye changes direction so frequently reading becomes choppy. Above 90, the reader struggles to find the start of the next line — the eye drifts and loses its place.

For a single-column layout, target 65–90 characters. The ch unit is relative to the width of the 0 glyph in the current font, so the constraint scales correctly when users change their browser font size — unlike px values.

Drag the slider — the optimal range is highlighted on the track.

30ch95ch · too wide100ch

Good interfaces rarely come from a single thing. It is usually a collection of small, compounding details — typography that breathes, shadows that follow the light, color that holds together across themes. Each detail is invisible in isolation, but together they create an experience that feels effortless and considered. When a line stretches this wide, the eye has to travel so far right that finding the start of the next line becomes a small but real cognitive task.

04Typography

Baseline Alignment

When text elements of different sizes share a row, aligning them to their vertical center creates a visible float. The smaller element appears to hover above the line. Align to the shared baseline instead — the eye reads the row as a single visual unit.

Pricing
$179.99/ mo
Active users
458customers
Trend
12.5%vs last quarter
Pricing
$179.99/ mo
Active users
458customers
Trend
12.5%vs last quarter

05Typography

Fluid Type Scale

Media query breakpoints produce abrupt size jumps between fixed values. clamp() interpolates smoothly across the full viewport range — no jumps, no breakpoints. Always include a rem-based component alongside vw; vw alone breaks when users increase their browser font size, failing accessibility requirements.

Drag the handle — every sample scales smoothly with the container width.

Type scaledisplay

Section headingheading

Subheadingsubhead

Body textbody reads comfortably at 1.7 line-height with no tracking adjustment. The generous space between lines lets the eye move from the end of one line to the beginning of the next without effort. Tighter line-height — 1.3 or 1.4 — works for single-line labels but causes lines to feel stacked and compressed in paragraph form.

Label / caption contextlabel

320px0px0px

06Typography

Font Feature Settings

Controls advanced typographic features in OpenType fonts. Whenever possible, prefer the higher-level font-variant shorthand or its longhands — font-variant-caps, font-variant-numeric, font-variant-ligatures — which produce more predictable results. Reach for font-feature-settings only when no other way exists to enable a specific OpenType feature. Each tag is a four-character string, optionally followed by a positive integer or on/off.

Reach for "tnum" (or the equivalent font-variant-numeric: tabular-nums) and "ss02" — Inter's disambiguation stylistic set that produces the slashed zero — wherever digits need to line up vertically or change in place without reflowing the layout: tables and data grids, dates and timestamps, prices and currency, counters and timers, diff views, code and log line numbers, numeric form inputs (phone, card, OTP, quantity), and chart axes or metric tiles. Skip it for body prose with the occasional number — proportional figures read more naturally there.

The default rendering. No OpenType features are explicitly toggled, so every glyph appears in the typeface's standard form.

ffiffl0O3.1402.710

07Typography

Text Decoration Refinement

Browser default underlines sit on the baseline and clip through descenders. Three properties fix this: text-decoration-thickness controls weight, text-underline-offset lifts it clear of the descender zone, and text-decoration-skip-ink breaks the line where it would intersect a glyph. All three are font-relative — they scale at any size.

A paragraph about jumping foxes, quirky typography, and paragraph rhythm — notice how the underline cuts straight through the descenders of j, p, q, g, and y. The same issue spreads across any link long enough to wrap across multiple lines, dragging its underline through every g and y in its path — every dropped tail, every angled descender picks up a horizontal slash.

08Typography

Optical Sizing

Variable fonts with an optical size axis (opsz) automatically adjust stroke contrast, x-height, and spacing based on the rendered size. A 12px label and a 64px display heading from the same typeface will each use the most legible version of themselves — not the same design scaled differently. Demo uses Roboto Flex, which exposes the axis between 8 and 144.

The optical-size axis is pinned to a single value, so every size renders from the same design — display sizes look slightly stiff, captions slightly fragile.

AaThe quick brown fox jumps over the lazy dog at 12 px caption size.

Color

09Color

Saturated Neutrals

Pure mathematical grays share no hue with any other color in the palette. Next to a saturated brand color they read as foreign — a different material placed inside the design. Infusing grays with a slight tilt toward the dominant hue (under 5% chroma in OKLCH) creates cohesion without visible color.

200
400
600
brand
0%1.8% · cohesive6%

10Color

OKLCH for Perceptual Color

HSL lightness is not perceptually uniform. A yellow at L 60% appears far brighter than a blue at L 60% — equal numbers, unequal perception. OKLCH is perceptually uniform: equal numerical steps in lightness produce equal visual steps across all hues. It also natively accesses the P3 gamut on modern displays.

The scale below shows 6 equidistant lightness steps in HSL vs OKLCH. Notice how the HSL steps feel uneven — the midtones cluster together.

HSL
OKLCH

11Color

Gradient Refinement

Refined gradients are not single linear blends. Color space defines the path between two stops; sRGB drags you through muddy mid-greys at the midpoint, OKLAB stays in pastel territory, and OKLCH longer hue walks the hue wheel itself.

Beyond color space, a small set of techniques does the real work: color-stop hints bias the perceptual midpoint without adding a third color, extra stops or grain overlays kill banding in subtle dark gradients, stacked radial gradients turn a flat backdrop into mesh-like ambient light, hard stops paint clean splits without extra elements, and conic gradients deliver progress rings and dial UI without SVG.

sRGB drags through muddy mid-grey at the midpoint, OKLAB stays in pastel territory, and OKLCH longer hue walks the hue wheel itself.

default sRGB · muddy at the midpoint
in oklab · pastel-clean midpoint
in oklch longer hue · walks the wheel

12Color

Container Brightness Limits

In dark UIs, surfaces create depth through brightness. But too much brightness difference makes containers compete with content. Too little and the separation disappears. The ceiling for dark UIs is 12% brightness difference; for light UIs, 7%.

Dark UI · 12% ceiling
Revenue↑ 8.2%
$12,486.00
Up from $11,540 last month — driven by enterprise renewals.
Deployment● Live
build-prod-2486 promoted
Verified by 4 checks · Rolled out to 100% of traffic 2 minutes ago.
Revenue↑ 8.2%
$12,486.00
Up from $11,540 last month — driven by enterprise renewals.
Deployment● Live
build-prod-2486 promoted
Verified by 4 checks · Rolled out to 100% of traffic 2 minutes ago.
Light UI · 7% ceiling
Revenue↑ 8.2%
$12,486.00
Up from $11,540 last month — driven by enterprise renewals.
Deployment● Live
build-prod-2486 promoted
Verified by 4 checks · Rolled out to 100% of traffic 2 minutes ago.
Revenue↑ 8.2%
$12,486.00
Up from $11,540 last month — driven by enterprise renewals.
Deployment● Live
build-prod-2486 promoted
Verified by 4 checks · Rolled out to 100% of traffic 2 minutes ago.

Surfaces & Depth

13Surfaces

Concentric Border Radius

When elements are nested inside rounded containers, their border radii must be concentric — not equal. Equal radii on parent and child create a gap where the corner curves diverge. The formula is simple: outer radius equals inner radius plus the padding between them.

A card with border-radius: 20px and 8px padding needs an inner button at border-radius: 12px. Mismatched radii on nested elements is the single most common detail that makes interfaces feel unfinished.

inner 8px
inner 16px
0px8px·padding32px

14Surfaces

Optical Over Geometric Alignment

When geometric centering looks visually off, trust your eye over the coordinates. A button with text plus a heavy trailing icon is the classic case: equal padding on both sides makes the content drift right, because the icon carries more visual weight than the empty space on the left. Compensate by giving the side with the lighter element a few extra pixels of padding — the content then reads as truly centered.

Geometric
2424·
2424·
1616·
Optical
2024+4
2024+4
1216+4

15Surfaces

Minimum Hit Area

Every interactive element needs at least 40×40px of tappable area. Small icon buttons — close icons, nav toggles, inline actions — are almost always under this threshold. Extend with a pseudo-element so the visible design and the interaction boundary are independent.

24 × 24 hit areahover near the icon
Click target collapses to the visible 24×24 — fingers and small cursors miss often.
40 × 40 hit areahover near the icon
::before pseudo-element pads the click target out to a 40px square — visible icon stays 24px.

16Surfaces

Elevation Shadow

A natural elevation shadow follows two rules. Blur is 2× the offset distance0 4px 8px reads as natural lift, 0 4px 4px reads as flat, 0 4px 24px reads as a glow. And stack a sharp directional layer with a soft diffuse layer — a single box-shadow looks digital, two layers mimic the way light actually behaves on a raised surface. Both layers should respect the 2× rule, and both should scale up proportionally on hover so the surface feels like it's rising further from the page.

Single layer · blur = offset
Revenue↑ 8.2%
$12,486.00

Up from $11,540 last month — driven by enterprise renewals.

Stacked layers · blur = 2× offset
Revenue↑ 8.2%
$12,486.00

Up from $11,540 last month — driven by enterprise renewals.

17Surfaces

CSS Filter Color Override

Third-party components, embedded SVGs, and icon fonts sometimes make direct color manipulation impossible. CSS filter combinations can tint any element to a target color when color and fill are inaccessible. Use a filter generator tool to calculate the exact sequence — this is precise, not approximate.

Notifications5 new
  • File sync complete

    design-system.fig synced to Cloud — 2.4 MB

    2m ago
  • Build deployed

    build-prod-2486 promoted to production

    just now
  • Storage almost full

    92% of 50 GB used — consider archiving older projects

    5m ago
  • Sign-in from new device

    macOS · Tucson, AZ — wasn't you?

    23m ago

18Surfaces

Inset Shadow for Pressed States

An inset shadow communicates physical depression — the element appears to push into the surface when activated. Combined with a subtle scale, it gives button presses genuine tactile feedback. Press the button below to feel the difference.

rest · stacked outer shadow

19Surfaces

Elevation + Z-Index Scale

Hardcoded z-index values create stacking conflicts that compound as a codebase grows. A named token scale with stepped increments solves this: gaps allow future layers to be inserted without rewriting the entire stack. An element inside a stacking context can never visually exceed its parent — use isolation: isolate on component roots to contain their internal stacking without polluting the global space.

AtlasSearch workspace…
Revenue · this month
$48,210.00

Up 14% vs last month, driven by enterprise renewals across three regions.

// scroll the frame · the toolbar sticks
// content keeps flowing
// while the header stays pinned
// stacking order is named, not magic numbers
// gaps allow new layers to slot in cleanly
Card actions
Duplicate⌘D
Rename⌘R
Move to…⌘⇧M
Delete

Delete this report?

This action cannot be undone. The report and all attached snapshots will be permanently removed from the workspace.

Snapshot savedAvailable in Reports → Archived.
dismiss layers · then reset

Visual Details

20Visual Details

De-emphasize to Emphasize

Creating visual hierarchy is more about reducing the prominence of secondary elements than increasing the prominence of primary ones. Lower the contrast, weight, and size of supporting content — the primary content reads as more important because the differential increased, not because it got louder.

Mira CaldwellStaff design engineer · AtlasBrooklyn, NY
24Projects
1.2kFollowers
318Following
typographymotiontokensinteractions
Mira CaldwellStaff design engineer · AtlasBrooklyn, NY
24Projects
1.2kFollowers
318Following
typographymotiontokensinteractions

21Visual Details

Cursor Affordance

The cursor is a free affordance signal. It communicates intent before the user commits to an interaction. Mismatching cursor to element behavior creates a moment of uncertainty that erodes trust. Hover each element below — the cursor tells you exactly what it does before you click.

pointerClickable button
Email…
textEditable input
Reorder
grabDrag handle
not-allowedDisabled action
ew-resizeResize handle
helpHelp tooltip

22Visual Details

Icon Weight Reduction

Icons rendered at the same opacity as adjacent text appear optically heavier. Text is composed of thin strokes with generous negative space. Icons — even outline-style — have more filled area. At full opacity next to a label, the icon dominates. Reducing to 80–90% opacity balances the pair without making the icon invisible.

Animations

23Animations

Skip Animation on Page Load

Enter animations are triggered by user interaction — opening a panel, navigating to a view, adding an item. Running them on the initial page render makes the interface feel unstable. The two side-by-side dashboards below let you compare: Reload page simulates a fresh navigation, and Open dialog simulates a user-driven interaction. The bad page replays its enter animation on every reload; the good page only animates the dialog when the user explicitly opens it.

Animates on load
Revenue$48,210
Sessions3,184
Conversion4.2%
Calm on load
Revenue$48,210
Sessions3,184
Conversion4.2%

24Animations

Animation Duration Standards

Duration should match complexity. Simple state changes need to feel instant. Complex enter sequences need room to breathe. Anything over 400ms makes the interface feel sluggish — the user has stopped and is waiting for the UI to catch up with them.

100ms
instant
200ms
snappy
300ms
smooth
400ms
limit
800ms
sluggish
past 400ms reads as waiting

25Animations

Ease-out for Interactive

Easing communicates physics. ease-out — fast start, slow deceleration — mimics an object responding immediately to input and settling gradually. This gives the impression of instant response. ease (slow start) feels like the interface is warming up before it moves.

linear
ease-in
ease
ease-out
cubic-bezier(0, 0, 0.3, 1)
ease-out feels instant; ease-in feels late

26Animations

Scroll-Driven Animations

Elements can animate as they enter the viewport using the native CSS scroll timeline API — no JavaScript, no IntersectionObserver, no library. Scroll the container below to see it in action. Chrome 115+, Firefox 110+.

atlas.app/feed
Latest activity12 events · last hourAuto-refreshed 30s ago
system
Pinned reportsQ3 revenue summaryPinned by Mira · 2d ago
pinned
System statusAll services operationalLast health check 5m ago
healthy
Fade-uptranslate-y · opacityDesign tokens released · 8m ago
shipped
Slide-intranslate-x · opacityStorage at 92% · cleanup advised
warning
Scale-inscale · opacityBuild 2486 · prod rollout 100%
live
Rotate-faderotate · opacityNew device · macOS · Tucson, AZ
review
Blur-revealfilter: blur · opacitySnapshot saved · 1.2 GB freed
done

27Animations

Reduced Motion

prefers-reduced-motion: reduce is set by users with vestibular disorders, motion sensitivity, or epilepsy. Vestibular disorders affect approximately 35% of adults over 40. Swap translate and scale animations for opacity-only fades — they convey transition without spatial movement.

full motion
reduced motion

28Animations

FLIP for Layout Animations

Animating width, height, top, or left triggers full layout recalculation on every frame. The FLIP technique converts these into compositor-only transform animations: record the First position, apply the Last state, Invert with a transform to the start position, Play to identity. Pin or shuffle the items below — every reorder runs a manual FLIP using the Web Animations API.

Atlas Dashboarddesign-eng
Pipeline tokensinfra
Type ramp v3typography
Color auditcolor
Motion librarymotion
transform-only · WAAPI

29Animations

Spring Physics via linear()

CSS linear() generates custom easing functions that simulate spring and bounce physics without a motion library. Use a spring generator tool to produce the values from spring parameters. If the project already includes motion or framer-motion, use their spring API instead — the result is identical with cleaner syntax. bounce should always be 0 or near 0 for UI transitions. Pick a preset below and tap Open to feel each spring against the same popover.

Standard cubic-bezier — fast start, settles smoothly. No overshoot.

Snapshot savedAvailable in Reports → Archived.
300ms · ease-out

Performance

30Performance

Never Use transition: all

transition: all registers a watcher on every animatable CSS property. Any change — including unintentional ones — triggers a transition. Hover both buttons below to see the difference. The left button animates its size and padding unexpectedly because all watches everything.

transition: allhover · padding, radius, font wobble
transition: bg, border, colorhover · only colors transition

31Performance

content-visibility: auto

Defers layout and paint for off-screen elements until they approach the viewport. The browser still reserves space but skips the rendering work — measurably effective on feed layouts, dashboards, and long lists. Always pair with contain-intrinsic-size to prevent the scrollbar from jumping as elements render. Toggle the chips below to see which cards are paying the rendering cost as you scroll.

rendered 60 / 60
Activity 01sync
$1200
Activity 02deploy
$1993
Activity 03alert
$996
Activity 04build
$459
Activity 05snapshot
$1595
Activity 06sync
$1839
Activity 07deploy
$640
Activity 08alert
$705
Activity 09build
$1887
Activity 10snapshot
$1517
Activity 11sync
$431
Activity 12deploy
$1081
Activity 13alert
$2000
Activity 14build
$1113
Activity 15snapshot
$423
Activity 16sync
$1487
Activity 17deploy
$1903
Activity 18alert
$731
Activity 19build
$617
Activity 20snapshot
$1819
Activity 21sync
$1623
Activity 22deploy
$472
Activity 23alert
$964
Activity 24build
$1988
Activity 25snapshot
$1233
Activity 26sync
$403
Activity 27deploy
$1373
Activity 28alert
$1952
Activity 29build
$833
Activity 30snapshot
$542
Activity 31sync
$1736
Activity 32deploy
$1720
Activity 33alert
$530
Activity 34build
$853
Activity 35snapshot
$1960
Activity 36sync
$1351
Activity 37deploy
$401
Activity 38alert
$1254
Activity 39build
$1984
Activity 40snapshot
$943
Activity 41sync
$482
Activity 42deploy
$1642
Activity 43alert
$1805
Activity 44build
$602
Activity 45snapshot
$749
Activity 46sync
$1914
Activity 47deploy
$1467
Activity 48alert
$418
Activity 49build
$1135
Activity 50snapshot
$1999
Activity 51sync
$1059
Activity 52deploy
$437
Activity 53alert
$1537
Activity 54build
$1876
Activity 55snapshot
$688
Activity 56sync
$656
Activity 57deploy
$1852
Activity 58alert
$1576
Activity 59build
$451
Activity 60snapshot
$1017

scroll inside · counter approximates the rendered window

32Performance

CSS Containment

contain: content tells the browser that an element's internal layout, paint, and style are sealed inside its box. Changes within the component cannot affect anything outside it — and paint is clipped at the boundary. Below, the same animated blob is rendered inside both cards. Without containment, the blob bleeds out and forces the browser to repaint the whole region around the card; with contain: paint, the paint is clipped to the card and nothing outside is touched.

Activity218 events · 1hLive span — rerenders every frame.
Blob bleeds beyond the card edge — siblings repaint every frame.
Activity218 events · 1hLive span — rerenders every frame.
Paint is sealed inside the card boundary. Sibling regions skip repaint.

33Performance

Compositor-Only Animations

The browser renders in three phases: layout → paint → composite. Only transform and opacity skip the first two phases and run on the GPU. Every other animated property forces recalculation on every frame. Never animate layout properties for motion effects — use FLIP to convert them to transforms.

Each technique below moves or alters a single 28-pixel box for the same 1400 ms. The pills next to each lane mark which pipeline stages fire every frame. transform and left look identical — but transform skips Layout and Paint entirely.

left
layoutpaintcomposite
background, box-shadow
layoutpaintcomposite
transform: translateX
layoutpaintcomposite
same visual — different frame cost