CSS Pseudo-Classes: Hover, Focus, and Interactive States
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.
/* 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:
.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):
.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):
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):
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:
.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:
/* 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
input:enabled {
background: white;
}
input:disabled {
background: #f0f0f0;
color: #999;
cursor: not-allowed;
}:checked
Targets checkboxes and radio buttons that are checked:
input[type="checkbox"]:checked + label {
color: #27ae60;
text-decoration: line-through;
}:required and :optional
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:
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):
input:placeholder-shown {
border-color: #ddd;
}
input:not(:placeholder-shown) {
border-color: #3498db;
}Practical Interactive Patterns
Button with Full State Feedback
.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
.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
.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
/* 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.