1. Hover, active, focus-visible

Clear feedback on interaction.

Primary CTA

Hover lifts; active presses; Tab shows ring.

Secondary

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.

Disabled

Cannot be clicked.

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

3. aria-busy / loading state

Show progress without breaking layout.

Loading

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.