CSS Selectors Basics
Introduction
Selectors decide which elements your CSS applies to. If a selector is too broad, you end up affecting things you didn't mean to; if it's too narrow or written incorrectly, nothing happens at all. This chapter covers the selector types you'll use every single day, plus a few practical patterns that keep your styles predictable and easy to maintain.
Prerequisites
- Basic HTML structure and attributes
- CSS syntax fundamentals
- A page linked to an external stylesheet
What a Selector Does
A selector answers one question:
Which elements should receive this declaration block?
p {
color: #334155;
}With the code above, this color will apply to every <p> element on the page.
Element Selector
An element selector targets a tag name directly:
/* Element selector: targets every <h1> heading tag on the page */
h1 {
font-size: 2rem; /* Set the font size to 2rem (a relative unit, roughly 2x the browser's default font size) */
}
/* Element selector: targets every <ul> unordered list tag on the page */
ul {
padding-left: 1.25rem; /* Set the left padding to 1.25rem, indenting the list items to the right */
}Good for:
- default values for basic typography
- broad, structural styling
Watch out: an element selector can affect a lot of places at once.
Class Selector
Class selectors start with . and are the most common approach in modern projects.
/* Selects every tag with the class "card" */
.card {
background: #fff; /* Set the background color to white */
border-radius: 12px; /* Set the corner radius to 12 pixels */
padding: 1rem; /* Set the padding to 1rem (a relative unit) */
}For example, the CSS selector above matches the following HTML tag:
<section class="card">...</section>Why class selectors are preferred:
- reusable across many elements
- clear intent
- easier to maintain than rules based on deep tag structure
ID Selector
ID selectors start with # and target one unique element.
/* Selects the tag whose id is "site-header" */
#site-header {
position: sticky;
top: 0;
}For example, the CSS selector above matches the following HTML tag:
<header id="site-header">...</header>Use IDs carefully:
- each ID should be unique on the page
- IDs carry very high specificity
- overusing IDs makes future overrides harder
Universal Selector
The universal selector * matches every element.
* {
box-sizing: border-box;
}Common use:
- a global
box-sizingreset
Avoid using a lot of universal-selector rules for complex visual styling — its reach is simply too broad.
Grouping Selectors
You can apply the same declarations to multiple selectors at once:
h1,
h2,
h3 {
/* This declaration block applies to the h1, h2, and h3 tags */
line-height: 1.2;
}This cuts down on duplication and keeps your style rules consistent.
Descendant Selector
A space between selectors means "located anywhere inside, at any depth."
.article p {
margin-bottom: 1rem;
}Meaning: style paragraphs inside any .article container, no matter how deeply nested.
This is very useful for scoping content styles, especially on documentation or blog pages.
Here's how the CSS above matches the following HTML:
Child Selector (>)
The child selector only targets direct children.
.menu > li {
list-style: none;
}Meaning: only selects <li> elements that are direct children of the element with class="menu".
Here's an example that shows how the CSS selector above behaves:
Adjacent Sibling Selector (+)
Targets the element immediately following a sibling.
h2 + p {
margin-top: 0.5rem; /* Add top spacing to the paragraph immediately following an h2 */
}Meaning: selects a <p> that comes immediately after an <h2> element (they must be siblings under the same parent).
Here's an example that shows how the CSS selector above behaves:
General Sibling Selector (~)
Targets all matching siblings that come after it under the same parent. Its reach is broader than +: + only selects the very next one, while ~ selects every matching sibling that follows.
/* General sibling selector: selects every p sibling that comes after an h2 under the same parent */
h2 ~ p {
color: #475569; /* Set the text color of these paragraphs to a slate gray-blue */
}Meaning: selects every <p> that is a sibling of, and appears after, the <h2> (other elements may sit in between).
Here's an example that shows how the CSS selector above behaves:
Tip
Choosing between + and ~
h2 + p only selects the single paragraph immediately after the h2; h2 ~ p selects every sibling paragraph that follows it. Use + when you only want to affect the first paragraph right after a heading; only reach for ~ when you want a consistent style across everything after the heading.
Attribute Selector
You can style elements based on their attributes, not just tag names or classes.
/* Attribute selector: selects inputs whose type attribute is "email" */
input[type="email"] {
border-color: #2563eb; /* Give email inputs a blue border */
}
/* Attribute selector: selects links with target="_blank" (typically links that open in a new tab) */
a[target="_blank"] {
text-decoration: underline; /* Underline external links to make them easy to spot */
}Meaning:
input[type="email"]: matches an<input>whosetypeis exactlyemaila[target="_blank"]: matches an<a>that has atarget="_blank"attribute
Here's an example that shows how the CSS selectors above behave:
This is very useful for targeting specific form controls or distinguishing external link behavior.
Combining Selectors
You can combine multiple selector types to make your matching conditions more precise. "Combining" here means writing selectors directly together, with no space, so that both conditions must be true at once — for example, "must be a certain tag AND have a certain class."
/* Combined selector: selects elements that are both a button tag and have the class "primary" */
button.primary {
background: #2563eb; /* Primary button background color */
color: #fff; /* Primary button text color */
}Meaning: only applies to elements that are both a <button> and carry the class primary.
Note: button.primary (no space) is different from .card .title (with a space) — the former means "the same element satisfies both conditions," while the latter means "a descendant relationship."
Here's an example that shows how the CSS selector above behaves:
This style of writing is usually clearer, and easier to maintain, than deeply nested selector chains.
Selector Strategy for Beginners
A good order of preference to follow:
- Start with semantic HTML
- Use classes for component styling
- Use element selectors for a few lightweight, global defaults
- Reserve IDs mainly for anchors or unique layout hooks
Try to avoid very deep selector chains like this:
main .content .wrapper .card .title span {
color: red;
}Overly deep selectors are fragile and hard to maintain.
Tip
Keep Selectors Shallow
Most UI components only need a single class selector, paired with a small number of local child selectors.
Common Selector Mistakes
| Mistake | Example | Better approach |
|---|---|---|
Missing . for a class | card { ... } | .card { ... } |
Missing # for an ID | header-main { ... } | #header-main { ... } |
| Too broad an element selector | div { ... } | .card, .panel, etc. |
| Over-nested selectors | .a .b .c .d { ... } | flatter, class-based rules |
| Relying on DOM structure by accident | ul li a span { ... } | an explicit component class |
The Styles panel in DevTools quickly shows you whether a selector is matching.
Mini Exercise
Create the following HTML:
<main class="content">
<h1>Selectors Demo</h1>
<p class="lead">Intro text.</p>
<section class="card">
<h2>Card title</h2>
<p>Card content.</p>
<a href="#" target="_blank">Read more</a>
</section>
</main>Then write CSS to practice:
- an element selector for
h1 - a class selector for
.card - a descendant selector
.content p - a child selector
.card > h2 - an attribute selector
a[target="_blank"]
Inspect each element to verify the rules match as expected.
FAQ
Should I use class selectors almost everywhere?
For component styling, yes. Class selectors are flexible and reusable, and easier to manage than deep structural selectors.
Are ID selectors bad?
Not bad exactly, just easy to overuse. They carry very strong specificity, so they're best kept for unique elements or anchors.
Is using a div selector always wrong?
Not always, but broad div styling often leads to unexpected side effects. Prefer semantic tags and classes.
How many levels deep should selectors go?
One to three levels is usually enough. If you find yourself needing deeper chains regularly, it's a sign your class naming strategy could use improvement.
What's the fastest way to debug a selector?
Use DevTools: inspect an element and see which rules match, which are overridden, and which selectors aren't matching at all.