CSS Selectors Cheat Sheet for Web Developers
A practical CSS selector reference for targeting elements by type, class, attribute, state, and document relationships, with examples and DOM API notes.
Use a type selector such as button to target elements by tag, class such as .notice to target a reusable class, attribute such as [type="email"] to target markup attributes, a pseudo-class such as :hover to target a condition, and combinators such as > or a space to describe relationships between elements. Combine conditions without a combinator when they must apply to the same element: button.primary[disabled].
This cheat sheet covers the selector forms developers use most, how to choose among them, and how to use selector strings with JavaScript DOM APIs. For the exhaustive, current syntax and compatibility details, see the MDN CSS selectors reference and the W3C Selectors Level 4 Working Draft.
1. Selector anatomy: simple, compound, and complex selectors
A selector is a pattern that matches elements in a document tree. In a CSS rule, the browser applies the declarations to matching elements. Selector patterns are also used by DOM matching and query APIs.
- Simple selector: one condition, such as
.noticeor[hidden]. - Compound selector: multiple simple selectors with no combinator, all applying to one element, such as
button.primary[disabled]. - Complex selector: compounds joined by a combinator, such as
main article p. - Selector list: comma-separated alternatives, such as
h1, h2. Each alternative matches independently.
/* One condition */
.notice { color: #842; }
/* All conditions apply to the same button */
button.primary[disabled] { opacity: 0.5; }
/* A paragraph anywhere inside an article */
article p { line-height: 1.6; }
/* Either heading gets the same declarations */
h1, h2 { font-family: sans-serif; }
2. Basic selectors quick reference
| Selector | What it matches | Example use |
|---|---|---|
* |
Any element | * { box-sizing: border-box; } |
p |
Elements with the p tag |
article p |
.notice |
Elements whose class list contains notice |
.notice { … } |
#main |
The element with ID main |
#main { … } |
[disabled] |
Elements with a disabled attribute |
button[disabled] |
[type="email"] |
Elements whose attribute value matches | input[type="email"] |
h1, h2 |
Elements matching either selector | Share declarations between heading levels |
Use the universal selector sparingly when a narrower target communicates intent better. Class selectors are useful for reusable styling. IDs identify a specific element, but keep them unique in the document. A selector list is useful when separate selector patterns share declarations.
3. Attribute selectors
Attribute selectors target elements by whether an attribute exists or by its value. The operators below distinguish exact matches from token, prefix, suffix, and substring matches.
| Pattern | Meaning | Example |
|---|---|---|
[attr] |
Attribute exists | a[target] |
[attr="value"] |
Exact value | input[type="email"] |
[attr~="value"] |
Whitespace-separated value list contains the token | [class~="notice"] |
[attr|="value"] |
Exact value or value followed by a hyphen | [lang|="en"] |
[attr^="value"] |
Value begins with the string | a[href^="https:"] |
[attr$="value"] |
Value ends with the string | a[href$=".pdf"] |
[attr*="value"] |
Value contains the substring | [data-state*="open"] |
/* Links with a PDF-like URL suffix */
a[href$=".pdf"] { text-decoration-style: dotted; }
/* Add i for ASCII case-insensitive matching */
input[type="EMAIL" i] { outline-color: teal; }
Attribute selectors inspect attribute values; they do not infer application meaning. For example, a custom data-state value is useful only if the page actually sets it. Case behavior depends on the attribute and document language; the optional i flag requests ASCII case-insensitive matching, while s requests case-sensitive matching where applicable.
4. Combinators: target by document relationship
| Combinator | Relationship | Example |
|---|---|---|
| Space | Descendant at any depth | article p matches paragraphs anywhere inside an article. |
> |
Direct child | ul > li matches list items whose parent is that list. |
+ |
Next sibling | h2 + p matches a paragraph immediately after an h2 under the same parent. |
~ |
Following sibling | h2 ~ p matches paragraphs that follow an h2 under the same parent. |
|| |
Column relationship | Column combinator syntax is described by the specification, but MDN’s guide reports no browser support; do not rely on it without checking support. |
Choose the narrowest relationship that reflects the structure you need. A descendant selector can match deeply nested elements; a child selector requires an immediate parent-child relationship. Sibling combinators only relate elements with the same parent.
5. Pseudo-classes: conditions, states, and position
Pseudo-classes select elements based on a condition rather than a literal class in the markup. Common examples include interaction state, form state, and structural position.
| Selector | Typical meaning |
|---|---|
:hover |
Pointer is over an element. |
:focus-visible |
Element is focused and the browser determines a visible focus indicator is appropriate. |
:checked |
Checkbox or radio control is checked. |
:disabled |
Form control is disabled. |
:first-child |
Element is the first element child of its parent. |
:nth-child(2) |
Element is the second element child of its parent. |
:not(.muted) |
Element does not match the argument selector. |
:is(h1, h2) |
Element matches one of the selectors in the argument. |
:where(article, aside) |
Element matches an argument selector; useful for grouping selectors without adding specificity from the wrapper. |
:has(img) |
Element matches when the relative selector condition is met. |
/* A checked control */
input:checked { accent-color: rebeccapurple; }
/* Every second list item */
li:nth-child(2n) { background: #f4f4f4; }
/* An article containing an image */
article:has(img) { border-block-start: 2px solid; }
For structural selectors, confirm whether the rule counts all element siblings or only siblings of a particular type: :nth-child() counts element siblings by position, while :nth-of-type() counts elements of the same type. The full pseudo-class catalog and browser support vary by feature; consult the MDN reference for the selector you plan to use.
6. Pseudo-elements: target an abstract part
Pseudo-elements target a part associated with an element, rather than selecting a separate element from the document tree. They are written with a double colon.
p::first-line { font-weight: 700; }
.notice::before {
content: "";
border-inline-start: 3px solid currentColor;
}
::before and ::after commonly generate presentational content; their content property is needed for generated boxes. They are not a substitute for meaningful document content. Other pseudo-elements target specific parts, such as a text selection or a form control part; check current reference documentation for availability and restrictions.
7. Selector lists and combining selectors
Separate alternatives with commas. Each item in the list is a complete selector pattern. If one invalid selector makes the list invalid, the rule may be discarded; use forgiving selector-list functions such as :is() where appropriate when mixing selectors that may not all be supported.
h1, h2, h3 {
line-height: 1.15;
}
/* Alternatives inside :is(), followed by a relationship */
:is(article, section) > h2 {
margin-block-start: 0;
}
In a compound selector, do not insert a space unless you intend a descendant relationship. .card.featured matches one element with both classes; .card .featured matches a descendant with class featured inside an element with class card.
8. Using selectors in JavaScript
The same selector syntax is accepted by common DOM query methods. querySelector() returns the first match or null; querySelectorAll() returns a static NodeList of all matches. matches() tests one element, and closest() checks that element and then its ancestors.
// Select the first enabled primary button.
const button = document.querySelector("button.primary:not(:disabled)");
// Select all email inputs currently in the document.
const emailInputs = document.querySelectorAll('input[type="email"]');
// Check one element and find its closest article.
if (button?.matches(".primary")) {
const article = button.closest("article");
}
querySelectorAll() returns a static snapshot, so later DOM changes do not update that NodeList. Call it again when you need fresh matches. Invalid selector syntax throws a SyntaxError DOMException. If a value comes from user input, do not concatenate it into a selector without escaping it; use CSS.escape() for identifier fragments.
const idFromData = "item:42";
const element = document.querySelector(`#${CSS.escape(idFromData)}`);
To capture a matching element with ScreenshotNeo, pass the CSS selector as the selector option. This is useful when you want a screenshot of one chart, card, or component instead of the whole page; see the ScreenshotNeo API documentation.
9. Specificity: matching is separate from the cascade
Specificity helps resolve which declaration wins when multiple rules apply to the same element. It does not alter which elements a selector matches. A selector can match correctly and still lose in the cascade due to specificity, source order, origin, importance, or cascade layers. Use the MDN specificity guide for the full cascade model; this selector cheat sheet is not a substitute for that reference.
10. Compatibility and choosing robust selectors
Selector support is feature-specific. The Selectors Level 4 document is a Working Draft, not a final W3C Recommendation, and selector features have different implementation histories. MDN’s selectors guide notes that the column combinator (||) currently has no browser support in that guide. Before using a newer pseudo-class or combinator in production, check current compatibility data for your target browsers and provide a fallback when needed.
- Prefer semantic, stable classes or attributes over selectors tied to incidental nesting.
- Use direct-child and sibling combinators only when that relationship is part of the intended structure.
- Test state selectors with keyboard and pointer interaction as appropriate.
- Keep complex selectors readable; split rules or add a purposeful class when that improves maintainability.
11. Troubleshooting selector mismatches
| Symptom | Likely cause | What to check or change |
|---|---|---|
| No element matches | Typo, wrong tag/class/attribute, or element is not yet in the DOM | Inspect the rendered DOM and test a simpler selector first. |
| Too many elements match | A broad descendant selector or universal selector | Add a class, attribute, or more specific parent relationship. |
| Compound selector does not match | Conditions were expected on different elements, or a space changed the meaning | Compare .card.featured with .card .featured. |
| Child rule misses nested item | > only matches direct children |
Use a descendant space if any depth is intended. |
| Sibling selector misses target | Elements have different parents, or + requires immediate adjacency |
Check shared parent and intervening element nodes; use ~ if a later sibling is intended. |
nth-child() picks an unexpected element |
It counts all element siblings, not just the requested tag | Use nth-of-type() if position among same-type siblings is intended. |
| JavaScript query throws | Malformed selector string or unescaped dynamic identifier | Fix syntax and escape dynamic identifier fragments with CSS.escape(). |
| Style appears overridden | Selector matches, but another declaration wins in the cascade | Inspect computed styles, specificity, source order, importance, and layers. |
| Works in one browser only | Feature support differs | Check compatibility for the exact selector feature and target browser versions. |
| Pseudo-element content is absent | Generated pseudo-element has no applicable content value |
Set content for generated ::before or ::after content. |
12. Performance, reliability, and maintainability
For ordinary stylesheets, choose selectors for clarity and intended matching first. Avoid brittle rules that depend on a long chain of incidental ancestors, because markup changes can break those assumptions. In JavaScript, scope queries to a relevant container when practical, avoid repeating expensive work in tight loops, and query again when a fresh DOM snapshot is needed. Measure a real bottleneck before optimizing selector syntax.
Reliability comes from testing against the rendered DOM, including dynamic states and responsive variants, and checking support for newer features. If a selector is used for automation or element screenshots, make sure the target exists in the loaded page and is unique when uniqueness matters. A wait for a stable target is more robust than assuming a fixed timing delay.
13. Or skip the browser setup
If you are capturing a whole page or a selector-matched element, ScreenshotNeo takes a screenshot with one GET request. It supports a selector for element capture, along with full-page capture, custom CSS and JavaScript, waits, viewport and device options, and PNG, JPEG, WebP, or PDF output. ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo; the API documentation lists its options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed; response headers say which page verdict and billing status applied. Its MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to get started.
14. FAQ
Can I use a CSS selector to select an element by visible text?
CSS selectors do not generally select elements by their rendered text content. Use DOM text inspection or an automation tool with text-based locators when text is the criterion.
Does a selector return one element or many?
In CSS, a rule applies to every matching element. In JavaScript, querySelector() returns the first match, while querySelectorAll() returns all matches in a static NodeList.
Where can I find the complete selector list?
Use the MDN selector index for practical entries and compatibility links, and the Selectors Level 4 draft for specification syntax.


