Skip to content
SiteEmail

High contrast mode makes UI elements distinguishable for players with low vision, cataracts, or visual fatigue. Like the other theming systems in this section, the implementation is a root class toggle that swaps a predefined set of CSS custom properties to a high-contrast palette. The architectural work for this is largely identical to the color blindness system, which means if you’ve already built that, adding high contrast mode costs very little extra effort.

High contrast mode is not only for players with medical visual impairments. Many players enable it in bright environments (playing on a laptop outdoors), on TVs with poor calibration, or simply because they prefer the clarity. The broader the potential audience, the more it is worth implementing correctly.

The key distinction from colorblind mode is the goal: colorblind mode remaps hue relationships; high contrast mode increases luminance contrast ratios between foreground elements and backgrounds. Both can be active simultaneously.


The WCAG (Web Content Accessibility Guidelines) defines contrast ratios as the luminance difference between two colors. The relevant thresholds for game UI are:

  • 4.5:1 for normal-size text
  • 3:1 for large text (18px+) and UI components (button borders, icon outlines)

Most game UIs fail these thresholds on their default palettes. Subtle grays, translucent overlays, and low-opacity text that looks fine to players with typical vision become illegible at lower contrast sensitivity.

High contrast mode should push contrast ratios well above these minimums, typically targeting 7:1 or higher for critical UI text.


Your default theme uses semantic CSS custom properties (following the pattern established in the Color Blindness article). High contrast mode overrides those same properties under a .theme-high-contrast root class:

/* Default palette (excerpt) */
:root {
--color-text-primary: #e8e8e8;
--color-text-secondary: #a0a0a0;
--color-text-disabled: #5a5a5a;
--color-bg-surface: #1a1a2e;
--color-bg-overlay: rgba(0, 0, 0, 0.6);
--color-bg-input: #16213e;
--color-border-default: rgba(255, 255, 255, 0.15);
--color-border-focus: #4a90d9;
--color-interactive-primary: #4a90d9;
--color-interactive-hover: #6baee8;
--color-status-danger: #e05050;
--color-status-success: #4caf50;
--color-status-warning: #f5a623;
}
/* High contrast overrides */
:root.theme-high-contrast {
--color-text-primary: #ffffff;
--color-text-secondary: #eeeeee;
--color-text-disabled: #aaaaaa;
--color-bg-surface: #000000;
--color-bg-overlay: rgba(0, 0, 0, 0.9);
--color-bg-input: #111111;
--color-border-default: rgba(255, 255, 255, 0.7);
--color-border-focus: #ffff00; /* high visibility yellow for focus */
--color-interactive-primary: #ffff00;
--color-interactive-hover: #ffffff;
--color-status-danger: #ff4444;
--color-status-success: #00ff88;
--color-status-warning: #ffcc00;
}

The high contrast palette choices above follow a deliberate logic:

  • Backgrounds go fully black. Any surface that previously had mid-range luminance is forced to the darkest possible value, maximizing contrast with light foreground elements.
  • Text goes fully white. Primary and secondary text converge toward white to ensure readability on black surfaces.
  • Interactive and focus colors use high-luminance yellow. Yellow on black is one of the highest-contrast color pairings possible and is the universal convention for focus indicators in high contrast interfaces.
  • Status colors are supersaturated. The default #e05050 red becomes #ff4444 and the default green becomes #00ff88 to maintain hue distinction while pushing luminance.

Apply the root class from your settings system using the same pattern used for colorblind mode:

function setHighContrast(enabled) {
if (enabled) {
document.documentElement.classList.add('theme-high-contrast');
} else {
document.documentElement.classList.remove('theme-high-contrast');
}
}
engine.on('HighContrastChanged', (enabled) => {
setHighContrast(enabled);
});
// Restore on load
const savedHighContrast = engine.call('GetHighContrastPreference') ?? false;
setHighContrast(savedHighContrast);

If a player has both colorblind mode and high contrast mode enabled, the root element carries both classes:

<html class="theme-colorblind theme-high-contrast">

