CSS Selectors

▶ Try It Yourself

CSS selectors are the patterns that tell the browser which HTML elements a style rule should apply to. Without selectors there would be no way to target individual elements — all styles would have to be inline. In this lesson you will learn the full range of CSS selectors, from simple element and class selectors to combinators and pseudo-classes, and understand when to reach for each one.

CSS Selector Types

Basic Selectors

Selector Syntax What It Targets
Element (type) p Every <p> element on the page
Class .card Every element with class="card"
ID #header The single element with id="header"
Universal * Every element — use sparingly
Attribute input[type="email"] Elements with a specific attribute value
Grouped h1, h2, h3 Applies the rule to all listed selectors

Combinator Selectors

Combinator Symbol Meaning
Descendant A B (space) B anywhere inside A (any depth)
Direct child A > B B that is an immediate child of A only
Adjacent sibling A + B B that immediately follows A
General sibling A ~ B All B siblings that follow A

Pseudo-classes and Pseudo-elements

Type Example When It Applies
Pseudo-class a:hover User hovers cursor over the element
Pseudo-class li:nth-child(2) The second child element in its parent
Pseudo-class input:focus Element has keyboard/mouse focus
Pseudo-element p::first-letter The first letter of a paragraph
Pseudo-element .card::before A generated element inserted before .card
Note: IDs must be unique per page. For styling, prefer classes — they are reusable, carry lower specificity, and make your CSS far easier to maintain.
Tip: Group selectors that share declarations with commas: h1, h2, h3 { font-family: Georgia; }. This follows the DRY principle and keeps stylesheets concise.
Warning: Avoid bare * { } — scope the universal selector to a container like .modal * { } to limit its performance impact.

Basic Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Selector Showcase</title>
  <style>
    /* Element selector */
    p { font-family: system-ui, sans-serif; line-height: 1.6; }

    /* Class selector */
    .highlight { background: #fef9c3; padding: 2px 6px; border-radius: 3px; }

    /* ID selector */
    #page-title { color: #1d4ed8; letter-spacing: -0.02em; }

    /* Attribute selector */
    input[type="text"] {
      border: 2px solid #6366f1;
      border-radius: 4px;
      padding: 6px 10px;
    }

    /* Pseudo-class */
    a:hover { color: #dc2626; text-decoration: underline; }

    /* Direct child combinator */
    .box > p { margin: 0; color: #374151; font-weight: 500; }

    /* Pseudo-element */
    article p::first-letter {
      font-size: 2.5rem;
      font-weight: 700;
      color: #7c3aed;
      float: left;
      line-height: 1;
      margin-right: 6px;
    }

    /* Adjacent sibling */
    input[type="text"] + label {
      display: block;
      margin-top: 6px;
      color: #64748b;
      font-size: 0.85rem;
    }
  </style>
</head>
<body>
  <h1 id="page-title">CSS Selectors Demo</h1>
  <p>This uses <span class="highlight">element and class</span> selectors.</p>

  <div class="box">
    <p>Direct child of .box — child combinator applies.</p>
    <div><p>Grandchild — child combinator does NOT apply.</p></div>
  </div>

  <article>
    <p>Drop-cap via ::first-letter pseudo-element.</p>
  </article>

  <input type="text" id="name" placeholder="Your name">
  <label for="name">Adjacent sibling label</label>
  <p><a href="#">Hover this link</a></p>
</body>
</html>

How It Works

Step 1 — Element Selector Sets a Base Style

p { font-family: system-ui; line-height: 1.6; } targets every <p> globally with specificity (0,0,0,1) — the lowest possible, easily overridden by classes or IDs.

Step 2 — Class Selector Adds Contextual Styling

.highlight has specificity (0,0,1,0) — one class beats one element selector. It can be applied to any element type without rewriting the rule.

Step 3 — ID Selector Targets a Unique Element

#page-title has specificity (0,1,0,0). It targets the one element with id="page-title". No class-based rule can override it without using an ID or !important.

Step 4 — Attribute Selector Targets Form Controls

input[type="text"] selects only text inputs, ignoring checkboxes and radio buttons — precision styling without extra classes.

Step 5 — Combinators Establish Relationships

.box > p selects only <p> elements that are direct children of .box. A nested <p> inside a nested <div> inside .box would NOT be selected.

Real-World Example: Navigation Menu

<nav class="site-nav">
  <ul class="nav-list">
    <li><a href="/" class="nav-link active">Home</a></li>
    <li><a href="/about" class="nav-link">About</a></li>
    <li><a href="/services" class="nav-link">Services</a></li>
    <li class="nav-dropdown">
      <a href="/work" class="nav-link">Work</a>
      <ul class="dropdown-menu">
        <li><a href="/work/web">Web Design</a></li>
        <li><a href="/work/branding">Branding</a></li>
      </ul>
    </li>
  </ul>
</nav>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

nav.site-nav {
  background: #1e293b;
  padding: 0 32px;
  position: relative;
}

.nav-list li { display: inline-block; position: relative; }

.nav-link {
  display: inline-block;
  padding: 16px 18px;
  color: #94a3b8;
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 500;
  font-family: system-ui, sans-serif;
  transition: color 0.2s ease;
}

.nav-link:hover { color: #f1f5f9; }
.nav-link.active { color: #818cf8; border-bottom: 2px solid #818cf8; }

.nav-list li + li .nav-link { border-left: 1px solid #334155; }

.nav-dropdown > .dropdown-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background: #1e293b;
  min-width: 180px;
  border-top: 2px solid #818cf8;
  list-style: none;
  padding: 8px 0;
}

.nav-dropdown:hover > .dropdown-menu { display: block; }

.dropdown-menu a {
  display: block;
  padding: 10px 20px;
  color: #94a3b8;
  text-decoration: none;
  font-size: 0.875rem;
  font-family: system-ui, sans-serif;
}
.dropdown-menu a:hover { color: #f1f5f9; background: #334155; }

Common Mistakes

Mistake 1 — Overusing ID selectors for styling

❌ Wrong — IDs have very high specificity and cannot be reused:

#submit-btn { background: blue; color: white; padding: 10px 20px; border: none; }
#cancel-btn { background: grey; color: white; padding: 10px 20px; border: none; }

✅ Correct — share common styles via a class:

.btn            { color: white; padding: 10px 20px; border: none; cursor: pointer; }
.btn-primary    { background: blue; }
.btn-secondary  { background: grey; }

Mistake 2 — Descendant vs child combinator confusion

❌ Wrong — accidentally styles deeply nested paragraphs:

.card p { color: crimson; } /* selects ALL p inside .card at any depth */

✅ Correct — target only immediate children:

.card > p { color: crimson; }

❌ Wrong — :visited after :hover overrides hover on visited links:

a:link    { color: blue; }
a:hover   { color: red; }
a:visited { color: purple; }

✅ Correct — follow the LVHA order:

a:link    { color: blue; }
a:visited { color: purple; }
a:hover   { color: red; }
a:active  { color: orange; }

▶ Try It Yourself

Quick Reference

Selector Syntax Specificity
Element p (0,0,0,1)
Class .name (0,0,1,0)
ID #name (0,1,0,0)
Attribute [type="text"] (0,0,1,0)
Pseudo-class :hover, :focus (0,0,1,0)
Pseudo-element ::before, ::first-letter (0,0,0,1)
Child combinator A > B Sum of A + B

🧠 Test Yourself

Which selector targets only <p> elements that are direct children of a <div>?





▶ Try It Yourself