Course topics

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 id attribute, 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 p selects any <p> nested anywhere inside an <article>, at any depth.
  • Child combinator (>) — ul > li selects only <li> elements that are direct children of a <ul>, not grandchildren.
  • Adjacent sibling combinator (+) — h2 + p selects a <p> that immediately follows an <h2> at the same nesting level.
  • General sibling combinator (~) — h2 ~ p selects 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

CSS
<!-- 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;
}
Output
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

CSS
<!--
<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 */
Output
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.

CSS · starter code
/* 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 + p and main > button.
Show solution
CSS · 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.

WebNest Studio

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