CSS Selectors — Complete Guide with Examples
CSS selectors are patterns that tell the browser which HTML elements to style. Beyond the basic type, class, and id selectors, CSS provides powerful tools like combinators, attribute selectors, pseudo-classes, and pseudo-elements that let you target elements with surgical precision.
What You’ll Learn
By the end of this tutorial, you’ll understand combinators (descendant, child, sibling) for context-based targeting, attribute selectors for targeting by attribute values, pseudo-classes for state and position-based styling, pseudo-elements for styling parts of elements, and modern selectors like :has(), :is(), and :not().
Where This Fits
flowchart LR
A["CSS Basics"] --> B["**CSS Selectors**"]
B --> C["CSS Cascade & Specificity"]
C --> D["Box Model"]
D --> E["Layout (Flexbox & Grid)"]
style B fill:#f97316,stroke:#c2410c,color:#fff
style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
style E fill:#22c55e,stroke:#16a34a,color:#fff
Combinators — Target Elements by Their Position
Combinators let you style elements based on where they appear in the HTML structure — inside other elements, as direct children, or next to siblings.
| Combinator | Symbol | Example | What it targets |
|---|---|---|---|
| Descendant | (space) | div p | Any <p> anywhere inside a <div> |
| Child | > | div > p | <p> that is a direct child of a <div> |
| Adjacent sibling | + | h2 + p | The first <p> immediately after an <h2> |
| General sibling | ~ | h2 ~ p | All <p> elements that are siblings after an <h2> |
Descendant vs Child — The Most Important Difference
<div class="card">
<h2>Title</h2>
<p>Direct child paragraph</p>
<div class="footer">
<p>Nested paragraph (not a direct child)</p>
</div>
</div>/* Descendant: targets BOTH paragraphs (any <p> inside .card) */
.card p { color: blue; }
/* Child: targets ONLY the direct child paragraph */
.card > p { color: blue; }Why this matters: The descendant selector (space) is broad — it catches everything nested at any depth. The child selector (>) is strict — only immediate children. Use child selector when you want to avoid styling deeply nested elements.
Adjacent and General Siblings
/* Adjacent: only the paragraph IMMEDIATELY after h2 */
h2 + p { font-weight: bold; }
/* General: ALL paragraphs after h2 */
h2 ~ p { margin-left: 1rem; }Real-world use: Style the first paragraph after a heading differently (make it a lead paragraph), or indent all content after a heading.
Attribute Selectors — Target by HTML Attributes
Attribute selectors target elements based on their attributes and attribute values.
/* Has the attribute (regardless of value) */
[disabled] { opacity: 0.5; cursor: not-allowed; }
/* Exact attribute value match */
[type="text"] { border-color: #3498db; }
[type="submit"] { background: #27ae60; color: white; }
/* Starts with (useful for links) */
a[href^="https"] { color: #27ae60; }
a[href^="mailto"]::before { content: "✉ "; }
/* Ends with (useful for file types) */
img[src$=".png"] { border: 2px solid #3498db; }
a[href$=".pdf"]::after { content: " (PDF)"; }
/* Contains substring */
a[href*="example.com"] { font-weight: bold; }Real-world use: Style external links differently with a[href^="https"], add file type icons with a[href$=".pdf"], highlight required form fields with [required].
Pseudo-Classes — Style by State or Position
Pseudo-classes start with a single colon (:) and style elements based on their state or position in the document.
Interactive States (User Action)
/* Link states (order matters: LVHA) */
a:link { color: #3498db; } /* Unvisited link */
a:visited { color: #8e44ad; } /* Visited link */
a:hover { text-decoration: underline; } /* Hover */
a:active { color: #e74c3c; } /* While clicking */
/* Form states */
input:focus { outline: 2px solid #3498db; } /* Focused input */
input:disabled { background: #eee; } /* Disabled */
input:required { border-left: 3px solid #e74c3c; }
/* Validation states */
input:valid { border-color: #27ae60; }
input:invalid { border-color: #e74c3c; }Structural Pseudo-Classes (Position)
/* First and last */
li:first-child { font-weight: bold; }
li:last-child { border: none; }
/* First/last of their type */
p:first-of-type { margin-top: 0; }
p:last-of-type { margin-bottom: 0; }
/* nth-child patterns */
li:nth-child(odd) { background: #f8f9fa; } /* Every odd */
li:nth-child(even) { background: white; } /* Every even */
li:nth-child(3) { color: #e74c3c; } /* Third child only */
li:nth-child(3n) { color: #e74c3c; } /* Every third */
li:nth-child(3n+1) { color: blue; } /* First in each group of 3 */
/* Empty elements */
p:empty { display: none; }The n in nth-child: Think of it as a counter starting from 0. 3n matches 3, 6, 9, 12… 3n+1 matches 1, 4, 7, 10…
Modern Pseudo-Classes
/* :not() — everything EXCEPT what matches */
input:not([type="submit"]) { border: 1px solid #ddd; }
li:not(.active) { opacity: 0.7; }
/* :has() — parent selector (widely supported since 2024) */
/* Style a card that CONTAINS an image */
.card:has(img) { padding: 0; }
/* Style a form that has an invalid input */
form:has(:invalid) { border-color: #e74c3c; }
/* :is() — group selectors with forgiving specificity */
:is(h1, h2, h3) { font-family: 'Georgia', serif; }
:is(h1, h2, h3) .title { color: #2c3e50; }
/* :where() — same as :is() but zero specificity */
:where(header, footer) p { color: #666; }
/* :target — element whose ID matches the URL hash */
:target { background: #ffeaa7; padding: 8px; border-radius: 4px; }Why :has() is exciting: For years, CSS couldn’t style a parent based on its children. :has() changes that. If you want to add a border to a card that contains an image — you can do that now without JavaScript.
Pseudo-Elements — Style Parts of Elements
Pseudo-elements use double colons (::) and let you style parts of an element or insert content before/after it.
/* Insert content before or after an element */
.note::before { content: "💡 "; }
.quote::after { content: " —Source"; }
/* Style the first letter or first line */
p::first-letter { font-size: 2em; float: left; color: #e74c3c; }
p::first-line { font-weight: bold; }
/* Selection highlight */
::selection { background: #3498db; color: white; }
/* Input placeholder text */
input::placeholder { color: #999; font-style: italic; }
/* List marker (bullet/number) */
li::marker { color: #e74c3c; }Tooltip with Pseudo-Elements
[data-tooltip] {
position: relative;
}
[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 4px 8px;
border-radius: 4px;
white-space: nowrap;
font-size: 0.875rem;
opacity: 0;
transition: opacity 0.2s;
pointer-events: none;
}
[data-tooltip]:hover::after {
opacity: 1;
}<button data-tooltip="Save your changes">Save</button>How it works: The tooltip text is stored in a data-tooltip attribute. content: attr(data-tooltip) reads that attribute and displays it. On hover, the tooltip fades in.
Common Selector Mistakes
1. Confusing Descendant and Child
/* ❌ Child selector doesn't match deeply nested elements */
.card > p { /* misses <p> inside <div class="card"><div><p>...</p></div></div> */ }
/* ✅ Descendant matches at any depth */
.card p { /* matches all */ }2. Forgetting the nth-child Formula
/* ❌ nth-child(2n) starts at 0, so 0, 2, 4 — that's even */
li:nth-child(2n) { background: red; } /* This is EVEN, not every 2nd */
/* ✅ Use the right formula */
li:nth-child(even) { background: #f8f9fa; } /* More readable */
li:nth-child(2n) { background: #f8f9fa; } /* Same thing */3. Wrong Pseudo-Class Syntax
/* ❌ Double colon for pseudo-class (wrong) */
p::hover { color: red; }
/* ✅ Single colon for pseudo-class */
p:hover { color: red; }
/* ❌ Single colon for pseudo-element (old syntax, works but deprecated) */
p:before { content: "→ "; }
/* ✅ Double colon for pseudo-element */
p::before { content: "→ "; }4. Overusing !important Instead of Specificity
When a selector isn’t working, beginners add !important. Instead, use a more specific selector:
/* ❌ Wrong: !important is a band-aid */
.card { color: blue !important; }
/* ✅ Correct: use specificity */
body .card { color: blue; }5. Not Considering Accessibility with :hover-Only Effects
/* ❌ Tooltip only shows on hover — mobile users never see it */
[data-tooltip]::after { opacity: 0; }
[data-tooltip]:hover::after { opacity: 1; }
/* ✅ Also show on focus for keyboard users */
[data-tooltip]:focus::after { opacity: 1; }Try It Yourself
See selectors in action:
Common Mistakes Beginners Make
1. Skipping the Fundamentals
Many beginners jump straight to advanced topics without mastering the basics. Take time to understand the core concepts before moving on.
2. Not Practicing Enough
Reading tutorials without writing code leads to shallow understanding. Code along with every example and experiment on your own.
3. Ignoring Error Messages
Error messages tell you exactly what went wrong. Read them carefully — they usually point to the line and type of issue.
4. Copy-Pasting Without Understanding
It’s tempting to copy code from tutorials, but typing it yourself and understanding each line builds real skill.
5. Giving Up Too Early
Every developer hits frustrating bugs. Take breaks, ask for help, and remember that struggling is part of learning.
Practice Questions
Q1: What’s the difference between div p and div > p?div p (descendant) targets any <p> anywhere inside <div>. div > p (child) targets only <p> that are direct children of <div>.
Q2: What does All a[href^="https"] select?<a> elements whose href attribute starts with “https” — useful for styling external links differently from internal ones.
Q3: What’s the difference between :first-child and :first-of-type?:first-child targets the first child of its parent regardless of type. :first-of-type targets the first element of its specific type (first <p>, first <h2>, etc.).
Q4: What does It inserts content before an element. The ::before do and what property controls its content?content property (e.g., content: "→ ") controls what’s inserted.
Q5: What does It chooses a parent element based on its children (e.g., :has() allow you to do that wasn’t previously possible in CSS?.card:has(img) selects cards that contain an image). This was only possible with JavaScript before.
Challenge
Build a styled “Task List” without using any classes or IDs — only type selectors, combinators, and pseudo-classes:
- Alternating row colors
- Checked items have strikethrough
- Hover highlights the row
- The first and last items have special styles
- Empty items are hidden
FAQ
{< faq >}
- What is Css Selectors?
- Css Selectors refers to the core concepts and practices used to build and manage modern web applications. Understanding it is essential for web developers.
- Do I need prior experience to learn Css Selectors?
- Basic familiarity with web development concepts helps, but Css Selectors can be learned step by step even as a beginner.
- How long does it take to learn Css Selectors?
- With consistent practice, you can grasp the fundamentals in a few days to a week. Mastery takes ongoing practice and real-world projects.
- Where can I use Css Selectors in real projects?
- Css Selectors is used in a wide range of applications — from simple websites to complex enterprise systems, depending on the specific tools and technologies involved.
- What are common tools used with Css Selectors?
- The specific tools depend on the technology stack, but version control (Git), package managers, and testing frameworks are commonly used alongside most development topics.
{< /faq >}
FAQ
What’s Next?
Now learn how CSS resolves conflicts between selectors:
What’s Next
Congratulations on completing this Css Selectors tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro