Home / Snippets / UI Components /

Disabled button

Visually and semantically disabled button with reduced opacity, suppressed hover effects, and cursor: not-allowed.

Widely Supported
uino-jsa11y

Quick implementation

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1.25rem;
  font-size: 0.9rem;
  font-family: inherit;
  font-weight: 600;
  background: var(--accent-bg);
  color: oklch(1 0 0);
  border: none;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: opacity 0.15s;
}

/* Only show hover on enabled buttons */
.btn:hover:not(:disabled):not([aria-disabled="true"]) {
  opacity: 0.85;
}

/* Disabled state — native HTML attribute */
.btn:disabled,
/* Disabled state — ARIA for non-native elements */
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building an accessible design system.

Goal: CSS for a disabled button state that handles both :disabled and aria-disabled — no JavaScript.

Technical constraints:
- Use :disabled for native <button disabled> elements and [aria-disabled="true"] for custom elements.
- Set opacity: 0.45 and cursor: not-allowed on the disabled state.
- Use pointer-events: none to prevent click events on the disabled button.
- Suppress hover effects only on enabled buttons using :not(:disabled):not([aria-disabled="true"]).
- Use oklch() for all color values, not hex or rgba().
- Do not change background color — use opacity so the disabled appearance scales across variants.

Framework variant (pick one):
A) Vanilla CSS using :disabled and attribute selectors on a .btn base class.
B) React component — accept disabled as a boolean prop, apply aria-disabled and tabindex="-1" when true.

Edge cases to handle:
- Disabled <a> elements styled as buttons: remove href and add role="button" aria-disabled="true".
- Form submission button: :disabled prevents form submission natively — preserve this behaviour.
- Loading state: pair with cursor: wait and an aria-busy="true" variant for async actions.

Return CSS.

Why this matters in 2026

Disabled buttons are present in nearly every form-driven UI, but the styling is frequently implemented incorrectly — hover effects are not suppressed, the cursor remains pointer, or aria-disabled is omitted for non-native elements. Getting the disabled state right communicates clearly to the user that the action is unavailable, and ensures assistive technologies announce the state correctly without relying on visual opacity alone.

The logic

The :disabled pseudo-class targets native <button disabled> elements; [aria-disabled="true"] covers interactive elements that cannot use the HTML disabled attribute. The :not(:disabled):not([aria-disabled="true"]) guard on the hover rule ensures hover styles only apply to enabled buttons, preventing a subtle visual bug when CSS specificity is not handled carefully. Using opacity: 0.45 rather than a separate muted background color means the disabled appearance is automatically correct for every button variant — primary, secondary, ghost — with a single rule.

Accessibility & performance

The native disabled attribute removes the element from the tab order and announces "dimmed" or "unavailable" to screen readers automatically. For aria-disabled="true" on non-native elements, also set tabindex="-1" to remove keyboard focus. WCAG 1.4.3 exempts disabled controls from contrast requirements, but aim for at least 3:1 contrast to keep the label legible to low-vision users. pointer-events: none is purely visual — always enforce the disabled state in your form validation logic too.