HC

CSS Selectors

HTML & CSS · 9 entries

Element, Class & ID Selectors

syntax
element { }
.class { }
#id { }
example
p {
  line-height: 1.6;
}

.alert {
  padding: 12px 16px;
  border-radius: 6px;
}

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

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.

Attribute Selectors

syntax
[attr]           /* has attribute */
[attr="value"]    /* exact match */
[attr^="prefix"]  /* starts with */
[attr$="suffix"]  /* ends with */
[attr*="part"]    /* contains */
example
a[target="_blank"]::after {
  content: " \2197";
}

input[type="email"] {
  padding-left: 32px;
}

a[href$=".pdf"] {
  color: #c0392b;
}

Note Attribute selectors are case-sensitive by default. Add i before the closing bracket for case-insensitive matching: [attr="value" i].

State Pseudo-Classes

syntax
:hover | :focus | :active | :focus-visible | :focus-within | :disabled | :checked
example
.button:hover {
  background-color: #2563eb;
  transform: translateY(-1px);
}

.button:active {
  transform: translateY(0);
}

input:focus-visible {
  outline: 2px solid #3b82f6;
  outline-offset: 2px;
}

input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

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.

Structural Pseudo-Classes

syntax
:first-child | :last-child | :nth-child(n) | :nth-of-type(n) | :only-child | :empty
example
li:first-child {
  border-top: none;
}

li:last-child {
  border-bottom: none;
}

tr:nth-child(even) {
  background-color: #f8f9fa;
}

.grid-item:nth-child(3n) {
  margin-right: 0;
}

Note nth-child counts all siblings regardless of type. nth-of-type counts only siblings of the same tag. Common patterns: odd, even, 3n, 3n+1.

Pseudo-Elements

syntax
::before | ::after | ::first-line | ::first-letter | ::placeholder | ::selection | ::marker
example
.required-field::after {
  content: " *";
  color: #ef4444;
}

.fancy-quote::first-letter {
  font-size: 3em;
  float: left;
  line-height: 1;
  margin-right: 8px;
}

::selection {
  background-color: #bfdbfe;
  color: #1e3a5f;
}

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.

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.

:has() Relational Selector

syntax
parent:has(child) { }
example
/* Style a card only if it contains an image */
.card:has(img) {
  padding: 0;
}

/* Style a form group with 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.

: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.

Specificity Rules

syntax
/* Specificity: (ID, Class, Element) */
/* #nav .item a  =>  (1, 1, 1) */
/* .menu a       =>  (0, 1, 1) */
/* a             =>  (0, 0, 1) */
example
/* Specificity (0, 0, 1) - easily overridden */
p { color: gray; }

/* Specificity (0, 1, 0) - overrides above */
.highlight { color: blue; }

/* Specificity (1, 0, 0) - overrides above */
#title { color: red; }

/* Inline styles beat all normal selectors */
/* !important overrides everything (avoid it) */

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.