Toolmingo
Guides8 min read

CSS Selectors Cheat Sheet: Every Selector You Need

Complete CSS selectors reference — basic, combinators, pseudo-classes, pseudo-elements, attribute selectors, and specificity. With examples and a quick reference table.

CSS selectors are the patterns you write to target HTML elements. Knowing all of them — not just class and id — lets you write less HTML and cleaner stylesheets.

Quick reference

The selectors you'll reach for every day.

Selector Example Matches
* * {} Every element
tag p {} All <p> elements
.class .btn {} Elements with class btn
#id #header {} Element with id header
A, B h1, h2 {} Both h1 and h2
A B nav a {} <a> inside <nav>
A > B ul > li {} <li> direct child of <ul>
A + B h2 + p {} <p> immediately after <h2>
A ~ B h2 ~ p {} All <p> after <h2> (same parent)
:hover a:hover {} Element being hovered
:focus input:focus {} Focused element
:nth-child(n) li:nth-child(2) {} Second <li> among siblings
::before p::before {} Generated content before <p>
[attr] [disabled] {} Has a disabled attribute
[attr="val"] [type="text"] {} Attribute equals value

Basic selectors

Universal selector

* {
  box-sizing: border-box;
}

Matches everything. Useful for resets and debugging (* { outline: 1px red }).

Type selector

