CSS Selectors Explained — Complete Guide
Read on to explore css selectors explained — complete guide — a beginner-friendly walkthrough by Codekilla.
A CSS selector is a pattern you write to target specific HTML elements on a page so you can style them. Think of selectors as the "address" that tells your browser which elements should receive the styles you define. Without selectors, your CSS rules wouldn't know where to apply — it's like writing a letter without putting an address on the envelope.
Selectors range from dead simple (targeting all paragraphs) to surgical precision (targeting the third list item inside a specific div when you hover over it). Mastering selectors means you can style anything on a page without polluting your HTML with unnecessary classes or IDs.
- Cleaner HTML — You can target elements by their structure or state instead of adding classes everywhere, keeping your markup semantic and maintainable.
- Performance control — Efficient selectors render faster; overly complex ones can slow down page rendering, especially on large DOMs.
- Dynamic styling — Pseudo-classes like
:hover,:focus, and:nth-child()let you create interactive experiences without JavaScript. - Component isolation — Combinators help you scope styles to specific component trees, preventing unwanted cascading.
- Accessibility wins — Attribute selectors let you style form states (
input[required]) or ARIA roles, improving UX for assistive technologies.
Start here. These are your bread-and-butter selectors that you'll use constantly.
Type selectors target all elements of a specific HTML tag. Class selectors target elements with a specific class attribute. ID selectors target the single element with a matching ID. Universal selectors target everything.
css/* Type selector — all paragraphs */ p { line-height: 1.6; } /* Class selector — elements with class="btn" */ .btn { padding: 0.5rem 1rem; border-radius: 4px; } /* ID selector — the element with id="header" */ #header { background: #1a1a1a; } /* Universal selector — every element */ * { box-sizing: border-box; }
| Selector | Targets | Specificity |
|---|---|---|
p | All <p> elements | Low (0,0,1) |
.btn | All elements with class="btn" | Medium (0,1,0) |
#header | Single element with id="header" | High (1,0,0) |
* | Every element | Lowest (0,0,0) |
Combinators let you select elements based on their relationship to other elements. This is where CSS gets powerful.
Descendant combinator (space) targets all nested children. Child combinator (>) targets only direct children. Adjacent sibling (+) targets the immediate next sibling. General sibling (~) targets all following siblings.
css/* Descendant — any <a> inside .nav */ .nav a { text-decoration: none; } /* Direct child — only <li> directly inside .menu */ .menu > li { display: inline-block; } /* Adjacent sibling — <p> immediately after <h2> */ h2 + p { font-weight: 600; color: #333; } /* General sibling — all <p> after <h2> */ h2 ~ p { margin-left: 1rem; }
The descendant combinator is most common but least performant at scale. The child combinator is more efficient because the browser stops searching after one level.
Attribute selectors let you target elements based on their attributes or attribute values. Incredibly useful for forms, links, and custom data attributes.
css/* Exact match — links to example.com */ a[href="https://example.com"] { color: green; } /* Contains substring — any href with "codekilla" */ a[href*="codekilla"] { font-weight: bold; } /* Starts with — external links */ a[href^="https://"] { background: url(external-icon.svg) no-repeat right center; } /* Ends with — PDF links */ a[href$=".pdf"]::after { content: " (PDF)"; } /* Required inputs */ input[required] { border-left: 3px solid orange; }
| Selector | Matches |
|---|---|
[attr] | Element has the attribute |
[attr="value"] | Exact value match |
[attr*="value"] | Contains substring |
[attr^="value"] | Starts with |
[attr$="value"] | Ends with |
[attr~="value"] | Contains word in space-separated list |
Pseudo-classes select elements based on their state or position in the DOM. They start with a single colon (:).
State-based pseudo-classes like :hover, :focus, and :checked respond to user interaction. Structural pseudo-classes like :first-child, :nth-child(), and :not() target elements by position or exclusion.
css/* Hover state */ button:hover { background-color: #0056b3; transform: translateY(-2px); } /* First and last children */ li:first-child { border-top: none; } li:last-child { border-bottom: none; } /* Every odd row — zebra striping */ tr:nth-child(odd) { background-color: #f9f9f9; } /* Exclude a class */ button:not(.disabled) { cursor: pointer; } /* Checked checkboxes */ input[type="checkbox"]:checked + label { font-weight: bold; color: green; }
:nth-child() accepts formulas: (2n) for evens, (3n+1) for every third starting at 1, etc. This is powerful for grids and tables.
Pseudo-elements let you style specific parts of an element or inject content. They use double colons (::), though single colon works for legacy support.
css/* First line of a paragraph */ p::first-line { font-variant: small-caps; } /* First letter — drop cap */ p::first-letter { font-size: 3em; float: left; line-height: 1; margin-right: 0.1em; } /* Before and after — injected content */ .quote::before { content: """; font-size: 2em; color: #ccc; } .quote::after { content: """; } /* Placeholder text styling */ input::placeholder { color: #999; font-style: italic; }
::before and ::after require the content property, even if it's empty (content: ""). They create inline elements you can position and style.
When multiple selectors target the same element, specificity determines which styles win. IDs beat classes, classes beat types, and inline styles beat everything (except !important, which you should avoid).
| Selector | Specificity | Example |
|---|---|---|
| Inline style | 1,0,0,0 | <div style="..."> |
| ID | 0,1,0,0 | #header |
| Class / Attribute / Pseudo-class | 0,0,1,0 | .btn, [type="text"], :hover |
| Type / Pseudo-element | 0,0,0,1 | div, ::before |
css/* Specificity: 0,0,0,1 */ p { color: blue; } /* Specificity: 0,0,1,0 — WINS */ .text { color: red; } /* Specificity: 0,1,0,0 — WINS */ #intro { color: green; } /* Specificity: 0,0,2,1 */ p.text.bold { color: purple; }
When specificity ties, the last rule in source order wins. Keeping specificity low makes your CSS more maintainable — favor classes over IDs.
| Need | Reach for |
|---|---|
| Target all of a tag type | p, div, a |
| Reusable component styles | .class-name |
| Unique element (sparingly) | #id-name |
| Direct children only | .parent > .child |
| All descendants | .ancestor .descendant |
| Links to external sites | a[href^="https://"] |
| Every third item | :nth-child(3n) |
| Hover, focus, active states | :hover, :focus, :active |
| Inject content | ::before, ::after |
| Style input placeholder | ::placeholder |
| Exclude elements | :not(.class) |
- Over-relying on IDs — IDs have nuclear specificity, making them hard to override. Stick to classes for styling; reserve IDs for JavaScript hooks or page anchors.
- Chaining too many selectors —
div.container > ul.menu > li.item > a.linkis specific but slow. Flatten where possible:.menu-link. - Forgetting specificity math — Writing
.btnthen wondering whybutton.btndoesn't override it. Calculate before debugging. - Using
!importantas a fix — It breaks the cascade and makes future changes painful. Fix the specificity instead. - Ignoring browser support —
:has(),:is(), and:where()are modern; always check caniuse.com for production code. - Styling
:first-childwhen you meant:first-of-type— If the first child is a different element type,:first-childwon't match. Use:first-of-typefor type-specific targeting.
💡 Think Like a Programmer: Selectors are your query language for the DOM. Write them like you'd write a database query — specific enough to get what you need, but efficient enough not to slow things down.
Keep Reading
Complete Guide to CSS Variables with Examples
Read on to explore complete guide to css variables with examples — a beginner-friendly walkthrough by Codekilla.
CSS :has() Pseudo-Class Guide
Read on to explore css :has() pseudo-class guide — a beginner-friendly walkthrough by Codekilla.
All CSS Properties Cheat Sheet with Examples
Read on to explore all css properties cheat sheet with examples — a beginner-friendly walkthrough by Codekilla.
