By WebNest Studio Engineering Team
CSS Tutorial
CSS Selectors
A selector is the part of a CSS rule that tells the browser which HTML elements a set of styles should apply to. Every rule you write starts with a selector, followed by a declaration block wrapped in curly braces, and choosing the right selector is what separates precise, maintainable stylesheets from fragile ones.
CSS ships with several selector categories — type (element), class, ID, attribute, and combinators that describe relationships between elements. Understanding how each one targets the DOM, and how they can be combined, is the foundation for everything else in CSS, including the cascade and specificity rules covered later in this module.
Why it matters
A selector decides which elements a rule applies to, so every line of CSS you write starts with one. Choosing a selector that is too broad restyles things you did not intend, and one that is too narrow breaks as soon as the markup changes. Combinators and attribute selectors let you target elements precisely without adding a class to everything.
Basic Selector Types
The four most common selectors form the backbone of nearly every stylesheet.
- Type (element) selector — targets every instance of an HTML tag, e.g.
p { }styles all paragraphs. - Class selector — starts with a dot and targets any element carrying that class, e.g.
.card { }. Classes are reusable across many elements. - ID selector — starts with a hash and targets the single element with that
idattribute, e.g.#header { }. IDs must be unique per page. - Universal selector — the asterisk
*matches every element, often used for resets like* { box-sizing: border-box; }.
Attribute Selectors
Attribute selectors match elements based on the presence or value of an HTML attribute, using square brackets. [disabled] matches any element with a "disabled" attribute regardless of value, while [type="email"] matches only elements whose "type" attribute equals "email" exactly. Partial-match operators add flexibility: [class^="btn-"] matches values that start with "btn-", [class$="-icon"] matches values ending in "-icon", and [class*="nav"] matches values containing "nav" anywhere.
Combinators
Combinators describe the relationship between two selectors rather than matching an isolated element.
- Descendant combinator (space) —
article pselects any <p> nested anywhere inside an <article>, at any depth. - Child combinator (
>) —ul > liselects only <li> elements that are direct children of a <ul>, not grandchildren. - Adjacent sibling combinator (
+) —h2 + pselects a <p> that immediately follows an <h2> at the same nesting level. - General sibling combinator (
~) —h2 ~ pselects every <p> that shares a parent with an <h2> and comes after it, not just the first one.
Grouping Selectors
A comma lets you apply the same declaration block to multiple, unrelated selectors without repeating yourself: h1, h2, h3 { font-family: sans-serif; } applies the same font to all three heading levels in a single rule.
Examples
Combining class, attribute, and combinator selectors
<!-- HTML context -->
<!--
<nav class="site-nav">
<a href="/">Home</a>
<a href="/about" class="active">About</a>
</nav>
<form>
<input type="email" required>
</form>
-->
.site-nav > a {
color: #333;
text-decoration: none;
}
.site-nav > a.active {
font-weight: bold;
color: #0057d9;
}
input[type="email"][required] {
border: 1px solid #d9534f;
}
Direct-child nav links are unstyled by default, the "active" link is bold and blue, and any required email input gets a red border to flag it as mandatory.
Descendant vs. child combinator behaving differently on nested lists
<!--
<ul class="menu">
<li>Item 1
<ul><li>Sub-item</li></ul>
</li>
<li>Item 2</li>
</ul>
-->
.menu li { color: black; } /* matches ALL li, including nested */
.menu > li { font-weight: bold; } /* matches only the two top-level li */
Every list item (including the nested sub-item) is black, but only the two direct children of .menu ("Item 1" and "Item 2") appear bold.
Common Mistakes
- Reaching for an ID selector for reusable styling, then being unable to reuse the rule anywhere else on the page since IDs must be unique.
- Confusing the child combinator (>) with the descendant combinator (space), leading to styles unexpectedly applying to deeply nested elements.
- Forgetting that attribute selectors are case-sensitive for attribute values by default, which can silently break matches like [lang="EN"] against lang="en".
- Overusing the universal selector (*) in performance-sensitive rules, which forces the browser to evaluate the rule against every single element in the DOM.
Exercise
Using only selectors (the page has no classes), make every paragraph inside main grey; make the paragraph that comes directly after the heading italic; and make a button that is a direct child of main bold.
Expected result: the paragraph is grey and italic, and the button text is bold.
/* The page contains: <main> with an <h1>, a <p> and a <button> */
/* TODO: paragraphs anywhere inside main: grey */
/* TODO: the paragraph immediately after the h1: italic */
/* TODO: a button that is a direct child of main: bold */
Show hints
- A space selects descendants,
>selects direct children, and+selects the next sibling. - The three selectors are
main p,h1 + pandmain > button.
Show solution
/* The page contains: <main> with an <h1>, a <p> and a <button> */
main p {
color: gray;
}
h1 + p {
font-style: italic;
}
main > button {
font-weight: bold;
}
Quick quiz
1. What does the selector .card p match?
- A. Only p elements that are direct children of .card
- B. Elements with both classes card and p
- C. Every p element at any depth inside an element with the class card
- D. The first p on the page
Show answer
C. A space is the descendant combinator. Use > to limit the match to direct children.
2. Which selector matches inputs whose type is email?
- A. input.email
- B. input#email
- C. input:email
- D. input[type="email"]
Show answer
D. Square brackets select by attribute and value.
3. What does h1, h2 { margin: 0; } do?
- A. Applies the rule to both h1 and h2 elements
- B. Selects h2 inside h1
- C. Selects an h2 that follows an h1
- D. It is invalid
Show answer
A. A comma groups selectors so that they share one rule.
Interview questions
What is the difference between the descendant, child and sibling combinators?
Show answer
The descendant combinator, a space, matches an element at any depth inside another: nav a. The child combinator, >, matches only direct children: ul > li. The next-sibling combinator, +, matches the element immediately after another with the same parent: h2 + p. The subsequent-sibling combinator, ~, matches all later siblings.
What is the difference between a class selector and an id selector?
Show answer
A class, written .name, can be used on any number of elements, and an element can have several classes; it is the normal tool for styling. An id, written #name, must be unique in the page and has much higher specificity, which makes rules based on it hard to override. Ids are therefore kept for fragment links, labels and JavaScript, and classes are used for styles.
Key Points to Remember
- Type, class, ID, and universal selectors are the four basic building blocks of CSS targeting.
- Attribute selectors ([attr], [attr="value"], [attr^=], [attr$=], [attr*=]) match based on HTML attributes.
- Combinators (space, >, +, ~) express relationships between elements rather than matching a single one in isolation.
- Grouping selectors with commas lets one declaration block apply to multiple unrelated selectors.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Shows a live preview of your HTML, CSS and JavaScript. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering