Selectors & Combinators
Selectors are the foundation of CSS — they determine which elements receive your styles. The three most fundamental selector types are type (element name), class (prefixed with .), and ID (prefixed with #).
Type selectors match all elements of a given tag name. Class selectors match any element with a specific class attribute. ID selectors match a single element with a given ID — and should be used sparingly due to their high specificity and the constraint that IDs must be unique per page.
| 1 | /* Type selector — targets all <article> elements */ |
| 2 | article { |
| 3 | padding: 24px; |
| 4 | border-bottom: 1px solid #222222; |
| 5 | } |
| 6 | |
| 7 | /* Class selector — reusable, low specificity */ |
| 8 | .card { |
| 9 | background: #0D0D0D; |
| 10 | border-radius: 8px; |
| 11 | padding: 16px; |
| 12 | } |
| 13 | |
| 14 | /* ID selector — high specificity, use sparingly */ |
| 15 | #primary-nav { |
| 16 | position: sticky; |
| 17 | top: 0; |
| 18 | z-index: 100; |
| 19 | } |
| 20 | |
| 21 | /* Combining type + class */ |
| 22 | button.primary { |
| 23 | background: #00FF41; |
| 24 | color: #0D0D0D; |
| 25 | font-weight: 600; |
| 26 | } |
| 27 | |
| 28 | /* Multiple classes */ |
| 29 | .card.highlighted { |
| 30 | border: 1px solid #00FF41; |
| 31 | } |
Attribute selectors match elements based on the presence or value of HTML attributes. They are extremely powerful for styling form elements, links, and data-driven components without adding extra classes.
| 1 | /* Presence — matches if attribute exists */ |
| 2 | [disabled] { opacity: 0.5; cursor: not-allowed; } |
| 3 | [hidden] { display: none; } |
| 4 | |
| 5 | /* Exact match */ |
| 6 | [type="text"] { border-color: #333; } |
| 7 | [data-theme="dark"] { background: #0D0D0D; } |
| 8 | |
| 9 | /* Substring match — contains */ |
| 10 | [class*="btn-"] { font-weight: 600; } |
| 11 | |
| 12 | /* Prefix match — starts with */ |
| 13 | [href^="https"] { color: #00FF41; } |
| 14 | [href^="/api"] { font-family: monospace; } |
| 15 | |
| 16 | /* Suffix match — ends with */ |
| 17 | [src$=".svg"] { padding: 4px; } |
| 18 | [href$=".pdf"]::after { content: " PDF"; } |
| 19 | |
| 20 | /* Space-separated — matches whole word in list */ |
| 21 | [rel~="external"] { target: _blank; } |
| 22 | |
| 23 | /* Case-insensitive flag */ |
| 24 | [data-type="primary" i] { background: #00FF41; } |
Combinators define relationships between selectors. They allow you to target elements based on their position relative to other elements in the DOM tree.
| Combinator | Symbol | Example | Description |
|---|---|---|---|
| Descendant | (space) | article p | <p> anywhere inside <article> |
| Child | > | article > p | <p> as a direct child of <article> |
| Adjacent Sibling | + | h2 + p | <p> immediately after <h2> |
| General Sibling | ~ | h2 ~ p | <p> anywhere after <h2> |
| Column | || | col || td | <td> that belongs to a column |
| 1 | /* Descendant — any level of nesting */ |
| 2 | .card p { color: #A0A0A0; line-height: 1.6; } |
| 3 | |
| 4 | /* Child — only direct children */ |
| 5 | nav > ul { display: flex; gap: 16px; list-style: none; } |
| 6 | |
| 7 | /* Adjacent sibling — immediately following */ |
| 8 | h2 + p { |
| 9 | margin-top: 0; |
| 10 | font-size: 1.125em; |
| 11 | color: #00FF41; |
| 12 | } |
| 13 | |
| 14 | /* General sibling — all following siblings */ |
| 15 | h2 ~ p { color: #808080; } |
| 16 | |
| 17 | /* Chaining combinators */ |
| 18 | .sidebar > .nav-item > a.active { |
| 19 | color: #00FF41; |
| 20 | border-left: 2px solid #00FF41; |
| 21 | } |
| 22 | |
| 23 | /* Complex targeting */ |
| 24 | main.content article.card:hover { |
| 25 | border-color: #00FF41; |
| 26 | } |
Pseudo-classes select elements based on their state, position, or user interaction. They begin with a single colon : and are appended to a selector.
1/* User interaction states */2button:hover { background: #00FF41; color: #0D0D0D; }3input:focus { outline: 2px solid #00FF41; }4a:active { color: #00FF41; }5a:visited { color: #666; }6 7/* Position-based */8li:first-child { font-weight: bold; }9li:last-child { border-bottom: none; }10li:nth-child(odd) { background: #0D0D0D; }11li:nth-child(3n+1) { color: #00FF41; }12li:nth-last-child(2) { color: #FFB000; }13 14/* Structural */15:root { --accent: #00FF41; }16:empty { display: none; }17:only-child { margin: 0; }18 19/* Functional pseudo-classes */20button:not(.primary) { opacity: 0.7; }21:is(h1, h2, h3) { line-height: 1.3; }22:where(.card, .panel) { padding: 1.5rem; }23form:has(input:invalid) { border-color: #EF4444; }24 25/* Form states */26input:checked { accent-color: #00FF41; }27input:disabled { opacity: 0.4; }28input:required { border-color: #00FF41; }29input:invalid { border-color: #EF4444; }30input:valid { border-color: #22C55E; }info
Pseudo-elements target specific parts of an element or generate virtual content. They use the double-colon :: syntax to distinguish them from pseudo-classes.
| 1 | /* Generated content — before and after */ |
| 2 | .card::before { |
| 3 | content: "◆ "; |
| 4 | color: #00FF41; |
| 5 | } |
| 6 | |
| 7 | .card::after { |
| 8 | content: ""; |
| 9 | display: block; |
| 10 | width: 100%; |
| 11 | height: 1px; |
| 12 | background: linear-gradient(90deg, #00FF41, transparent); |
| 13 | margin-top: 12px; |
| 14 | } |
| 15 | |
| 16 | /* Typography pseudo-elements */ |
| 17 | p::first-line { |
| 18 | font-size: 1.25em; |
| 19 | font-weight: 600; |
| 20 | color: #E0E0E0; |
| 21 | } |
| 22 | |
| 23 | p::first-letter { |
| 24 | font-size: 3em; |
| 25 | float: left; |
| 26 | line-height: 0.8; |
| 27 | margin-right: 6px; |
| 28 | color: #00FF41; |
| 29 | } |
| 30 | |
| 31 | /* Selection styling */ |
| 32 | ::selection { |
| 33 | background: #00FF41; |
| 34 | color: #0D0D0D; |
| 35 | } |
| 36 | |
| 37 | /* Placeholder styling */ |
| 38 | input::placeholder { |
| 39 | color: #525252; |
| 40 | font-style: italic; |
| 41 | } |
| 42 | |
| 43 | /* Marker for list items */ |
| 44 | li::marker { |
| 45 | color: #00FF41; |
| 46 | font-size: 0.8em; |
| 47 | } |
| 48 | |
| 49 | /* File selector button */ |
| 50 | input[type="file"]::file-selector-button { |
| 51 | background: #00FF41; |
| 52 | color: #0D0D0D; |
| 53 | border: none; |
| 54 | padding: 8px 16px; |
| 55 | border-radius: 4px; |
| 56 | } |
When multiple selectors target the same element, the browser uses specificity to determine which declaration wins. Understanding specificity is crucial for debugging and writing maintainable CSS.
| Level | Selector Type | Weight |
|---|---|---|
| A | Inline styles (style="") | 1, 0, 0, 0 |
| B | ID selectors (#id) | 0, 1, 0, 0 |
| C | Classes, attributes, pseudo-classes | 0, 0, 1, 0 |
| D | Elements, pseudo-elements | 0, 0, 0, 1 |
| 1 | /* Specificity: 0,0,0,1 */ |
| 2 | p { color: #A0A0A0; } |
| 3 | |
| 4 | /* Specificity: 0,0,1,0 — beats type selector */ |
| 5 | .text { color: #E0E0E0; } |
| 6 | |
| 7 | /* Specificity: 0,1,1,0 — beats class alone */ |
| 8 | #main .text { color: #00FF41; } |
| 9 | |
| 10 | /* Specificity: 0,1,2,1 — highest normal specificity */ |
| 11 | #main p.text.highlight { color: #FFB000; } |
| 12 | |
| 13 | /* The :where() pseudo-class zeroes specificity */ |
| 14 | :where(.card) p { color: #808080; } |
| 15 | /* Equivalent to: p { color: #808080; } (specificity: 0,0,0,1) */ |
| 16 | |
| 17 | /* The :is() pseudo-class takes highest argument specificity */ |
| 18 | :is(h1, #title) { color: #00FF41; } |
| 19 | /* Specificity: 0,1,0,0 (from #title, ignoring h1) */ |
warning
CSS nesting lets you nest selectors inside other selectors, mirroring the HTML structure. This feature landed natively in browsers in 2024, reducing the need for preprocessors like Sass for basic nesting.
| 1 | /* Without nesting */ |
| 2 | .card { background: #0D0D0D; border-radius: 8px; } |
| 3 | .card .title { color: #E0E0E0; font-size: 1.25rem; } |
| 4 | .card .body { color: #A0A0A0; line-height: 1.6; } |
| 5 | .card .footer { border-top: 1px solid #222; padding-top: 12px; } |
| 6 | .card .footer .meta { color: #525252; font-size: 0.75rem; } |
| 7 | |
| 8 | /* With native CSS nesting */ |
| 9 | .card { |
| 10 | background: #0D0D0D; |
| 11 | border-radius: 8px; |
| 12 | |
| 13 | .title { |
| 14 | color: #E0E0E0; |
| 15 | font-size: 1.25rem; |
| 16 | } |
| 17 | |
| 18 | .body { |
| 19 | color: #A0A0A0; |
| 20 | line-height: 1.6; |
| 21 | } |
| 22 | |
| 23 | .footer { |
| 24 | border-top: 1px solid #222; |
| 25 | padding-top: 12px; |
| 26 | |
| 27 | .meta { color: #525252; font-size: 0.75rem; } |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | /* Using & for compound selectors */ |
| 32 | .button { |
| 33 | &.primary { background: #00FF41; color: #0D0D0D; } |
| 34 | &:hover { opacity: 0.9; } |
| 35 | & .icon { margin-right: 8px; } |
| 36 | } |
| 37 | |
| 38 | /* Nesting at-rules */ |
| 39 | .card { |
| 40 | @media (max-width: 768px) { |
| 41 | padding: 12px; |
| 42 | .title { font-size: 1rem; } |
| 43 | } |
| 44 | } |
best practice