Navigation
Available for work

Free Tools

Design System Toolkit.

Five generators for the decisions that are tedious to make by hand. Pin one size you already know and the rest of the scale derives from it, in both directions. Every panel exports CSS custom properties, Tailwind v4 theme tokens, and Figma variables.

The exports are for the web: CSS custom properties, Tailwind theme tokens and Figma variables. The maths is not web-specific though, so the type scale also shows what each step maps to on iOS and Android, and the panels that genuinely do not port say so.

For the reasoning behind the defaults, read how to build a type scale: it covers why the golden ratio needs a square root, and specs every UI element from buttons to table cells.

01 / Type Scale

If your H1 is 48px, what is everything else?

Pick the one size you have already decided, pin it, and the rest of the ladder derives up and down from it. The default ratio is the square root of the golden ratio, so every second step is a true 1.618 interval: you get a genuinely golden scale without the dead zone pure phi leaves between body copy and your smallest heading.

Role Preview px rem Line height Tracking Weight

On iOS and Android

Your scale against what each platform ships by default. The gap is the useful part: near zero means you can use the platform's own style untouched, a large number means you are defining a custom one. Points, dp and CSS pixels are all density-independent, so the numbers compare directly. Android sp also scales with the reader's font-size preference, so treat that column as a starting point rather than a fixed size.

Role Yours iOS Dynamic Type Material 3

02 / Spacing & Grid

A grid that stays whole-pixel

Multiples of 4 survive every common device density, and 8 halves cleanly twice. The second table is the part most systems get wrong: the space above a heading has to beat the space below it, or the heading floats between two blocks instead of belonging to the one it introduces.

Token Scale px rem Multiple

Vertical rhythm

Space above a heading must exceed the space below it: a heading belongs to the content that follows, so the gap separating it from the previous block has to read as larger. A symmetric margin: 1em 0 is the usual mistake. These use a 2:1 ratio, expressed in em so they scale with each heading rather than needing a value per level.

Element Margin top Margin bottom At body size
03 / Colour & Combinations

Enter one colour, get the ones that go with it

Combinations are generated in OKLCH, so every member holds the same perceived lightness. Rotating hue in HSL does not: at an identical HSL lightness, yellow and blue differ by nearly nine times in actual luminance, which is why so many generated palettes have one member you cannot read. Trend directions bias chroma and lightness only, so the colour you type always drives the result.

Worth knowing before you trust any of these: the classic wheel rules are art-school theory, not measured fact. What research there is finds that preference peaks in the contrast region around 160 to 220 degrees, which supports complementary, while harmony rises with hue similarity, which supports analogous. Those are different things. The same work also finds that hue independence does not hold: blues and orange-yellows combine well, reds, greens and purples less so, whatever the geometry says.

The combination

The four roles

The short version, if you think in main, accent, neutral and contrast. Everything here is drawn from the scale below, with each pairing already checked for contrast rather than left to the eye.

As a UI palette

Twelve steps where each one has a job, rather than a lightness number you have to reinterpret for every hue. Step 9 is the chroma peak, which is why it is the natural brand solid. Greys are tinted with your hue at very low chroma, which is most of what stops a generated palette looking generated.

Step Job Swatch Hex Contrast Perceptual

How much of each

The 60-30-10 split, with one correction. Your colour is not the 60%: at full chroma across most of a page it is overwhelming. The 60% is your hue at near-neutral chroma, which is what steps 1 and 2 already are. The accent only works as a call to action while it stays scarce.

60-30-10 comes from interior design, not screen design. Treat it as an area check rather than a formula: a real system has two surface tiers, several border and text tiers and a set of semantic scales, so three colours was never the whole model.

Full scale

Step Swatch Hex On white On black Readable text

Trend directions compiled August 2026. They bias chroma and lightness only: the hue is always yours. Forecasters disagree with each other in any given year, so treat these as directions rather than facts.

04 / Radius & Elevation

Judge these by eye, not by number

Both ramps are rendered rather than listed, because nobody has ever picked a shadow off a spreadsheet. Each elevation is drawn in two layers: a tight contact shadow that anchors the element, and a wider ambient one that carries its height.

Radius and elevation are the cheapest parts of a system to derive and the fastest to judge, so both are rendered here rather than listed. Shadows are drawn in two layers: a tight contact shadow that anchors the element, and a wider ambient one that carries the height.

Radius

Elevation

Shown on a light canvas on purpose: a black shadow cast onto a near-black surface has nowhere to go, so dark interfaces generally signal height by lightening the surface instead of deepening the shadow.

