skip to content

Selector Types and Combinators

The matching vocabulary: type, class, ID, attribute, and universal selectors plus the four combinators that express relationships. Interviewers hand you a selector and expect you to read it out loud and name exactly which elements it matches.

part ofCSSoverview, primer and where to startread it →
on this pageshow

questions

5

In CSS, how do the selectors `.card.featured`, `.card .featured`, and `.card, .featured` differ in what they match?

level: juniorimportance: must knowfreq 70%

answer

  1. punctuation carries the meaning
  2. no separator means one element
  3. whitespace is not decoration
  4. comma builds independent selectors
  5. an element is not its own descendant

basics

~20 s

Written together, .card.featured matches one element carrying both classes. With a space, .card .featured matches a .featured element inside a .card. With a comma, .card, .featured is a selector list: two independent selectors sharing one declaration block.

solid answer

~40 s

They are three different things separated only by punctuation. `.card.featured` is a single compound selector: no separator means both simple selectors must match the *same* element, so it matches `<div class="card featured">` and nothing else. `.card .featured` has whitespace, which is the descendant combinator, so it matches a `.featured` element anywhere inside a `.card` — two different elements. `.card, .featured` is a selector list: the comma separates independent selectors that happen to share one declaration block, so the rule applies to every `.card` and every `.featured` regardless of any relationship between them. The trap in interviews is the space, because it is invisible punctuation with real meaning — accidentally typing `.btn .primary` when you meant `.btn.primary` is one of the most common CSS bugs there is.

code

