Digital Design, 14 October 2026

Easel 3: where we start

One foundation for Figma and code: what is proposed, how it works, and what we decide together.

Kurt, 14 October 2026. For the design team.

Part 1

Where Easel is today

What has drifted between Figma and code, and why it keeps happening.

Easel has drifted, in Figma and in code

In Figma

  • Buttons of the same size come in several different heights.
  • Lacking an icon-only button component.
  • The page gutter changes can be inconsistent.

In code

  • The Button component in Storybook is fairly well built and consistent.
  • It has no icon-only button either. The carousel needed one, so devs bent the tertiary Button into shape with Tailwind overrides. This creates an unexpected dependency.
  • Gutters vary wildly across pages and components.

Nobody is doing a bad job. Keeping Figma and code in step is nobody’s job, so every team makes a sensible local call and the system drifts.

Every inconsistency is a decision someone has to make again

First by a designer, then by an engineer, and soon by an AI agent that copies whatever it finds.

Easel’s foundation grew as we needed it. Now we know enough to simplify it

  • Figma

    Components, text styles and variables

  • Code

    React components and Storybook, in one repo

One foundation, agreed together

  • Type
  • Spacing
  • Page gutter
  • Colour
  • Radius, shadows and the rest

Growing one need at a time is fine. Now the four of us agree one foundation first, and engineering can follow. It is built for what comes next: AI agents that work from our tokens, and fluid values that fit every screen.

Part 2

Tokens and patterns

A token is a decision with a name

A value: 16px

A number in a file. Nobody can tell whether it is the side of the page, the gap in a card or a guess.

A token: page-gutter

The same number, named for its job. Change it once and every page and every section updates.

The name also says what to use and when. A designer picks it without measuring, an engineer builds it without guessing, and QA has one right answer to check, so all three go faster.

In Figma a token is a variable. A pattern is how we agree to use it

Variable

Figma’s word for a token. A variable can have more than one value, each in its own mode: one for each screen width, or one for each colour scheme.

Pattern

A decision we have agreed and written down, with the reason. For example: every section takes its side padding from page-gutter, so content lines up down the page.

An AI agent can read a token, but not why we chose it. The pattern is where a designer, an engineer and an agent all read the reason.

Tokens come in three layers

  1. Component tokens

    One component’s own colours, used only inside that component.

    • button-primary-background
    • button-primary-foreground
    • button-primary-border
    • button-secondary-background
    • button-secondary-foreground
    • button-secondary-border
  2. Semantic tokens

    A job anywhere on the page. Designers and engineers use these every day.

    • background-primary
    • foreground-primary
    • foreground-secondary
    • border-primary
  3. Primitive tokens

    The values. Only other tokens use these.

    • black
    • black-300
    • white
    • white-alpha-60
    • grey-400
    • brand-c-red

In Figma, each layer is a variable collection, and each variable is an alias of one in the layer below. With its own tokens, a button can change without moving every link too. The names are placeholders until we agree them as a team.

Designers, engineers and agents share one set of names

  1. Figma variable

    foreground-primary

  2. CSS

    --foreground-primary

  3. Tailwind

    text-foreground-primary

  4. An agent's brief

    ‘Use foreground-primary.’

An agent reads the Figma file and the codebase. When both use the same names, it copies the system instead of guessing at hex values.

Part 3

The proposal

Fewer values, one set of names, and sizes that grow smoothly with the screen instead of jumping at breakpoints.

Fewer values to keep in step, and fewer frames to draw

  • 6212

    type sizes

  • 3916

    text styles

  • 623

    line heights

  • 72

    widths to draw

The look & feel is good. The system underneath it is what we are building, and every number here is a draft to tune together.