Radius travels: the same number is dp on Android and points on iOS. The shadows do not. Android Material takes a single elevation value in dp and draws the shadow for you, so a two-layer CSS shadow has no direct equivalent: match the level, not the offsets. iOS gives you the layer properties individually, as shadowOffset, shadowRadius and shadowOpacity, so the two layers here would need two views or a redraw.

05 / Fluid & Breakpoints

One value that scales between two viewports

The slope is computed in pixels and only the intercept is converted to rem, which is where most published clamp examples go wrong by a factor of sixteen. Keeping a rem term in the preferred value is not stylistic: a pure viewport value cannot respond to browser text sizing, and that is a named WCAG failure.

Web only, unavoidably. Neither iOS nor Android has a viewport-relative unit, so there is no clamp() equivalent to port. iOS scales type through Dynamic Type and adapts layout with size classes; Android uses resource qualifiers and window size classes. The idea of interpolating between two anchor sizes carries over; this syntax does not.

How it is derived

 

Rendered across the range

Viewport Computed size Preview
06 / Using the output

One source, three places it has to land

A design system fails at the handoff, not at the maths. The panels export the same values three ways so the stylesheet, the Tailwind theme and the Figma file start from one set of numbers rather than three that agree on Monday and disagree by Friday.

CSS custom properties

Paste into :root. Tokens, not utilities, so they work in plain CSS, in a component library, and inside a framework you have not chosen yet.

Tailwind v4 theme tokens

Tailwind v4 reads its theme from CSS, so these go straight into @theme and the utility classes generate themselves. No config file to keep in sync.

Figma variables

The same values as collections, so a designer picking a size in Figma and a developer typing a token name are choosing from one list. This is the export that stops the drift.

The maths is not web-only. The type scale shows what each step maps to in iOS Dynamic Type and on Android, and the panels whose output does not port cleanly to native say so rather than quietly handing you a number that means something else there.

FAQ

Frequently asked questions

What base size should I pin?

Sixteen pixels, unless you have a reason. It is the default text size in every desktop browser, so a user who has raised or lowered that default gets a scale that moves with them. Pinning the base is the one decision worth making by eye: set the size your body copy has to be at arm’s length on a real screen, then let the ladder derive from it rather than choosing seven sizes independently.

Which ratio should I choose?

The default here is 1.272, the square root of the golden ratio, because every second step is then a true 1.618 interval. Pure phi is too coarse for interface work: it leaves a gap between body copy and the smallest heading with nothing usable in it. Denser products want a smaller ratio around 1.2; editorial pages can carry 1.333 or more. If you are unsure, pick the smaller one — a scale that is too tight is a smaller problem than one with unusable steps.

How many steps do I actually need?

Six to nine. Fewer and you start reaching for sizes outside the system; more and two adjacent steps become close enough that nobody can tell you which to use, so the scale stops making the decision for them. Every step should have a job you can name.

Should type sizes be in px or rem?

Rem, for anything a person reads. A rem is relative to the browser’s root size, so a reader who has increased their default text size gets larger text; pixel sizes ignore that entirely, and ignoring it is a WCAG failure, not a preference. Think in pixels while designing if that is more natural, then ship rem — the exports here do the conversion.

How do I make the scale responsive without a second set of sizes?

Use the fluid panel rather than redeclaring every size at every breakpoint. It builds a clamp() value that moves smoothly between two viewports, which means one declaration instead of three. The important detail is that the preferred value keeps a rem term: a pure viewport unit cannot respond to browser text sizing at all, which is the failure most published clamp snippets ship with.

Why does my generated palette have one colour nobody can read?

Because it was almost certainly generated by rotating hue in HSL, where lightness is a coordinate rather than a perception. At identical HSL lightness, yellow and blue differ by nearly nine times in actual luminance. The colour panel works in OKLCH instead, so every member of a combination holds the same perceived lightness and the readable one is not a matter of luck.

Why is the space above a heading larger than the space below it?

Because a heading belongs to the block it introduces. If the two gaps are equal, or the one below is larger, the heading floats between two sections and the reader has to work out which one it labels. The spacing panel treats that asymmetry as a rule rather than a judgement call.

Can I get these tokens into Tailwind or Figma?

Yes — every panel exports CSS custom properties, Tailwind v4 theme tokens and Figma variables from the same values, so the design file and the stylesheet start from one source instead of two that drift. The type scale additionally maps each step to its iOS and Android equivalent, and the panels whose maths genuinely does not port to native say so rather than pretending.

Let’s work together

Let’s build something great.

Open for freelance projects and full-time roles.