Hover lifts; active presses; Tab shows ring.
1. Hover, active, focus-visible
Clear feedback on interaction.
Ghost style with border darkens on hover.
.btn {
transition: transform 0.15s, box-shadow 0.15s;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: 3px solid #6366f1; outline-offset: 2px; }2. Disabled buttons
Lower contrast and no pointer events.
Cannot be clicked.
button:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}3. aria-busy / loading state
Show progress without breaking layout.
Spinner via pseudo-element.
.btn[aria-busy="true"] {
position: relative;
color: transparent;
}
.btn[aria-busy="true"]::after {
content: "";
position: absolute;
inset: 0;
margin: auto;
width: 1rem;
height: 1rem;
border: 2px solid #fff;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}Frequently asked questions
- :focus vs :focus-visible?
- Use :focus-visible for rings; avoid large outlines on mouse click.
- Should disabled buttons be in tab order?
- No. Disabled is skipped; use aria-disabled if it must stay focusable.
- Transition all on buttons?
- Prefer specific properties (transform, background) for snappier feel.