Skip to content
Template Empire
Browse components

Contrast Mode Switch

A free React theme switcher for high contrast mode, with a morphing icon and a stable accessible name.

Deck display

Standard, for the chart table and the run home after dark.

Next tidal gate

Barmouth bar carries 2.1 m at 06:40. Slip St Tudwals by 03:15 and five knots over the ground puts you on the bar with an hour of flood still under you.

Position

GPS and AIS agree to 3 m

Customise

44px
0.3s
999px

Code

const [highContrast, setHighContrast] = useState(false);

<ThemeContrastSwitch
  highContrast={highContrast}
  onHighContrastChange={setHighContrast}
/>

Contrast Mode Switch is a free React high contrast toggle for Next.js, styled with Tailwind and animated with Motion, built as a single native switch button whose thumb translates across the track while a decorative glyph plays a rotate, iris or crossfade transition. It ships with a stable accessible name, separate state text, and a visible focus ring, so it drops straight into a settings panel or preferences menu.

The switch is fully controlled and has no side effects of its own: pass the current highContrast value and it calls onHighContrastChange with the opposite one on activation, never touching document classes, storage or a media query. Wiring the result to an actual contrast mode, a CSS class, or nothing at all stays entirely in your app. Reduced motion snaps the thumb and glyph to their final state instantly, with the controlled semantics unchanged.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/theme-contrast-switch

Usage

"use client";

import { useState } from "react";
import { ThemeContrastSwitch } from "@/components/theme-contrast-switch";

export default function Page() {
  const [highContrast, setHighContrast] = useState(false);

  return (
    <ThemeContrastSwitch
      highContrast={highContrast}
      onHighContrastChange={setHighContrast}
    />
  );
}

Props

PropTypeDefaultDescription
highContrastbooleanrequiredControlled contrast state.
onHighContrastChange(value: boolean) => voidrequiredCalled after explicit user activation with the opposite value.
sizenumber44Control height in CSS pixels, clamped to 36 through 64.
orientation"icon-only" | "labelled""labelled"Visible-label treatment for the current-state text.
iconMotion"rotate" | "iris" | "swap""iris"Decorative state-transition style for the glyph.
transitionDurationnumber0.3Non-spring transition duration in seconds, clamped to 0 through 0.7.
radiusnumber999Track and thumb corner radius in CSS pixels, clamped to 0 through 999.
classNamestringundefinedAdditional classes merged onto the switch button.

Customisation

Retheme the checked track and thumb border with contrast-switch-accent, the checked glyph with contrast-switch-accent-text, the resting track with contrast-switch-track, and the shared hover and focus-visible ring with contrast-switch-focus. Every token defaults to a semantic token, so retheming is optional:

.te-theme-contrast-switch {
  --contrast-switch-accent: var(--ring);
  --contrast-switch-accent-text: var(--primary-accent);
  --contrast-switch-track: var(--surface);
  --contrast-switch-focus: var(--primary-accent);
}

Frequently asked questions

Is Contrast Mode Switch free for commercial use?
Yes. Contrast Mode Switch is MIT licensed, so you can use it in personal and commercial projects without attribution.
Is Contrast Mode Switch accessible and performant?
Yes. It renders one native switch button with an accurate checked state, a stable accessible name that never changes with state, and separate description text announcing the current state. State is shown through thumb position, glyph shape and that text together, never colour alone, and hover and focus-visible receive the same ring treatment. The thumb and every icon mode animate only transform and opacity, and reduced motion snaps every transition to its final state instantly with identical controlled semantics.
How do I customise the Contrast Mode Switch?
Use size, orientation, iconMotion, transitionDuration and radius to tune the control's scale, label treatment, glyph transition and shape. Use the four component tokens (contrast-switch-accent, contrast-switch-accent-text, contrast-switch-track, contrast-switch-focus) to retheme the checked accent, the checked glyph, the resting track and the focus ring without touching the component source. The accent and its glyph are separate tokens because a fill rung and a text rung cannot share one value and both clear AA.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with accessible settings panels and this motion system throughout. Browse the paid templates that pair with these free components.Explore Template Empire templates