Project: a pricing section you could ship
Build one real component from a client's brief: tokens, a card, the states people need, a row that works at every width, and dark mode. The HTML is given. All the CSS is yours.
By the end you can
- Start a component from tokens rather than loose values
- Style a card, its featured variation and its button states
- Make a row of cards work at every width without media queries
- Finish with dark mode and respect for reduced motion
step 1 of 4
Tokens first, then the card
This is how a real component starts. The HTML is written and will not change. Everything you see is decided by your CSS.
Begin with tokens, not with the card. Decide your colours, your spacing and your rounding once, on :root, and refer to them from then on. It feels slower for five minutes and saves an hour later, because changing the brand colour becomes one line.
You need, at minimum: a brand colour, a page background, a surface colour for the card, an ink colour for text, a muted colour for secondary text, a border colour, one spacing value and one radius.
Then style the card itself: surface background, a border, generous padding, rounded corners, and the title and price marked out clearly.
Keep every selector a single class, as in the architecture lab. The HTML already uses the block and element names.
One rule for this whole project: no fixed pixel widths on the cards. Everything must survive being squeezed.
:root {
--brand: #0b7d4f;
--bg: #ffffff;
--surface: #f7faf8;
--ink: #111827;
--muted: #5b6b63;
--line: #e3e8e5;
--space: 1.25rem;
--radius: 14px;
}
.plan {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: var(--space);
}What these words mean
- token
- A named value used everywhere instead of the raw colour or size.
- surface
- The background of a card, one step from the page background.
- ink
- The main text colour. Named for its job, so dark mode can change it.
- single class selector
- Every rule one class, so nothing out-ranks anything else.
Easy to get wrong
Writing #0b7d4f in eleven places
--brand, used eleven times
The day the client sends a new brand colour you are hunting through the file, and you will miss one. With a token it is a single line.
width: 320px on the card
No width at all; let the layout decide
A fixed width cannot shrink on a phone or stretch in a grid. A card should be told its width by whatever contains it.
What we check
- Tokens are declared on :root
- There are at least six tokens
- They are used, not just declared
- The card is rounded
- The card has padding
- The price is the biggest thing on the card
- No descendant selectors
- No fixed widths