Style a small page
The bakery page from the HTML course gets its colours, spacing and layout from about 40 lines of CSS, and the HTML does not change at all.
Start with decisions, not rules
Before writing rules, make a handful of decisions: one accent colour, a text colour and a background with enough contrast, one font stack, a base size, and a spacing step that repeats everywhere (for example 8, 16 and 24 pixels). A page that reuses a few values looks calm and deliberate; a page where every box has its own slightly different margin looks messy even when nobody can say why. CSS has a feature made for this, custom properties.
Custom properties
A custom property is a name you invent, starting with two hyphens, that holds a value: --accent: #1a5fb4;. Define your decisions once on :root, which is the html element, and use them anywhere with var(): background: var(--accent);. Custom properties inherit like text properties, so every element can read the ones defined on the root. Changing the accent colour of the whole site now means editing one line. You can also redefine a property inside a media query, for example to swap colours for prefers-color-scheme: dark.
:root {
--accent: #1a5fb4; /* 6.29:1 on white */
--ink: #222;
--paper: #fffdf8;
--space: 16px;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.6;
color: var(--ink);
background: var(--paper);
}
header { display: flex; justify-content: space-between; align-items: center;
flex-wrap: wrap; gap: var(--space); padding: var(--space); }
nav ul { display: flex; gap: var(--space); list-style: none; margin: 0; padding: 0; }
main { max-width: 60rem; margin: 0 auto; padding: var(--space); }
.cards { display: grid; gap: var(--space);
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
.card { padding: var(--space); border: 1px solid #ddd; border-radius: 8px; }
img { max-width: 100%; height: auto; }
.btn { background: var(--accent); color: white; border: 0;
padding: 8px var(--space); border-radius: 6px; font: inherit; }
.btn:hover { background: #154c90; }
.btn:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }Reading the stylesheet from top to bottom
The stylesheet above uses something from almost every unit. The root holds the decisions. body sets the font, line height and colours once, and inheritance carries them into every paragraph. The header is a wrapping flex row, so the menu drops below the name on a narrow phone instead of being squeezed. main has a max-width and margin: 0 auto, which keeps lines readable and centres the content on wide screens. The cards use the auto-fill grid pattern, so they reflow from one column to several with no media query, and images are fluid. The button gets a hover colour and, just as important, a visible focus outline for keyboard users.
Check it on real screens
Testing CSS means looking. Open the page in a narrow window (320 pixels for the WCAG reflow check) and a wide one, and look for sideways scrolling and text that touches the edges. Zoom to 200 percent. Press Tab through the page and make sure you can always see where the focus is. Check the contrast of text and buttons in the developer tools. Turn on the dark mode or reduced motion setting of your system if you styled for them. Browser developer tools let you change any value live and see the box model of any element, which is the fastest way to answer the question of why something is where it is.
From here, the same pieces scale up to any site. Bigger projects split the stylesheet into files, name classes by what a component is (card, button, site header), and keep a short list of shared custom properties, but the rules underneath are the ones in these nine units.
| custom property | --name: value; read with var(--name) |
| :root | The html element; where shared values live |
| var() | Uses the value of a custom property |
| :hover | Pointer over the element |
| :focus-visible | Visible keyboard focus; never leave it unstyled after removing the outline |
| spacing scale | A few repeated spacing values, such as 8, 16, 24 |
- How does a custom property name start?
- Two hyphens
- A dollar sign
- An at sign
- How do you use the value of --accent?
- var(--accent)
- get(--accent)
- $accent
- Why define shared values on :root?
- Custom properties inherit, so every element can read them
- Only :root accepts custom properties
- It makes them faster
- What is the contrast ratio of #1a5fb4 on white, rounded to two decimals?
- Why does the header use flex-wrap: wrap?
- On narrow screens the menu moves below the name instead of being squeezed
- To hide the menu
- To make the text bold
- What should a button always have for keyboard users?
- A visible focus style
- A hover animation
- A shadow
- Which layout lets the cards go from one column to many with no media query?
- repeat(auto-fill, minmax(180px, 1fr))
- grid-template-columns: 180px
- display: block
- --space is 16px. How many pixels is calc(var(--space) * 1.5)?
- Why reuse a few spacing values everywhere?
- The page looks consistent and is easier to change
- Browsers require it
- It improves contrast
- Decide colours, fonts and a spacing scale first; store them as custom properties on :root.
- body sets text styles once; flex for the header, grid for cards, fluid images.
- Test at 320px, at 200% zoom, with Tab, and in the developer tools.