# Pseudo-Classes: :hover, :focus-visible, :nth-child() and More

Pseudo-classes select elements by their state, such as hovered, focused, checked or disabled, or by their position, such as first, last or every other, and :not() leaves some out.

Apprentice · CSS · Updated November 1, 2026

## After this lesson you can

- Style an element's state with :hover, :focus-visible, :checked and :disabled, keeping a visible focus for keyboard users
- Pick elements by position with :first-child, :last-child and :nth-child(), and leave some out with :not()

## Key takeaways

- A pseudo-class starts with a colon and adds a condition to a selector, such as `li:hover`, read "li when hovered".
- `:hover`, `:active`, `:focus`, `:checked` and `:disabled` match an element's state, which changes as people use the page.
- People using a keyboard see where they are only by the focus outline. Never remove it without putting a clear `:focus-visible` style in its place.
- `:first-child`, `:last-child` and `:nth-child()` pick elements by position. `:nth-child(odd)` stripes rows, and `:nth-child(3n)` reaches every third.
- `:not()` leaves elements out, as in `li:not(:last-child)`: every song but the last.

Think of any music app. A song lights up when your pointer moves over it, the rows alternate shades so your eye can follow them, the heart turns red when you like a song, and a button goes gray when it can't be used yet. None of that is written in the HTML: the HTML has no idea where your pointer is. **Pseudo-classes** let CSS react to what's happening on the page, and to where an element sits in a list. You'll use them on every site you ever style.

## A colon means "when"

A **pseudo-class** is a word after a colon (`:`) that adds a condition to a selector. `li:hover` is read "li when hovered": a list item, but only while the pointer is over it. *Pseudo* means "not quite": it works like a class, but nobody wrote it into the HTML. The browser adds and removes it as things change.

Here are two songs from the *Road Trip Mix* page, the playlist this chapter styles, plus the Share button from its footer:

```html
<ol>
  <li>
    <strong>Golden Hour Highway</strong>
    <span>Juniper &amp; the Jets</span>
    <input type="checkbox" aria-label="Like Golden Hour Highway">
  </li>
  <li>
    <strong>Windows Down</strong>
    <span>Maple Avenue</span>
    <input type="checkbox" aria-label="Like Windows Down" checked>
  </li>
</ol>
<button disabled>Share this mix</button>
```

Two attributes may be new: `checked` starts a checkbox ticked, and `disabled` switches a button off. Each is written on its own, with no value.

## The pointer: :hover and :active

- **`:hover`** matches while the pointer is over the element.
- **`:active`** matches while it's being pressed: the moment between the mouse button going down and coming up.

```css
li:hover {
  background-color: #e0f2fe;
}

button:active {
  background-color: #1e3a8a;
}
```

The song under the pointer turns light blue, and the button goes a darker blue while it's pressed, so it feels pushed. Phones and tablets have no pointer to hover with, so never hide something people *need* behind `:hover` alone. Links have one more state, **`:visited`**, for a link the person has already opened. For privacy, browsers let it change only colors.

## The keyboard: :focus and :focus-visible

Many people move through a page with the Tab key instead of a mouse: people who can't hold a mouse steady, people who are blind and use a screen reader, and plenty of fast typists. The element that Tab has landed on has **focus**, and browsers draw an outline around it. That outline is the only way a keyboard user knows where they are, like the cursor when you type.

- **`:focus`** matches the element that has focus, however it got there: Tab, a click or a tap.
- **`:focus-visible`** matches it only when the browser thinks an outline helps, mainly when focus came from the keyboard. Clicking a button with a mouse doesn't trigger it, which is why designers like it.

```css
input:focus-visible,
button:focus-visible {
  outline: 3px solid #1d4ed8;
  outline-offset: 2px;
}
```