html · 7 lines
html
<style>
  .card.featured  { outline: 2px solid gold; }  /* one element, both classes */
  .card .featured { background: #eee; }         /* .featured inside .card */
  .card, .featured { padding: 1rem; }           /* every .card AND every .featured */
</style>
<div class="card featured">both classes on one element</div>
<div class="card"><span class="featured">nested</span></div>

go deeper

for a junior

Be able to look at a class selector and say instantly whether the space is there, and state which elements each of the three forms matches in a small piece of markup.

for a middle

Explain that a compound requires one element to satisfy every part while whitespace introduces a second element, and know that one invalid selector drops the entire comma-separated rule.

for a senior

Demonstrate debugging instinct: when a correctly spelled rule never applies, show that you check for an accidental descendant space and for a poisoned selector list before suspecting anything deeper.

for a principal

Own the conventions that make these mistakes rare — lint rules and review habits around selector lists and depth limits are cheaper than debugging invisible whitespace across a large stylesheet.

## Three kinds of punctuation, three meanings CSS selector syntax packs a lot of meaning into characters that are easy to skim past. Between two class selectors there are three possibilities, and each builds a structurally different query. ### No separator: a compound selector `.card.featured` is one *compound selector*. A compound selector is a run of simple selectors — type, class, ID, attribute, pseudo-class — written with nothing between them, and **every part must match the same element**. Order within the compound does not matter, so `.featured.card` is equivalent, with one syntactic rule: if a type selector is present it must come first, so you write `a.btn[target]`, never `.btn a`-style reordering. ```css .card.featured { border-color: gold; } ``` ```html <div class="card featured">matched</div> <div class="card">not matched</div> <div class="featured">not matched</div> ``` The same principle applies across selector kinds: `input[type="checkbox"]`, `li.active`, `#main.dark` are all compounds requiring one element to satisfy every part. ### Whitespace: the descendant combinator `.card .featured` inserts a combinator, so the selector now describes **two** elements and a relationship between them: a `.featured` element with a `.card` ancestor at any depth. The element that actually receives the declarations is the rightmost one, `.featured`. ```html <div class="card"><span class="featured">matched</span></div> <div class="card featured">not matched</div> ``` Note the second line carefully: an element cannot be its own descendant, so a single element carrying both classes is matched by the compound but *not* by the descendant selector. This is the exact pair interviewers use to check whether you read whitespace as syntax. ### Comma: a selector list `.card, .featured` is a *selector list* (the specification's term; "grouping" in older material). The comma does not create any relationship. It is shorthand for writing the same declaration block twice: ```css .card, .featured { padding: 1rem; } /* identical in effect to */ .card { padding: 1rem; } .featured { padding: 1rem; } ``` Each selector in the list is matched and weighted independently — the list itself contributes nothing. ## The invalid-selector rule There is one behaviour of selector lists that surprises people and shows up in real bugs. If **any** selector in a comma-separated list is invalid or unrecognised by the browser, the *whole rule* is dropped, including the parts the browser understood perfectly well: ```css /* one unknown pseudo-class poisons the entire rule */ .button, .link:some-unsupported-thing { color: red; } ``` Neither `.button` nor the link gets `color: red` in a browser that does not know that pseudo-class. This is why a single experimental or vendor-specific selector must be given its own rule rather than being folded into a shared list — a habit worth keeping even though the selectors involved change over time. ## Compounds and combinators together Real selectors mix all three. `.card.featured > .title, .card.archived > .title` is a list of two selectors, each of which chains a compound (`.card.featured`) to another compound (`.title`) with the child combinator. When reading such a selector aloud, split on commas first, then read each selector right to left. ## Why the space bug is so common The compound/descendant distinction is invisible in a diff view, produces no error, and silently changes which elements a rule reaches — usually to *none*, because the intended element carries both classes rather than nesting them. Symptoms are a rule that appears in the stylesheet, is spelled correctly, and simply never applies. When a class-based rule mysteriously does nothing, checking for an accidental space between the two class selectors is one of the fastest things to rule out. The reverse mistake also happens: writing `.modal.close` when the close button is a child, so the rule silently targets a nonexistent element that is both a modal and a close button. ## Reading habit Before committing to an answer, tokenise the selector out loud: "class card, *no space*, class featured — same element" versus "class card, *space*, class featured — one inside the other" versus "class card, *comma*, class featured — two separate rules". Saying the punctuation aloud is what stops the mistake.

  • What happens if one selector in a comma-separated list is invalid?
    The entire rule is discarded, not just the offending selector. If a browser cannot parse `.button, .link:some-unknown-thing { color: red }`, then `.button` loses the declaration too. That is why an experimental or browser-specific selector should be given its own rule rather than folded into a shared list.
  • Does the order of simple selectors inside a compound change what it matches?
    No — `.card.featured` and `.featured.card` match exactly the same elements, since every part of a compound must match the same element. There is one syntactic constraint: a type selector, if present, must be written first, so `a.btn` is valid and `.btn` followed by `a` in the same compound is not.
  • Why can `.card .featured` fail to match an element that has both classes?
    Because the whitespace demands two distinct elements: a `.featured` element with a `.card` *ancestor*. An element is never its own ancestor or descendant, so `<div class="card featured">` satisfies the compound `.card.featured` but not the descendant selector. This is the classic accidental-space bug.

saying these in an interview costs you the question

  • Treating `.a.b` and `.a .b` as interchangeable
  • Saying the comma means "and" rather than a selector list
  • Claiming `.a .b` matches an element with both classes
  • Thinking an invalid selector in a list is skipped harmlessly
  • Believing compound order changes what is matched

context

open as a page

In CSS, what do the selectors `div p`, `div > p`, `div + p`, and `div ~ p` each match?

level: juniorimportance: must knowfreq 85%

basics

~20 s

div p matches a p at any depth inside a div. div > p matches only a p that is a direct child. div + p matches the p immediately after a div, sharing a parent. div ~ p matches any later p sibling.

open as a page

In CSS, what is the difference between the attribute selectors `[href^="/docs"]`, `[href$=".pdf"]`, `[href*="api"]`, `[class~="btn"]`, and `[lang|="en"]`, and what does adding the `i` flag do?

level: middleimportance: should knowfreq 50%

basics

~20 s

They are attribute value operators: ^= matches a prefix, $= a suffix, *= a substring anywhere, ~= one whitespace-separated word in the value, and |= an exact value or one followed by a hyphen. Adding i before the closing bracket makes the comparison case-insensitive.

open as a page

In the CSS selector `.sidebar ul li a`, which compound is the key selector, and why does matching order matter for how you write descendant chains?

level: seniorimportance: should knowfreq 32%

basics

~20 s

The key selector is the rightmost compound, here the a. Browsers test candidate elements against it first and only then walk up the ancestor chain, so a broad rightmost compound with a long chain to its left forces the most ancestor walking.

open as a page

In CSS, what does the selector `.stack > * + *` match, and why is it used to space a list of children?

level: middleimportance: nice to knowfreq 36%

basics

~20 s

It matches every direct child of .stack that has a preceding element sibling — in other words, every child except the first. Applying margin-top there spaces the children apart without leaving a stray margin above the first one or below the last.

open as a page