mgifford/accessibility-skills/light-dark-mode
Claude Code and agent skill by mgifford/accessibility-skills
- community
Checking that the SKILL.md file is still in the repository.
Load this skill whenever the project supports light/dark mode, colour theme switching, high-contrast mode, or responds to prefers-color-scheme. Under no circumstances hard-code colours that break in alternative themes. Absolutely always test colour contrast in both light and dark themes, and respect user OS-level colour preferences via CSS media queries.
Install
npx vskill@latest install mgifford/accessibility-skills/light-dark-modeSKILL.md
Light/Dark Mode Accessibility Skill
Canonical source:examples/LIGHT_DARK_MODE_ACCESSIBILITY_BEST_PRACTICES.mdinmgifford/ACCESSIBILITY.mdThis skill is derived from that file. When in doubt, the example is authoritative.
Apply these rules whenever implementing or reviewing colour theme support in HTML, CSS, or JavaScript. Only load this skill if the project supports light/dark mode or user theme switching.
Critical Rule: Focus Is Navigation, Not Selection
A theme must not change until the user explicitly activates an option.
Moving keyboard focus or pointer hover across theme choices MUST NOT preview, select, or apply a theme. Tabbing across System, Light, and Dark MUST leave the page colours unchanged until the user presses Enter or Space.
This is a Critical requirement. Violations cause colour flashing during keyboard navigation and disorient users with vestibular disorders, cognitive disabilities, and low vision.
Core Mandate
All colour themes must meet WCAG 2.2 Level AA contrast in both light and dark modes, including forced-colours / high contrast modes. Test all three — not just the default.
Required: Three-Option Theme Selector Pattern
Use a visible three-option selector that shows all three options at the same time:
- System
- Light
- Dark
Interaction pattern requirements
- MUST use a labelled group containing three native
<button type="button">elements - MUST use
aria-pressed="true"for the selected option andaria-pressed="false"for others - MUST NOT use a single cycling button
- MUST NOT use a two-state light/dark toggle
- MUST NOT use a menu that hides the available options
- MUST NOT use custom
role="radio"elements - MUST NOT use a radiogroup that changes theme when arrow-key focus moves
- MUST NOT use hover or focus previews
- MUST NOT automatically select when a user merely tabs to an option
Keyboard requirements
The selector MUST work with standard native button behaviour. Users MUST be able to:
- Tab to each option
- Use Shift+Tab to move backwards
- Activate an option with Enter
- Activate an option with Space
- Activate an option with mouse, touch, switch device, voice control, or other pointer
Moving focus between the options MUST NOT change the theme. The theme MUST change only after deliberate activation.
Do NOT add unnecessary custom keyboard handlers where native button behaviour already provides the required functionality.
Prevent colour flashing during keyboard navigation
Theme changes MUST occur only after explicit activation. Moving keyboard focus or pointer hover across theme choices MUST NOT preview, select, or apply a theme.
Tabbing across System, Light, and Dark MUST leave the page colours unchanged until the user presses Enter or Space.
Required: HTML Structure
Use a group with an accessible label containing three native buttons:
Required: Default Behaviour
Use system as the default preference when the user has not made an explicit choice.
Do NOT convert the system preference into a stored light or dark preference.
For example, when the user selects system while their operating system is currently dark, store:
Do NOT store:
This distinction is required so that later operating-system changes continue to affect the page.
Required: Storage Requirements
Use a clearly named storage key such as theme-mode. Valid stored values are only: system, light, dark.
- Validate stored values before using them
- Wrap all
localStoragereads and writes intry/catch - The theme control MUST continue to work for the current page when storage is unavailable
- Do NOT allow a storage failure to stop script execution
Storage can be unavailable in:
- Privacy-restricted browsers
- Sandboxed contexts
- Private browsing modes
- Environments where storage access throws an exception
Required: Theme Application Model
Maintain separate values for the selected mode and resolved theme:
Apply both to the root element using explicit attributes:
data-theme-moderepresents the user's actual selectiondata-themerepresents the currently rendered light or dark scheme
When system is active, listen for changes to:
Update the resolved theme when that preference changes. Do NOT overwrite the stored mode when the system preference changes.
Required: Initial Rendering and Flash Prevention
Provide an early initialization pattern that runs before the primary stylesheet or before the page is visibly rendered. The pattern MUST:
- Safely read the stored preference
- Validate the stored value
- Default to system
- Resolve system using
prefers-color-scheme - Set the root theme attributes before the first significant paint
- Set an appropriate
color-schemevalue
Include early in <head>:
Explain that this early script is intended to reduce a flash of the incorrect colour scheme during page load. Do NOT claim that a script can eliminate every possible visual transition in every browser. Describe it as reducing or preventing the common flash caused by applying the preference too late.
Required: CSS Custom Properties Pattern
Use semantic theme tokens through CSS custom properties. Do NOT hard-code component colours independently throughout the example.
Component styles MUST read from the tokens above rather than hardcode colour values.
Required: Selected and Focus State Styling
The selected state MUST NOT be communicated through colour alone. Use multiple cues such as:
aria-pressed- A visible checkmark or icon
- A thicker or otherwise distinct border
- Font-weight
- Text labels
Focus and selected state MUST be visually distinguishable from one another. A focused but unselected option MUST NOT look identical to the selected option.
Use :focus-visible with a real outline. Do NOT remove the outline unless it is replaced by an equal or stronger visible focus indicator.
Required: Forced-Colours Support
The selector MUST remain usable in Windows High Contrast Mode and other forced-colours environments. Do NOT rely only on:
- Background colours
- Gradients
- Box shadows
- Opacity
- SVG fill colours
- Colour differences
Use system colours where appropriate within @media (forced-colors: active).
Ensure that:
- Button boundaries remain visible
- The selected option remains identifiable
- The focused option remains identifiable
- Selected and focused states remain distinct
- Icons using
currentColorremain visible
Do NOT use forced-color-adjust: none unless there is a specific, documented reason and the resulting colours are tested.
Required: Accessible Naming and Status
Use visible text labels: System, Light, Dark. Icons MAY supplement the labels but MUST NOT replace them.
Decorative icons MUST use:
Do NOT dynamically replace stable button labels with phrases such as:
- Switch to dark
- Current dark mode
- Use system dark
The button label SHOULD describe the preference the button selects. The selected state is communicated through aria-pressed.
It is acceptable to include a separate status message explaining the resolved system theme, for example:
Do NOT make this announcement excessively verbose. Avoid announcing the same state repeatedly when nothing has changed.
Required: Reduced Motion
Do NOT use animated colour transitions by default. Theme changes can affect most of the viewport and may be uncomfortable or disorienting.
If a transition example is included, it MUST:
- Be subtle
- Avoid transitioning every CSS property
- Respect
prefers-reduced-motion - Not delay the actual state change
- Not create intermediate low-contrast states
Prefer no theme-transition animation in the canonical example.
Required: Contrast Requirements
The skill MUST continue requiring WCAG 2.2 Level AA contrast testing in every relevant state. Test at least:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/light-dark-mode/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/mgifford/accessibility-skills/light-dark-mode)