CSS Pseudo-Classes: Style Elements Based on State

Pseudo-classes like :hover, :focus, and :nth-child let you style elements based on their state or position — no JavaScript needed. Learn the syntax, the most useful ones, and real patterns you'll actually ship.

Published August 5, 20269 min readBy ACY Partner Indonesia
CSS pseudo-classes — a:hover changing link color on interaction
300 × 250Ad Space AvailablePlace your ad here

Up to now, every selector you’ve written targets an element by what it is — a tag, a class, an id. But elements aren’t static. A link can be hovered, an input can be focused, a checkbox can be checked, and a list item can be the first or the last of its kind. Wouldn’t it be handy to style an element based on what’s happening to it, or where it sits among its siblings?

That’s exactly what pseudo-classes do. They’re keywords you bolt onto a selector to target an element in a particular state or position — when it’s hovered, when it’s the third child, when its checkbox is checked — without writing a single line of JavaScript. They’re one of the things that make CSS feel alive, and once they click, your stylesheets get a lot more powerful.

What is a pseudo-class?

A pseudo-class is a keyword that starts with a single colon (:) and gets attached to a selector. It narrows the selection to elements that are in a certain state or match a certain condition:

a:hover {
  color: orange;
}

Read that out loud: “an <a> element, while it’s being hovered, gets orange text.” The a part is the normal selector you already know. The :hover part is the pseudo-class — it’s the condition. Together they say “only apply this when the mouse is over the link.”

The key word is pseudo. There’s no :hover element in your HTML; you never write <a:hover>. The browser figures out the state on its own and applies the rule whenever the condition is true. You’re describing a situation, and CSS reacts to it.

One colon for pseudo-classes, two for pseudo-elements

Pseudo-classes use a single colon: :hover, :focus, :first-child. Their close cousins, pseudo-elements (like ::before and ::after), use two colons. They’re related but different — pseudo-classes target an existing element in a state, while pseudo-elements create a sub-part of an element to style. Keep the colon count straight and you’ll keep the two ideas straight too.

Because a pseudo-class is just part of a selector, it builds on everything you already learned about CSS selectors. You can stack it onto a class, an id, or a descendant selector — anywhere a selector goes.

Interaction states: the everyday ones

The pseudo-classes you’ll reach for most often respond to user interaction. They’re what make buttons and links feel responsive instead of dead.

:hover

:hover kicks in when the user’s pointer is over the element. It’s the classic “this is clickable” feedback:

.button {
  background: #00B8E6;
  color: white;
}

.button:hover {
  background: #0095bb;
}

The button starts cyan, and the moment the mouse moves over it, it darkens slightly. That tiny shift tells the user “yes, this responds to you.” Almost every interactive element on a polished site has a hover style.

:focus

:focus applies when an element is focused — selected via keyboard (tabbing to it) or by clicking into an input. It’s essential for accessibility, because keyboard users rely on a visible focus indicator to know where they are on the page:

input:focus {
  outline: 2px solid #00B8E6;
  border-color: #00B8E6;
}

Don't remove focus outlines without a replacement

A common mistake is writing :focus { outline: none; } to kill the default outline because it looks “ugly.” Don’t — that strands keyboard and screen-reader users with no visual cue of where focus is. If you dislike the default outline, replace it with your own visible style (a custom outline, a glowing border, a background change). Never just erase it.

:active

:active is the brief state while an element is being clicked — between pressing the mouse button down and letting it go. It gives that satisfying “pressed” feel:

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

That nudges the button down by one pixel on click, mimicking a real button being pushed in. Small, but it makes the interface feel tactile.

:visited

:visited targets links the user has already been to. Browsers traditionally show visited links in a different color so you can tell what you’ve already clicked:

a:visited {
  color: #8b5cf6;
}

The LVHA order matters for links

When you style all four link states, declare them in this order: :link, :visited, :hover, :active — remembered as LVHA (“LoVe HAte”). Because all four can apply at once and they have equal specificity, the last one wins. Put them out of order and, say, :hover might never show because :visited overrides it. Stick to LVHA and they all work as expected.

Structural pseudo-classes: targeting by position

The second big family targets elements by where they sit among their siblings — first, last, even, odd, the nth one. These are incredibly handy for styling lists, tables, and grids without adding extra classes to every item.

:first-child and :last-child

These match an element that’s the first (or last) child inside its parent:

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

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

The first list item gets bold text; the last one drops its bottom border (handy for removing the trailing divider in a list). You didn’t have to add a first or last class anywhere — CSS works out the position for you.

:nth-child()

:nth-child() is the powerhouse of the family. It takes a number, a keyword, or a formula and matches elements at those positions. The simplest forms are odd and even:

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

That’s the classic “zebra-striped” table — every even row gets a light gray background, making long tables far easier to read. You can also pass a plain number to hit one specific element:

li:nth-child(3) {
  color: red;
}

And the real magic is the an + b formula, where n counts up from 0. For example, 3n matches every third element (the 3rd, 6th, 9th…):

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

This pattern is perfect for grids — say you’ve got a 3-column grid and want to remove the right margin from every third item so the rows line up cleanly. The formula does it without touching the HTML.

Reading an+b formulas

In :nth-child(an + b), plug in n = 0, 1, 2, 3… and read off the results. 2n gives 0, 2, 4, 6 → every even item. 2n + 1 gives 1, 3, 5, 7 → every odd item. 3n + 1 gives 1, 4, 7, 10. Once you see it as “step by a, starting offset of b,” the formulas stop looking cryptic.

:nth-of-type()

