Button States You Shouldn't Miss
A simple guide showing the important button states.
Most buttons that feel off aren’t designed wrong. They’re missing states.
Button states are small details that make a big difference in how a website feels.
In this post, I’ll cover the key button states you shouldn’t miss, and a simple baseline you can reuse for every project.
Try switching between each state:
background-color: #0d6ce8;
transform: translateY(-1px); Transitions
Before we dive into individual states, here’s one rule that applies to all of them: Keep transitions short and responsive.
A good baseline is 100ms to 300ms. Anything over 300ms can start to feel sluggish. Buttons are micro-interactions. They should respond instantly.
Drag the slider, then hover the button
.button {
transition: transform 220ms ease, background-color 220ms ease;
}
Hover
Hover will be the most used or popular state among all of these states.
Unless you’re creating a Awwwards style animation site, hover can be subtle and simple.
Hover states are useful but not completely necessary. Especially if it’s a web app UI, and the user knows it’s a clickable button, hover states aren’t necessary for that.
One thing I try to avoid is hover styles on touch devices. Touch devices don’t really “hover”, and it can sometimes cause weird states while scrolling.
Pressed (active state)
This will be one of the most missed and underrated states of a button. Pressed is also the one users notice instantly.
Adding a subtle pressed scale down effect will make the button more responsive and alive.
I picked this up from Emil Kowalski’s posts. Since then, I try to add a pressed state to almost every button.
Try clicking both
A simple active state:
.button:active {
transform: scale(0.97);
}
Focus-visible
Focus-visible is where polish and accessibility meet.
If a user is browsing with keyboard, then their cursor is controlled by focus.
Avoiding focus state is like removing the mouse cursor from them.
We use :focus-visible, not :focus.
Because :focus also triggers on mouse click, which can make focus rings show up everywhere and tempt people to remove them. :focus-visible keeps the ring for the moments it matters most: keyboard and assistive navigation.
Click inside and press Tab to navigate
.button:focus-visible {
outline: 3px solid rgba(0, 0, 0, 0.75);
outline-offset: 3px;
}
Disabled
Disabled is a message to the user: “don’t click me”.
A disabled button should look clearly unavailable. If it looks normal and nothing happens, users will think it’s a bug.
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Prevent hover effects on disabled buttons */
.button:not(:disabled):hover {
background-color: #0056b3;
}
The :not(:disabled) selector ensures hover styles only apply to active buttons. This way disabled buttons don’t give mixed signals.
Accessibility and Reduced Motion
Use Real Buttons
Use a real <button> when it’s a button. Not a div with click handlers.
Native <button> elements come with built-in keyboard support and screen reader announcements.
If you must use a custom element, you’ll need to add at least:
role="button"tabindex="0"- and handle keyboard interactions (Enter / Space)
Motion Preferences
Not everyone wants motion. Some people have motion sensitivity.
The prefers-reduced-motion media query lets you respect their system preferences:
@media (prefers-reduced-motion: reduce) {
.button {
transition: none;
}
.button:active {
transform: none;
}
}
You don’t have to remove states. You just remove the movement. Color and outlines still do the job.
Copy and Paste Starter
Here’s a complete baseline you can copy into any project:
.button {
/* Base styles */
padding: 0.75rem 1.5rem;
border: none;
border-radius: 6px;
background-color: #157cff;
color: white;
font-size: 1rem;
cursor: pointer;
/* Smooth transitions */
transition: transform 220ms ease, background-color 220ms ease;
}
/* Hover (only on devices that support it) */
@media (hover: hover) {
.button:hover {
background-color: #0d6ce8;
}
}
/* Pressed */
.button:active {
transform: scale(0.97);
}
/* Focus for keyboard users */
.button:focus-visible {
outline: 3px solid rgba(0, 0, 0, 0.75);
outline-offset: 3px;
}
/* Disabled */
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.button:not(:disabled):hover {
background-color: #0d6ce8;
}
/* Respect reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
.button {
transition: none;
}
.button:active {
transform: none;
}
}