p { margin-bottom: 1rem; }
a { color: #0070f3; }
button { cursor: pointer; }

Matches all elements of that tag. Lowest specificity besides *.

Class selector

.card { border-radius: 8px; }
.btn.primary { background: #0070f3; }   /* both classes */

Most common selector. Multiple classes on one element are matched with chained dots.

ID selector

#main-nav { position: sticky; top: 0; }

Matches one element per page. High specificity — avoid for styling, prefer for JS anchors.

Selector list (grouping)

h1,
h2,
h3 {
  font-family: 'Inter', sans-serif;
}

Applies the same rules to multiple selectors. One invalid selector invalidates the whole list in older browsers.


Combinators

Descendant combinator (space)

nav a { text-decoration: none; }    /* any <a> inside <nav> */
.card p { color: #666; }            /* any <p> inside .card */

Matches nested elements at any depth.

Child combinator (>)

ul > li { list-style: disc; }       /* direct children only */
.dropdown > .menu { display: none; }

Matches only direct children, not grandchildren.

Adjacent sibling (+)

h2 + p { margin-top: 0; }          /* first <p> after any <h2> */
label + input { border-color: blue; }

Matches the element immediately after another (same parent).

General sibling (~)

h2 ~ p { color: #444; }            /* all <p> after a <h2> */
.toggle:checked ~ .panel { display: block; }

Matches all later siblings, not just the first one. Useful for CSS-only toggles.


Pseudo-classes

Pseudo-classes select elements based on state or position.

User action states

a:hover  { color: #0057b8; }
a:focus  { outline: 2px solid #0070f3; }
a:active { color: #003d80; }
a:visited { color: purple; }

/* keyboard focus only (not mouse click) */
button:focus-visible { outline: 2px solid #0070f3; }

Order matters for <a>: LoVe HAte:link, :visited, :hover, :active.

Form states

input:disabled        { opacity: 0.5; }
input:enabled         { border: 1px solid #ccc; }
input:checked         { accent-color: #0070f3; }
input:required        { border-color: #e44; }
input:optional        { border-color: #ccc; }
input:valid           { border-color: #2a2; }
input:invalid         { border-color: #e44; }
input:placeholder-shown { color: #999; }
input:focus-within    { }   /* parent when child is focused */

Structural pseudo-classes

/* position among siblings */
li:first-child    { font-weight: bold; }
li:last-child     { border-bottom: none; }
li:nth-child(2)   { background: #f5f5f5; }       /* 2nd */
li:nth-child(odd) { background: #fafafa; }        /* 1, 3, 5… */
li:nth-child(even){ background: #f0f0f0; }        /* 2, 4, 6… */
li:nth-child(3n)  { color: red; }                 /* every 3rd */
li:nth-child(3n+1){ color: blue; }                /* 1, 4, 7… */

/* position among same-type siblings */
p:first-of-type   { font-size: 1.1em; }
p:last-of-type    { margin-bottom: 0; }
p:nth-of-type(2)  { }

/* only child */
p:only-child      { }                             /* no siblings at all */
p:only-of-type    { }                             /* no <p> siblings */

/* empty element */
div:empty         { display: none; }

Negation and matching

/* not */
:not(.disabled) { cursor: pointer; }
:not(p):not(div) { color: #333; }    /* chain multiple */
:not(.a, .b)    { }                  /* list in :not() — modern browsers */

/* matches any in list (like grouping, but with specificity of highest) */
:is(h1, h2, h3) a { color: inherit; }

/* same as :is() but zero specificity */
:where(h1, h2, h3) a { color: inherit; }

/* has (parent selector) */
.card:has(img)    { padding: 0; }    /* .card that contains an <img> */
form:has(:invalid){ border: 1px solid red; }

:has() is the long-awaited parent selector — supported in all modern browsers as of 2024.

Language and direction

p:lang(fr)     { quotes: "«" "»"; }
:dir(rtl)      { text-align: right; }

Pseudo-elements

Pseudo-elements create virtual elements or target specific parts.

p::before {
  content: "→ ";
  color: #0070f3;
}

p::after {
  content: " ←";
}

/* first letter / first line */
p::first-letter { font-size: 2em; float: left; }
p::first-line   { font-weight: bold; }

/* text selection */
::selection {
  background: #0070f3;
  color: #fff;
}

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

/* scroll marker (modern) */
::scroll-marker { content: "•"; }

Always use :: (double colon) for pseudo-elements. :before (single colon) still works but is the old CSS2 syntax.

content is required on ::before / ::after. Use content: "" if you only need an empty box.


Attribute selectors

/* has attribute */
[disabled]            { opacity: 0.4; }
[data-active]         { background: yellow; }

/* equals */
[type="email"]        { }
[rel="noopener"]      { }

/* contains word (space-separated) */
[class~="btn"]        { }    /* matches class="btn primary" */

/* starts with */
[href^="https"]       { }    /* secure links */
[href^="mailto"]      { }

/* ends with */
[href$=".pdf"]        { }    /* PDF links */
[src$=".svg"]         { }

/* contains substring */
[href*="example"]     { }    /* URL contains "example" */

/* hyphen-separated starts with */
[lang|="en"]          { }    /* en, en-US, en-GB */

/* case-insensitive flag */
[href$=".PDF" i]      { }    /* matches .pdf and .PDF */

Specificity

When two selectors match the same element, the more specific one wins.

Specificity scoring

Selector Score (a, b, c)
Inline style (1, 0, 0, 0)
#id (0, 1, 0, 0)
.class, :pseudo-class, [attr] (0, 0, 1, 0)
tag, ::pseudo-element (0, 0, 0, 1)
*, combinators (0, 0, 0, 0)

Scores are compared left to right. One ID beats any number of classes.

/* specificity (0,0,1,0) */
.btn { color: red; }

/* specificity (0,0,1,1) — wins */
button.btn { color: blue; }

/* specificity (0,1,0,0) — wins regardless of class count */
#submit { color: green; }

:is(), :not(), :has() specificity

:is() and :has() take the specificity of their most specific argument:

/* same specificity as #id (0,1,0,0) */
:is(#id, .class) { }

/* always zero specificity — use for base styles */
:where(.card, .panel) p { }

!important

.text { color: blue !important; }   /* beats everything except another !important */

Avoid !important in component CSS. Reserve it for utility overrides (!important on .sr-only, .hidden, etc.) where it's intentional.


Practical patterns

Zebra striping

tr:nth-child(odd)  { background: #f9f9f9; }
tr:nth-child(even) { background: #fff; }

Style last item differently

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

Cards that contain images

.card:has(img) {
  padding: 0;
}
.card:not(:has(img)) {
  padding: 1.5rem;
}

CSS-only accordion

.toggle:checked + .content { display: block; }
.toggle:not(:checked) + .content { display: none; }

Focus ring only for keyboard users

button:focus { outline: none; }
button:focus-visible { outline: 2px solid #0070f3; }

External links

a[href^="http"]:not([href*="yourdomain.com"])::after {
  content: " ↗";
  font-size: 0.75em;
}

Common mistakes

1. IDs for everything Using #id forces you into specificity wars. Prefer classes.

2. Forgetting LoVe HAte order a:visited after a:hover means hover styles never apply to visited links. Use :link, :visited, :hover, :active in that order.

3. Descendant when you need child nav a matches <a> nested anywhere — including dropdown sub-menus. Use nav > a if you only want top-level links.

4. ::before without content ::before and ::after require content. Without it, the pseudo-element doesn't render. Use content: "" for an empty box.

5. :nth-child counts all siblings, not just same-type p:nth-child(2) matches a <p> only if it's the second child of its parent, regardless of type. Use p:nth-of-type(2) to count only <p> siblings.

6. Not accounting for :has() fallback :has() is baseline-supported since 2024 but needs a fallback for older browsers. Add a no-JS/no-:has() default style first, then enhance.


FAQ

What is the difference between :nth-child and :nth-of-type? :nth-child(2) selects the second child element regardless of its tag. :nth-of-type(2) selects the second element of a specific tag type. p:nth-child(2) only matches if the second child happens to be a <p>.

What is CSS specificity and how do I calculate it? Specificity is a score (a, b, c): a counts ID selectors, b counts class/attribute/pseudo-class selectors, c counts tag/pseudo-element selectors. Compare left to right. The higher score wins. Inline styles beat everything except !important.

How does :is() differ from a selector list? A regular selector list (,) fails entirely if one selector is invalid. :is() is forgiving — invalid selectors are ignored. Also, :is() inherits the specificity of its most specific argument.

What does :where() do that :is() doesn't? :where() has zero specificity, making it easy to override. Use :where() for default/reset styles you expect developers to override, and :is() when you want the selector's natural specificity.

Is :has() (parent selector) safe to use? Yes, as of 2024 it has baseline-wide support (Chrome 105+, Firefox 121+, Safari 15.4+). Add graceful degradation for older browser support if needed.

Why does ::before need content: ""? The CSS spec requires content for generated content. Even an empty string content: "" tells the browser to render the pseudo-element box. Without it, nothing renders — not even width/height.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools