Pseudo Classes in CSS

Pseudo classes in CSS select elements based on state, position, user interaction, or relationships that are not always visible from the element name alone. They are written with a single colon, such as :hover, :focus, :first-child, and :checked.

Pseudo classes make CSS more dynamic without JavaScript. They can style links when hovered, inputs when focused, list items based on their position, form controls based on validity, and parent elements based on what they contain.

Pseudo Class Syntax

A pseudo class is attached to a selector with a colon. It narrows the selector to a specific state or condition.

a:hover {
  color: #2563eb;
}

input:focus {
  outline: 2px solid #2563eb;
}

The first rule styles a link when the user hovers it. The second rule styles an input when it receives focus.

User Action Pseudo Classes

User action pseudo classes respond to interaction. The most common are :hover, :focus, :active, and :focus-visible.

.button:hover {
  background: #1d4ed8;
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 3px solid #93c5fd;
}

Hover is useful for pointer devices, active is useful during pressing, and focus-visible is important for keyboard accessibility. Do not remove focus outlines unless you replace them with a clear alternative.

Link Pseudo Classes

Links have pseudo classes such as :link, :visited, :hover, and :active. They can style links based on whether they have been visited or interacted with.

a:visited {
  color: #7c3aed;
}

Browsers limit what can be styled on visited links for privacy reasons. Color changes are common, but layout-affecting changes are restricted.

Structural Pseudo Classes

Structural pseudo classes select elements based on their position in the document tree. They are useful for lists, tables, cards, and repeated content.

li:first-child {
  font-weight: 700;
}

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

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

These rules style the first list item, remove the last border, and zebra-stripe table rows. No extra classes are needed in the HTML.

nth-child

The :nth-child() pseudo class is powerful because it accepts formulas, keywords, and numbers.

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

.item:nth-child(odd) {
  background: #f3f4f6;
}

The formula 3n selects every third item. The keyword odd selects odd-numbered children. This is useful for repeated layouts and data display.

Form State Pseudo Classes

Forms have many useful pseudo classes, including :checked, :disabled, :required, :optional, :valid, and :invalid.

input:invalid {
  border-color: #dc2626;
}

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

These states help users understand form behavior. Invalid fields can be highlighted, disabled controls can look inactive, and checked controls can trigger visual changes.

:not()

The :not() pseudo class excludes elements that match a selector.

.menu a:not(.active) {
  color: #4b5563;
}

This styles menu links that are not active. It can reduce extra classes, but complex negation can become hard to read.

:is() and :where()

The :is() and :where() pseudo classes group selectors. The difference is specificity. :is() keeps specificity from its most specific argument, while :where() has zero specificity.

:is(h1, h2, h3) a {
  text-decoration-thickness: 0.08em;
}

:where(article, aside) p {
  margin-block: 1rem;
}

Use :is() when grouping normal selectors. Use :where() when writing low-specificity defaults that should be easy to override.

:has()

The :has() pseudo class selects an element based on what it contains. It is often called a parent selector because it can style a parent when a child matches.

.field:has(input:invalid) {
  border-color: #dc2626;
}

.card:has(img) {
  padding-top: 0;
}

This opens many layout and form styling possibilities that previously required JavaScript or extra classes. Use it thoughtfully because complex relational selectors can become expensive and hard to maintain.

:focus-within

The :focus-within pseudo class selects an element when it or any descendant has focus.

.search-box:focus-within {
  box-shadow: 0 0 0 3px #bfdbfe;
}

This is useful for styling form groups, search boxes, dropdown wrappers, and interactive cards when an inner control is focused.

Specificity of Pseudo Classes

Most pseudo classes have the same specificity weight as a class selector. This means a:hover is stronger than a plain a selector. Specificity matters when pseudo class styles do not apply as expected.

The special grouping pseudo classes have special rules. :where() intentionally adds zero specificity, which is useful for reset styles and base defaults.

:root

The :root pseudo class selects the root element of the document. In HTML, that is usually the html element. It is commonly used for global CSS variables.

:root {
  --brand: #2563eb;
  --radius: 8px;
}

Using :root for custom properties makes them available throughout the document. Components can then use var() to reuse the values.

:empty

The :empty pseudo class selects elements with no children. It can be useful for hiding empty placeholders or styling empty states.

.notice:empty {
  display: none;
}

Be careful: whitespace text nodes can affect whether an element is considered empty in some situations. Test the actual HTML output from your template or CMS.

:target

The :target pseudo class selects the element that matches the current URL fragment. If the URL ends with #faq, the element with id="faq" becomes the target.

section:target {
  outline: 3px solid #f59e0b;
  scroll-margin-top: 5rem;
}

