Skip to content

How CSS works

docs.scrimba.com

A plain HTML page arrives with the browser's own opinions already applied: blue underlined links, black serif-free text, headings that step down in size. CSS is how you take that raw structure and decide how it should look instead. Before you can change a single colour or move a single box, though, you need to know how your styles reach the page in the first place, and what happens when two of your rules disagree about the same element.

Attaching CSS to a page

There are three ways to add CSS to a page, and the difference is mostly about where you write the styles.

The first is an inline style, written straight on the element with a style attribute:

html
<p style="color: teal;">A single teal paragraph.</p>

That styles one paragraph and nothing else. The second way is an internal <style> block in the page's <head>, which can style many elements at once. The third, and the one you should reach for, is a separate file of CSS linked to the page. You write your styles once in that file, and every page that links to it picks them up. Think of it like a wardrobe: instead of describing an outfit on each person one by one, you write the dress code down once and everyone follows it.

CSS reaches a page in three ways, and they are not equal. An inline style lives on the element itself in a style attribute and affects only that one element. An internal stylesheet is a <style> block in the <head> that can target elements across the whole page. An external stylesheet is a .css file linked from the <head>:

html
<link rel="stylesheet" href="styles.css">

Reach for the external stylesheet as your default. It keeps your styling in one place instead of scattered through the markup, so structure stays in HTML and presentation stays in CSS, the same separation described in what CSS is. The browser also caches the file, so a visitor moving between pages downloads your styles once and reuses them, and you write a rule such as your button styling a single time rather than copying it onto every button.

Three mechanisms attach CSS, and each maps to a place in the cascade you will meet later in this chapter. An inline style (a style attribute on the element) applies to that element alone and carries very high weight in conflicts, which is exactly why leaning on it hurts you: it is hard to override cleanly from a stylesheet. An internal <style> block scopes to one document. An external stylesheet, linked with <link rel="stylesheet">, is the production default for reasons beyond tidiness.

External files give you three things at once: separation of concerns, so markup and styling change independently; HTTP caching, so a returning visitor reuses the already-downloaded file across every page rather than re-fetching styles embedded in each HTML response; and reuse, so one authored rule serves an entire site. The trade-off is a separate request, which is why very small critical styles are sometimes inlined for the first paint, but the general rule holds: author in an external stylesheet and treat inline styles as a targeted exception, not a habit.

Here is the external approach in full: an HTML file that links a stylesheet, and the stylesheet itself.

html
<!-- index.html -->
<head>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <p class="intro">Welcome to the site.</p>
</body>
css
/* styles.css */
.intro {
  color: teal;
  font-size: 1.25rem;
}
JunoAttaching CSS to a page There are three ways to add CSS: on the element with style, in a <style> block, or in a separate file you link with <link>. Use the separate file. You write your styles once and every page that links to it looks the same.
JunoAttaching CSS to a page Inline styles hit one element, a <style> block covers one page, and an external .css file covers your whole site. Default to the external file: it keeps presentation out of your markup, the browser caches it, and one rule serves every matching element. Inline styles are the exception, not the routine.
JunoAttaching CSS to a page Author in an external stylesheet: separation, caching, and reuse in one move. Inline styles carry heavy weight in the cascade, so they are painful to override later; keep them as a deliberate exception. The one time to inline is a small block of critical styles for the first paint, and even then the bulk of your CSS stays in the linked file.

The anatomy of a rule

A piece of CSS is written as a rule, and every rule has the same shape. Here is one that styles a card:

css
.card {
  color: navy;
  padding: 16px;
}

The .card at the front is the selector: it picks which elements the rule applies to, in this case anything with the class card. Everything inside the curly braces is the declaration block. Each line inside, like color: navy;, is one declaration: a property (color) and the value you want it to have (navy), separated by a colon and ended with a semicolon. Read the whole rule as a sentence: "for every card, make the colour navy and the padding 16 pixels."

