Skip to content
Lucas Bertolozzi Open to new roles

Building a colour palette
nobody misreads

Project scope

  • Audit of the previous palette
  • Choice of colour space
  • Scale of 7 families and 12 shades
  • WCAG contrast matrix
  • Palettes for light and dark backgrounds
  • Figma variables
  • Living documentation

Introduction

On an investment screen, misreading a number costs money. So Clear’s colour was built from contrast, not checked against it afterwards.

Company
Clear Corretora, XP Inc.
Basis
OKLCH and WCAG 2.2
Scroll to explore
OKLCH lettering with the words lightness, chroma and hue, over a colourful relief and a WCAG 2.2 accessible stamp.

Result

Contrast at the highest level

On the final screens, all text passes WCAG level AAA, the international standard for digital accessibility. Contrast is measured as a ratio between the brightness of the text and that of the background. 4.5:1 is the accepted minimum and 7:1 is the highest level.

Primary text at 14.61:1, secondary at 9.21:1, status labels between 7.09:1 and 12.62:1. One single exception, declared. In the previous palette, chart labels sat close to 3.5:1, below the minimum.

Problem

A scale that only worked by trial and error

The minimum and maximum labels on the chart sat close to 3.5:1 on white. In a brokerage app that is not a question of style. It is a share price made unreadable for anyone with low vision.

Swapping that grey would have fixed one screen and nothing else, because the problem was the whole scale. The lightness gap between one shade and the next ranged from 0.031 to 0.144, almost five times apart. Anyone picking shade 600 expecting one contrast got another, and every screen became a new negotiation.

There were three families, primary, secondary and neutral, for a product that has to say price up, price down, warning, error and information.

Primary AAA 17.17:1 AAA 15.68:1 AAA 13.25:1 AAA 10.35:1 AA 6.90:1 AA 4.97:1 AAA 9.00:1 AAA 11.20:1 AAA 13.35:1 AAA 15.42:1 AAA 17.25:1 AAA 18.82:1 Secondary AAA 17.31:1 AAA 15.87:1 AAA 13.87:1 AAA 12.00:1 AAA 9.91:1 AAA 7.79:1 AA 5.53:1 AA 4.92:1 AAA 7.33:1 AAA 9.15:1 AAA 11.55:1 AAA 15.70:1 Neutral AAA 17.66:1 AAA 16.73:1 AAA 14.90:1 AAA 12.54:1 AAA 8.98:1 AA 5.85:1 AA 4.81:1 AAA 7.48:1 AAA 10.23:1 AAA 12.65:1 AAA 15.50:1 AAA 18.75:1
On each shade, the best possible text colour, white or near-black, and the contrast it reaches.

Decision

Why not HSL?

HSL is the industry standard and it gets lightness wrong. With the same recipe, 60% saturation and 40% lightness, the green lands at 3.25:1 on white and fails. The red lands at 7.23:1 and passes the highest level. The number is the same, the brightness the eye sees is not.

I chose OKLCH, a model that describes colour by lightness, chroma and hue following how the eye perceives it, not how the screen emits light. In it, the scale stops being a collection of picked colours and becomes a function. Shade 600 has a lightness of 0.61 across all six colour families. That is a measurement, not an approximation.

The steps from one shade to the next now range from 0.034 to 0.081. Twice as regular as before.

Same recipe, two models

HSLsaturation 60% · lightness 40%

+2.35% 3.25:1 · fails #29A352
−1.80% 7.23:1 · AAA #A32929

OKLCHlightness 0.45

+2.35% 7.07:1 · AAA #27643A
−1.80% 7.91:1 · AAA #952B29

In HSL, the same lightness value produces different brightness. In OKLCH, the same lightness looks the same to the eye.

Blue AAA 17.17:1 AAA 15.51:1 AAA 13.19:1 AAA 10.88:1 AAA 8.62:1 AA 6.50:1 AA 4.77:1 AA 5.56:1 AAA 8.00:1 AAA 9.90:1 AAA 12.31:1 AAA 15.68:1 Green AAA 17.27:1 AAA 15.66:1 AAA 13.80:1 AAA 11.52:1 AAA 9.16:1 AAA 7.04:1 AA 5.22:1 AA 4.97:1 AAA 7.07:1 AAA 9.03:1 AAA 11.53:1 AAA 15.22:1 Red AAA 17.18:1 AAA 15.39:1 AAA 13.03:1 AAA 10.65:1 AAA 8.25:1 AA 6.10:1 — 4.39:1 AA 5.75:1 AAA 7.91:1 AAA 9.79:1 AAA 12.32:1 AAA 15.80:1 Amber AAA 17.14:1 AAA 15.48:1 AAA 13.14:1 AAA 10.80:1 AAA 8.48:1 AA 6.48:1 AA 4.81:1 AA 5.30:1 AAA 7.49:1 AAA 9.40:1 AAA 11.88:1 AAA 15.55:1 Teal AAA 17.19:1 AAA 15.70:1 AAA 13.62:1 AAA 11.37:1 AAA 9.03:1 AA 6.92:1 AA 5.10:1 AA 5.02:1 AAA 7.14:1 AAA 9.07:1 AAA 11.67:1 AAA 15.32:1 Purple AAA 17.13:1 AAA 15.42:1 AAA 13.10:1 AAA 10.71:1 AAA 8.31:1 AA 6.16:1 — 4.40:1 AA 5.91:1 AAA 8.08:1 AAA 9.95:1 AAA 12.40:1 AAA 15.77:1 Neutral AAA 17.81:1 AAA 16.78:1 AAA 15.01:1 AAA 12.66:1 AAA 9.26:1 AA 6.14:1 — 4.32:1 AAA 7.83:1 AAA 9.50:1 AAA 11.96:1 AAA 14.99:1 AAA 18.44:1
On each shade, the best possible text colour, white or near-black, and the contrast it reaches.

