yugodesign

Foundations·00

The token layer

Everything a component is allowed to spend, named once. Fifteen radii that nest inside each other, twelve type steps, two curves, five springs, three materials.

Materials

Everything is a stack of three: the bezel things sit in, the panel lifted out of it, the well cut into that. Depth is real. A border standing in for depth is the thing this replaces.

mat-panellifted out of the bezel
mat-floatlifted far out, floating over everything
mat-wellcut into the panel
mat-capa key you could press: bottom lip, hairline all round
mat-rowa row that has been picked out of a list
--bezel#ffffff / #141312the frame things sit in
--panel#ffffff / #1d1d1athe lifted card, where content lives
--well#ffffff / #252522the recessed slot, where things go in
--sub#fafafa / #1a1a17a subdued band inside a panel

Ink and hairlines

Three inks and two hairlines. Both ramps were raised twice: the first pass read as refined in a mockup and as broken on a real screen. When the choice is between subtle and legible, legible wins. Restraint lives in the type sizes and the motion.

--ink#131313 / #f8f8f4headings, primary text
--ink-2#454545 / #c2c2babody copy
--ink-3#6e6e6e / #93938bmetadata, labels, disabled rows
--hairlinergba(0,0,0,0.14) / rgba(255,255,255,0.16)dividers, compartment edges
--hairline-strongrgba(0,0,0,0.26) / rgba(255,255,255,0.28)hover borders, scrollbar thumb

Accent, and the two verdicts

Accent marks interaction and state, never decoration. Selection and “on” are said with ink and depth instead: if every selected thing were blue, focus would stop meaning anything. The accent is also chosen against the surface it is drawn on, not against the page, so an inverted button in light mode takes the dark-mode accent.

--accent#4568ff / #93b0ffinteraction, focus, selected state
--accent-soft#edf1ff / rgba(147,176,255,0.14)text selection, accent wash
--moss#3d7a4e / #5bd79csuccess, confirmation
--flag#c0442f / #f5897frejection: validation errors, failed states

Radii nest

Outer radius = inner radius + the padding between them. Nothing here was picked because it looked right on its own; each number is derived from the one it wraps, which is why the scale is dense at the bottom and why two adjacent boxes never read as almost-concentric.

p-nest · 5px14 wraps 9, 11 wraps 6
p-shell · 4px12 wraps 8
this box is a 14 wrapping an 11 wrapping three 6s
rounded-page20page panel
rounded-frame16preview frame, code block frame
rounded-card14card, props table, overlay panel, section
rounded-card-sm13list card
rounded-shell12tabs shell
rounded-panel11well in a 16 frame, popover, dropdown, row card
rounded-field10field, drop target, avatar tile
rounded-row9list row, button, search field, tile in a 12
rounded-row-sm8nested list row, tab plateau, step tile
rounded-icon7icon button in a panel, avatar well
rounded-cap6cap, chip, chevron button, small action
rounded-cell5cell, small chip, kbd, checkbox
rounded-track4progress track
rounded-fill2fill, tick, rail
rounded-mark1.5ripple cell, marquee mark

Type

Sans by default, mono only for metadata, numbers, ids and keycaps. Headings are medium, never bold: the negative tracking does the work that weight would do badly. Every step carries its family, weight, leading and tracking with it, so there is no way to spell four of the five right.

Two rules the ramp enforces on your behalf. leading-none is banned on anything containing a word: it shrinks the box to the em square and a descender then sits high in it. The two mono steps are the exemption, because they only ever hold a numeral or a short token in a fixed-height row. And uppercase never ships at default tracking, which is a wall, so the label step carries its 0.08em.

text-displayclamp(32px, 5.6vw, 52px) · 500 · 1.08 · -0.04emlanding headline

making things feel right

text-title27px · 500 · 1.15 · -0.03empage title

making things feel right

text-title-220px · 500 · 1.2 · -0.03emsticky-header expanded title

making things feel right

text-lede15px · 400 · 1.625lede, modal title, otp char

making things feel right

text-body13.5px · 400 · 1.625body, panel copy

making things feel right

text-control13px · 500 · 1.4card title, row name, control

making things feel right

text-secondary12.5px · 400 · 1.5sidebar row, secondary line, chip

making things feel right

text-caption11.5px · 400 · 1.45hint, error, tooltip, caption

making things feel right

text-label11px · 600 · 1.4 · 0.08emsection label, column header

making things feel right

text-dock11px · 500 · 1.4dock label

making things feel right

text-meta10.5px · 400 · 1 · 0.06emmetadata, counts

making things feel right

text-id9.5px · 400 · 1 · 0.06emids, counters, keycaps

making things feel right

Two curves

arrivedecelerates into place, which is what “something arrived and stopped” looks like. leaveis the mirror: it starts slow and accelerates out of frame, which is what “this is over, do not wait for it” looks like. There is no third one.

ease-arrive0.23, 1, 0.32, 1

decelerates into place: something arrived and stopped

ease-leave0.4, 0, 1, 1

accelerates out of frame: this is over, do not wait for it

Durations

A departure is always shorter than an arrival. An exit that takes as long as an entrance means the thing replacing it either waits, which reads as lag, or crossfades through it, which reads as a smear. The two modes across the whole component set are 140 and 200; everything else rounds to one of them.

--duration-flash100msopacity beating its own surface out
--duration-exit140msa departure
--duration-enter200msan arrival
--duration-settle260msa surface that travelled a real distance
--duration-draw300msa stroke drawing itself

Five springs

Read the table as a shape rather than a list. Stiffness rises as the distance falls and mass falls the same way, because the distance chooses the spring. Measure the travel: over 200px takes disclose, 20 to 200 takes cell, under 20 takes small. The ζ column is the damping ratio: under 1 overshoots, at 1 is critical.

cellζ 1.11
{ stiffness: 520, damping: 34, mass: 0.45 }

the default: a cell lighting, a tick landing, a row taking its slot

crossfadeζ 1.18
{ stiffness: 260, damping: 34, mass: 0.8 }

opacity between two faces, or content being replaced wholesale

smallζ 1.23
{ stiffness: 700, damping: 46, mass: 0.5 }

a label lifting, a caret rotating, a chip settling

discloseζ 1.10
{ stiffness: 150, damping: 27, mass: 1 }

a surface travelling a real distance: a drawer, a card flying home

surfaceζ 0.93
{ stiffness: 420, damping: 36, mass: 0.9 }

the modal panel: alive, does not bounce

Strokes and the icon grids

Icons are Phosphor geometry drawn inline, never imported, so a component still costs one dependency. strokeWidth 16 on a 256 viewBox renders as 1.5px at 24px: the grids are the same line weight, so use whichever the source glyph came from and do not convert.

--stroke-hairline1pxdividers, compartment edges, the ring in every shadow
--stroke-focus2pxthe focus outline, at 1px offset
256viewBox 0 0 256 256stroke 16Phosphor geometry, scaled by width/height
24viewBox 0 0 24 24stroke 1.5a custom mark on the 24 grid
12viewBox 0 0 12 12stroke 1.6a mark inside a control

Layers

Four stops. A fifth is someone losing an argument rather than a new kind of thing being on screen.

10--layer-raisez-10the fade strips and the page header
20--layer-headerz-20the docs pane header, over its own scroll
40--layer-scrimz-40the dim behind a drawer
50--layer-overlayz-50the drawer, the launcher