Suggest an editImprove this articleRefine the answer for “CSS selectors”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**CSS selectors** - patterns that target HTML elements by tag, class, ID, attribute, or DOM position to apply styles. Types: `p` (tag), `.class`, `#id`, `[attr]`, `:hover` (pseudo-class), `::before` (pseudo-element). Combinators: `>` (child), `+` (adjacent sibling), `~` (general sibling), space (descendant). **Key:** specificity determines which rule wins when two selectors target the same element.Shown above the full answer for quick recall.Answer (EN)Image**CSS selectors** - patterns that match HTML elements by tag name, attribute, position in the DOM, or state, telling the browser which nodes to style. ## Theory ### TL;DR - Selectors work like a filter on the DOM. `p` grabs every paragraph. `p.note` grabs only paragraphs with class `note`. `nav > ul > li` grabs only direct list items inside a direct `ul` inside `nav`. - Basic selectors (tag, class, ID) match elements by what they are. Combinators match by where they sit in the tree. - Specificity score: inline = 1000, ID = 100, class = 10, tag = 1. Higher score wins. Equal scores go to the rule that appears later in the stylesheet. - Decision rule: class for reusable components, ID for one unique element per page, combinators to target DOM structure without adding extra classes. ### Quick example ```css p { color: red; } /* tag: every p */ .note { background: yellow; } /* class: reusable across elements */ #main { font-weight: bold; } /* ID: highest non-inline specificity */ div > p { color: green; } /* child combinator: only direct p inside div */ ``` `div > p` scores 0-0-0-2 (two tags). The plain `p` scores 0-0-0-1. Green overrides red on any `p` that is a direct child of a `div`. A `p` nested two levels deep stays red. That is exactly what the child combinator is for. ### Selector types **Basic:** - `p` (tag) - matches all elements of that tag - `.btn-primary` (class) - reusable, the standard choice for UI components - `#header` (ID) - unique per page, specificity 100 - `*` (universal) - matches everything; use only in scoped resets like `*, *::before, *::after { box-sizing: border-box; }` **Combinators:** - `div p` (descendant) - any `p` inside `div` at any nesting depth - `div > p` (child) - only direct `p` children of `div` - `h1 + p` (adjacent sibling) - the single `p` right after `h1` - `h1 ~ p` (general sibling) - all `p` elements after `h1` at the same level **Attribute selectors:** - `[type="text"]` - exact value match - `[class~="foo"]` - exact word in a space-separated list - `[href*="cdn"]` - substring match anywhere in the value - `[src^="https"]` - value starts with a string - `[src$=".svg"]` - value ends with a string **Pseudo-classes and pseudo-elements:** - `:hover`, `:focus`, `:disabled` - element state - `:nth-child(2n)`, `:first-child` - position among siblings - `::before`, `::after` - generated content before or after the element ### Key difference: basic vs combinators Basic selectors grab elements by what they are. Combinators grab elements by where they sit in the tree. Writing `ul > li.active` styles only top-level active list items and ignores nested `li.active` elements inside inner lists. Without the child combinator you would need a separate class or JavaScript to do the same thing. In my experience, switching `.container p` to `.container > p` in a real codebase removed dozens of unintended style overrides in a single pass. ### When to use - **All elements of a type** - tag selector (`p`, `h1`, `a`) - **Reusable UI components** - class (`.btn`, `.card`, `.modal`) - **One unique element per page** - ID (`#skip-nav` for accessibility anchors) - **DOM structure targeting** - child combinator (`nav > ul > li`) - **Dynamic states** - pseudo-class (`:hover`, `:focus-visible`, `:disabled`) - **Alternating table rows** - `:nth-child(2n)` on `tbody tr` - **Skip** - deep descendant chains like `div div div p`; specificity becomes hard to override and matching slows on large DOMs ### How the browser matches selectors Browsers parse selectors right-to-left. For `nav > ul > li.active`, the engine first finds all `.active` elements, checks if the parent is `ul`, then checks if that `ul` is a direct child of `nav`. Fewer ancestor lookups means faster matching. Chrome's Blink caches matched results in `StyleResolver`, so repaints skip re-matching for unchanged nodes. Specificity is stored as a three-part score (ID count, class count, tag count). When two rules target the same property on the same element, the higher score wins. Equal scores go to the later rule in the stylesheet. For a deeper look at how cascade order and specificity interact, see [CSS cascade and specificity](/questions/css-cascade). ### Common mistakes **Using descendant selector when child would do:** ```css /* Matches every p inside .container at any depth */ .container p { color: blue; } /* Matches only direct p children: faster, more predictable */ .container > p { color: blue; } ``` The descendant version hits nested components you did not intend to style. It also makes specificity harder to override later. **Overusing ID selectors for styling:** ```css /* Breaks if a second #header appears (duplicate IDs are invalid HTML) */ #header p { display: none; } /* Class composes cleanly across components */ .site-header p { display: none; } ``` ID specificity (100) is hard to override without another ID or `!important`. Classes (10) are easier to work with in large projects. **Removing focus outlines without a replacement:** ```css /* Removes keyboard focus indicator, a WCAG violation */ button:hover { outline: none; } /* Shows outline only for keyboard users, not mouse clicks */ button:focus-visible { outline: 2px solid currentColor; } ``` **Reaching for `!important` to fix specificity conflicts:** ```css /* Creates a chain reaction: the next developer adds more !important */ p { color: red !important; } /* Add specificity through structure instead */ body .content p { color: red; } ``` **Confusing `:nth-child` and `:nth-of-type`:** ```html <ul> <div>Not a list item</div> <li>First li, second child</li> <!-- matches li:nth-child(2) --> <li>Second li, third child</li> <!-- matches li:nth-of-type(2) --> </ul> ``` `li:nth-child(2)` matches the element that is both the second child AND an `li`. `li:nth-of-type(2)` matches the second `li` regardless of other tags in between. Use `:nth-of-type` when the parent contains mixed element types. ### Real-world usage - **React / styled-components** - class selectors like `.btn--primary` for theme variants - **Tailwind** - arbitrary selectors `[data-state=open] > *` for accordion panels - **Bootstrap** - combinators `navbar > .container` for responsive layout structure - **Material-UI** - pseudo-classes `button:disabled` for state-driven styles - **Accessible UI** - `:focus-visible` replaces `:focus` overrides across component libraries ### Follow-up questions **Q:** How do you calculate specificity for `#id .class p`? **A:** ID = 100, class = 10, tag = 1. Total = 111. Compare to `.class p` which scores 11. The ID rule wins regardless of where it sits in the stylesheet. **Q:** What is the performance difference between child `>` and descendant (space)? **A:** Child checks only the immediate parent. Descendant walks the entire ancestor chain. The gap is measurable with Chrome DevTools style recalculation profiling on pages with 10,000+ nodes. **Q:** How does `:nth-child` differ from `:nth-of-type`? **A:** `:nth-child(n)` counts all siblings regardless of tag. `:nth-of-type(n)` counts only siblings of the same element type. Use `:nth-of-type` when mixed tags appear inside the same parent. **Q:** Write a selector for every other row in a table body, ignoring the header. **A:** `tbody tr:nth-child(2n)`. The `tbody` scope excludes `thead` automatically, so no extra filter is needed. Works correctly on 1000-row tables without JavaScript. **Q:** What is the difference between `[class~="foo"]` and `[class*="foo"]`? **A:** `~=` matches an exact word in a space-separated list, so `class="foo bar"` matches but `class="foobar"` does not. `*=` is a substring match, so both match. Use `~=` when you need word-boundary precision on class names. ## Examples ### Basic selector cascade ```html <style> p { color: red; } /* specificity 0-0-1 */ .note { background: yellow; } /* specificity 0-1-0 */ #main { font-weight: bold; } /* specificity 1-0-0 */ div > p { color: green; } /* specificity 0-0-2 */ </style> <p>Red text.</p> <p class="note">Yellow background, red text.</p> <div> <p>Green text (child combinator beats tag selector).</p> </div> <div id="main"> <p>Green text, bold weight (two rules, different properties).</p> </div> ``` `div > p` scores 0-0-2 and beats `p` at 0-0-1 for the `color` property. The `#main` rule targets the `div`, not the `p`, so `font-weight: bold` reaches the paragraph through inheritance. That is a common source of confusion when reading devtools: the bold appears on `p` but the matching rule is on `div`. ### Child combinator in a React navigation component ```jsx // Nav.jsx const Nav = () => ( <nav> <ul> <li className="active">Dashboard</li> <li>Reports</li> </ul> <ul> <li className="active">Sub-item</li> </ul> </nav> ); ``` ```css /* nav.css */ nav > ul > li.active { background: #007bff; color: white; } ``` Both `ul` elements are direct children of `nav`, so both `li.active` items match this rule. If the requirement is to style only the top navigation, add a class to the first `ul` instead of relying on position alone. Chaining combinators narrows the target but does not guarantee uniqueness. Know your HTML structure before writing the selector. ### Specificity tiebreaker with nth-child ```html <style> li:nth-child(2) { color: red; } /* specificity 0-1-1 */ li:nth-of-type(2) { color: blue; } /* specificity 0-1-1 */ </style> <ul> <div>Not a list item</div> <!-- child 1 --> <li>First li (child 2)</li> <!-- red: matches nth-child(2) --> <li>Second li (child 3)</li> <!-- blue: matches nth-of-type(2) --> </ul> ``` Both rules have specificity 0-1-1. The first `li` is the second child overall, so `:nth-child(2)` matches it. The second `li` is the second `li` by type, so `:nth-of-type(2)` matches it. No conflict here. But in a table, `tbody tr:nth-child(2)` starts counting from the first `tr` inside `tbody`, not from the top of the entire table. Scoping to `tbody` resets the child index. That is the kind of detail that comes up in senior interviews.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.