What the proposal covers, and where each part stands

  • Type

    12 sizes and 16 roles, each size set at 390, 1440 and 2560. Drafted, with the 2560 sizes still to tune.

  • Spacing

    One scale of 15 steps, and fluid steps that grow with the type.

  • Page gutter

    One token: 16 at 390 and 48 at 1440, then growing like the rest of the spacing.

  • Widths we draw

    390 and 1440, plus any width where the layout changes, such as the switch to the mobile view. 2560 is checked.

  • Colour

    Tokens named for their job, so one component works in any section. The names are placeholders.

  • Everything else

    Radius, shadows, motion and sizes.

    Planned

Part 4

Type

Too big on large screens today, and far harder to manage than it needs to be.

Type that jumps at breakpoints can only be right for one screen size at a time

Type was made bigger for people viewing on large screens. Now others find it too big.

The same text on a 2560px screen, today and as proposed

Today

Page title 68px

Body text 24px

Proposed

Page title 54px

Body text 18px

Each size grows smoothly to 1440, then more gently to 2560

50607080390Mobile1440Desktop2560LargeTodayProposed
The largest heading, in px, against screen width. At 2560px it is 84px today and 60px in the draft.

Two layers, and the same name everywhere

12 sizes

A font size that grows with the screen. Roles are built from these, so you choose a role, not a size.

16 roles

The text styles you choose: a typeface, a size and a line height. Weight, italic and link are options on a role.

One name

Where Name
Figma Heading XL
Code easel-heading-xl
A brief ‘Use heading-xl’

Every new name starts with easel-, so we can see what uses the new system, and count it.

A first draft: the larger headings

Role390 · 1440 · 2560

  • heading-xxl48 · 56 · 60

    Post-War and Contemporary Art

  • heading-xl36 · 48 · 54

    Post-War and Contemporary Art

  • heading-l28 · 32 · 34

    Upcoming auctions

  • heading-m-sans24 · 28 · 30

    20th/21st Century Evening Sale

  • heading-m-serif24 · 28 · 30

    20th/21st Century Evening Sale

Every role, name and size is open to change, and now is the best time.

The smaller headings and body text

Role390 · 1440 · 2560

  • heading-s-sans18 · 18 · 20

    From free valuations to market advice

  • heading-s-serif20 · 24 · 26

    From free valuations to market advice

  • heading-xs-sans14 · 16 · 17

    From free valuations to market advice

  • heading-xs-serif16 · 20 · 22

    From free valuations to market advice

  • body16 · 16 · 18

    Our specialists are here to answer your questions

  • body-s14 · 14 · 16

    Our specialists are here to answer your questions

  • body-xs12 · 12 · 14

    Our specialists are here to answer your questions

Built from today’s text styles. Say which roles you use, which you never touch and what is missing.

Labels and actions

Role390 · 1440 · 2560

  • label14 · 14 · 16

    18 Sep – 2 Oct · Online

  • label-s12 · 12 · 14

    18 Sep – 2 Oct · Online

  • action-l16 · 16 · 18

    Browse auction

  • action14 · 14 · 16

    Browse auction

This draft drops Action M, which matched Action in size, line height and capitals. More roles may merge the same way.

The whole system has three line heights: 100%, 120% and 140%

100% for a single line, 120% for headings, labels and actions, and 140% for body text. Today there are 62.

Each is a share of the font size, so it follows the type at any width.

In code, one class replaces four

Today

<h1
  class="text-4xl md:text-4xl-lg
  2xl:text-4xl-xl 3xl:text-4xl-2xl"
></h1>

One class for each breakpoint, and a jump at each one.

Proposed

<h1 class="easel-heading-xl"></h1>
--easel-text-4xl: calc(
  2.25rem + clamp(0rem, (100vw - 390px) * 0.01143, 0.75rem) +
    clamp(0rem, (100vw - 1440px) * 0.00536, 0.375rem)
);

Read the variable as: start at 36px, grow by up to 12px between 390 and 1440, then by up to 6px more by 2560. Plain CSS, with nothing new to install.

Keeping each largest size within 2.5 times its smallest keeps browser zoom working

Accessibility guidelines (WCAG) ask that text can be zoomed to 200%. The vw part of a clamp() doesn’t grow when someone zooms, so a scale that stretches too far can stop zoom from enlarging the text.

Within 2.5 times, 200% zoom always works. Every line in the fitted scale stays at twice its smallest size or less.

Part 5

Spacing and the page gutter

One scale where every step looks different, and spacing that grows with the type.

Figma’s spacing scale has 16 steps. The proposal has 15, each visibly different

Figma today, in the atoms file 16 steps
  1. 4
  2. 8
  3. 12
  4. 16
  5. 20
  6. 24
  7. 28
  8. 32
  9. 36
  10. 40
  11. 48
  12. 56
  13. 60
  14. 64
  15. 72
  16. 80
Proposed 15 steps
  1. 4
  2. 8
  3. 12
  4. 16
  5. 20
  6. 24
  7. 32
  8. 40
  9. 48
  10. 64
  11. 80
  12. 96
  13. 128
  14. 192
  15. 256

Dark bars are on one scale only. Above 48, today’s steps are too close to tell apart: 56 to 60 is a 7% change. Each proposed step is a clear jump from the one before.

The steps we drop are little used, and each has a near neighbour

Step today Uses in live code Nearest proposed step
28 38 24 or 32
36 21 32 or 40
56 20 48 or 64
60 16 64
72 Fewer than five 64

Together they are under 5% of the 2,131 spacing classes in live code. Moving one to its neighbour changes it by 4 to 8px, and that is fine.

A scale gives us the values. It doesn’t say which one to use

Imagine two of us each design a large section

One of us spaces it at 80 and the other at 96. Both values are on the scale, so neither is wrong, and a review cannot say which to keep. The site ends up with both.

A pattern makes that decision once

Say we agree that large sections use 80, and we write down the reason. Now we both use 80, and choosing 96 needs a good reason.

The numbers are only an example. When we make the same decision again and again, we make it once and call it a pattern.

Fixed spacing jumps at a breakpoint. A fluid step grows with the type

Screen width 390 768 1024 1280 1440 2560
Today, p-4 md:p-6 16 16 24 24 24 24
A fluid step, 16 → 24 16 18.9 20.8 22.8 24 27

Type will grow smoothly between breakpoints. If the space around it only jumps, the two drift out of proportion between them. With a fluid step we set one value and get movement at every width, with no changes to design at each breakpoint.

So fluid is the default, and a fixed step is the exception. 83% of spacing on the live site is fixed at every width today, so this is a change of direction.

Gentle steps by default, and strong steps where a design asks for more

Gentle: one step up

12 → 16, 16 → 20, 20 → 24, 24 → 32, 32 → 40, 40 → 48 and 48 → 64.

A little movement for the spacing that is fixed today. 4 and 8 stay fixed.

Strong: where live code grows today

390 → 1440 Uses What it is
16 → 24 17 Cards and gaps
24 → 48 15 Section padding
40 → 64 16 Section spacing, with four endpoints today
80 → 128 4 Large sections

Every step then grows by another eighth from 1440 to 2560, fixed ones included, because body text grows from 16 to 18. So 16 → 24 reaches 27, and a fixed 16 reaches 18.

The question of naming

px 4 8 12 16 20 24 32 40 48 64 80 96 128 192 256
Number 1 2 3 4 5 6 8 10 12 16 20 24 32 48 64
T-shirt 2xs xs sm md lg xl 2xl 3xl 4xl 5xl 6xl 7xl 8xl 9xl 10xl

Numbers

Tailwind’s unit, where 1 is 4px. Engineering’s classes use it today: p-4 is 16px. A new size is a new number, and nothing else changes.

T-shirt sizes

With md anchored to 16, the names run from 2xs to 10xl. You cannot read the px value from a name, so each one has to be learnt. In our type scale, 16 is sm.

Five of seven other systems number their spacing

System How a step is named 16px, or the base step
Tailwind A multiple of 4px 4
Radix Themes Steps 1 to 9 --space-4
IBM Carbon Steps 01 to 13 $spacing-05
Atlassian A percentage of an 8px base space.200
GitHub Primer The px value --base-size-16
Utopia T-shirt sizes, with s as the base s
Nord Health T-shirt sizes, with m as the base m