This can highlight linked sections in documentation or long tutorial pages. It helps users see where a jump link has taken them.

:placeholder-shown

The :placeholder-shown pseudo class selects an input or textarea while placeholder text is visible. It is useful for form styling and floating label patterns.

input:placeholder-shown {
  border-color: #d1d5db;
}

Once the user types a value, the placeholder is no longer shown and the selector stops matching.

:lang()

The :lang() pseudo class selects content based on language. This is useful when typography, quotes, spacing, or font choices need to change by language.

p:lang(hi) {
  line-height: 1.8;
}

Language-aware styling can improve readability for multilingual websites. It is more meaningful than adding random utility classes for every language-specific rule.

Pseudo Classes and Accessibility

Pseudo classes should support accessibility, not work against it. :focus-visible helps keyboard users see where they are. :hover should not be the only way to reveal critical content because touch users may not have hover.

For interactive controls, style hover, focus, active, disabled, and invalid states deliberately. Users should understand what can be clicked, what is currently focused, and what needs attention.

LVHA Link Order

When styling link states, the classic order is link, visited, hover, active. This prevents one state from accidentally overriding another in normal cascade situations.

a:link { color: #2563eb; }
a:visited { color: #7c3aed; }
a:hover { color: #1d4ed8; }
a:active { color: #1e40af; }

Modern CSS can be more component-based, but understanding this order helps when link styles behave unexpectedly.

:nth-of-type and :first-of-type

The :nth-of-type() pseudo class selects elements based on their type among siblings. This is different from :nth-child(), which counts all child elements.

p:first-of-type {
  font-size: 1.125rem;
}

p:nth-of-type(2) {
  color: #4b5563;
}

This is useful in article content where paragraphs may be mixed with headings, images, code blocks, and tables. Type-based selection can be more predictable than child counting.

:only-child and :only-of-type

The :only-child pseudo class selects an element that is the only child of its parent. The :only-of-type pseudo class selects an element that is the only one of its element type among siblings.

.card:only-child {
  max-width: 520px;
}

p:only-of-type {
  margin-block: 0;
}

These selectors can help components adjust when there is only one item, but they should be used carefully because content changes can alter whether they match.

Combining Pseudo Classes

Pseudo classes can be combined to target very specific states. This is powerful, but it should stay readable.

.button:not(:disabled):hover {
  background: #1d4ed8;
}

.field:focus-within:has(input:invalid) {
  border-color: #dc2626;
}

The first rule applies hover only to enabled buttons. The second styles a field group when it contains an invalid input and focus is somewhere inside the group.

Form Example with Pseudo Classes

A form can use pseudo classes to provide clear visual feedback without extra JavaScript.

input:required {
  border-left: 4px solid #2563eb;
}

input:valid {
  border-color: #16a34a;
}

input:invalid:not(:placeholder-shown) {
  border-color: #dc2626;
}

This keeps required fields visible, shows valid state, and avoids showing an error color before the user has typed anything. Real forms should still include accessible text messages for errors.

Pseudo Classes in Components

Pseudo classes are especially useful in reusable components. A card can respond to hover, a form field can respond to focus-within, and a navigation item can respond to active state without adding many extra classes.

.card:hover,
.card:focus-within {
  box-shadow: 0 12px 30px rgb(15 23 42 / 0.12);
}

Using both hover and focus-within makes the interaction work better for mouse users and keyboard users. This is a better pattern than styling hover alone.

Pseudo Classes and JavaScript

Pseudo classes can replace small JavaScript interactions, but they do not replace application state. Use CSS for natural states like focus, hover, checked, and invalid. Use JavaScript when the state comes from data, routing, authentication, or complex interaction.

This keeps CSS responsible for styling states and JavaScript responsible for behavior that CSS cannot know.

That boundary matters.

Common Pseudo Class Mistakes

  • Using hover-only behavior for important mobile interactions.
  • Removing focus outlines without an accessible replacement.
  • Confusing pseudo classes with pseudo elements.
  • Writing overly complex nth-child formulas.
  • Using :not() in a way that makes selectors hard to understand.
  • Forgetting specificity when pseudo class styles are overridden.

Pseudo Classes in CSS FAQ

What is a pseudo class in CSS?

A pseudo class selects an element based on state, position, interaction, or relationship.

What is the difference between pseudo class and pseudo element?

A pseudo class selects a state or condition, while a pseudo element styles a specific part of an element.

Is hover a pseudo class?

Yes. :hover is a user action pseudo class.

What does :has() do in CSS?

:has() selects an element when it contains or relates to something that matches the selector inside it.


Continue learning CSS in order
Follow the topic sequence with the previous and next lesson.