Design.dev design.dev

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;
}