A fluid step is two steps of the scale

390 → 1440 Kind T-shirt pair Number pair
12 → 16 Gentle sm-md 3-4
16 → 20 Gentle md-lg 4-5
16 → 24 Strong md-xl 4-6
24 → 32 Gentle xl-2xl 6-8
24 → 48 Strong xl-4xl 6-12
40 → 64 Strong 3xl-5xl 10-16

One size at 390 and one at 1440, and the browser works out every width between. A single name for each fluid step, such as md, stops working once a size has both a gentle and a strong version.

To discuss: numbers, t-shirt sizes, or both?

What happens when we need a size between two steps

  • With numbers, it is a new number: 28 is 7.
  • With t-shirt sizes, we rename every step above it, add an in-between name such as lg-plus, or keep the scale closed and use the nearest step.

What happens when a designer needs another fluid step

  • With pairs, any two steps make one: 24 → 48 is 6-12 or xl-4xl.
  • With named fluid steps, the set is short and closed, and a new one has to be agreed.

In code, a fluid step is one variable

Today

<div class="p-4 md:p-6"></div>

Two classes, and a jump at 1024.

Proposed

<div class="p-md"></div>
--easel-space-md: clamp(1rem, 0.8143rem + 0.7619vw, 1.5rem);
  • 1rem: never smaller than 16px
  • 0.8143rem + 0.7619vw: grows with the screen, from 16 at 390 to 24 at 1440
  • 1.5rem: never larger than 24px

p-md stands in for whichever name we choose. A tool writes the clamp() from the two sizes and the two widths.

In Figma, a fluid step is one variable with a mode for each width

Spacing39014402560
space-md162424
space-lg243232
page-gutter164854

Apply the variable, then switch a frame’s mode to see any width. Still in test: whether steps keep growing above 1440 by the same share as body text, so 24 becomes 27 at 2560.

The page gutter is four values to remember, in Figma and in code

Screen width Gutter Class
Under 390px 8px px-2
390–1023px 16px sm:px-4
1024–1279px 40px md:px-10
1280px and up 48px lg:px-12
<header class="px-2 sm:px-4 md:px-10 lg:px-12"></header>

These are the global header’s values, in production today. Newer React pages such as Calendar don’t match them, and Figma isn’t consistent either, so design is as much to blame as engineering.

The gutter becomes one token: 16, 48, then growing with the text

Screen width Today Proposed To discuss
390 16 16 16
1024 40 35.3 35.3
1440 48 48 48
1920 48 50.6 68.6
2560 48 54 96
--easel-page-gutter: clamp(1rem, min(0.2571rem + 3.0476vw, 2.5179rem + 0.5357vw), 3.375rem);
<header class="px-gutter"></header>

It matches today at 390 and 1440, the two widths we draw. Above 1440 it grows by the same share as the text and the other spacing, 12.5% by 2560, so one rule covers everything. Growing to 96 would frame the page more on a large monitor, at the cost of a second rule.

Above 1440, everything keeps growing, more gently

At screen width 390 1440 2560
Page title 36 48 54
Body text 16 16 18
A fluid step, 16 → 24 16 24 27
Page gutter 16 48 54

Type grows at about half its earlier rate. Spacing and the gutter follow the type, by the same share as body text: 12.5% by 2560. Still in test: try ‘Space follows type’ in the prototype, with ‘Gutter follows type’ on.

From 1440 to 2560, every type size grows by 1 to 6px

Size 1440 2560 Change
5xl 56 60 +4
4xl 48 54 +6
3xl 32 34 +2
2xl sans 28 30 +2
2xl serif 28 30 +2
xl sans 18 20 +2
Size 1440 2560 Change
xl serif 24 26 +2
lg sans 16 17 +1
lg serif 20 22 +2
sm 16 18 +2
xs 14 16 +2
2xs 12 14 +2

