Ad – 728×90
🌱 CSS Basics

CSS Selectors – The Complete Guide

A CSS selector is the part of a rule that tells the browser which HTML elements to style. Choosing the right selector is one of the most important skills in CSS — the wrong selector means styles don't apply, or they apply to the wrong elements. In this lesson you will learn every type of CSS selector, how they combine, and how the browser resolves conflicts between them using specificity.

⏱️ 22 min read 🎯 Beginner 📅 Updated 2026 👁️ Lesson 1 of 7

Selector Types – Overview

CSS has six main categories of selectors:

  1. Basic selectors — element, class, ID, universal, attribute
  2. Combinator selectors — descendant, child, adjacent sibling, general sibling
  3. Pseudo-class selectors — target elements in a specific state (:hover, :nth-child)
  4. Pseudo-element selectors — target a part of an element (::before, ::first-line)
  5. Grouping — apply the same styles to multiple selectors
  6. Specificity — determines which rule wins when multiple rules conflict

Basic Selectors

Element (Type) Selector

Targets every instance of an HTML tag. Lowest specificity among named selectors.

CSS
p { color: #333; line-height: 1.7; }
h1 { font-size: 2rem; font-weight: 800; }
a { color: #1572B6; text-decoration: underline; }
button { cursor: pointer; border: none; }

Class Selector

Targets elements with a matching class attribute. Starts with a dot .. Reusable across any element type. The workhorse of modern CSS.

HTML + CSS
<p class="alert">Warning message</p>
<div class="alert">Same style, different element</div>
<p class="alert highlight">Multiple classes on one element</p>
CSS
.alert {
  background-color: #fff3cd;
  border-left: 4px solid #ffc107;
  padding: 12px 16px;
  border-radius: 4px;
}

/* Target element AND class together */
p.alert { font-style: italic; }

/* Target elements with BOTH classes */
.alert.highlight { background-color: #ffeeba; }

ID Selector

Targets the single element with a matching id. Starts with #. IDs must be unique per page. Very high specificity — harder to override.

HTML + CSS
<header id="site-header">...</header>
CSS
#site-header {
  position: sticky;
  top: 0;
  background: #1572B6;
  color: white;
  z-index: 100;
}
💡
Prefer classes over IDs for styling

ID selectors have very high specificity, making them hard to override. Reserve IDs for JavaScript (getElementById) and anchor links (href="#section"). Use classes for all CSS styling.

Universal Selector

Targets every element. Written as *. Used primarily for CSS resets.

CSS
/* The modern CSS reset — used on nearly every real project */
*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

Attribute Selectors

Target elements based on the presence or value of HTML attributes.

CSS – Attribute selector patterns
/* Has the attribute (any value) */
[disabled] { opacity: 0.5; cursor: not-allowed; }

/* Exact value match */
input[type="submit"] { background: #1572B6; color: white; }
input[type="text"]   { border: 1px solid #ccc; }

/* Value starts with */
a[href^="https"] { color: green; }   /* secure links */
a[href^="mailto"] { color: #e63946; }

/* Value ends with */
a[href$=".pdf"] { padding-left: 20px; }  /* PDF links */

/* Value contains */
img[src*="hero"] { width: 100%; }

/* Space-separated word match (like class) */
[class~="btn"] { display: inline-block; }

/* Hyphen-separated (useful for language codes) */
[lang|="en"] { font-family: Georgia, serif; }
Ad – 336×280

Combinator Selectors

Combinators select elements based on their relationship to other elements in the HTML tree.

CSS – All four combinators
/* 1. DESCENDANT (space) — any matching element inside the ancestor */
nav a { color: white; text-decoration: none; }
/* targets ALL <a> tags anywhere inside <nav>, regardless of depth */

/* 2. CHILD (>) — direct children only */
ul > li { list-style: square; }
/* targets <li> that are DIRECT children of <ul>, not nested <li> */

/* 3. ADJACENT SIBLING (+) — immediately following sibling */
h2 + p { font-size: 1.15rem; color: #555; margin-top: 4px; }
/* targets a <p> that immediately follows an <h2> */

/* 4. GENERAL SIBLING (~) — all following siblings */
h2 ~ p { margin-bottom: 14px; }
/* targets ALL <p> tags that follow an <h2> (same parent) */
HTML – Combinator demo
<ul>
  <li>Direct child — styled by ul > li</li>
  <li>Direct child
    <ul>
      <li>NOT a direct child of the outer ul</li>
    </ul>
  </li>
</ul>

<h2>Section Title</h2>
<p>This paragraph is adjacent to h2 — h2 + p applies</p>
<p>This one is a general sibling — h2 ~ p applies</p>
<p>This one too — h2 ~ p still applies</p>

Pseudo-Class Selectors

Pseudo-classes style elements in a specific state. They use a single colon :.

User interaction states

CSS
/* Mouse hover */
button:hover { background-color: #0d5fa0; transform: translateY(-1px); }

/* Currently focused (keyboard navigation / click) */
input:focus { outline: 3px solid #1572B6; outline-offset: 2px; }

/* Being clicked / pressed */
button:active { transform: translateY(1px); }

/* Visited link */
a:visited { color: purple; }

/* Unvisited link */
a:link { color: #1572B6; }

/* Checkbox/radio that is checked */
input[type="checkbox"]:checked + label { font-weight: bold; }

/* Disabled form element */
input:disabled { opacity: 0.5; cursor: not-allowed; }

/* Required form field */
input:required { border-color: #e63946; }

Structural pseudo-classes

CSS
/* First child of its parent */
li:first-child { font-weight: bold; }

/* Last child */
li:last-child { border-bottom: none; }

/* Only child */
p:only-child { text-align: center; }

/* Nth child — count from first */
tr:nth-child(even) { background-color: #f5f5f5; }   /* zebra stripe */
tr:nth-child(odd)  { background-color: white; }
li:nth-child(3)    { color: red; }                    /* exactly the 3rd */
li:nth-child(3n)   { color: red; }                    /* every 3rd */
li:nth-child(3n+1) { color: blue; }                   /* 1st, 4th, 7th… */

/* nth-last-child — count from last */
li:nth-last-child(2) { color: orange; }               /* 2nd from end */

/* First/last of a specific element type */
p:first-of-type { font-size: 1.1rem; }
p:last-of-type  { margin-bottom: 0; }

/* Not a specific selector */
li:not(:last-child) { border-bottom: 1px solid #eee; }
input:not([type="submit"]) { border: 1px solid #ccc; }

/* Has a specific descendant (modern CSS) */
div:has(img) { padding: 0; }                          /* div containing an image */

Pseudo-Element Selectors

Pseudo-elements style a specific part of an element — or insert virtual content. They use a double colon ::.

CSS
/* Insert content BEFORE an element (requires content property) */
.required-field::before {
  content: "* ";
  color: red;
  font-weight: bold;
}

/* Insert content AFTER an element */
.external-link::after {
  content: " ↗";
  font-size: 0.8em;
}

/* Style the first line of a paragraph */
p::first-line {
  font-variant: small-caps;
  font-weight: bold;
}

/* Style the first letter (drop caps) */
p::first-letter {
  font-size: 3rem;
  float: left;
  line-height: 1;
  margin-right: 8px;
  color: #1572B6;
}

/* Style selected text */
::selection {
  background-color: #1572B6;
  color: white;
}

/* Style placeholder text in inputs */
input::placeholder {
  color: #aaa;
  font-style: italic;
}

Specificity – How the Browser Resolves Conflicts

When multiple rules target the same element and the same property, the browser uses specificity to decide which wins. Specificity is calculated as a three-part score: (ID, Class, Element).

CSS – Specificity scores
/* Specificity: (0, 0, 1) — 1 element */
p { color: black; }

/* Specificity: (0, 1, 0) — 1 class */
.intro { color: blue; }

/* Specificity: (0, 1, 1) — 1 class + 1 element */
p.intro { color: green; }

/* Specificity: (1, 0, 0) — 1 ID */
#main { color: red; }

/* Specificity: (1, 1, 0) — 1 ID + 1 class */
#main .intro { color: orange; }

/* Inline style — (1, 0, 0, 0) — beats all selectors */
/* <p style="color: purple;"> */

/* !important — overrides everything (use as last resort) */
p { color: pink !important; }

When specificity scores tie, the rule that appears later in the CSS wins (source order).

⚠️
Avoid specificity wars

Using very high-specificity selectors (long chains, IDs, !important) makes CSS hard to maintain. Keep specificity low and consistent — mostly single classes. If you find yourself using !important frequently, that's a sign the CSS architecture needs restructuring.

📋 Summary

  • Element (p) — targets all of that tag. Lowest specificity.
  • Class (.name) — reusable, the primary tool for styling.
  • ID (#name) — unique, high specificity. Prefer classes for styling.
  • Attribute ([type="text"]) — target by HTML attribute.
  • Combinators: descendant (space), child (>), adjacent (+), general sibling (~).
  • Pseudo-classes (:hover, :nth-child) — state-based targeting.
  • Pseudo-elements (::before, ::first-line) — target parts of elements.
  • Specificity score: (ID, Class, Element). Higher score wins. Ties resolved by source order.

Frequently Asked Questions

What is the difference between a pseudo-class and a pseudo-element? +

A pseudo-class (:) targets an element in a specific state — like being hovered, focused, or the first child of its parent. The element itself exists in the HTML. A pseudo-element (::) targets a part of an element that doesn't exist as a separate HTML element — like the first line of a paragraph, or virtual content inserted before/after an element with ::before / ::after.

Why does my CSS rule not apply even though the selector looks correct? +

Most likely a specificity conflict. Another rule with higher specificity is winning. Open DevTools (F12 → Elements → Computed/Styles panel) and inspect the element. Styles that are overridden appear with a strikethrough. Check which rule is winning and why. The fix is usually to increase the specificity of your rule (add a class) or reduce the specificity of the conflicting rule.

What does :nth-child(2n+1) mean? +

:nth-child(An+B) uses a formula where n starts at 0 and increments by 1. 2n+1 means positions 1, 3, 5, 7… (all odd children) — same as :nth-child(odd). 2n gives even positions. 3n gives every 3rd child. -n+3 gives the first 3 children. It's a powerful pattern for list and table styling.

Can I use !important to fix specificity issues? +

It works but it's a last resort. !important bypasses the cascade entirely and creates a separate "important" layer. The only way to override an !important is with another !important at higher specificity — leading to specificity wars. Use it for utility classes that must always apply (like .sr-only for accessibility) or to override third-party library styles. Never use it to "fix" your own specificity problems — restructure your selectors instead.