What "contained" means and when to use it
Putting everything in a card is the fastest way to make a page feel like a grid of boxes. Here's how to use containment selectively to create visual rhythm rather than visual noise.
4 min read
The mistake most design systems make is building components before establishing tokens. Here's why the order matters and what happens when you get it backwards.
Sahil Jadhav
Founder, Sero Studio
The most common design system mistake is building components before you've defined the token layer underneath them. You end up with a card component that has a hardcoded background colour, a button that references a specific hex value, an input with spacing that doesn't relate to anything else on the page. It works. And then the first time someone asks to update the colour scheme, you're doing a find-and-replace across 40 files.
A design token is a named value. --accent: #6366F1 is a token. --surface: #111111 is a token. The name carries meaning. The value can change.
When a button's background is var(--accent) rather than #6366F1, you've created a layer of abstraction that lets you change every button on the site by changing one line in one file. When the background is #6366F1, you have a system that requires you to remember every place that specific hex value appears.
The value of tokens scales with the size of the system. For a three-page marketing site, it's a minor convenience. For a design system used across multiple products, it's the difference between a maintainable system and a liability.
Tokens have levels. The base level is primitive tokens — raw values with no semantic meaning:
/* Primitive tokens — raw values */
--indigo-500: #6366F1;
--zinc-950: #09090B;
--white: #FAFAFA;
/* Semantic tokens — names that describe usage */
--accent: var(--indigo-500);
--background: var(--zinc-950);
--foreground: var(--white);
/* Component tokens — specific to a component */
--button-bg: var(--accent);
--button-text: var(--foreground);Most small projects only need the semantic layer. The primitive layer becomes useful when you have multiple themes or products that share some tokens but not others.
Tokens are what make dark mode maintainable rather than painful. Instead of duplicating every component with a dark: variant, you redefine the semantic tokens at the .dark class level:
/* Default (dark) */
--background: #0A0A0A;
--surface: #111111;
--foreground: #FAFAFA;
/* Light mode override */
.light {
--background: #FAFAFA;
--surface: #F4F4F5;
--foreground: #09090B;
}Every component that uses bg-background, bg-surface, and text-foreground switches automatically. No dark: prefixes scattered through component files. The theme lives in one place.
Before you build the first component. The discipline of naming tokens before you need them forces decisions about the colour system, the spacing scale, and the surface hierarchy that are much harder to make retroactively.
The question "what do we call the colour behind modals?" is easy to answer in a planning session. It's a refactoring exercise when you have forty components that all use slightly different values for that same concept.
More from the Studio
Putting everything in a card is the fastest way to make a page feel like a grid of boxes. Here's how to use containment selectively to create visual rhythm rather than visual noise.
4 min read
Most CMS choices are made too early and regretted too late. Here's why we reach for Sanity on most projects and what makes it worth the setup cost compared to the alternatives.
5 min read
Thoughts on building software, shipped occasionally.