CSS

How CSS Works: Rules, Selectors, and Where to Put Them

What a CSS rule is made of, how the browser matches rules to elements, and inline vs style tag vs external stylesheet, explained with live demos.

RazakTerra
Reading time
11 min read
Comments
Discussion
How CSS Works: Rules, Selectors, and Where to Put Them
On this page
  1. A stylesheet is a list of rules
  2. The anatomy of a rule
  3. Small syntax, strict reader
  4. How the browser uses your rules
  5. When two rules disagree
  6. Where your CSS lives
  7. 1. Inline styles
  8. 2. The <style> tag
  9. 3. An external stylesheet
  10. Choosing between them
  11. Try it yourself
  12. Wrapping up

If you followed the HTML path, you’ve already built real pages. They have headings, paragraphs, links, images and forms, and every one of them means something. They also look like a document from 1995: black serif text on white, blue underlined links, and not a single decision about how anything should look.

That’s not a flaw in your HTML. It’s the job split working exactly as intended. HTML says what each piece of content is. CSS says how it should look. And underneath that one-line description, there’s a small grammar to learn, a browser quietly checking every rule against every element on the page, and a choice about where your CSS should live that will decide how painful your next redesign is.

In this post we’ll build the whole picture from the ground up. Every section has a live demo, and the little pages inside them are real pages running real CSS, so anything you see there is exactly what a browser does. Poke at them. Break them. That’s what they’re for.

A stylesheet is a list of rules

Here’s the mental model that everything else in this path builds on: CSS is a list of rules, and every rule answers two questions: which elements? and what should they look like?

That’s it. There’s no program running from top to bottom, no variables to set up first. You hand the browser a list of rules, and it works out where each one applies.

Here’s one rule:

styles.css
CSS
h1 {
color: tomato;
font-size: 40px;
}

Read it out loud and it almost sounds like English: “for every h1, make the colour tomato and the font size 40 pixels.”

The anatomy of a rule

Every piece of that rule has a name, and you’ll hear those names constantly, in documentation, in error messages, and in your browser’s developer tools. Pick a part below to see where it sits. Then change the selector or the values, and watch the page follow.

The anatomy of a CSS rule
h1 {
color: tomato;
font-size: 40px;
}

Selector. Which elements the rule is for. A bare tag name like h1 means every <h1> on the page. The dashed outline in the preview shows what it picked.

color
font-size40px
Pick a part to see where it sits. Change the selector or a value and the page follows.

A few things worth noticing while you play:

  • Switch the selector from h1 to p and the styles move. The declarations didn’t change at all. Only the answer to “which elements?” did.
  • The paragraph never gets styled while the selector says h1. A rule only touches what its selector picks, nothing else.
  • The value has to suit the property. color wants a colour, font-size wants a size. We’ll spend a whole post on units like px later in the path.

Small syntax, strict reader

Now press Delete a semicolon. Both styles disappear, even the font size, whose line you didn’t touch.

Here’s what happened. The semicolon is what ends a declaration. Without it, the browser keeps reading, and it ends up with this:

One missing semicolon
CSS
h1 {
color: tomato
font-size: 40px;
}

As far as the browser is concerned, the value of color is now tomato font-size: 40px. That isn’t a colour, so it throws the whole declaration away and carries on. Your font size went down with it, because it had been swallowed into the broken value.

The last declaration in a block doesn’t strictly need its semicolon, since the closing brace ends it anyway. Write it every time regardless. The day you add another line underneath, you’ll be glad it’s already there.

How the browser uses your rules

So you’ve handed the browser a stylesheet with a few rules. What does it actually do with them?

Remember from the HTML path that the browser turns your markup into a tree of elements, the DOM. For each element in that tree, it goes through your rules and asks one question: does this selector pick me? Every rule that says yes gets its declarations applied.

Below is a stylesheet with five rules, the tree of a tiny page, and the page itself. Pick a rule to see which elements it selects. Then use the checkboxes to switch rules off, the same way you can in the browser’s developer tools.

The browser checks every rule against every element
  • <body>
  • <h1>
  • <p>
  • <a>
  • <p class="note">

h1 selects 1 element.

Pick a rule to see what it selects. Switch rules off to peel the stylesheet away.

Three things in there are worth slowing down for.

One rule can select many elements. The p rule lights up both paragraphs. You wrote line-height once, and it applies everywhere it’s needed. On a real site, a single p rule might style hundreds of paragraphs across dozens of pages.

Some styles flow downward. Pick the body rule. It only selects one element, yet the heading, the paragraphs and the link all switch to Georgia. That’s inheritance: some properties, mostly the ones about text like font-family and color, are passed down from an element to everything inside it. It’s why you can set the font for a whole site with one rule on body.

The page is never truly unstyled. Press Switch every rule off. The heading is still big and bold, the paragraphs still have space between them, and the link is still blue and underlined. None of that came from you. Every browser ships with its own default stylesheet, and your rules are layered on top of it. That’s why an h1 looks like a heading before you’ve written a single line of CSS.

When two rules disagree

You might be wondering what happens when two rules select the same element and set the same property to different values. Try this:

Two rules, one property
CSS
p {
color: navy;
}

p {
color: crimson;
}

The paragraphs come out crimson. When two rules are equally specific, the one that comes later wins. That’s the “cascade” in Cascading Style Sheets, and there’s more to it than “last one wins”: a more specific selector beats a less specific one no matter where it sits. That’s a whole post of its own, a little later in this path. For now, “later wins” will get you a long way.

