⚡IntermediateAdvanced Selectors

CSS Combinator Selectors

Select elements by their DOM relationship using descendant, child (>), adjacent sibling (+), and general sibling (~) combinators.

Overview

CSS combinators define the **relationship** between two selectors. Instead of selecting all `p` elements, you can select only `p` elements that are direct children of a `.card`, or only the paragraph that immediately follows an `h2`. The four combinators: | Combinator | Syntax | Meaning | |---|---|---| | Descendant | `A B` | B anywhere inside A (any depth) | | Child | `A > B` | B that is a **direct child** of A | | Adjacent sibling | `A + B` | B immediately following A (same parent) | | General sibling | `A ~ B` | All B elements following A (same parent) | Combinators are composable you can chain them: `.card > header + p` selects a `p` that immediately follows a `header` which is a direct child of `.card`.

Key Points

  • •Descendant (space): matches any nesting depth "ul a" matches links inside any ul, no matter how deeply nested
  • •Child (>): only matches direct children "ul > li" won't select li inside a nested ul
  • •Adjacent sibling (+): selects the one element immediately after "h2 + p" matches only the first p after h2
  • •General sibling (~): selects all following siblings "h2 ~ p" matches all p elements after any h2
  • •Combinators do not increase specificity only the selector parts themselves count
  • •The checkbox hack uses input:checked + label to toggle visibility without JavaScript

Examples

Child combinator for direct list items only

/* Only direct <li> children ignores nested lists */
.nav > li {
  display: inline-block;
  padding: 8px 16px;
}

/* Descendant all links at any depth */
.nav a {
  color: #374151;
  text-decoration: none;
}

.nav > li targets only the first-level li items, not those inside a dropdown.

Adjacent sibling for form labels after inputs

/* Float label technique: label follows input */
.field > input:focus + label,
.field > input:not(:placeholder-shown) + label {
  transform: translateY(-24px) scale(0.85);
  color: #6366f1;
}

The HTML order must be <input> then <label> for + to work.

General sibling for "zebra striping" without nth-child

/* Every paragraph after an h2 in an article */
article h2 ~ p {
  margin-left: 16px;
  border-left: 3px solid #e5e7eb;
  padding-left: 12px;
}

/* All siblings after a .divider */
.divider ~ * {
  opacity: 0.5;
}

Common Mistakes

  • Using descendant space when you mean child >: "nav li" accidentally styles nested submenu items too
  • Expecting + to skip hidden elements it matches the next element in the DOM, even if display: none
  • Forgetting that siblings must share the same parent elements in different containers are never siblings

Note

The "owl selector" (* + *) adds margin-top to every element that follows another element a clean way to add vertical rhythm without margin on the first child: .stack > * + * { margin-top: 1rem; }

Exercises

Write CSS to style the HTML scaffold. The preview updates live — when it looks right, mark as done.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What is the difference between "nav li" and "nav > li"?