11 min read

CSS Pseudo-Classes: Hover, Focus, and Interactive States

11 min read
356 words
20 sections16 code blocks

What Are Pseudo-Classes?

Pseudo-classes are keywords added to selectors that target elements in a specific state or condition. They let you style elements based on user interaction, position in the DOM, or form status — without adding extra classes in HTML.

CSS
/* selector:pseudo-class { } */
a:hover { color: red; }
input:focus { border-color: blue; }
li:first-child { font-weight: bold; }

Interactive State Pseudo-Classes

:hover

Targets an element when the user's mouse pointer is over it:

CSS
.button {
  background: #3498db;
  color: white;
  transition: background 0.2s ease;
}

.button:hover {
  background: #2980b9;
}

.card:hover {
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  transform: translateY(-2px);
}

:active

Targets an element while it is being clicked (mouse button is held down):

CSS
.button:active {
  background: #1a6fa0;
  transform: scale(0.98);
}

The :active state is brief — it only lasts as long as the user is pressing.

:focus

Targets an element when it has keyboard focus (clicked or tabbed to):

CSS
input:focus {
  border-color: #3498db;
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.25);
  outline: none;
}

a:focus {
  outline: 2px solid #3498db;
  outline-offset: 2px;
}

Important: Never remove focus styles without providing a visible alternative. Users navigating with keyboards need to see which element is focused.

:focus-visible

Targets elements that have focus and the browser determines the focus should be visible (typically keyboard navigation):

CSS
button:focus-visible {
  outline: 2px solid #3498db;
  outline-offset: 2px;
}

This is better than :focus for buttons because it only shows the focus ring when a user is navigating with a keyboard, not when they click with a mouse.

:focus-within

Targets a parent element when any of its children have focus:

CSS
.search-wrapper {
  border: 2px solid #ddd;
  transition: border-color 0.2s;
}

.search-wrapper:focus-within {
  border-color: #3498db;
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15);
}

This is useful for styling form groups or search bars when any input inside them is focused.

Combining Interactive States

You can combine pseudo-classes for more specific targeting:

CSS
/* Only when hovering AND focused */
input:hover:focus {
  border-color: #2c3e50;
}

/* A hovered link that's not active */
a:hover:not(:active) {
  text-decoration: underline;
}

Form State Pseudo-Classes

:enabled and :disabled

CSS
input:enabled {
  background: white;
}

input:disabled {
  background: #f0f0f0;
  color: #999;
  cursor: not-allowed;
}

:checked

Targets checkboxes and radio buttons that are checked:

CSS
input[type="checkbox"]:checked + label {
  color: #27ae60;
  text-decoration: line-through;
}

:required and :optional

CSS
input:required {
  border-left: 3px solid #e74c3c;
}

input:optional {
  border-left: 3px solid #ddd;
}

:valid and :invalid

Targets form fields based on their validation status:

CSS
input:valid {
  border-color: #27ae60;
}

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

:placeholder-shown

Targets an input when its placeholder text is visible (meaning the field is empty):

CSS
input:placeholder-shown {
  border-color: #ddd;
}

input:not(:placeholder-shown) {
  border-color: #3498db;
}

Practical Interactive Patterns

Button with Full State Feedback

CSS
.btn {
  padding: 0.75rem 1.5rem;
  background: #3498db;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.btn:hover {
  background: #2980b9;
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}

.btn:active {
  background: #1a6fa0;
  transform: translateY(0);
  box-shadow: none;
}

.btn:focus-visible {
  outline: 2px solid #3498db;
  outline-offset: 3px;
}

.btn:disabled {
  background: #bdc3c7;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

Input with Validation Feedback

CSS
.form-input {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid #ddd;
  border-radius: 4px;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.form-input:focus {
  border-color: #3498db;
  box-shadow: 0 0 0 3px rgba(52,152,219,0.15);
  outline: none;
}

.form-input:valid:not(:placeholder-shown) {
  border-color: #27ae60;
}

.form-input:invalid:not(:placeholder-shown):not(:focus) {
  border-color: #e74c3c;
}

Interactive Card

CSS
.card {
  padding: 1.5rem;
  background: white;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  transition: transform 0.2s, box-shadow 0.2s;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

Accessibility Considerations

  • Always provide :focus styles — never just outline: none without a replacement
  • Use :focus-visible for buttons to avoid focus rings on mouse click
  • Match :hover and :focus styles so keyboard users get the same visual feedback
  • Ensure sufficient contrast in all interactive states
CSS
/* Good: Both hover and focus get visual feedback */
.link:hover,
.link:focus-visible {
  color: #2980b9;
  text-decoration: underline;
}

Summary

CSS pseudo-classes enable dynamic, interactive styling without JavaScript. Use :hover for mouse interactions, :focus and :focus-visible for keyboard accessibility, :active for click feedback, and form pseudo-classes for validation states. Always provide visible focus indicators and combine multiple states for polished, accessible user interfaces.