webdevpuneet.com

High Contrast Mode Toggle — Free Accessible UI Theme Switch

High Contrast Mode Toggle · Buttons · Plain HTML, CSS & JS · Live preview

CategoryButtons

Copy & paste

Source Code

<div class="demo-root" id="demoRoot" data-contrast="normal">
  <header class="demo-header">
    <div class="demo-brand">Northline Analytics</div>
    <button class="contrast-toggle" id="contrastToggle" aria-pressed="false">
      <span class="toggle-track"><span class="toggle-thumb"></span></span>
      <span class="toggle-label">High contrast</span>
    </button>
  </header>

  <main class="demo-main">
    <div class="stat-row">
      <div class="stat-card">
        <span class="stat-label">Revenue</span>
        <span class="stat-value">$48.2k</span>
        <span class="stat-delta up">&#9650; 4.1%</span>
      </div>
      <div class="stat-card">
        <span class="stat-label">Errors</span>
        <span class="stat-value">12</span>
        <span class="stat-delta down">&#9660; 2.3%</span>
      </div>
    </div>

    <div class="panel">
      <h2>Recent activity</h2>
      <ul class="activity-list">
        <li><span class="dot dot-ok"></span> Deploy succeeded on <strong>main</strong></li>
        <li><span class="dot dot-warn"></span> Build warning in <strong>api-service</strong></li>
        <li><span class="dot dot-err"></span> Payment webhook failed</li>
      </ul>
      <button class="secondary-btn">View all activity</button>
    </div>
  </main>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Single root attribute
data-contrast drives every override, mirroring a dark-mode toggle.
Thicker, always-visible borders
1px translucent lines become solid 2px white borders.
AAA-leaning contrast
Pure black/white replaces softened dark-theme grays.
No color-only status
Text labels back up every color-coded status dot.
OS preference respected
Checks prefers-contrast: more on first load.
Persisted choice
localStorage remembers the toggle across visits.
Wider focus rings
Keyboard focus outline also thickens under high contrast.
Accessible toggle control
aria-pressed communicates on/off state to assistive tech.

About this UI Snippet

High Contrast Mode Toggle — A Real Accessibility Theme, Not Just Inverted Colors

Screenshot of the High Contrast Mode Toggle snippet rendered live

Dark-mode toggles are everywhere; genuine high-contrast toggles are rarer, and the two are not the same feature. A dark theme optimizes for comfort in low light. A high-contrast theme optimizes for legibility — for users with low vision, certain color-vision deficiencies, or anyone in bright glare — by widening every ratio, thickening borders that would otherwise be a faint 1px line, and making sure status is never communicated by color alone. This snippet builds a working toggle that flips a small dashboard between the two, using the same "one attribute on the root" architecture as a dark-mode switch, applied to a genuinely different design goal.

One data attribute drives everything

Clicking the toggle sets data-contrast="high" (or "normal") on #demoRoot, and every override lives in CSS selectors scoped under .demo-root[data-contrast="high"]. The JavaScript never touches a color value directly — it only flips the attribute, the same separation of concerns used in the dark mode toggle and color mode toggle snippets. That separation matters here specifically because it keeps every contrast decision in one auditable place instead of scattered across component-level JS logic.

What actually changes, and why

Borders go from a subtle 1px translucent line to a solid 2px white border, because a barely-visible card edge is exactly the kind of detail low-vision users lose first. Backgrounds and text collapse to pure black and white rather than the dark theme's softened grays, pushing contrast ratios toward WCAG's stricter AAA target of 7:1 for body text rather than the AA minimum of 4.5:1. Status dots that relied on green/yellow/red alone gain text labels ("OK", "UP", "DOWN") in high-contrast mode, because color-coding without a redundant cue fails WCAG 1.4.1 (Use of Color) for anyone who can't reliably distinguish the hues, not just users who are fully colorblind.

Respecting the OS-level signal too

Beyond the manual toggle, the JS checks window.matchMedia('(prefers-contrast: more)') on load, so a user who has already told their operating system they want more contrast gets it automatically here too, without hunting for an in-app switch — mirroring how a reduced-motion toggle checks prefers-reduced-motion. A saved localStorage preference takes priority over the OS default on repeat visits.

Where this belongs

Any dashboard, admin panel, or data-dense interface benefits from an explicit high-contrast option, since low-vision users are disproportionately concentrated in exactly those data-heavy, small-text contexts. Pair it with a text size adjuster and a color contrast checker for a fuller accessibility toolbar, or with focus-visible styling so keyboard focus rings also widen under high contrast, as this snippet's toggle button itself does.

Step by step

How to Use

  1. 1
    Toggle "High contrast"The whole dashboard flips to a black-and-white, thick-bordered theme.
  2. 2
    Compare the stat cardsBorders go from a faint line to a solid 2px outline in the high-contrast state.
  3. 3
    Look at the status dotsIn high contrast, "OK" text is added next to the dot — status is never color-only.
  4. 4
    Reload the previewYour choice persists via localStorage, same as a saved dark-mode preference.
  5. 5
    Check your OS contrast settingIf your OS has "increase contrast" enabled, the toggle starts on automatically.
  6. 6
    Inspect data-contrastEvery override lives under [data-contrast="high"] on the root element in CSS.

Real-world uses

Common Use Cases

Data-dense dashboards
Give low-vision users of data-dense dashboards a genuine high-contrast theme, with solid two-pixel borders replacing thin translucent lines.
Admin and internal tools
Pair with the colour contrast checker to verify the ratios of the colours you choose for the theme.
Accessibility settings panels
Combine with a text size adjuster and reading mode toggle, driven by one data-contrast attribute on the root.
Public sector and regulated apps
Help public sector and regulated apps meet WCAG contrast requirements, using near-pure black and white instead of softened dark-theme greys.
Alongside dark mode
Offer high contrast as a third theme beside a dark mode toggle, with text labels backing up every colour-coded status for people who cannot distinguish hues.
Related: Payment Request API Button
See the Payment Request API Button for a related buttons pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

No. Dark mode is primarily a comfort preference for low-light viewing and typically uses softened, muted colors. A high-contrast theme is an accessibility feature aimed at legibility — it pushes text/background ratios higher, thickens borders, and removes any reliance on subtle color differences, regardless of whether the base theme is light or dark.

It sets a single data-contrast attribute on the root element and lets CSS selectors scoped to [data-contrast="high"] own every visual override. The JavaScript never assigns a color directly — it only flips the attribute — so every contrast decision lives in one place in the stylesheet and stays easy to audit or extend.

WCAG 1.4.1 (Use of Color) requires that color never be the only way information is conveyed, because colorblind users and some low-vision users cannot reliably distinguish hues. The status dots already rely on color in the normal theme; the high-contrast theme adds a redundant text cue so the same information reaches users who cannot pick up on color alone.

Yes — on load, the script checks window.matchMedia("(prefers-contrast: more)") and turns the toggle on automatically if the user has that OS accessibility setting enabled and hasn't already saved an explicit preference of their own, similar to how a reduced-motion toggle checks prefers-reduced-motion.

Yes — swap the boolean data-contrast value for a small enum (normal / high / high-inverted, for example) and add corresponding CSS blocks keyed to each value. The toggle button would become a small select or segmented control instead of a switch, but the single-attribute architecture stays exactly the same.