Turning Learners Into Developers
Codekilla
CODEKILLA
CSS 8 min

CSS Selectors Explained — Complete Guide

Read on to explore css selectors explained — complete guide — a beginner-friendly walkthrough by Codekilla.

Rahul Chaudhary Thu Apr 30 2026
What is a CSS Selector?

A CSS selector is a pattern you write to target specific HTML elements on a page so you can style them. Think of selectors as the "address" that tells your browser which elements should receive the styles you define. Without selectors, your CSS rules wouldn't know where to apply — it's like writing a letter without putting an address on the envelope.

Selectors range from dead simple (targeting all paragraphs) to surgical precision (targeting the third list item inside a specific div when you hover over it). Mastering selectors means you can style anything on a page without polluting your HTML with unnecessary classes or IDs.

Why It Matters
  • Cleaner HTML — You can target elements by their structure or state instead of adding classes everywhere, keeping your markup semantic and maintainable.
  • Performance control — Efficient selectors render faster; overly complex ones can slow down page rendering, especially on large DOMs.
  • Dynamic styling — Pseudo-classes like :hover, :focus, and :nth-child() let you create interactive experiences without JavaScript.
  • Component isolation — Combinators help you scope styles to specific component trees, preventing unwanted cascading.
  • Accessibility wins — Attribute selectors let you style form states (input[required]) or ARIA roles, improving UX for assistive technologies.
Basic Selectors — The Foundation

Start here. These are your bread-and-butter selectors that you'll use constantly.

Type selectors target all elements of a specific HTML tag. Class selectors target elements with a specific class attribute. ID selectors target the single element with a matching ID. Universal selectors target everything.

css
/* Type selector — all paragraphs */
p {
  line-height: 1.6;
}

/* Class selector — elements with class="btn" */
.btn {
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

/* ID selector — the element with id="header" */
#header {
  background: #1a1a1a;
}

/* Universal selector — every element */
* {
  box-sizing: border-box;
}
SelectorTargetsSpecificity
pAll <p> elementsLow (0,0,1)
.btnAll elements with class="btn"Medium (0,1,0)
#headerSingle element with id="header"High (1,0,0)
*Every elementLowest (0,0,0)
Combinators — Defining Relationships

Combinators let you select elements based on their relationship to other elements. This is where CSS gets powerful.

Descendant combinator (space) targets all nested children. Child combinator (>) targets only direct children. Adjacent sibling (+) targets the immediate next sibling. General sibling (~) targets all following siblings.

css
/* Descendant — any <a> inside .nav */
.nav a {
  text-decoration: none;
}

/* Direct child — only <li> directly inside .menu */
.menu > li {
  display: inline-block;
}

/* Adjacent sibling — <p> immediately after <h2> */
h2 + p {
  font-weight: 600;
  color: #333;
}

/* General sibling — all <p> after <h2> */
h2 ~ p {
  margin-left: 1rem;
}

The descendant combinator is most common but least performant at scale. The child combinator is more efficient because the browser stops searching after one level.

Attribute Selectors — Targeting by Data

Attribute selectors let you target elements based on their attributes or attribute values. Incredibly useful for forms, links, and custom data attributes.

css
/* Exact match — links to example.com */
a[href="https://example.com"] {
  color: green;
}

/* Contains substring — any href with "codekilla" */
a[href*="codekilla"] {
  font-weight: bold;
}

/* Starts with — external links */
a[href^="https://"] {
  background: url(external-icon.svg) no-repeat right center;
}

/* Ends with — PDF links */
a[href$=".pdf"]::after {
  content: " (PDF)";
}

/* Required inputs */
input[required] {
  border-left: 3px solid orange;
}
SelectorMatches
[attr]Element has the attribute
[attr="value"]Exact value match
[attr*="value"]Contains substring
[attr^="value"]Starts with
[attr$="value"]Ends with
[attr~="value"]Contains word in space-separated list
Pseudo-Classes — State and Position