Method

When does contrast come in?

Checked at the end, contrast turns into rework. Pick, test, fail, start again. Used as the starting point, it becomes a guarantee.

I calculated the cross matrix of every text and background pair in the system and turned the result into usage rules. Which shades take text, which do not, and from what text size the answer changes. Designers do not need to measure. They need to follow.

Matrix excerpt · text on background, light mode

BackgroundSubtle backgroundSunken backgroundPrimary actionUp, fillDown, fill
Primary text 18.44 · AAA 17.81 · AAA 16.78 · AAA 2.31 · fails 2.61 · fails 2.33 · fails
Secondary text 9.50 · AAA 9.17 · AAA 8.64 · AAA 1.19 · fails 1.34 · fails 1.20 · fails
Tertiary text 7.83 · AAA 7.56 · AAA 7.12 · AAA 1.02 · fails 1.11 · fails 1.01 · fails
Accent 8.00 · AAA 7.72 · AAA 7.28 · AAA 1.00 · fails 1.13 · fails 1.01 · fails
Up, text 9.03 · AAA 8.72 · AAA 8.21 · AAA 1.13 · fails 1.28 · fails 1.14 · fails
Down, text 7.91 · AAA 7.64 · AAA 7.20 · AAA 1.01 · fails 1.12 · fails 1.00 · fails
Text on colour 1.00 · fails 1.04 · fails 1.10 · fails 8.00 · AAA 7.07 · AAA 7.91 · AAA
Pair approved by the system. Each cell is painted with the real text and background pair.

Trade-off

How do you show up and down without relying on colour?

Green for up and red for down is the strongest convention in financial markets and one of the worst for accessibility. It is the classic case of colour blindness, which affects around 8% of men. Here the mistake is not aesthetic, it is someone’s balance.

Dropping the convention was not an option, because traders read the colour before the number. The way out was never to leave colour on its own. The plus or minus sign always sits right next to the number, and on charts each series gets its own line style and marker.

This is where OKLCH paid for itself. The green and red for up and down sit on the same shade of the scale, 800, with the same lightness, 0.45. The down line does not look heavier than the up line for no reason. The cost of that choice is that, without hue, the two become the same tone. The sign stopped being a detail and became a rule.

Limit

How far can the scale go?

The previous palette lived on the edge of what the screen can show, between 95% and 100% of the sRGB limit, the range of colours an ordinary screen can display, on most shades. Colour in that region is unstable. It saturates, loses the difference between neighbouring shades and changes from one device to the next.

The new scale follows a chroma curve, the intensity of the colour, running from 28% to 88% of that limit. It rises to the middle of the ramp and drops at the ends. The very light and very dark shades give up intensity to keep lightness predictable. The system loses a few vivid shades at the extremes and earns the right to be used by rule.

HOW MUCH OF THE sRGB LIMIT EACH SHADE USES

Previous palette New palette

Decision

Is dark mode just the light palette inverted?

No. Highly saturated colour on a dark background seems to vibrate and lift off the surface, an effect that tires the eye quickly during long reading. Those colours were left out of dark mode, with the Atlassian design system as the tonal reference. The dark surfaces went from three levels to four.

Instead of redefining the same variables depending on context, I built two complete sets of primitives, the raw colours the whole system refers to. One for light backgrounds, one for dark. The alternative would have made the file leaner and broken the handoff to development, because Figma’s Dev Mode loses track of a value that changes meaning depending on where it sits. A smaller file is not worth a worse handoff.

Dark mode surfaces · before 3, now 4

Background #0D1116

Subtle background #14171E

Raised #1B1F26

Overlay #24282F

Limit

And when the maths doesn’t add up?

Six chart series distinguishable from each other at 3:1 would require 3 to the power of 5, that is 243:1 between the extremes. An ordinary screen offers around 21:1. No palette can solve that, and a system that promises to is getting its sums wrong.

The rule that stuck: each series has at least 3:1 against the background, and the difference between series never depends on colour alone. Line style, marker and label do the rest, as the standard itself requires.

Result

What did the team keep?

A living document in eight sections, the kind a team looks up instead of asking. Properties, palette, typographic accessibility, a token matrix at AAA level, contrast for non-text elements, focus appearance, chart series and the migration guide from the previous version.

In Figma, 224 variables in three collections. Primitives with the raw colours, semantic tokens with the meaning of each use, such as background, text, up and down, and components. Two modes, light and dark.

The declared exception is the placeholder text in the search field, at 5.75:1. It passes AA but not AAA. It is documented as an exception instead of being rounded up, because a system that hides its own exception stops being verifiable.

Learnings

What I take from this project

  1. 01

    Contrast is the starting point

    When the rule comes from the measurement, nobody has to measure again. Checking at the end only finds the mistake later.

  2. 02

    A declared exception beats a perfect score

    A 5.75:1 written into the document is more trustworthy than an AAA everywhere that nobody can verify.

  3. 03

    When colour isn’t enough, the answer isn’t another colour

    Colour blindness and chart series had the same way out, a second channel. Sign, line style and marker.

Credits

Colour system and accessibility verification, mine end to end, built on the foundation of Clear primitive tokens I had created in SOMA.

Next project

Documentation pages from the SOMA design system with the XP Inc. logo: typography, effects and the colour scale.

XP Investimentos, Clear Corretora, XP Inc.

Teaching three brands to speak the same language

Coming soon