Note Prefer classes over IDs for styling since IDs have much higher specificity and cannot be reused. IDs are fine for JavaScript hooks and anchor targets.
css selectorclass selectorid selectorelement selectorselect element
Attribute Selectors
syntax
[attr] /* has attribute */
[attr="value"] /* exact match */
[attr^="prefix"] /* starts with */
[attr$="suffix"] /* ends with */
[attr*="part"] /* contains */
Note Use :focus-visible instead of :focus for keyboard-only focus rings. It avoids showing outlines on mouse clicks while keeping them for keyboard navigation.
Note Pseudo-elements use double colons (::) by convention. ::before and ::after require the content property. They are not in the DOM so they cannot be selected by users or read reliably by all screen readers.
before afterpseudo elementadd contentfirst lettertext selection styleplaceholder style
Combinators
syntax
A B /* descendant */
A > B /* direct child */
A + B /* adjacent sibling */
A ~ B /* general sibling */
example
/* All paragraphs inside .content */
.content p {
margin-bottom: 1em;
}
/* Only direct children */
.nav > li {
display: inline-block;
}
/* Paragraph right after an h2 */
h2 + p {
font-size: 1.125em;
}
/* All paragraphs after an h2 */
h2 ~ p {
color: #374151;
}
Note The descendant selector (space) matches at any depth. The child selector (>) matches only direct children. Adjacent (+) matches only the immediately following sibling.
/* Style a card only if it contains an image */
.card:has(img) {
padding: 0;
}
/* Style a form groupwith an invalid input */
.form-group:has(input:invalid) {
border-left: 3px solid #ef4444;
}
/* Style a heading followed by a subtitle */
h2:has(+ .subtitle) {
margin-bottom: 4px;
}
Note :has() is often called the parent selector. It selects an element based on whether it contains or is followed by a matching element. Fully supported in modern browsers as of 2024.
has selectorparent selectorselect parentconditional styleif contains
:is(), :where(), :not()
syntax
:is(selector-list) { } /* takes highest specificity in list */
:where(selector-list) { } /* zero specificity */
:not(selector) { }
example
/* Apply to h1, h2, or h3 inside .article */
.article :is(h1, h2, h3) {
color: #1e293b;
}
/* Reset styles with zero specificity (easy to override) */
:where(.prose) p {
margin-bottom: 1em;
}
/* All inputs except submit buttons */
input:not([type="submit"]) {
border: 1px solid #d1d5db;
}
Note :is() and :where() take a selector list and simplify complex selectors. The key difference: :is() takes the specificity of its most specific argument, while :where() always has zero specificity. :not() can now accept complex selectors.
is selectorwhere selectornot selectorexclude selectorselector listzero specificity
Specificity Rules
syntax
/* Specificity: (ID, Class, Element) */
/* #nav .item a => (1, 1, 1) */
/* .menu a => (0, 1, 1) */
/* a => (0, 0, 1) */
Note When two rules conflict, the one with higher specificity wins. If equal, the last one in source order wins. Avoid !important except for utility classes. Use @layer to manage specificity across libraries.