Pseudo-classes select elements based on their state or position in the DOM. They start with a single colon (:).

State-based pseudo-classes like :hover, :focus, and :checked respond to user interaction. Structural pseudo-classes like :first-child, :nth-child(), and :not() target elements by position or exclusion.

css
/* Hover state */
button:hover {
  background-color: #0056b3;
  transform: translateY(-2px);
}

/* First and last children */
li:first-child {
  border-top: none;
}

li:last-child {
  border-bottom: none;
}

/* Every odd row — zebra striping */
tr:nth-child(odd) {
  background-color: #f9f9f9;
}

/* Exclude a class */
button:not(.disabled) {
  cursor: pointer;
}

/* Checked checkboxes */
input[type="checkbox"]:checked + label {
  font-weight: bold;
  color: green;
}

:nth-child() accepts formulas: (2n) for evens, (3n+1) for every third starting at 1, etc. This is powerful for grids and tables.

Pseudo-Elements — Styling Parts

Pseudo-elements let you style specific parts of an element or inject content. They use double colons (::), though single colon works for legacy support.

css
/* First line of a paragraph */
p::first-line {
  font-variant: small-caps;
}

/* First letter — drop cap */
p::first-letter {
  font-size: 3em;
  float: left;
  line-height: 1;
  margin-right: 0.1em;
}

/* Before and after — injected content */
.quote::before {
  content: """;
  font-size: 2em;
  color: #ccc;
}

.quote::after {
  content: """;
}

/* Placeholder text styling */
input::placeholder {
  color: #999;
  font-style: italic;
}

::before and ::after require the content property, even if it's empty (content: ""). They create inline elements you can position and style.

Specificity and the Cascade

When multiple selectors target the same element, specificity determines which styles win. IDs beat classes, classes beat types, and inline styles beat everything (except !important, which you should avoid).

SelectorSpecificityExample
Inline style1,0,0,0<div style="...">
ID0,1,0,0#header
Class / Attribute / Pseudo-class0,0,1,0.btn, [type="text"], :hover
Type / Pseudo-element0,0,0,1div, ::before
css
/* Specificity: 0,0,0,1 */
p { color: blue; }

/* Specificity: 0,0,1,0 — WINS */
.text { color: red; }

/* Specificity: 0,1,0,0 — WINS */
#intro { color: green; }

/* Specificity: 0,0,2,1 */
p.text.bold { color: purple; }

When specificity ties, the last rule in source order wins. Keeping specificity low makes your CSS more maintainable — favor classes over IDs.

Quick Cheat Sheet
NeedReach for
Target all of a tag typep, div, a
Reusable component styles.class-name
Unique element (sparingly)#id-name
Direct children only.parent > .child
All descendants.ancestor .descendant
Links to external sitesa[href^="https://"]
Every third item:nth-child(3n)
Hover, focus, active states:hover, :focus, :active
Inject content::before, ::after
Style input placeholder::placeholder
Exclude elements:not(.class)
Common Mistakes
  • Over-relying on IDs — IDs have nuclear specificity, making them hard to override. Stick to classes for styling; reserve IDs for JavaScript hooks or page anchors.
  • Chaining too many selectorsdiv.container > ul.menu > li.item > a.link is specific but slow. Flatten where possible: .menu-link.
  • Forgetting specificity math — Writing .btn then wondering why button.btn doesn't override it. Calculate before debugging.
  • Using !important as a fix — It breaks the cascade and makes future changes painful. Fix the specificity instead.
  • Ignoring browser support:has(), :is(), and :where() are modern; always check caniuse.com for production code.
  • Styling :first-child when you meant :first-of-type — If the first child is a different element type, :first-child won't match. Use :first-of-type for type-specific targeting.

💡 Think Like a Programmer: Selectors are your query language for the DOM. Write them like you'd write a database query — specific enough to get what you need, but efficient enough not to slow things down.

// was this useful?
Did this article answer your question?
// CSS · published by Codekilla
// related articles

Keep Reading