Every rule is built from the same parts, and naming them makes the rest of CSS easier to talk about.

css
.card {
  color: navy;
  padding: 16px;
}

The selector (.card) chooses the elements the rule targets. The curly braces hold the declaration block. Inside it, each property: value; pair is a declaration: color is the property, navy is the value, the colon joins them and the semicolon terminates them. The semicolon is a separator between declarations, so the last one technically does not need it, but keep it anyway: adding the next declaration later becomes a one-line change instead of two. Which elements a selector matches, and how specific it is, is the subject of the selectors chapter.

The grammar is small and worth stating precisely, because every later concept is defined in these terms. A rule is a selector (the pattern that decides which elements the rule applies to) followed by a declaration block (the curly braces). Inside the block sits a list of declarations, each one a property paired with a value by a colon and terminated by a semicolon.

css
.card {
  color: navy;
  padding: 16px;
}

Precision here pays off in debugging. A malformed declaration, say a misspelled property or an invalid value, is discarded by the parser on its own, and the rest of the block still applies; CSS fails per-declaration, not per-rule, which is why a single typo quietly drops one line rather than breaking the whole card. The semicolon is a separator, not a terminator, so it is optional after the final declaration, but keeping it prevents a broken rule the day you append another line and forget to add it above. When you meet the cascade next, remember that "which declaration wins" always operates at this declaration level, property by property.

JunoThe anatomy of a rule A rule is a selector plus a set of declarations in curly braces. The selector like .card picks the elements, and each declaration like color: navy; is a property and a value with a colon between them and a semicolon at the end. Once you can name those parts, every rule you read looks the same.
JunoThe anatomy of a rule Selector, declaration block, and property: value; declarations inside it: that is every rule. The semicolon separates declarations, so keep it on the last one too and adding the next line stays a one-line edit. How selectors match and how specific they are comes next in the selectors chapter.
JunoThe anatomy of a rule A rule is a selector and a block of property: value; declarations, and CSS fails per declaration: one invalid line is dropped, the rest of the block survives. That is why a stray typo silently removes a single style instead of breaking the card. Keep the trailing semicolon and appending a declaration never bites you.

The cascade

Sometimes two rules try to style the same element, and CSS needs a way to decide which one wins. That decision is the cascade, and it is where the C in CSS comes from.

css
.card {
  color: navy;
}

.card {
  color: teal;
}

Both rules target the card and both set the colour. The card ends up teal, because when two rules have the same strength, the one written later wins. Order matters, and it flows down the file like water, which is where the name cascade comes from. There is a second tie-breaker too: a more specific selector beats a more general one, so a rule aimed precisely at your card can override a broader rule even if the broader one comes later. You will meet that specificity in detail in the selectors chapter.

When several rules set the same property on the same element, the cascade is the process that picks a winner. Two factors decide it here: specificity and source order.

css
.card {
  color: navy;
}

.card {
  color: teal;
}

These two rules have identical specificity, so source order breaks the tie and the later rule wins: the card is teal. When specificity differs, though, it takes priority over order: a more specific selector beats a more general one even when the general rule comes later in the file. More specific wins; on a specificity tie, later wins. This is why stacking overrides late in a file is a fragile habit, and why understanding specificity from the selectors chapter is what makes the cascade predictable rather than a guessing game.

The cascade is the algorithm that resolves competing declarations for one property on one element, and it runs through an ordered set of criteria until one declaration is left standing.

css
.card {
  color: navy;
}

.card {
  color: teal;
}

The two most common criteria are specificity (a weight the browser computes from the selector) and source order (later wins on a tie), and the example above turns on the second: equal specificity, so the later teal wins. But those two sit inside a larger ordering. The cascade first sorts by origin and importance: browser default styles, then your author styles, then any user styles, with a normal-versus-important layer on top. A declaration marked !important jumps to a higher band, which is precisely why it exists and precisely why to avoid it. !important wins by leaving the normal ordering rather than by being correct, so it is not an override but an escape hatch, and once one !important appears, the next fix is often another one. Reach for it only to defeat styles you do not control, such as an inline style or a third-party widget, and otherwise let specificity and order do the work. When you meet the box model and start layering many rules, this ordering is what keeps the result predictable.

