Selector Types – Overview
CSS has six main categories of selectors:
- Basic selectors — element, class, ID, universal, attribute
- Combinator selectors — descendant, child, adjacent sibling, general sibling
- Pseudo-class selectors — target elements in a specific state (
:hover,:nth-child) - Pseudo-element selectors — target a part of an element (
::before,::first-line) - Grouping — apply the same styles to multiple selectors
- 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.
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.
<p class="alert">Warning message</p>
<div class="alert">Same style, different element</div>
<p class="alert highlight">Multiple classes on one element</p>
.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.
<header id="site-header">...</header>
#site-header {
position: sticky;
top: 0;
background: #1572B6;
color: white;
z-index: 100;
}
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.
/* 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.
/* 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; }
Combinator Selectors
Combinators select elements based on their relationship to other elements in the HTML tree.
/* 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) */
<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
/* 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
/* 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 ::.
/* 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).
/* 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).
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
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.
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.
: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.
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.