Munsif.
AboutExperienceProjectsAchievementsBlogsContact
HomeAboutExperienceProjectsAchievementsBlogsContact
Munsif.

Frontend Developer crafting scalable web applications with modern technologies and clean code practices.

Quick Links

  • About
  • Experience
  • Projects
  • Achievements
  • Blogs
  • Contact

Connect

© 2026 Shaik Munsif. All rights reserved.

Built with Next.js & Tailwind

0%
Welcome back!Continue where you left off
Back to Blogs
CSS

CSS Selectors Deep Dive: From Basic to :has() & :is()

Master CSS selectors from basic to advanced. Covers combinators, attribute selectors, pseudo-classes, modern selectors (:is, :where, :not, :has), specificity scoring, nth-child patterns, pseudo-elements, and practical selector patterns for real-world styling.

Mar 17, 202622 min read
CSSSelectorsSpecificityFrontendDeep Dive
CSS MasteryPart 5 of 10
  • 1. CSS Flexbox Mastery: The Complete Visual Guide to Flexible Layouts
  • 2. CSS Grid Mastery: From Basics to Advanced Layouts
  • 3. CSS Custom Properties (Variables) Mastery: Dynamic Theming & Beyond
  • 4. CSS Animations & Transitions Mastery: From Hover Effects to Keyframe Sequences
  • 5. CSS Selectors Deep Dive: From Basic to :has() & :is()
  • 6. CSS Box Model Mastery: Content, Padding, Border & Margin Explained
  • 7. CSS Responsive Design Mastery: Mobile-First, clamp(), & Container Queries
  • 8. CSS Specificity & Cascade Mastery: How Browsers Resolve Conflicts
  • 9. CSS Modern Layout Techniques: Scroll Snap, Subgrid, Logical Properties & More
  • 10. CSS Pseudo-Elements Mastery: ::before, ::after, Counters & Decorative Techniques

CSS selectors are how you tell the browser which elements to style. They're the targeting system of CSS — from simple element names to complex combinators and modern pseudo-classes like :has() that can select parent elements.

Understanding selectors deeply is essential for writing clean, efficient CSS. In this guide, we'll start with the basics and work our way up to modern selectors that are changing how we write CSS entirely.


1. Basic Selectors

Universal Selector (*)

Matches every element on the page:

css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

Type Selector

Matches all elements of a specific HTML tag:

css
p { color: #334155; }
h1 { font-size: 2rem; }
a { text-decoration: none; }

Class Selector (.)

Matches elements with a specific class attribute:

css
.card { padding: 1rem; }
.btn-primary { background: blue; }

ID Selector (#)

Matches the single element with a specific ID:

css
#hero { height: 100vh; }
#main-nav { position: sticky; }

Best Practice: Avoid using IDs for styling. Use classes instead. IDs have very high specificity and can cause cascading issues. Save IDs for JavaScript hooks and anchor links.


2. Combinator Selectors

Combinators define the relationship between elements.

Descendant Combinator (space)

Matches any element nested inside another, at any depth:

css
.card p { color: #64748b; }
/* Styles ALL paragraphs inside .card, no matter how deep */

Child Combinator (>)

Matches only direct children — not grandchildren:

css
.nav > li { display: inline-block; }
/* Only the direct <li> children of .nav */

Adjacent Sibling (+)

Matches the next sibling immediately following an element:

css
h2 + p { font-size: 1.1rem; }
/* Only the first <p> directly after each <h2> */

General Sibling (~)

Matches all siblings that follow an element:

css
h2 ~ p { color: #475569; }
/* ALL paragraphs after an <h2>, at the same level */
Live Preview
.parent > .child (direct children only)
Parent
Direct Child ✓
Direct Child ✓
Grandchild (not selected by >)

3. Attribute Selectors

Target elements based on their HTML attributes:

SelectorMatches
[href]Elements that have an href attribute
[type="email"]Elements where type exactly equals "email"
[class~="btn"]Elements where class contains the word "btn"
[href^="https"]href starts with "https"
[href$=".pdf"]href ends with ".pdf"
[href*="google"]href contains "google" anywhere
[data-theme="dark" i]Case-insensitive match (the i flag)
css
/* Style external links differently */
a[href^="https://"] {
  color: #3b82f6;
}

/* PDF download links */
a[href$=".pdf"]::after {
  content: " 📄";
}

/* Required form fields */
input[required] {
  border-color: #ef4444;
}

4. Pseudo-Classes: State-Based Selectors

Pseudo-classes select elements based on their state or position.

User Interaction States

Pseudo-classWhen it matches
:hoverMouse is over the element
:focusElement has keyboard focus
:focus-visibleFocus from keyboard (not mouse click)
:activeElement is being pressed/clicked
:visitedLink has been visited
css
.btn:hover { background: #1d4ed8; }
.btn:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
.btn:active { transform: scale(0.98); }

Structural Pseudo-Classes

Pseudo-classWhat it selects
:first-childFirst child of its parent
:last-childLast child of its parent
:nth-child(n)n-th child (1-indexed)
:nth-child(odd)Odd children (1st, 3rd, 5th...)
:nth-child(even)Even children (2nd, 4th, 6th...)
:nth-child(3n)Every 3rd child
:nth-child(3n+1)1st, 4th, 7th... (every 3rd starting from 1)
:only-childElement with no siblings
:emptyElement with no children or text
Live Preview
:first-child (red) | :nth-child(odd) (blue) | :last-child (green)
1
2
3
4
5
6
7
8

Form Pseudo-Classes

Pseudo-classMatches
:checkedChecked checkboxes/radio buttons
:disabledDisabled form elements
:requiredRequired fields
:valid / :invalidValid/invalid form inputs
:placeholder-shownInput currently showing placeholder text

5. Modern Selectors: :is(), :where(), :not(), :has()

These are game-changers for writing cleaner, more powerful CSS.

:is() — Shorter Selectors

is() lets you group selectors without repeating the common part:

css
/* ❌ Without :is() — verbose */
article h1,
article h2,
article h3 {
  color: #1e293b;
}

/* ✅ With :is() — clean */
article :is(h1, h2, h3) {
  color: #1e293b;
}

Key: :is() takes the highest specificity of its arguments.

:where() — Zero Specificity

:where() works exactly like :is(), but with zero specificity. This makes it perfect for default styles that should be easily overridden:

css
/* Default link styles (easy to override) */
:where(a) {
  color: #3b82f6;
  text-decoration: none;
}

/* This wins because regular selectors have higher specificity */
.custom-link {
  color: red; /* Always wins over :where(a) */
}

:not() — Exclusion

not() selects elements that do NOT match a selector:

css
/* Style all buttons EXCEPT disabled ones */
.btn:not(:disabled) {
  cursor: pointer;
}

/* All list items except the last one get a border */
li:not(:last-child) {
  border-bottom: 1px solid #e5e7eb;
}

:has() — The Parent Selector

:has() is the holy grail CSS developers have wanted for decades. It selects an element based on what it contains:

css
/* Card that CONTAINS an image */
.card:has(img) {
  padding: 0;
}

/* Form field with an invalid input inside */
.form-group:has(:invalid) {
  border-color: #ef4444;
}

/* Container that has a checked checkbox */
.option:has(input:checked) {
  background: #dbeafe;
  border-color: #3b82f6;
}
Live Preview
.card:has(.badge) gets a highlighted border
Regular Card
No badge here
NEW
Featured Card
Has a badge → styled!
Regular Card
No badge here

6. Specificity: How Conflicts Are Resolved

When multiple rules target the same element, specificity determines which one wins.

The Scoring System

Selector TypeScoreExample
Inline styles1,0,0,0style="color: red"
ID0,1,0,0#header
Class, attribute, pseudo-class0,0,1,0.card, [type], :hover
Type, pseudo-element0,0,0,1div, ::before
Universal (*), combinators0,0,0,0*, >, +

Calculating Specificity

css
/* (0,0,0,1) — one type selector */
p { color: black; }

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

/* (0,0,1,1) — one class + one type */
p.text { color: green; }

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

/* (0,1,1,1) — ID + class + type */
#intro p.text { color: purple; }
Live Preview
SelectorSpecificityWinner?
p(0,0,0,1)Lowest
.text(0,0,1,0)↑
p.text(0,0,1,1)↑
#intro(0,1,0,0)↑
#intro .text(0,1,1,0)Highest ✓

7. The :nth-child() Deep Dive

Common Patterns

ExpressionSelects
:nth-child(3)The 3rd child
:nth-child(odd)1st, 3rd, 5th...
:nth-child(even)2nd, 4th, 6th...
:nth-child(3n)Every 3rd (3, 6, 9...)
:nth-child(3n+1)1, 4, 7, 10...
:nth-child(-n+3)First 3 children only
:nth-child(n+4)4th child and beyond
:nth-last-child(2)2nd from the end

The of S Syntax (Modern)

The newer of S syntax lets you filter by selector:

css
/* Select the 2nd item that has class .important */
:nth-child(2 of .important) {
  background: yellow;
}

This is different from :nth-child(2).important, which selects the 2nd child only if it also has .important.


8. Pseudo-Elements: Styling Parts of Elements

Pseudo-elements style specific parts of an element. They use double colons (::) to distinguish them from pseudo-classes.

Pseudo-elementWhat it targets
::beforeInserts content before the element
::afterInserts content after the element
::first-lineFirst line of text
::first-letterFirst letter of text
::placeholderPlaceholder text in inputs
::selectionText selected by the user
::markerList item bullets/numbers
css
/* Decorative line before headings */
h2::before {
  content: "";
  display: inline-block;
  width: 4px;
  height: 1em;
  background: #3b82f6;
  margin-right: 0.5rem;
  border-radius: 2px;
  vertical-align: middle;
}

Important: ::before and ::after require the content property to work. Even if you don't want text, use content: "".


9. Practical Selector Patterns

Pattern 1: Zebra Stripe Table

css
tbody tr:nth-child(odd) {
  background: #f8fafc;
}

Pattern 2: Separator Between Items

css
li:not(:last-child) {
  border-bottom: 1px solid #e5e7eb;
}

Pattern 3: Highlight Required Fields

css
input:required:invalid {
  border-color: #ef4444;
}

input:required:valid {
  border-color: #10b981;
}

Pattern 4: Style Based on Sibling Count

css
/* If an item is the only child */
.tag:only-child {
  width: 100%;
}

/* If there are exactly 3 items */
.tag:first-child:nth-last-child(3),
.tag:first-child:nth-last-child(3) ~ .tag {
  width: 33.33%;
}

10. Common Mistakes & Gotchas

Mistake 1: Over-Specific Selectors

css
/* ❌ Too specific — hard to override */
div.container section.main article.post p.text { color: #333; }

/* ✅ Just enough specificity */
.post-text { color: #333; }

Mistake 2: Confusing :nth-child with :nth-of-type

css
/* :nth-child(2) — second child regardless of type */
p:nth-child(2) { } /* Only matches if the 2nd child IS a <p> */

/* :nth-of-type(2) — second <p> among its siblings */
p:nth-of-type(2) { } /* Always matches the 2nd <p> */

Mistake 3: Using IDs for Styling

IDs have specificity of (0,1,0,0) — higher than any combination of classes. This makes them hard to override without !important. Use classes instead.

Mistake 4: Not Using :focus-visible

css
/* ❌ Annoying focus ring on mouse clicks */
button:focus { outline: 2px solid blue; }

/* ✅ Focus ring only for keyboard navigation */
button:focus-visible { outline: 2px solid blue; }

Conclusion

CSS selectors are your precision tools for targeting elements:

  • Basic: Type, class, ID, attribute selectors
  • Combinators: Descendant (space), child (>), sibling (+, ~)
  • Pseudo-classes: State (:hover, :focus), structural (:nth-child, :first-child)
  • Modern: :is() for grouping, :where() for zero-specificity defaults, :not() for exclusion, :has() for parent selection
  • Pseudo-elements: ::before, ::after, ::selection, ::marker

The key to mastering selectors is understanding specificity and choosing the right level of specificity for each situation. Keep things simple, use classes as your primary targeting mechanism, and reach for IDs and complex selectors only when truly needed.

🧠 Test Your Knowledge

Now that you've learned the concepts, let's see if you can apply them! Take this quick quiz to test your understanding.

PreviousCSS Animations & Transitions Mastery: From Hover Effects to Keyframe SequencesNextCSS Box Model Mastery: Content, Padding, Border & Margin Explained

Written by

Shaik Munsif

Read more articles

Found this helpful? Share it with your network!

On this page

0/38
Question 1 of 10Easy
Score: 0/0

What's the difference between .card p (space) and .card > p (>)?