Body text is sm. Drafted, with the 2560 sizes still to tune.

From 1440 to 2560, every space grows by an eighth, as body text does

At 1440 4 8 12 16 20 24 32 40 48 64 80 96 128 192 256
At 2560 4.5 9 13.5 18 22.5 27 36 45 54 72 90 108 144 216 288

The page gutter follows the same rule, 48 to 54. A fluid step grows from its 1440 size, so 24 → 48 reaches 54.

In test. The 2560 values fall between steps, which is fine because 2560 is checked, not drawn.

Fluid steps grow to 1440, then every step grows by an eighth

050100150390Mobile1440Desktop2560Large80 → 128 · 14440 → 64 · 7224 → 48 · 5416 → 24 · 27Fixed 16 · 18
Each token in px against screen width. Fluid pairs, in red, grow to their 1440 step. Above 1440 every token grows by an eighth, then holds from 2560.

Each token at the widths we check

Token 390 1024 1440 1920 2560
Fixed 16 16 16 16 16.9 18
Fixed 24 24 24 24 25.3 27
16 → 24 16 20.8 24 25.3 27
24 → 48 24 38.5 48 50.6 54
40 → 64 40 54.5 64 67.4 72
80 → 128 80 109 128 134.9 144
Gutter 16 35.3 48 50.6 54

Designers choose the 390 and 1440 values. Every other width is worked out, in code and in Figma’s modes.

Part 6

Widths and layout

Which frames we draw once type and spacing grow without us.

We design two widths and check the others

  1. 390
  2. 768If the layout changes
  3. 1024If the layout changes
  4. 1280If the layout changes
  5. 1440
  6. 1920If the layout changes
  7. 2560Check

Type and spacing no longer need a frame at every breakpoint. A layout still does: if the layout changes at a breakpoint, we design that breakpoint. If nothing changes there, there is nothing to design.

To agree with QA: they test a build against designs today, so drawing fewer frames needs their agreement.

Some layouts change at a breakpoint. Others follow the space they have

At a breakpoint

A switch between a layout made for a phone and one made for a desktop, such as navigation that becomes a menu. We choose the width, and we draw both sides of it.

By the space available

How many cards fit in a row. The browser can work that out from one rule, at every width, with no breakpoint.

A RAM grid lets the browser choose the number of columns

grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
  • Lot 1
  • Lot 2
  • Lot 3
  • Lot 4
  • Lot 5
  • Lot 6
  • Lot 7
  • Lot 8
The browser fits as many 10rem columns as the space allows.

ch sets a line length in characters, at any type size

.article p {
  max-inline-size: 65ch;
}

A ch is the width of a zero in the current font. A paragraph stays at a readable length whatever size its type becomes, so fluid type needs no matching fluid widths.

Text widths need a scale too, set in characters

Today

A text box in Figma is, say, 438px wide. A developer copies the number, and the reason for it is lost. Live code has 323 one-off widths and heights, in 189 different values. max-w-[330px] alone appears 13 times.

Proposed

A short scale of line lengths in characters: one for body text, at about 65, and narrower ones for headings. A width then follows from the content and its type size, at every screen width.

Still to work out: the values, and how many we need.

Above 2560, the page only gets wider

Type and spacing stop growing at 2560, so a bigger screen spreads the same layout further apart. Text sections stretch and leave a lot of empty space.

The prototype compares four ways to treat a wide screen

Option Above 2560 What we found
Bleed Today. Every section runs edge to edge Text sections stretch
Page The whole page stops at 2560, backgrounds included Colour bands stop short, with white either side
Content Backgrounds and full-width pictures reach the edges. Content stops Keeps the full-bleed feel. Text sections are still wide
Two widths As Content, and text sections stop at a narrower width The one we prefer so far

Two widths, or more: 2560 for pictures and grids, narrower for text

Stops at 2560

Grids, carousels and lists of auctions. A carousel starts on the left edge of the content and runs off the right edge of the screen. A hero video or a banner picture reaches the edge, and stops growing at the height it has at 2560.