JunoThe cascade The cascade is how CSS settles a fight when two rules style the same thing. If the rules are equally strong, the one written later wins, so order counts. A more specific selector can also win over a more general one, and that part is coming up in the selectors chapter.
JunoThe cascade When rules collide, specificity decides first and source order breaks the tie: more specific wins, and on an even match the later rule wins. Piling overrides at the bottom of a file works until it does not, which is why the selectors chapter on specificity is worth reading before you trust the cascade.
JunoThe cascade The cascade sorts by origin and importance first, then specificity, then source order, and settles this per property. !important wins by jumping bands, not by being right, so treat it as an escape hatch for styles you do not control, never a routine override. One !important tends to breed the next.

Inheritance

Set a colour on a box and the text inside it often turns that colour too, even though you never styled that text directly. That passing-down is called inheritance.

css
.site-header {
  color: white;
  font-family: Georgia, serif;
}

Every piece of text inside the header, a heading, a link, a tagline, picks up that white colour and that font without a rule of its own, because color and font-family are inherited properties. Not everything passes down, though. Spacing properties like padding do not inherit: padding you put on the header stays on the header and does not appear on the elements inside it. As a rough guide, text-related styles flow down to children, and box-related styles like spacing and borders stay put.

Some properties pass their value down to descendant elements automatically, and some do not. That behaviour is inheritance, and knowing which properties inherit saves you a lot of repeated rules.

css
.site-header {
  color: white;
  font-family: Georgia, serif;
}

Text-related properties inherit: color, font-family, font-size, line-height, and similar all flow to descendants, so setting them once on a container styles everything inside it. Box-related properties do not: padding, margin, border, and background apply only to the element you set them on. When you do want a value to inherit that normally would not, the inherit keyword forces it, taking the property's computed value from the parent. Inheritance and the cascade are separate systems that work together: the cascade decides the winning declaration for an element, and inheritance is what a child falls back to when no rule sets that property on it at all. Typography leans heavily on this, which the typography chapter builds on.

Inheritance is the mechanism by which a property with no cascaded value on an element takes its value from that element's parent. Each CSS property is defined as either inherited or not: most text properties (color, font-family, font-size, line-height, text-align) inherit; most box properties (padding, margin, border, background) do not, because inheriting spacing onto every descendant would be unusable.

css
.site-header {
  color: white;
  font-family: Georgia, serif;
}

Four explicit keywords control this per declaration when the defaults are not what you want: inherit takes the parent's computed value even on a non-inherited property, initial resets to the property's specification default, unset acts as inherit for inherited properties and initial for the rest, and revert rolls back to the browser's default styling. The larger picture is that inheritance and the cascade together produce an element's computed value: the cascade selects a winning declaration where one exists, and where none does, an inherited property draws from its parent while a non-inherited one falls to its initial value. That single computed value per property, per element is what layout and painting actually consume, and the custom properties chapter leans directly on inheritance to make design tokens flow through a component tree.

JunoInheritance Inheritance is text styles flowing down into the elements inside a box: set color or font-family on a header and the text inside picks it up for free. Spacing like padding does not pass down, it stays where you put it. Rough rule: text styles travel to children, box styles stay put.
JunoInheritance Text properties like color and font-family inherit to descendants; box properties like padding and border do not. Style a container once and everything text-related inside it follows. When you need a value that normally would not inherit, the inherit keyword pulls it from the parent.
JunoInheritance Each property is inherited or not by definition: text properties inherit, box properties do not. The keywords inherit, initial, unset, and revert let you override that per declaration. Inheritance and the cascade together settle on one computed value per property per element, and that value is what layout and painting actually use.