Home / Snippets / Color & Theming /

Dark mode button

Three common variants — primary fill, muted secondary, and outline ghost — with keyboard-visible focus rings.

Widely Supported
colorno-js

Quick implementation

.dm-btn {
  font: inherit;
  font-weight: 600;
  padding: 0.45rem 1rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
}
.dm-btn--primary {
  background: linear-gradient(180deg, oklch(0.52 0.14 265), oklch(0.42 0.14 265));
  color: oklch(0.98 0.01 265);
  border-color: oklch(0.55 0.12 265 / 0.6);
}
.dm-btn:focus-visible {
  outline: 2px solid oklch(0.72 0.14 250);
  outline-offset: 2px;
}

Prompt this to your LLM

Constraints for states, tokens, and frameworks.

You are implementing dark-theme buttons for a design system.

Goal: Primary (filled), secondary (muted surface), and ghost (outline) variants that share one radius scale.

Technical constraints:
- Use oklch() for fills and borders; pair with var(--radius) and var(--card-border) where appropriate.
- Every interactive state must have :focus-visible with a 2px ring — never remove focus for “cleaner” UI.
- Include :disabled with reduced opacity and not-allowed cursor.
- Hover may use filter: brightness() on gradients — avoid animating box-shadow on every frame.

Framework variant A: plain HTML + CSS.
Framework variant B: React — <Button variant="primary|secondary|ghost" disabled>.

Edge cases:
- High contrast / forced-colors: test that borders remain visible.
- prefers-reduced-motion: skip transform “press” animations if you add them.
- Loading state: optional aria-busy + spinner slot without breaking min tap height.

Return HTML + CSS.

Why this matters in 2026

Dark dashboards ship dozens of actions per screen. Consistent button ramps in oklch() keep hierarchy obvious without resorting to neon fills or muddy grays.

The logic

The primary uses a short vertical gradient so the control reads tactile without a flat blob. Secondary sits one step above the page background; ghost trades fill for a low-chroma border so it works on var(--card) and on elevated panels.

Accessibility & performance

Native button elements preserve disabled semantics. Focus rings use a single outline — no JS. Paint cost stays low: no backdrop-filter on buttons.