Skip to main content
Syllabus
On this page

CSS Pseudo-classes: :hover, :focus, :nth-child & Modern Selectors

Badar KhalilUpdated September 29, 2026 6 min read

CSS Pseudo-classes: Styling Elements by State and Position

A CSS pseudo-class is a keyword added to a selector, starting with a single colon, that targets an element in a specific state or position, such as when a user hovers a button, focuses an input, or when an element is the third item in a list. Pseudo-classes let you build interactive, dynamic designs with no JavaScript and no extra classes. This lesson covers :hover, :focus and :nth-child in depth, plus the most important modern pseudo-classes.

Syntax

selector:pseudo-class { property: value; }, for example a:hover { color: red; }. Pseudo-classes use a single colon. Pseudo-elements (such as ::before and ::after) use a double colon.

Pseudo-classes Have Class-Level Specificity

Each pseudo-class adds (0,1,0) to specificity, the same as a class. So a:hover scores (0,1,1). Exceptions: :where() adds zero, and :is(), :not() and :has() take the specificity of their most specific argument.

User Action Pseudo-classes

:hover

Applies while the pointer is over an element. Use it for buttons, links, cards and menus. On touch devices there is no true hover, so never hide essential content behind hover only. Wrap hover styling in @media (hover: hover) to apply it only on devices that really support it.

:active

Applies while an element is being pressed (mouse down or tap). Great for button press feedback.

:focus

Applies when an element has keyboard or programmatic focus (links, buttons, inputs). Focus styles are essential for accessibility: keyboard users need to see where they are. Never write outline: none without providing a clear replacement.

:focus-visible

Applies focus styles only when the browser decides a visible indicator is helpful, typically keyboard navigation. It stops the focus ring appearing on mouse clicks while keeping it for keyboard users. It is the recommended modern choice for buttons and links.

:focus-within

Matches a parent when any of its descendants has focus. Ideal for highlighting a whole form group or search bar when the input inside is focused.

  • :link - unvisited links.
  • :visited - visited links (browsers only allow limited properties, mostly colors, for privacy).

Because these have the same specificity, order matters. Remember LVHA: :link, :visited, :hover, :active.

Structural Pseudo-classes

:first-child and :last-child

Match an element that is the first or last child of its parent. For example, li:last-child { border-bottom: none; }.

:only-child, :first-of-type, :last-of-type, :nth-of-type()

The -of-type versions count only siblings of the same tag name, which is useful when an element mixes tags such as h2 and p.

:nth-child(an+b)

Selects elements by their position among all siblings. The argument can be a number, a keyword or a formula.

SelectorMeaning
:nth-child(3)The third child
:nth-child(odd)1st, 3rd, 5th... (same as 2n+1)
:nth-child(even)2nd, 4th, 6th... (same as 2n)
:nth-child(3n)Every third child: 3, 6, 9...
:nth-child(3n+1)1, 4, 7, 10...
:nth-child(n+4)The fourth child and everything after it
:nth-child(-n+3)Only the first three children
:nth-last-child(2)Second from the end
:nth-child(2 of .item)The second child among those matching .item

In the formula an+b, n counts up from 0 (0, 1, 2, ...), a is the step and b is the offset. A classic use is zebra striping of tables and lists: tr:nth-child(even) { background: #f2f2f2; }.

Logical (Functional) Pseudo-classes

  • :not(selector) - matches elements that do not match the argument. Example: li:not(:last-child).
  • :is(a, b, c) - shorthand to group selectors. :is(h1, h2, h3):hover replaces three long selectors.
  • :where(a, b, c) - same as :is() but with zero specificity, ideal for easily overridable defaults and resets.
  • :has(selector) - the 'parent selector'. .card:has(img) selects cards that contain an image. It is one of the most exciting modern CSS features, and it is supported in current major browsers.

Form and Input State Pseudo-classes

  • :checked - checked checkboxes and radios.
  • :disabled and :enabled - form control state.
  • :required and :optional.
  • :valid and :invalid - based on HTML validation. Combine with :user-invalid, where supported, to avoid showing errors before the user has interacted.
  • :placeholder-shown - an input whose placeholder is currently visible.
  • :read-only and :read-write.

Other Useful Pseudo-classes

  • :root - the html element, the standard place to define CSS variables.
  • :empty - elements with no children or text.
  • :target - the element whose id matches the URL hash (for example #section2).

Best Practices

  • Always pair :hover styles with matching :focus-visible styles so keyboard users get the same feedback.
  • Use transition to make hover and focus changes smooth.
  • Avoid removing default focus outlines unless you replace them with something at least as visible.
  • Use :where() for low-specificity base styles.
  • Do not hide important information behind hover only.

Common Mistakes

  • Putting a space before the colon: a :hover means 'any hovered descendant of a', not 'a hovered link'.
  • Writing link states in the wrong order, so :hover never appears to work.
  • Forgetting that :nth-child counts all siblings, not only elements of the same type.
  • Starting the count at 0: nth-child counts from 1.

Key Takeaways

  • Pseudo-classes target state (:hover, :focus, :checked) and position (:nth-child, :first-child).
  • Use :focus-visible for accessible keyboard focus and :focus-within for grouped highlighting.
  • :nth-child(an+b) unlocks striping, grids and ranges.
  • :is(), :where(), :not() and :has() are powerful modern tools for cleaner, smarter CSS.
Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Exercise: Interactive Button StatesHTML

Style the button: on :hover change the background to darkgreen, on :active scale it down slightly with transform: scale(0.95), and on :focus-visible add a 3px solid orange outline with a 3px offset. Add a transition for smooth changes.

Try it Yourself HTML
Output

Press Run to execute.

Show expected output
Darker green on hover, scaled on press, orange outline on keyboard focus, smooth transition.

This is a self-check — compare your result with the expected output above.

Exercise: Zebra Stripes and Ranges with nth-childHTML

Use :nth-child to (1) give even list items a light gray background, (2) make only the first three items bold, and (3) make every item from the 6th onward semi-transparent (opacity 0.5).

Try it Yourself HTML
Output

Press Run to execute.

Show expected output
li:nth-child(even) { background: #eee; } li:nth-child(-n+3) { font-weight: bold; } li:nth-child(n+6) { opacity: 0.5; }

This is a self-check — compare your result with the expected output above.

Exercise: Highlight the Form GroupHTML

Use :focus-within so that the whole .group wrapper gets a blue border and a light blue background when the input inside is focused. Then use :not(:last-child) to add a bottom margin of 12px to every group except the last.

Try it Yourself HTML
Output

Press Run to execute.

Show expected output
.group:focus-within { border-color: blue; background: #e6f0ff; } .group:not(:last-child) { margin-bottom: 12px; }

This is a self-check — compare your result with the expected output above.

Was this page helpful?