Skip to content
article

Our design system had 47 greys. Now it has 9 (and some semantic tokens)

How we did the audit, what we named things, and the one fight we lost

Giulia Bellandi@giulia_designs

June 21, 2026 · 3 min read

Allora. Last month I opened our Figma library and counted the greys. Not the colours, only the greys. 47 ✨. Forty-seven! Some were different by one hex digit, like #F4F4F4 and #F5F5F5, and both were used in production for "background light". Boh.

This is how we cleaned it, in a fintech app with two squads, one designer (me) and a lot of developpers who are very patient with me.

1. The audit (boring, necessary)

I did not trust Figma for this. I asked our frontend lead to search the codebase for every hex and rgb value:

rg -o --no-filename '#[0-9a-fA-F]{6}\b|#[0-9a-fA-F]{3}\b|rgba?\([^)]*\)' src/ \
  | tr 'A-F' 'a-f' | sort | uniq -c | sort -rn > colours.txt

312 unique values. The 47 greys in Figma became 61 in code ahah. But the top 9 values covered more than 80% of the usages, which already told us the answer.

2. A scale, then meanings

We kept a primitive scale of neutrals and put semantic tokens on top. Components can only use the semantic ones. Primitives are private.

:root {
  --neutral-50: #f7f7f8;
  --neutral-100: #eeeef0;
  --neutral-200: #dcdce0;
  --neutral-300: #c2c2c8;
  --neutral-400: #9a9aa3;
  --neutral-500: #73737d;
  --neutral-600: #565660;
  --neutral-700: #3d3d45;
  --neutral-900: #18181c;

  --surface-page: var(--neutral-50);
  --surface-card: #ffffff;
  --surface-sunken: var(--neutral-100);
  --border-subtle: var(--neutral-200);
  --border-strong: var(--neutral-400);
  --text-primary: var(--neutral-900);
  --text-secondary: var(--neutral-600);
  --text-disabled: var(--neutral-400);
}

Yes, there is no 800. Nobody used it. A scale is not a religion 🤌

3. Check contrast before naming anything

Every text token was checked against every surface it can sit on. WCAG AA asks 4.5:1 for normal text and 3:1 for large text. Our old "secondary text" grey was #999999 on white, which is about 2.8:1. It was on every transaction date in the app. For years. Nobody complained, because the people who can't read it don't write to support. They just leave.

4. The fight we lost

I wanted to remove pure black text completely. Marketing wanted #000000 for the big balance number "because it feels more serious". We made a research with 6 users, nobody noticed the difference, marketing still won. Fine. I choose my battles, this was not the hill.

What changed after

  • Dark mode became one file: we remap the semantic tokens, the components do not change.
  • New screens get reviewed in half the time, because "which grey?" is not a question anymore.
  • Developpers stopped asking me for hex codes in Slack. I miss them a little. No I don't ☕

If your library has more than 15 greys, I promise you, nobody chose them. They just happened.

Written by

Giulia Bellandi

Product designer in Milano ✏️ design systems, typography, and fighting for the 4px grid. Ex-agency, now in-house at a fintech. Pasta is also a design system.

Get the best of Dynamo Geeks weekly

One email each Monday with the most useful posts. No spam; unsubscribe in one click.

Comments

8

Sign in to join the conversation.

Sign in to comment
Sofía Ramírez@sofi_dev_mx
jajaja 47 greys. nosotros teníamos "gray", "grey", "grayLight", "greyLighter" y "lightGray" en el mismo repo, todos distintos 😅 the semantic token layer is the thing, we did the same with the Tailwind theme and it stopped the bleeding
0 net votes
Giulia Bellandi@giulia_designs
"greyLighter" 😭 I felt this in my soul. Tailwind theme is perfect for this, you just never expose the primitives!
1 net votes
Frank Ostrowski@frank_cpp98
Nine shades of grey and a CSS variable. In 1994 we had 16 colours Total and the software worked. Regards.
4 net votes
Giulia Bellandi@giulia_designs
And it looked like 1994 ahah ✨
3 net votes
Rizal Pratama@rgb_rizal
the rg command is gold, I run it on my portfolio already, 38 unique colours for a 4 page site wkwkwk
1 net votes
Kuya Migs@kuya_migs_codes
Saving this for my students. The #999 on white thing, nakakahiya, I literally have that on my own blog. Fixing it tonight.
4 net votes
Jo Brennan@jo_ships
'nobody chose them, they just happened' is also true of every pricing page i have ever built
3 net votes
Mariana Costa@mari_costa
I found this from the figma_fatigue topic and I have to say, the part about people who can't read the grey just leaving instead of complaining is exactly what we saw in our checkout research. Thank you for writing it so clearly 💛
4 net votes