How CSS chooses which elements to style — type, class, and ID selectors, grouping and descendant selectors, pseudo-classes, inheritance, specificity, and the cascade that resolves every style conflict
In Chapter 11 you wrote your first stylesheet, linked it to an HTML page, and saw how CSS rules connect to HTML elements. You learned the basic shape of a rule: a selector, a declaration block, a property, and a value.
But CSS becomes complicated quickly once you have more than a handful of rules. What happens when two rules both try to style the same element? What happens when a child element inherits a color from its parent but you want to override it? What happens when a class rule and a type rule both apply at the same time?
These are not edge cases. They happen on every real website, often many times per page. Understanding how CSS resolves these conflicts is one of the most important skills in front-end development, and it is the subject of this chapter.
This chapter teaches you how CSS selects elements, how selector types differ, how inheritance works, how specificity determines which rule wins, and how source order matters when specificity is tied. Together these ideas form the cascade — the algorithm CSS uses to decide which style applies when multiple rules compete.
Two CSS rules both set the color of the same paragraph. One rule uses a class selector. The other uses a type selector and appears later in the file. Which rule wins, and why? By the end of this chapter, you will be able to answer that question for any pair of conflicting rules — instantly, and with confidence, using nothing more than the structure of the selectors themselves.
By the end of this chapter, you should be able to:
:hover, :focus, :first-child, :last-child, and :nth-child() to style elements by state or position.!important — and correct them.:hover, :focus, :first-child, :last-child, :nth-child(), and accessibility implications!importantEvery CSS rule begins with a selector. The selector tells the browser which elements in the HTML document the rule applies to.
You already know the simplest form: writing the element name selects every element of that type.
This rule applies to every <p> element on the page. It does not apply to headings, links, list items, or anything else — only paragraphs.
But real pages have complex structures. You often want to style some paragraphs differently from others, or style an element only when it appears inside a certain container, or change the style of a link when the user hovers over it. CSS provides many types of selectors to handle these situations.
Before studying each type individually, it helps to understand what a selector actually does. When the browser loads a page, it reads all of the CSS and builds an internal map: for each element on the page, which rules apply? The browser uses the selector to answer that question.
Think of a selector as a filter or a question. The selector p asks: "Is this element a paragraph?" The selector .highlight asks: "Does this element have the class highlight?" The selector nav a asks: "Is this element an anchor that lives inside a nav?"
Every element on the page is tested against every selector. If the element matches, the rule's declarations apply. If the element does not match, the declarations are ignored for that element.
CSS rules do not apply to the whole page. They apply to individual elements that match the selector. Understanding selectors means understanding which elements match, and which do not.
A type selector targets every element of a given HTML tag name. It is the most basic kind of selector.
Each of these rules applies to every element of that type, anywhere on the page.
Type selectors are powerful because they establish baseline styles across the entire document. You typically use them for setting foundational typographic styles: heading sizes, paragraph spacing, link colors, list padding.
Type selectors are best when the style should apply globally. If all <h2> elements on your site should be a specific size, a type selector for h2 is the right choice. If all links should share a base color, a type selector for a is appropriate.
Type selectors become a problem when you need variation. Imagine a page with both a navigation menu and a list of article tags. Both are built with <ul> and <li> elements. If you write:
This rule will apply to both the navigation items and the article tags. But you probably want the navigation items to be styled differently from the tags. A type selector alone cannot make that distinction. This is exactly why more specific selectors exist.
A class selector targets any element that has been given a specific class attribute in the HTML. Class selectors are written with a dot (.) followed by the class name.
This rule applies to any element — regardless of its tag — that has class="highlight" in the HTML.
All three elements above will receive the yellow background, even though they are different HTML elements. The class is the shared trait; the type does not matter.
Classes solve the problem we identified with type selectors. You can style all <p> elements with a base rule, then add a class to modify specific paragraphs.
The first paragraph receives both the base p rules and the .intro rules. The other paragraphs only receive the base p rules.
An element can have more than one class. You separate class names with spaces in the HTML attribute.
The featured card receives styles from both .card and .card--featured. This pattern — combining a base class with a modifier class — is very common in real CSS.
Class names should be meaningful and describe what the element is or does, not what it looks like. Good class names describe purpose:
Avoid class names that describe appearance, because if the design changes, the name becomes misleading:
The double-hyphen naming pattern (.card--featured, .button--large) is part of a methodology called BEM (Block Element Modifier). You will encounter BEM in professional codebases. It is not required at this stage, but it is useful to recognize.
An ID selector targets a single element by its id attribute. ID selectors are written with a hash symbol (#) followed by the ID name.
This rule applies only to the element with id="main-header". No other element on the page will receive this style.
IDs are meant to be unique on a page. An ID should not appear more than once. This is an HTML rule, not just a style convention. If two elements have the same ID, your HTML is technically invalid and browser behavior may become inconsistent.
This uniqueness is also the main limitation of ID selectors for styling. Because an ID can only appear once per page, an ID-based CSS rule can only ever style one element. If you want to style many elements the same way, you need a class.
As you will learn in Section 12.9, ID selectors have very high specificity — far higher than class selectors and type selectors. This means that an ID-based rule is extremely difficult to override later. Once you style something with an ID selector, you need an even more specific rule to change it.
This makes ID selectors fragile for styling. Overusing them creates specificity problems that are hard to debug.
IDs are appropriate for:
href="#section-name")for="email-input" matching id="email-input")For CSS styling, prefer class selectors. Use ID selectors sparingly in stylesheets, and only when you are certain the element is unique and you will never need to reuse the style.
Sometimes you want to apply the same declarations to multiple different selectors. You can write separate rules, but there is a more efficient option: grouping selectors.
Grouping selectors are written by separating selectors with commas. The declarations inside the rule apply to all listed selectors.
This is equivalent to writing three separate rules:
Both produce the same result. The grouped version is shorter and easier to maintain. If you want to change the font family for all three headings, you only need to edit one place.
Even though the selectors are grouped, each one is independent. They are not linked relationships — they are just a shorthand for writing the same declarations under multiple selectors.
This matters when one selector in a group is invalid. If you write:
The third selector (..h3) is invalid. Some older browsers would discard the entire rule when they found one invalid selector in a group. Modern browsers are more forgiving, but writing careful, valid CSS remains the better habit.
Professional stylesheets typically put each grouped selector on its own line. This makes the grouping easier to read, easier to edit, and easier to compare.
Either format is valid CSS. The multi-line format is preferred for maintainability.
A descendant selector targets an element only when it appears inside another specific element. You write it by placing a space between two selectors.
This rule applies only to <a> elements that are descendants of a <nav> element. Anchor elements elsewhere on the page — in paragraphs, in footers, in articles — are not affected.
The word "descendant" is important. An element is a descendant if it is nested anywhere inside the ancestor, not just directly inside. It does not matter how many levels deep.
The anchor inside the <nav> matches nav a. The anchor inside the <p> does not match — it has no <nav> ancestor.
The most common use of descendant selectors is to style elements differently based on where they appear. Navigation links are often styled differently from body text links. List items inside a footer may be styled differently from list items inside the main content.
This pattern is clean and organized. A base style is set for all links. Then specific contexts override that base style.
Descendant selectors work at any depth, which can become a problem. Writing main section article div p a { color: red; } targets anchors nested six levels deep. This is technically valid but almost never appropriate. Deep descendant selectors are fragile (if the HTML structure changes slightly, the selector stops matching), difficult to read and maintain, and high in specificity, which makes them harder to override. Prefer shorter descendant selectors — two levels is common, three is sometimes appropriate, and more than that is usually a sign the selector should be replaced with a class.
Sometimes you need to style an element not based on what it is, but based on its state or position. A link might need a different color when the user hovers over it. A button might need a visible outline when it is focused with a keyboard. The first item in a list might need different spacing from the others.
CSS handles these situations with pseudo-classes. A pseudo-class is added to a selector using a colon (:), followed by the pseudo-class name.
The :hover pseudo-class applies styles when the user's cursor is over the element.
When the user moves their cursor over a link, the browser applies the :hover styles. When the cursor leaves, the :hover styles are removed and the base styles return. Hover effects are very common for links, buttons, navigation items, and cards. They give users visual feedback that an element is interactive.
The :focus pseudo-class applies when an element receives keyboard focus — typically when a user tabs to it or clicks on a form field.
Focus styles are critically important for accessibility. Users who navigate with keyboards — including people with motor disabilities, power users, and those without a mouse — rely on visible focus indicators to know where they are on the page.
This is one of the most common accessibility mistakes in web development.
Removing the outline makes the page visually cleaner for mouse users but nearly unusable for keyboard users. If you want a custom focus style, replace it with something equally visible:
The :first-child pseudo-class selects an element that is the first child of its parent. The :last-child pseudo-class selects an element that is the last child.
This is a common pattern for lists: add a dividing line below each item, then remove it from the last item so the list does not end with a stray border.
The :nth-child() pseudo-class selects elements based on their numeric position. You pass a number, a keyword, or a formula inside the parentheses.
This creates alternating row colors in a table — a pattern called zebra striping, which makes long tables easier to read.
You will work more with :nth-child() in later chapters. For now, knowing that it exists and understanding its basic use is sufficient.
| Pseudo-Class | Applies To |
|---|---|
:visited | Links the user has already visited |
:active | An element during the moment a user clicks it |
:checked | Checked checkboxes and radio buttons |
:disabled | Form elements that are disabled |
:not() | Elements that do not match the argument inside the parentheses |
You will see these in context as the course progresses. The pattern — selector followed by colon followed by pseudo-class name — is the same for all of them.
CSS inheritance is the mechanism by which certain CSS properties automatically pass from parent elements to their child elements, without you writing any rule for the children.
Consider this example:
You are styling the <body> element — the outermost container for all visible page content. Every <p>, <h1>, <li>, <td>, and other element inside <body> is a descendant of <body>.
Because font-family, color, and line-height are inherited properties, all those descendant elements automatically receive the same font family, color, and line height. You do not need to repeat those declarations on every element.
This is one of the most useful features in CSS. You can set foundational typographic styles once on the body or a major container, and they flow through the entire document.
Not all properties inherit. Properties related to text and typography typically do. Properties related to spacing, borders, and layout typically do not.
| Properties That Inherit | Properties That Do Not Inherit |
|---|---|
color | background-color |
font-family | border |
font-size | margin |
font-weight | padding |
font-style | width |
line-height | height |
text-align | display |
text-transform | position |
letter-spacing | box-shadow |
visibility, cursor |
The reasoning is sensible. If you put a border around a <div>, you almost certainly do not want every element inside that <div> to also have a border. If you set a background color, you probably do not want all children to share it. But if you set a text color, you usually do want all the text inside a container to use that color unless otherwise specified.
Inheritance is a fallback. If an element has a specific rule targeting it, that rule takes priority over inherited values. Inheritance only takes effect when no more specific rule applies.
The <body> rule sets color to dark gray. All elements inherit this — paragraphs, list items, spans, and others will be dark gray. But <h1> has its own rule with a darker navy color. The specific h1 rule takes priority, so headings will be navy, not the inherited dark gray.
inherit KeywordIf you ever need to explicitly force an element to inherit a property, you can use the value inherit.
a { color: inherit; }
This is useful in situations where the browser applies a default link color (usually blue) that overrides the inherited parent color. Writing color: inherit forces the anchor to use whatever color its parent provides.
When two or more CSS rules target the same element and set the same property, a conflict exists. The browser cannot apply both values. It must choose one. The rule that wins is determined by specificity.
Specificity is a numeric weight assigned to a selector. A selector with higher specificity wins over a selector with lower specificity, regardless of which rule appears first in the stylesheet.
CSS specificity is calculated using three categories, often represented as three numbers: (A, B, C).
A — ID selectors. Each ID selector in the selector string contributes 1 to the A value.
B — Class selectors, attribute selectors, and pseudo-classes. Each class selector (.highlight), attribute selector ([type="text"]), or pseudo-class (:hover) contributes 1 to the B value.
C — Type selectors and pseudo-elements. Each type selector (p, h1, a) or pseudo-element (::before, ::after) contributes 1 to the C value.
The universal selector (*) contributes nothing — its specificity is (0, 0, 0).
To compare two selectors, read the A values first. The higher A value wins. If A values are equal, read the B values. The higher B value wins. If B values are also equal, read the C values. The higher C value wins.
Only if all three values are equal does source order matter (which you will learn about in Section 12.10).
p has specificity (0, 0, 1). .intro has specificity (0, 1, 0). For a <p class="intro">, both have A = 0, so compare B: .intro has B = 1, p has B = 0. .intro wins — the paragraph color is #111111..card a and nav .link both have specificity (0, 1, 1) — exactly equal. For an element matching both, source order decides: whichever rule appears later in the stylesheet wins.<p class="highlight" id="intro">, three rules apply: p at (0,0,1), .highlight at (0,1,0), and #intro at (1,0,0). The ID selector wins because A = 1, the highest value among the three. The color is navy.Some developers picture specificity like a three-digit number where each digit represents one category. This is not perfectly accurate mathematically (ten class selectors do not beat one ID selector), but it helps with intuition.
| Selector | A | B | C |
|---|---|---|---|
| p | 0 | 0 | 1 |
| .intro | 0 | 1 | 0 |
| p.intro | 0 | 1 | 1 |
| nav a | 0 | 0 | 2 |
| nav .link | 0 | 1 | 1 |
| #header | 1 | 0 | 0 |
| #header nav a | 1 | 0 | 2 |
Most experienced developers follow a consistent pattern: use type selectors for global, foundational styles (reset, base typography); use class selectors for component and pattern styles; use ID selectors sparingly, and only for truly unique structural elements; and avoid deep compound selectors, which create high specificity that is hard to override. The more you rely on class selectors, the easier your CSS is to maintain and override. When specificity is predictable, debugging becomes much simpler.
You have now learned three factors that affect which CSS rule wins a conflict: the origin of the rule (browser defaults, author styles, user styles), specificity, and source order. Together these form the cascade.
The cascade is the algorithm CSS uses to resolve all style conflicts. Here is how it works, in order:
Step 1 — Origin and importance. Rules can come from multiple sources: the browser's built-in stylesheet, the page author's stylesheet, and user preferences. Author styles (the CSS files you write) typically override browser defaults. Rules marked with !important can override this order, but !important should be used very sparingly.
Step 2 — Specificity. Among rules from the same origin, higher specificity wins.
Step 3 — Source order. Among rules with equal specificity, the rule that appears later in the stylesheet wins.
When specificity is equal, later rules override earlier rules.
The second p rule wins because it appears later. The color will be #111111. This is why the order of your CSS rules matters. If you want one style to be the default and another to be an override, write the override later in the file.
Source order also applies across multiple <link> elements in the HTML. If you link two stylesheets, the one linked second can override the one linked first (assuming equal specificity).
Rules in theme.css can override rules in base.css if specificity is equal. This is a useful pattern for organizing stylesheets: write foundational styles in a base file, then write overrides in a theme or page-specific file.
Let us trace through a realistic example using all three cascade factors.
For the <p class="intro"> element inside the article, all four rules match. Let us trace the color conflict:
| Rule | Selector | Specificity | Color Value |
|---|---|---|---|
| Rule 1 | p | (0, 0, 1) | #555555 |
| Rule 2 | .intro | (0, 1, 0) | #222222 |
| Rule 3 | .card p | (0, 1, 1) | #333333 |
| Rule 4 | #featured-post .intro | (1, 1, 0) | #0057a8 |
Rule 4 has A = 1 (one ID), which is highest. The color is #0057a8. For font-size, only Rules 1 and 2 set that property. Rule 2 has higher specificity, so font-size is 1.1rem. This is how every CSS conflict resolves: the browser collects all matching rules for each property, evaluates them through the cascade, and applies the winner.
!important — Handle with CareYou can mark any declaration with !important to override the normal cascade.
This declaration will override any other rule setting the color of <p> elements, regardless of specificity or source order — with one exception: another !important declaration with higher specificity will still win.
!important is occasionally useful — for example, in utility classes that must never be overridden, or in external style overrides where you cannot change the original stylesheet. But using it carelessly creates a specificity arms race: you use !important to override something, then something else uses !important to override your override. The cascade becomes unpredictable. Good CSS rarely needs !important. If you find yourself reaching for it regularly, the root cause is usually a specificity organization problem that would be better solved by restructuring the selectors.
Let us apply everything in this chapter to a realistic example. We will build a styled article card step by step, writing selectors intentionally and understanding why each rule applies.
The CSS is built layer by layer, from the most general rules to the most specific.
h2, p, and a apply to every matching element on the page and provide a foundation. Each has specificity (0, 0, 1)..cards, .card, .card-title, .card-summary, and .card-link each target a specific part of the card component, all at a consistent specificity of (0, 1, 0)..card--featured changes the featured card's border and background. .card--featured .card-title has specificity (0, 2, 0) — two class selectors — which is higher than .card-title alone at (0, 1, 0), so the featured card's title correctly shows a different color..card:hover has specificity (0, 2, 0) — higher than .card at (0, 1, 0). This is correct: hover styles should override base styles.Now, for the <h2 class="card-title"> inside the featured card, three rules apply: h2 at (0,0,1) sets font-size, font-weight, and margin-bottom; .card-title at (0,1,0) sets color: #1a1a2e; and .card--featured .card-title at (0,2,0) sets color: #0057a8.
The font-size, font-weight, and margin-bottom come from the h2 rule because no higher-specificity rule sets those properties. The color comes from .card--featured .card-title because it has the highest specificity among rules that set color. This is the cascade working exactly as intended.
Fragile: #header { background-color: #1a1a2e; }, #nav { display: flex; }, #footer { background-color: #333333; }
Fix — use class selectors instead: .site-header { background-color: #1a1a2e; }, .site-nav { display: flex; }, .site-footer { background-color: #333333; } Reserve IDs for JavaScript and anchor targets.
!important!important to force a style to apply without understanding why a rule is being overridden: p { color: red !important; }nav a intending to style all links, then being confused when links outside of <nav> are not styled.nav a only matches anchors inside <nav>. For all links on the page, use a alone..highlight { color: orange; } followed by p { color: black; } — for a <p class="highlight">, the color will be orange because .highlight has higher specificity than p, regardless of source order.a:focus, button:focus, input:focus { outline: none; }a:focus, button:focus, input:focus { outline: 2px solid #0057a8; outline-offset: 3px; }div.card p.card-summary a.card-link { color: #0057a8; } has specificity (0, 3, 3), which is unnecessarily high and very hard to override..card-link { color: #0057a8; }:hover and :focus provide interactive feedback — and visible focus styles are essential for keyboard accessibility — while :first-child, :last-child, and :nth-child() select elements by position among siblings.color, font-family, and line-height to flow automatically from parent elements to their descendants. Spacing, border, and layout properties generally do not inherit. Inheritance only applies when no more specific rule targets the element.!important overrides the normal cascade but should be used sparingly, since overuse leads to unpredictable stylesheets.!important instead of understanding the cascade, confusing descendant selectors with universal ones, removing focus styles without replacement, and writing over-qualified selectors that are harder to override than necessary.class attribute. Written with a dot prefix: .card, .highlight.nav a, .card p.h1, h2, h3 { }.id attribute. Written with a hash prefix: #main-header. Carries very high specificity.:hover, :focus, :first-child.p, h1, a, ul.Read each CSS snippet carefully. Answer the questions that follow without writing any new code.
<p> inside <main> inherit from <body>?main rule?p rule?<p> have the line-height: 1.7 style? Explain why or why not.For each anchor element below, predict which color it will have:
.button--danger:hover?id="confirm-delete" to the second button and wrote #confirm-delete { background-color: navy; }, what background color would the button have at rest?Trace the cascade for the <h2> element below. List each rule that applies, its specificity, and which property values it contributes. State the final value for each conflicting property.
font-size? Which rule wins?color? Which rule wins?font-weight? Which rule provides it?A student is building a navigation menu and writes the following CSS rules in order:
<a class="nav-link">, with its specificity..site-nav .nav-link rule, which rule wins? What color appears?a.nav-link, which rule wins? What color appears?Predict the text color for each element. Explain your reasoning using specificity.
Predict the color of each list item. Then explain what would happen if there were only two list items.
This CSS has a conflict. Before reading further, identify which declaration wins and predict what the heading looks like.
<h2>?color: which rule wins? What is the color?font-size: which rule provides this value?font-weight: which rule provides this value?Each scenario describes a CSS problem. Identify what is wrong and write a corrected version.
.sidebar to be gray, but all links on the page are turning gray.
.card h2 loses. Write a corrected fix using only class selectors.<h3> elements get a dark gray color (#222222) and 1.25rem font size; all <p> elements get a medium gray color (#555555), 1rem font size, and 1.6 line height; <p> elements with class .lead get a dark color (#111111) and 1.2rem font size. Test your specificity: the .lead rule must win over the base p rule.font-weight: 700 to h1, h2, h3, h4, h5, and h6 — all six heading levels — using one CSS rule.<nav> white and not underlined; links inside <nav> on hover light blue (#a0c8ff) and not underlined; links inside <nav> on focus a 3px solid white outline with 2px offset; links inside <footer> medium gray (#888888) and underlined..tag items with background-color: #e8f0fe, border-radius: 12px, padding: 0.25rem 0.75rem, and font-size: 0.875rem; style the first tag with background-color: #1a1a2e and color: #ffffff; and style every even-numbered tag with background-color: #d0e4ff..card component including: a base .card rule with border, padding, border-radius, and background; a .card:hover rule with a subtle box shadow; a .card--featured modifier with a different border color and background; a .card-title rule with a specific color and font-size; and a .card--featured .card-title rule with a different color to override the base title color. Use only class selectors. Confirm that your specificity planning is correct.<h2> inside .lesson-content, not any <h2> outside it. (b) Write a CSS selector to target both .lesson-title and .lesson-intro using a grouping selector. (c) What specificity does main article .lesson-title have? (d) What specificity does .lesson .lesson-title have? (e) If both of the above rules set color, which wins?.active link is #ffcc00. Does nav a also match this element? What color does the element actually use, and why? (b) If the student wants .active to always win over nav a, is the current specificity sufficient? Show your work. (c) On hover, the .active link does not turn #a0c8ff. Explain why. (d) The student wants the active link to remain #ffcc00 even on hover. How would you write that rule?Apply the selector and cascade skills from this chapter to build a styled page section for the Student Learning Site.
index.html file with a <header> containing a <nav> with at least four links (Home, Lessons, Resources, Contact), and a <main> section containing a heading, a short introductory paragraph with class .intro, and a grid of three cards using <article class="card">, each with a title, a short description, and a link.css/main.css: set foundational body styles using type selectors (font-family, color, line-height); style navigation links using a descendant selector (nav a) with :hover and :focus states; style .card with a class selector including border, padding, and background, plus a .card:hover shadow or border change; create a .card--featured modifier for one card; style .intro to differ from regular <p> elements; and use a grouped selector for all heading levels that should share a font family./* (0,1,0) */. Verify that modifiers have higher specificity than base classes where needed..intro rule has higher specificity than the base p ruleReview your stylesheet and HTML from this chapter's project, or a provided sample page, and answer the following.
outline: none without replacing it? If so, fix it.<nav> element so screen readers can identify it as a landmark?Chapter 12 gave you full control over which elements your CSS rules affect, and full confidence in predicting how conflicts resolve. With your selector and cascade knowledge now solid, you can write rules knowing exactly which elements they will affect and why.
Chapter 13 turns to the visual properties that make pages readable and appealing: color formats (hex, RGB, HSL), text color, background color, font families, font size, line height, font weight, text alignment, margins, padding, borders, and border radius. You will apply these properties using the selectors from this chapter, building a complete, readable page style for the Student Learning Site.