:nth-child() counts all children regardless of tag. :nth-of-type() counts only siblings of the same element type. If your parent mixes <p> and <img> tags, p:nth-of-type(2) finds the second paragraph specifically — ignoring any images in between, which :nth-child would have counted.

Form pseudo-classes: reacting to input state

Forms have their own set of pseudo-classes that respond to the state of inputs — checked, disabled, valid, and more. These let you build rich, responsive forms with pure CSS.

:checked

:checked matches a checkbox or radio button that’s currently selected. Pair it with a sibling selector and you can react to a toggle without JavaScript:

input:checked {
  accent-color: #00B8E6;
}

This is the backbone of CSS-only toggles, custom checkboxes, and “click to reveal” widgets — the checkbox holds the state, and CSS styles based on whether it’s checked.

:disabled and :enabled

:disabled targets form controls that are turned off (with the disabled attribute), and :enabled targets the ones that are active:

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

Greying out a disabled button and showing a “not-allowed” cursor is a clear, standard way to signal “you can’t use this right now.”

:required, :valid, and :invalid

These react to HTML form validation. :required matches inputs marked required, while :valid and :invalid reflect whether the current value passes the field’s validation rules:

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

input:valid {
  border-color: #22c55e;
}

As the user types an email, the border turns red while it’s malformed and green once it’s a valid address — instant feedback, no script involved. (Be a little careful: :invalid applies even before the user has typed anything, so for a gentler experience you’d often combine it with :focus or only show errors after interaction.)

A couple of handy extras

A few more pseudo-classes are worth knowing because they save real effort.

:not() is the negation pseudo-class — it matches everything except what’s inside it. Want every button except the primary one?

.button:not(.primary) {
  background: #e5e7eb;
}

:first-of-type and :last-of-type work like :first-child/:last-child but restrict to a specific element type, mirroring the nth-of-type idea.

And :root matches the document’s root element (the <html> tag), which is the conventional home for declaring CSS custom properties:

:root {
  --brand: #00B8E6;
}

You’ll see :root constantly once you start working with CSS variables — it’s where global values live.

Chaining pseudo-classes

Pseudo-classes aren’t mutually exclusive — you can combine them with each other and with regular selectors to get very precise. Each one you add narrows the match further:

.menu li:first-child a:hover {
  color: #00B8E6;
}

That reads as: “the <a> inside the first <li> of .menu, but only while it’s hovered.” You’re stacking a structural pseudo-class (:first-child) and an interaction one (:hover) in the same rule. This composability is what makes pseudo-classes so expressive — you describe an exact element in an exact state, and CSS handles the rest.

Wrapping up

Pseudo-classes let you style elements based on state and position, no JavaScript required — and that’s a genuine superpower for building interfaces that feel responsive. Here’s the core to carry forward:

  • A pseudo-class uses a single colon and targets an element in a state or position: a:hover, li:first-child.
  • Interaction states:hover, :focus, :active, :visited — make links and buttons feel alive. Always keep a visible :focus style for accessibility, and order link states as LVHA.
  • Structural pseudo-classes — :first-child, :last-child, :nth-child(), :nth-of-type() — target elements by position. :nth-child() with an an + b formula handles zebra stripes, grids, and patterns.
  • Form pseudo-classes — :checked, :disabled, :required, :valid, :invalid — react to input state for rich, script-free forms.
  • Handy extras: :not() to exclude, :root for global variables.
  • You can chain pseudo-classes together for precise, expressive rules.

Pseudo-classes target existing elements in a state. Their close relatives, pseudo-elements, go one step further: they let you create and style brand-new sub-parts of an element — like inserting content before or after it, or styling just the first line of a paragraph. That’s the natural next step, and it’s where we’re headed next.

Tags:cssfrontendpseudo-classesselectorsintermediate
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

Frontend best practices overview cover
Frontend / Fundamentals

Frontend Best Practices: An Overview

A friendly map of what good frontend really means — semantic HTML, clean CSS, unobtrusive JavaScript, responsive layouts, performance, and progressive enhancement, with pointers to go deeper.

Sep 20, 20268 min read
A Frontend Developer Roadmap cover with the path HTML to CSS to JavaScript
Frontend / Fundamentals

A Frontend Developer Roadmap: What to Learn, and in What Order

A calm, step-by-step learning path for beginners: HTML, CSS, JavaScript, developer tools, responsive and accessible design, a framework, TypeScript, and deployment, with the reasoning behind each step.

Sep 20, 202611 min read
Title card reading Styling and Interactivity over a dark blue ACY Partner background
Frontend / Fundamentals

How Styling and Interactivity Work

A beginner-friendly look at how CSS turns plain HTML into a designed layout, and how JavaScript adds behavior — the structure, style, behavior mental model for building web pages.

Sep 20, 20269 min read
Three front-end layers combining on one web page
Frontend / Fundamentals

How HTML, CSS, and JavaScript Work Together

A hands-on walkthrough for beginners: build one small button, give it structure with HTML, looks with CSS, and behavior with JavaScript, and watch the three layers cooperate on a real page.

Sep 20, 20268 min read
The Frontend Developer Toolkit cover with editor, browser, and CLI motifs
Frontend / Fundamentals

The Frontend Developer Toolkit

A friendly tour of the everyday tools a frontend developer relies on — code editor, browser and DevTools, terminal, version control, package managers, and a dev server — and what each one is actually for.

Sep 20, 202610 min read
Cover illustration for What Frontend Developers Do
Frontend / Fundamentals

What Frontend Developers Do: A Beginner's Guide to the Role

A clear, beginner-friendly look at what frontend developers actually do every day: turning designs into working interfaces, building reusable UI, and caring about responsiveness, accessibility, and speed.

Sep 20, 202610 min read