Modern CSS Selectors Guide
Complete reference for CSS selectors, from basics to modern features like :has(), :is(), and :where(). Includes practical use cases.
Basic Selectors
Selector | Example | Description |
---|---|---|
* |
* { } |
Universal selector - matches all elements |
element |
div { } |
Type selector - matches element by tag name |
.class |
.button { } |
Class selector - matches elements with class |
#id |
#header { } |
ID selector - matches element with specific ID |
element.class |
div.card { } |
Compound selector - both conditions must match |
selector, selector |
h1, h2 { } |
Selector list - matches any of the selectors |
Combinators
Combine selectors to target elements based on their relationship.
Combinator | Example | Description |
---|---|---|
A B |
div p { } |
Descendant - all B inside A (any level) |
A > B |
ul > li { } |
Direct child - B that are direct children of A |
A + B |
h1 + p { } |
Adjacent sibling - B immediately after A |
A ~ B |
h1 ~ p { } |
General sibling - all B siblings after A |
Practical Examples
/* Style paragraphs immediately after headings */
h2 + p {
font-size: 1.2em;
margin-top: 0;
}
/* Style all list items that are direct children */
nav > ul > li {
display: inline-block;
}
/* Style all siblings after a specific element */
.active ~ .item {
opacity: 0.5;
}
Pseudo-Classes
Target elements based on their state or position.
Interactive States
Selector | Description |
---|---|
:hover |
Element is being hovered by pointer |
:focus |
Element has focus |
:focus-visible |
Focus that should be visible (keyboard navigation) |
:focus-within |
Element or any descendant has focus |
:active |
Element is being activated (clicked) |
:visited |
Link has been visited |
:target |
Element with ID matching URL fragment |
/* Better focus management */
button:focus-visible {
outline: 2px solid blue;
}
/* Style parent when child has focus */
form:focus-within {
border-color: blue;
}
/* Style active tab content */
.tab-content:target {
display: block;
}
Form State Selectors
Target form elements based on their state.
Selector | Description |
---|---|
:enabled |
Form element that is enabled |
:disabled |
Form element that is disabled |
:checked |
Checkbox or radio button that is checked |
:indeterminate |
Checkbox in indeterminate state |
:required |
Form field with required attribute |
:optional |
Form field without required attribute |
:valid |
Form field with valid input |
:invalid |
Form field with invalid input |
:in-range |
Input value within min/max range |
:out-of-range |
Input value outside min/max range |
:placeholder-shown |
Input showing placeholder text |
:read-only |
Element that is read-only |
:read-write |
Element that is editable |
Form Styling Examples
/* Style invalid fields only after interaction */
input:invalid:not(:placeholder-shown) {
border-color: red;
}
/* Style labels for required fields */
input:required + label::after {
content: " *";
color: red;
}
/* Custom checkbox styling */
input[type="checkbox"]:checked + label {
font-weight: bold;
}
/* Disabled state */
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
Structural Pseudo-Classes
Select elements based on their position in the document tree.
Selector | Description |
---|---|
:first-child |
First child of its parent |
:last-child |
Last child of its parent |
:only-child |
Only child of its parent |
:first-of-type |
First sibling of its type |
:last-of-type |
Last sibling of its type |
:only-of-type |
Only sibling of its type |
:nth-child(n) |
Nth child (1-indexed) |
:nth-last-child(n) |
Nth child from the end |
:nth-of-type(n) |
Nth sibling of its type |
:nth-last-of-type(n) |
Nth sibling of its type from end |
:empty |
Element with no children (including text) |
:root |
Root element of document (html) |
nth-child Formulas
/* Even and odd */
li:nth-child(even) { } /* 2, 4, 6, 8... */
li:nth-child(odd) { } /* 1, 3, 5, 7... */
/* Every third element */
li:nth-child(3n) { } /* 3, 6, 9, 12... */
/* Every third starting from 2nd */
li:nth-child(3n+2) { } /* 2, 5, 8, 11... */
/* First 3 elements */
li:nth-child(-n+3) { } /* 1, 2, 3 */
/* All but first 3 */
li:nth-child(n+4) { } /* 4, 5, 6, 7... */
/* Specific element */
li:nth-child(5) { } /* 5 */
Try it: See which elements match
1
2
3
4
5
6
7
8
9
10
11
12
Practical Examples
/* Zebra striping */
tr:nth-child(even) {
background: rgba(255, 255, 255, 0.05);
}
/* Style first paragraph differently */
article > p:first-of-type {
font-size: 1.2em;
}
/* Remove margin from last element */
.container > *:last-child {
margin-bottom: 0;
}
/* Hide empty elements */
.message:empty {
display: none;
}
Logical Pseudo-Classes
Modern selectors for complex logic and relationships.
:is() - Matches Any
Modern/* Old way - repetitive */
h1 a, h2 a, h3 a {
color: blue;
}
/* New way - cleaner */
:is(h1, h2, h3) a {
color: blue;
}
/* Works with complex selectors */
:is(.card, .panel) :is(h2, h3) {
margin-top: 0;
}
Reduces specificity to the most specific selector in the list.
:where() - Matches Any (Zero Specificity)
Modern/* Same as :is() but with 0 specificity */
:where(h1, h2, h3) {
margin-top: 0;
}
/* Easy to override */
h2 {
margin-top: 1rem; /* This wins */
}
/* Perfect for resets and defaults */
:where(ul, ol) {
list-style: none;
padding: 0;
}
Great for utility classes and resets that should be easy to override.
:not() - Negation
/* Exclude specific elements */
p:not(.intro) {
font-size: 1rem;
}
/* Multiple exclusions (modern syntax) */
button:not(.primary, .secondary) {
background: gray;
}
/* Combine with other selectors */
li:not(:last-child) {
border-bottom: 1px solid gray;
}
/* Exclude multiple attributes */
input:not([type="checkbox"], [type="radio"]) {
display: block;
}
:has() - Parent Selector
Modern/* Card with an image */
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
/* Form with errors */
form:has(input:invalid) {
border-color: red;
}
/* Article with headings */
article:has(> h2) {
padding-top: 2rem;
}
/* List item with checkboxes */
li:has(input[type="checkbox"]:checked) {
text-decoration: line-through;
}
/* Section without content */
section:not(:has(*)) {
display: none;
}
Game Changer: :has() enables "parent selectors" - style elements based on their descendants. This was impossible in CSS before!
Pseudo-Elements
Create and style virtual elements.
Selector | Description |
---|---|
::before |
Insert content before element's content |
::after |
Insert content after element's content |
::first-letter |
First letter of text content |
::first-line |
First line of text content |
::selection |
User-selected text |
::placeholder |
Placeholder text in inputs |
::marker |
List item marker (bullet/number) |
::backdrop |
Backdrop of dialog/fullscreen elements |
Practical Examples
/* Decorative icons */
.external-link::after {
content: " ↗";
}
/* Custom bullets */
li::marker {
content: "→ ";
color: blue;
}
/* Drop cap */
p::first-letter {
font-size: 3em;
float: left;
line-height: 1;
margin-right: 0.1em;
}
/* Custom selection color */
::selection {
background: yellow;
color: black;
}
/* Dialog backdrop */
dialog::backdrop {
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
}
Attribute Selectors
Target elements based on their attributes and values.
Selector | Example | Description |
---|---|---|
[attr] |
[disabled] |
Has the attribute |
[attr="value"] |
[type="text"] |
Attribute equals exactly |
[attr~="value"] |
[class~="button"] |
Contains word in space-separated list |
[attr|="value"] |
[lang|="en"] |
Starts with value followed by hyphen |
[attr^="value"] |
[href^="https"] |
Starts with value |
[attr$="value"] |
[href$=".pdf"] |
Ends with value |
[attr*="value"] |
[href*="example"] |
Contains substring |
[attr="value" i] |
[href="test" i] |
Case-insensitive match |
[attr="value" s] |
[href="test" s] |
Case-sensitive match |
Real-World Examples
/* Style external links */
a[href^="http"]::after {
content: " ↗";
}
/* Style PDF links */
a[href$=".pdf"]::before {
content: "📄 ";
}
/* Target email links */
a[href^="mailto:"] {
color: orange;
}
/* Custom file input styling */
input[type="file"] {
/* styles */
}
/* ARIA attributes */
[aria-expanded="true"] .icon {
transform: rotate(180deg);
}
/* Data attributes */
[data-state="loading"] {
pointer-events: none;
opacity: 0.5;
}
/* Language specific styles */
[lang="ar"] {
direction: rtl;
}