Where your CSS lives

So far we’ve looked at rules on their own. But a rule has to live somewhere for the browser to find it, and you have three options: on the element itself, in a <style> tag inside the page, or in a separate .css file.

All three work. The difference shows up the day you want to change something. Below is a tiny bakery website with two pages. Every colour in the source code is clickable. Your job: give every heading on the site a new colour. Try it in each of the three modes and count how many clicks it takes.

Three places CSS can live
index.html
<h2 style="color: ">Fresh bread</h2>
<p>Baked every morning at six.</p>
<h2 style="color: ">Opening hours</h2>
<p>Monday to Saturday, until it runs out.</p>
about.html
<h2 style="color: ">Our story</h2>
<p>Three friends and one old oven.</p>
<h2 style="color: ">The team</h2>
<p>Say hi when you visit.</p>

The heading colour is written in 4 places. Every heading matches, for now.

Click any colour in the code to change it. Try to give the whole site one new colour.

With inline styles, the colour is written four times, and it’s easy to miss one and end up with a site where the pages don’t match. With a <style> tag, it’s written once per page. With an external file, it’s written exactly once, and both pages follow. Now imagine the real thing: forty pages, a dozen headings each.

Let’s look at each option properly.

1. Inline styles

An inline style goes straight onto the element, in its style attribute:

index.html
HTML
<h2 style="color: tomato; font-size: 24px;">Fresh bread</h2>

Notice that there’s no selector and no curly braces. The declarations apply to this one element and nothing else, so there’s no question of “which elements?” left to answer.

That’s also the problem. An inline style can’t be reused, so every element that should look the same needs its own copy. It mixes content and presentation back together, which is exactly what CSS was invented to separate. And it beats almost any rule in a stylesheet, which makes it hard to override later.

Inline styles do have a place. JavaScript often sets them for values that change as you interact with the page, like the position of something you’re dragging. But for styling a page by hand, reach for one of the other two.

2. The <style> tag

You can put rules in a <style> element, usually inside the <head>:

index.html
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fresh bread</title>
<style>
h2 {
color: tomato;
}
</style>
</head>
<body>
<h2>Fresh bread</h2>
<h2>Opening hours</h2>
</body>
</html>

Now you have real rules with real selectors, so one rule styles every h2 on the page. That’s a big step up. It’s a good fit for a single-page experiment, an HTML email, or a demo like the ones in this post.

But the rules only exist on this page. The moment your site has a second page, you’ll be copying the <style> block across, and keeping the copies in sync by hand.

3. An external stylesheet

The third option moves the rules into their own file, and each page links to it with a <link> tag in the <head>:

styles.css
CSS
h2 {
color: tomato;
}
index.html
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fresh bread</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h2>Fresh bread</h2>
<h2>Opening hours</h2>
</body>
</html>

Two attributes do the work. rel="stylesheet" tells the browser what kind of file this is, and href says where to find it, as a path relative to the page, just like a link or an image.

This is how almost every website is styled, for three good reasons:

  • One place to change things. Every page that links the file gets every rule. A redesign is an edit to one file.
  • Content and style stay apart. Your HTML files describe content. Your CSS file describes looks. Each one is easier to read on its own.
  • It’s faster. The browser downloads styles.css once and keeps it in its cache. When the visitor opens the next page, the styles are already there.

Choosing between them

Inline style="" <style> tag External .css file
Styles One element One page Every page that links it
Reusable No Within the page Across the whole site
Cached by the browser No No Yes
Good for Values set by JavaScript Single pages, emails, demos Almost everything
Where to put your CSS.

Try it yourself

Here’s a small page with its own stylesheet, linked exactly the way we just saw. Change the colours, add a declaration, switch a selector, or delete a semicolon on purpose and watch what breaks.

Your first stylesheet
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Field notes</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <h1>Field notes</h1>
  <p>Day three. The tide came in faster than the map said.</p>
  <p class="note">Note: pack a second pair of socks.</p>
</body>
</html>
Result
Cmd/Ctrl + Enter runs it · Esc leaves the editor

You might have spotted .note in there, a selector that starts with a dot. That’s a class selector, and it picks elements by their class attribute instead of their tag name. It’s the first of many kinds of selector, and they’re what the next post is all about.

Wrapping up

Everything in CSS starts from the picture we drew at the beginning: a list of rules, each one asking which elements? and saying what should they look like? The browser walks the page, checks every element against every rule, layers the answers on top of its own defaults, and lets a few properties flow down to the elements inside.

Here’s what to take with you into the rest of the path:

  1. A rule is a selector plus a declaration block. Each declaration is property: value;.
  2. End every declaration with a semicolon, even the last one. A missing one silently drops styles.
  3. A selector can pick many elements, so you write a style once and reuse it everywhere.
  4. Text properties like font-family and color are inherited by the elements inside.
  5. The browser has a default stylesheet. Your rules sit on top of it.
  6. When equal rules disagree, the later one wins.
  7. Put your CSS in an external file and link it from the <head> of every page.

Next up: selectors. Tag names are only the start. You can pick elements by class, by id, by attribute, by their position in the tree, and even by what the reader is doing with them right now. See you there! 🎨

Written by

Razak

Writes about the web, slowly, and builds small tools on the side.

Discussion

Comments

Loading comments…

Keep reading