Skip to main content

CSS Selectors

CSS selectors are the targeting system of the web. They instruct the browser rendering engine exactly which DOM nodes to select and style. As layout complexity scales, writing clean, maintainable selectors becomes essential for preventing specificity bloat and redundant rule chains.

1. Core Selector Taxonomy​

CSS selectors range from fundamental type matchers to advanced functional pseudo-classes:

                [ CSS Selectors ]
|
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β–Ό β–Ό β–Ό β–Ό
Simple Attribute Combinators Pseudo-Classes /
Selectors Selectors (& Relational) Elements
(type, class, ([attr=val]) (>, +, ~) (:hover, ::before,
id, universal) :is(), :has())

Basic & Combinator Reference​

TypeSyntax ExampleDescriptionSpecificity Vector
Universal*Targets every element in the document scope(0, 0, 0)
Type (Element)h1, p, articleMatches nodes by HTML tag name(0, 0, 1)
Class.card, .btnMatches nodes with a specific class attribute value(0, 1, 0)
ID#main-headerMatches the unique element with a specific id attribute(1, 0, 0)
Descendantarticle pMatches p anywhere nested inside article(0, 0, 2)
Childul > liMatches li that is a direct child of ul(0, 0, 2)
Next Siblingh2 + pMatches p immediately following h2 at the same DOM level(0, 0, 2)
Subsequent Siblingh2 ~ pMatches all p elements following h2 at the same DOM level(0, 0, 2)

2. Advanced Attribute Matching​

Attribute selectors target elements based on the presence, exact value, or partial matching of their HTML attributes.

Attribute Selector Examples
/* 1. Exact match */
input[type="text"] { border-color: #cbd5e1; }

/* 2. Contains word in space-separated list (~=) */
[class~="primary"] { font-weight: 700; }

/* 3. Starts with prefix, followed by hyphen or exact (-=) */
[lang|="en"] { quotes: "β€œ" "”"; }

/* 4. Starts with exact substring (^=) */
a[href^="https://"] { color: #059669; }

/* 5. Ends with exact substring ($=) */
a[href$=".pdf"] { padding-right: 1.5rem; }

/* 6. Contains substring anywhere (*=) */
[class*="nav-item"] { opacity: 0.9; }

/* 7. Case-insensitive matching flag (i) */
input[placeholder*="search" i] { background-color: #f1f5f9; }

3. Modern Pseudo-Classes: :is(), :where(), and :has()​

Modern CSS introduces functional pseudo-classes that dramatically streamline complex rule sets and introduce relational selector logic.

:is() β€” Grouping & Forgiving Lists​

Reduces repetition when applying styles across multiple matching trees. The specificity of :is() equals the highest-specificity selector in its argument list.

/* Legacy repetitive approach */
header h1, header h2, main h1, main h2 {
margin-bottom: 1rem;
}

/* Modern clean approach with :is() */
:is(header, main) :is(h1, h2) {
margin-bottom: 1rem;
}

:where() β€” Zero-Specificity Abstraction​

Functions identically to :is(), but always maintains a specificity vector of (0, 0, 0), making it ideal for base resets and design systems that users can easily override.

/* Specificity is strictly (0, 0, 0) regardless of internal arguments */
:where(header, main) :where(h1, h2) {
color: #1e293b;
}

/* Easily overridden by a single type selector (0, 0, 1) */
h1 {
color: #2563eb;
}

:has() β€” The Parent & Relational Selector​

Matches a parent element based on its children or sibling relationships.

/* Target a .card parent ONLY if it contains an image */
.card:has(img) {
grid-template-columns: 120px 1fr;
}

/* Target an h2 ONLY when directly followed by a sub-heading p */
h2:has(+ p.subtitle) {
margin-bottom: 0.25rem;
}

Interactive Playground: Selector Precision​

Experiment with basic combinators, attribute matching, and functional pseudo-classes in the sandbox below:

Loading Interactive Editor...

Summary Reference Table​

Selector / Pseudo-ClassPrimary Use CaseSpecificity Impact
[attr^="val"]Matches starting value (e.g., protocol links)Counts as 1 Class (0, 1, 0)
[attr$="val"]Matches ending value (e.g., file extensions)Counts as 1 Class (0, 1, 0)
:is(A, B)Dry up duplicate selector treesHighest argument in list
:where(A, B)Low-specificity design system defaultsAlways zero (0, 0, 0)
:has(Selector)Relational parent/sibling conditional stylingHighest argument in condition