Stops at a narrower width

Sections that are mostly text: a centred banner, three steps, a list of questions. In a half-and-half banner the picture reaches the edge, and the text starts where the text sections start.

The prototype uses 1680, the 1440 design at the larger type. It could as well be 1440, and some content may need a third width. That needs design experiments, not a number agreed today.

To decide: on screens wider than 2560, can pages jump between contained and edge to edge?

Why it happens

Pages take the new widths one at a time, as each is rebuilt. Until they all have, a visitor on a very wide screen sees content in a container on one page and edge to edge on the next. Below 2560 nothing changes.

Do we care?

Few visitors have a screen that wide, so we could accept it while pages move over. Or we hold back the containers until every page can take them. The header and footer are shared, so they can go either way on every page at once.

Part 7

Semantic colour

Tokens named for their job rather than their colour, so one component works in any section.


The decision for today: start now, or park it?

Today a colour variable names a colour. A semantic one names its job

Today

Layer in Figma Fill
Heading black
Caption black-300
<h2 class="text-black">…</h2>
<p class="text-black-300">…</p>

Says what it looks like. Right in a light section, and wrong the moment the section turns dark.

Easel 3.0

Layer in Figma Fill
Heading foreground-primary
Caption foreground-secondary
<h2 class="text-foreground-primary">…</h2>
<p class="text-foreground-secondary">…</p>

Says what it’s for. The section decides what it looks like.

One card, three sections

Live auction, London

Post-War and Contemporary Art

View lotsRegister to bid
Figma mode
Light
Code
data-color-scheme="light"

Live auction, London

Post-War and Contemporary Art

View lotsRegister to bid
Figma mode
Dark
Code
data-color-scheme="dark"

Live auction, London

Post-War and Contemporary Art

View lotsRegister to bid
Figma mode
Red
Code
data-color-scheme="red"

In Figma, you select the section and switch its mode. In code, the section sets data-color-scheme. The card inside is the same component each time.

On red, 60% white measures 3.9:1, under the 4.5:1 body text needs, so the Red mode uses 80% (6.1:1).

In Figma, each scheme is a mode

ColourLightDarkRed
Semantic
background-primarywhiteblackbrand-c-red
foreground-primaryblackwhitewhite
foreground-secondaryblack-300white-alpha-60white-alpha-80
border-primarygrey-400white-alpha-40white-alpha-40
Component: button
button-primary-backgroundbrand-c-redwhitewhite
button-primary-foregroundwhiteblackbrand-c-red
button-primary-borderbrand-c-redwhitewhite
button-secondary-backgroundtransparenttransparenttransparent
button-secondary-foregroundblackwhitewhite
button-secondary-borderblackwhite-alpha-60white-alpha-80

In code, each mode is one block of variables

[data-color-scheme="light"] {
  --background-primary: var(--color-white);
  --foreground-primary: var(--color-black);
  --button-primary-background: var(--color-brand-c-red);
  --button-primary-foreground: var(--color-white);
  --button-primary-border: var(--color-brand-c-red);
}
[data-color-scheme="red"] {
  --background-primary: var(--color-brand-c-red);
  --foreground-primary: var(--color-white);
  --button-primary-background: var(--color-white);
  --button-primary-foreground: var(--color-brand-c-red);
  --button-primary-border: var(--color-white);
}

The classes stay the same

<section data-color-scheme="red" class="bg-background-primary">
  <h2 class="text-foreground-primary">Post-War and Contemporary Art</h2>
  <Button variant="primary">View lots</Button>
</section>
// Inside Button, written once
<a class="bg-button-primary-background text-button-primary-foreground border-button-primary-border" />

Designers choose a section’s scheme, not the colour of everything inside it

How four other design systems name their tokens

