On this page
CSS Pseudo-classes: :hover, :focus, :nth-child & Modern Selectors
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 Pseudo-classes and the LVHA Order
: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.
| Selector | Meaning |
|---|---|
| :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):hoverreplaces 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.:disabledand:enabled- form control state.:requiredand:optional.:validand: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-onlyand: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
:hoverstyles with matching:focus-visiblestyles so keyboard users get the same feedback. - Use
transitionto 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 :hovermeans '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-childcounts 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.
Press Run to execute.
Press Run to execute.
Press Run to execute.
Press Run to execute.
Press Run to execute.
Press Run to execute.
Press Run to execute.
Press Run to execute.
Press Run to execute.
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.
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.
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).
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.
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.
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?