CSS specificity handles the cascade correctly as long as both override blocks target the same custom properties. The last defined block wins for any property both attempt to override, so the order of your @import statements or CSS file concatenation matters. Define high contrast overrides after colorblind overrides if your intent is for high contrast to take final precedence.


Low-vision players often rely on element boundaries to distinguish interactive components from backgrounds. High contrast mode should increase border opacity and width:

:root.theme-high-contrast .menu-button {
border-width: 2px;
border-color: var(--color-border-default);
}
:root.theme-high-contrast .menu-button:focus {
box-shadow: 0 0 0 3px var(--color-border-focus);
}
:root.theme-high-contrast .input-field {
border-width: 2px;
background-color: var(--color-bg-input);
}

The focus indicator (yellow box-shadow ring on button focus) is critical in high contrast mode. Players who rely on keyboard or gamepad navigation need to see exactly which element is focused. In high contrast mode, the focus indicator should be more prominent than in the default theme, not less.


Translucent backgrounds (the semi-transparent overlay behind a modal, blurred panel backgrounds) are a common source of contrast failures. A background at rgba(0, 0, 0, 0.5) that looks stylish in the default theme might make text sitting above it unreadable for low-vision players.

High contrast mode should replace translucent backgrounds with opaque ones:

:root.theme-high-contrast .modal-overlay {
background-color: rgba(0, 0, 0, 0.95);
}
:root.theme-high-contrast .panel-header {
background-color: #000000;
border-bottom-width: 2px;
border-bottom-style: solid;
border-bottom-color: var(--color-border-default);
}
:root.theme-high-contrast .hud-panel {
background-color: #000000;
border-width: 2px;
border-style: solid;
border-color: var(--color-border-default);
}

Removing translucency in high contrast mode is not a visual regression; it is the feature. The visual richness of layered glass effects is not accessible to the target audience for this mode.


High contrast mode and font scaling are independent systems that should compose correctly. Both operate through CSS custom properties and root classes. A player can have theme-high-contrast and scale-large active simultaneously without either breaking the other. For a full explanation of the root rem scaling system and how scale step classes are defined, see Building Responsive Game UI.

<html class="theme-high-contrast scale-large">

Verify this combination explicitly during QA. The most common failure mode is a component that hardcodes a color directly (color: #a0a0a0) instead of using a custom property, which means it escapes the high contrast override. Any hardcoded color value in your codebase is a potential high contrast failure point.


The player toggle should live in the accessibility settings alongside colorblind mode and reduced motion:

HighContrastToggle.tsx
import { createSignal } from 'solid-js';
import Block from '@components/Layout/Block/Block';
import Button from '@components/Basic/Button/Button';
const HighContrastToggle = () => {
const [enabled, setEnabled] = createSignal(false);
const toggle = () => {
const next = !enabled();
setEnabled(next);
document.documentElement.classList.toggle('theme-high-contrast', next);
engine.call('SaveAccessibilitySetting', 'highContrast', next);
};
return (
<Block class="settings-row" attr:role="group" attr:aria-label="High Contrast">
<Button
class="toggle-btn"
attr:aria-pressed={String(enabled())}
onClick={toggle}
>
{enabled() ? 'Disable' : 'Enable'}
</Button>
</Block>
);
};

Before shipping the high contrast implementation, run through these checks at every screen:

  • All text elements use --color-text-primary or --color-text-secondary. No hardcoded color values.
  • All interactive elements (buttons, inputs, tabs) have a visible 2px+ border in high contrast mode.
  • The focused element has a clearly visible focus ring (3px+ box-shadow in yellow - outline is not supported in Gameface).
  • Translucent backgrounds are replaced by opaque alternatives.
  • Status indicators (health bars, damage counters, objective markers) are distinguishable without relying on hue alone.
  • The setting persists across session restarts.
  • High contrast mode composes correctly with colorblind mode and font scaling simultaneously.

A component fails this audit if it requires a player to read the color to understand its state. Shape, icon, border, and text content should always carry the meaning alongside color.