Takes best served hot

Search

Not tokens

6 min read

Over the years, I’ve seen some folks choose to create tokens such as --color-black. And those tokens get completely inverted in dark mode, which confuses teams, who then reach for something more semantic. The funny thing is, designers say black but don’t often mean black. More often, they expect a color like #111 or eigengrau.

One reason people reach for primitive tokens like this is to communicate complex values. Trying to say something like hash-one-one-one is cumbersome so we give it a name. The token bridges the gap between a raw hex code and what it represents. That’s useful but not every value needs that bridge. Some values already communicate exactly what they are. And when you tokenize something that’s already clear, you’re not adding meaning. You’re organizing for the sake of organizing.

The other reason is flexibility. A token represents a decision that could change. When you tokenize a value that has no reason to change, you’re not creating flexibility. You’re creating a false promise. You’re telling your team “this might be different someday” when it won’t be. And that false promise leads to one of two outcomes: either the team treats the token as sacred and never questions it, or they start questioning every token and lose trust in the system entirely.

Zero

If you have no border or no spacing, creating a token called 0 is not helpful because it’s not going to change. At what point is --width-0 going to be anything other than 0? It won’t and it subverts expectations.

Unlike a hex code or a pixel value from a scale, 0 doesn’t need a token to explain what it means. It already communicates perfectly. When a developer writes margin: 0, they’re making a clear declaration: “I want no margin here.” Wrapping that in var(--spacing-0) doesn’t add clarity. It adds indirection to a value that was already self-evident. And it bloats your token set with entries that serve no design decision.

If you’re using zero tokens to maintain consistency in a scale (like --spacing-0 through --spacing-8), consider whether that consistency is actually helping anyone. A scale exists to give people meaningful options to choose from. The choice between 4px or 8px is trying to feel out the relationship between objects. Zero is more specific, saying the relationship couldn’t be closer. That choice is more deliberate than trying find a value from a scale.

Transparent

Choosing to make something transparent is similar to choosing zero. Transparency is the absence of color, the same way zero is the absence of dimension. And like 0, the keyword transparent already says exactly what it means. There’s no complex value to decode. A token like --color-transparent will never resolve to anything other than transparent, and if it did, it would break every context where it’s used.

Think about where transparent typically shows up. You might use it on a ghost button to let the background behind it show through, or on an overlay’s initial state before it fades in. In both cases, the transparency is the design expectation. It’s not a value that varies by theme or context. It’s a deliberate choice to show nothing.

There’s also a practical concern. When teams see --color-transparent in a token set, they sometimes assume it’s there to be overridden per theme, similar to other color tokens. That assumption leads to bugs. Someone decides that transparent should become rgba(0, 0, 0, 0.05) in a specific context, and suddenly every ghost button has a faint background nobody asked for.

Full rounding

This is one of the most common tokens I see. It’s usually meant to be applied as border-radius: var(--rounding-full) where --rounding-full is set as some large value like 9999px. I ask the same question as the earlier examples above: do you ever expect this value to change?

The value 9999px has always smelled like a magic number. It works because browsers clamp border-radius to half the element’s shortest side, so any sufficiently large number produces a pill shape. But “sufficiently large” and “correct” are different things. There’s no specification behind 9999. It’s an arbitrary value that happens to be big enough. Now that we have the CSS keyword infinity, we can replace it with something that actually means what we intended.

.rounding-full {
    border-radius: calc(infinity * 1px);
}

But even with a better value, full rounding still doesn’t belong in a token. The decision to fully round something is made very close to the component. A pill-shaped tag, an avatar, a toggle thumb. These components know they need full rounding because of what they are, not because of what theme they’re in. A theme token implies that full rounding is a decision that lives far away from the component and might change across contexts. It won’t. An avatar is round in light mode and dark mode. A toggle thumb doesn’t become a squircle when the density changes.

When full rounding lives in a theme-level token, it also gives teams the impression that it’s part of a scale alongside --rounding-sm, --rounding-md, and --rounding-lg. But it’s not on the same spectrum. Those values represent varying degrees of a design decision. Full rounding represents a specific component-level choice. Treating them as peers in the same scale conflates two different kinds of decisions.

I’m also afraid that making concentric rounding semantic is practically impossible.

The underlying principle

The question to ask before creating any token is simple: will this value ever need to change independently of the property it’s applied to? If the answer is no, you don’t have a design decision. You have a constant. And well-named constants don’t need the overhead of a token system.

Tokens should capture intent that varies. Color tokens vary by expression. Spacing tokens might vary by density. Typography tokens might vary by theme. That variability is what gives tokens their power. When you dilute your token set with values that can never vary, you make it harder for you to maintain your token system. Not to mention, a value like 0 will be less bytes than any token you might want to introduce. The bottom line is you should weigh out how you expect the token to be used against the expected maintenance of that token over time. Is this an area where we can hard code the value like 0 or transparent? Does the ability for this to be a circle hinge on the theme or the person choosing to make it circular?

Do not reach to make another token first. Consider the scientific method; set the value and find out.

Theming atproto