System How a name is built Quieter text A primary button’s background
Atlassian Property, role, emphasis, state color.text.subtle color.background.brand.bold
GitHub Primer Property, role, emphasis fgColor-muted button-primary-bgColor-rest
Nord Health Property, then a word from weakest to strong --n-color-text-weak --n-color-accent
Reshaped Property, role, then faded or highlighted --rs-color-foreground-neutral-faded --rs-color-background-primary

All four put the property first: text, background or border. Only Primer names the button in the token itself.

What we would take from each

Atlassian

One order for every name: property, role, emphasis, then state. A hover colour is the same name with hovered on the end.

GitHub Primer

A third layer of component tokens, used only inside the component. Each strong background comes with a named foreground, fgColor-onEmphasis.

Nord Health

A small set: about 60 colour tokens. Light, dark and high contrast are each one short file that gives the same names new values.

Reshaped

Names that come in pairs: background-primary and on-background-primary. Its Tailwind classes drop the repeat, so the class is bg-primary.

To decide: all four use a word such as muted, subtle, weak or faded for quieter text. None uses ‘secondary’, which we also want for the secondary button.

Part 8

Everything else

Type, spacing and colour are the start. The same treatment applies to the rest.

Radius, shadows, motion and sizes need a system too

  • Radius

    21 one-off values in code, in 11 different sizes.

  • Shadows

    11 one-off shadows. Six are variants of one 24px blur.

  • Motion

    16 one-off easing curves. 12 are the same curve, all in the header.

  • Widths and heights

    323 one-off sizes, in 189 different values.

  • Aspect ratios

    15 one-off ratios, in six values.

  • And more

    Possible scales too: background blur, letter spacing, font weights, border widths, opacity, durations, layers and icon sizes.

A one-off is a value typed by hand in place of a step from a scale. Counted in engineering’s code on 12 October 2026. Nothing is proposed for these yet.

Part 9

The path

How this reaches Figma and code without a big change that slows every squad down.

Eight steps, none of them a big bang

  1. Agree the numbers. Settle the 2560 sizes. Check the three widths against visitor data.
  2. Agree the names. Design and engineering sign off one set of names.
  3. Add new beside old. New variables in Figma, new classes in code. Nothing changes on screen.
  4. Pilot one page. Build it with the new roles. Compare before and after on big screens.
  1. Move shared packages. Start with the one Typography lives in. New work uses the roles.
  2. Move each area. One package at a time, at each team’s pace.
  3. Flag the old. Mark the old sizes as deprecated, with a warning on new uses.
  4. Remove the old. Once no package uses them.

This is the rollout for type. Each step ships on its own, and the round repeats for spacing, colour and the rest.

Progress is a count, not an opinion

Measure Today Goal
Sizes in the Tailwind config 62 12
Line heights in the Tailwind config 62 3
Typography variants that differ from Figma 4 of 22 0
Files that use the old size classes directly 43 0
One-off font sizes 13 0
Font sizes forced with !important 10 0

Counted on engineering’s main branch on 30 September 2026. Because every new name starts with easel-, we can count how much of the site has moved.

What we decide today

Decision Starting position Today?
Where we start Type first, then spacing and the gutter. Colour: start now or park it Yes
Which widths we draw at 390 and 1440, with 2560 checked, plus any breakpoint where the layout changes Yes
The gutter 16 and 48, then growing with the text to 54. Or growing faster, to 96 Yes, or after a week with the prototype
Screens wider than 2560 2560 for pictures and grids, narrower for text A first view
Above 2560, pages that differ Accept it for now, or wait Yes
Spacing names Numbers or t-shirt sizes A first view
The type and spacing numbers The 12 sizes as drafted, and the 15-step scale An owner and a date
How we agree and record a token or a pattern Who proposes one, who decides, and where it is written down An owner and a date

From an agreed date, new design work uses the agreed tokens

So the four of us are consistent before we ask engineering to be.

Two things to name today, not solve

Ways of working across squads

There is no shared process today, and nobody maintains the system. We can name the gap and agree to return to it.

Why QA approves work that is still wrong

If seven frames do not prevent that, fewer frames with clearer rules may do better. What do you see in your squads?

See it working