Skip to content
← All patterns

design

Design Token Similarity

Which approved colour is this one?

The nearest approved colour to one a designer pasted, in a space where distance means difference.

A design system is a few hundred approved colours and a steady trickle of hex values that are nearly one of them. Answering 'which token is this, and is it close enough to just use' is a nearest-neighbour question over three numbers. Convert each colour to CIELAB first, because Euclidean distance in that space is the CIE's own definition of how different two colours look, and then the distance function is not a preference you are picking.

The model

Token

One approved colour, as the hex a stylesheet uses and as the L*a*b* the index searches.

pk
PALETTE#<paletteId>
sk
TOKEN#<tokenName>

Attributes: palette (S), hex (S), lab (L)

Access patterns

  • SearchVectors · by-labThe nearest approved token

    The three closest colours in one palette, with the score as a delta-E you can act on.

  • QueryEvery token in a palette

    The palette itself, by key, in token order.

Design notes

The space was built so that distance means differencewhy

CIELAB was defined in 1976 to be perceptually uniform: the same numeric gap should look like the same amount of change wherever in the space it happens. Euclidean distance between two L*a*b* points is delta-E 1976, the CIE's own colour-difference figure, so choosing EUCLIDEAN here is not a preference. The metric arrived with the space. Anywhere you can do that, do it, because a metric you picked because it seemed reasonable is a decision nobody will revisit and nothing will check.

The score is a number you can write a rule againstmodelling

#3577D4 comes back 2.82 from brand-500. A delta-E of about 2.3 is the usual figure for a just-noticeable difference, so 2.82 is a colour a careful eye can separate and a build pipeline should not: the honest answer is use brand-500. brand-700 is next at 16.4 and brand-300 at 34.0, both plainly different colours. Very few vector searches hand you a score with units. This one does, because the space it is searching has them.

Raw RGB is three numbers, not a spacetrade-off

Index the sRGB triplet instead and the same machinery gives wrong answers with total confidence. Paste the teal #40C8A8 and an sRGB search picks brand-500, a blue, because 64/200/168 happens to be arithmetically near 59/125/216. The Lab search picks success-500, a green, which is the answer any human would give, and reports it at delta-E 25.9 so you can also tell that nothing in the palette is really close. Same index type, same distance function, same dimensionality. The only difference is which three numbers you stored.

One palette per search, because the nearest colour is not the right colourmodelling

Harbour's brand-500 sits 1.40 from the query, nearer than anything Atlas owns, and returning it would be a correct nearest-neighbour answer and a serious mistake: it is another client's brand colour. palette is the SearchSchema's HASH, so a search has to name one and cannot wander. This is the ordinary case for a HASH - not a tenant boundary imposed for safety, just the observation that the question was never 'nearest colour anywhere'.

The vector is derived, so it can go stale silentlyoperations

lab is computed from hex, and nothing in DynamoDB knows that. Change a token's hex in a PutItem and forget to recompute its vector and the table is internally inconsistent in a way no read will report: the swatch sheet renders the new colour and the search still answers with the old one's neighbours. Derive the vector in the same code path as the write, or accept that you now have two sources of truth for one colour.

There is a conversion in here, and that is the modelwhy

sRGB to linear RGB to XYZ to L*a*b* is a gamma curve, a three-by-three matrix and a cube root against a fixed white point, D65 here. No weights, no inference, nothing to re-embed when a library moves. But it is still a choice: pick D50 instead, or delta-E 2000 rather than 1976, and every stored vector means something slightly different. Vector indexes stop needing a neural network on the write path. They do not stop needing somebody to decide what the numbers mean.

Taught in the course

design-token-similarityDynamoDB workbench
Ready to run
Explore an access patternSelect to load & run

The three closest colours in one palette, with the score as a delta-E you can act on.

Request
Execute against the local Dynoxide engine
ReturnedFiltered outChanged
PK(pk)
SK(sk)
hex
lab
palette
PALETTE#atlas
TOKEN#accent-500S
#E4572ES
[55.6, 52.82, 50.73]L
atlasS
PALETTE#atlas
TOKEN#brand-300S
#8FB6ECS
[73.12, 0.26, -31.09]L
atlasS
PALETTE#atlas
TOKEN#brand-500S
#3B7DD8S
[52.46, 9.71, -52.75]L
atlasS
PALETTE#atlas
TOKEN#brand-700S
#1F4FA8S
[35.42, 17.72, -52.37]L
atlasS
PALETTE#atlas
TOKEN#ink-300S
#A8B0BCS
[71.56, -0.39, -7.07]L
atlasS
PALETTE#atlas
TOKEN#ink-600S
#4A525ES
[34.6, -0.24, -8.05]L
atlasS
PALETTE#atlas
TOKEN#ink-900S
#14171CS
[7.64, 0.04, -3.99]L
atlasS
PALETTE#atlas
TOKEN#success-500S
#2E9E5BS
[57.84, -46.62, 26.19]L
atlasS
PALETTE#atlas
TOKEN#surface-50S
#F6F8FBS
[97.51, -0.13, -1.67]L
atlasS
PALETTE#atlas
TOKEN#warn-500S
#E0A526S
[71.49, 11.29, 67.89]L
atlasS
PALETTE#harbour
TOKEN#accent-500S
#D6452BS
[50.37, 55.48, 46.3]L
harbourS
PALETTE#harbour
TOKEN#brand-500S
#3A7AD6S
[51.46, 10.7, -53.2]L
harbourS
PALETTE#harbour
TOKEN#ink-800S
#1B2129S
[12.49, -0.33, -6.25]L
harbourS
Awaiting request
Your next query starts here.

Choose an access pattern above, or build your own request. See what comes back and what it costs.

Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.