`outline-offset` moves the outline 2 pixels out from the edge, so it doesn't touch the box. You'll find `outline: none;` in many stylesheets, written because someone disliked the default outline. Without a replacement, keyboard users are lost. If you change the outline, change it to something at least as clear, and check by pressing Tab through the page yourself. Accessibility guidelines (WCAG, as a class may call them) require a visible focus.

**Try it.** Open any site you use and press Tab a few times. Can you always see where you are? Then add the rule above to your `road-trip.css` and Tab through your playlist's checkboxes.

## Forms: :checked and :disabled

- **`:checked`** matches a checkbox or radio button that's ticked, and changes the moment someone clicks it.
- **`:disabled`** matches a form control that's switched off. **`:enabled`** is its opposite.

```css
input:checked {
  accent-color: #e63973;
}

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

A liked song's checkbox turns pink (`accent-color` colors a checkbox's tick and box), and the Share button looks unavailable, with a "no" sign as the pointer. Pair `:checked` with the combinators from *Combinators: Descendant, Child and Sibling* and the click can change other elements: `input:checked + span` reaches the span right after a ticked box.

## Position: :first-child, :last-child and :nth-child()

The next pseudo-classes don't change: they depend on where an element sits among its siblings.

- **`:first-child`** and **`:last-child`** match the first and the last child of a parent.
- **`:nth-child()`**, read "nth child", matches by number. Inside the parentheses go `odd` (1, 3, 5…), `even` (2, 4, 6…), one number (`:nth-child(4)`, the fourth), or a pattern such as `3n` (every third: 3, 6, 9, 12). In a pattern, `n` counts 0, 1, 2, 3… so `3n` gives 0, 3, 6, 9…, and there is no zeroth child. `2n+1` is another way to write `odd`.

```css
li:nth-child(even) {
  background-color: #f1f5f9;
}

li:first-child strong {
  font-size: 1.25rem;
}
```

Every second song gets a light gray stripe, like the rows of a printed bank statement, and the first song's title is bigger. Add a song to the middle of the list and the stripes stay correct, since the browser counts again. One catch: `:nth-child()` counts *every* child, whatever its type. In a song, `span:nth-child(2)` is the artist, since the `<strong>` is child 1.

## Leaving some out: :not()

**`:not()`**, read "not", matches everything its parentheses *don't*. `li:not(:last-child)` is "every song except the last":

```css
li:not(:last-child) {
  border-bottom: 1px solid #cbd5e1;
}
```

That draws a line between songs, with no stray line under the last one. Without `:not()`, you'd write a border for every song, then a second rule to take it off the last one.

![The playlist with every second row striped, the hovered row light blue, a liked song's checkbox pink, and a thick blue focus outline around the checkbox that keyboard focus is on](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/10/52ae7677465341d3a704e242d090a409.webp)

## Check yourself

2 questions to try on the lesson page.

## So what

Pseudo-classes make a page respond to the people using it, and they keep long lists readable without a class on every row. Keep the focus visible, and the page works for everyone who can't or doesn't use a mouse.

## Key terms

- [Pseudo-class](https://www.ask-codey.com/glossary/pseudo-class)
- [:hover](https://www.ask-codey.com/glossary/hover)
- [:focus](https://www.ask-codey.com/glossary/focus)
- [:checked](https://www.ask-codey.com/glossary/checked-css)
- [:nth-child()](https://www.ask-codey.com/glossary/nth-child)
- [:not()](https://www.ask-codey.com/glossary/not)

## Cheat sheets

- [Pseudo-Classes: :hover, :focus-visible, :nth-child() and More](https://www.ask-codey.com/cheat-sheets/css-pseudo-classes-cheat-sheet)

## Related

- [Pseudo-Elements: ::before, ::after and ::marker](https://www.ask-codey.com/lessons/css-pseudo-elements)
- [Modern Selectors: :is(), :where() and :has()](https://www.ask-codey.com/lessons/css-is-where-has)

Source: https://www.ask-codey.com/lessons/css-pseudo-classes
