July 1, 2026
A Practical Checklist for Testing Theme Switching, Dark Mode, and Persisted UI Preferences
A practical dark mode testing checklist for QA engineers and frontend teams, covering refreshes, cross-tab persistence, system preference overrides, storage, and accessibility.
Theme switching looks simple until you test it across real browsers, real storage behavior, and real users who change settings at awkward times. A toggle that appears to work on a single page can still fail after refresh, across tabs, in a private window, or when the operating system prefers dark mode but the app has stored a different choice. That is why a dark mode testing checklist is less about color palettes and more about state management, persistence rules, and accessibility.
For QA engineers, frontend developers, and design-system teams, theme switching is a compact but revealing feature. It exposes whether the app stores state correctly, whether components inherit tokens consistently, whether contrast remains readable, and whether system preferences are treated as signals or hard overrides. If you are building a checklist, you are really building a small project plan for UI state testing, browser storage testing, and responsive testing in one feature.
The practical goal is not just “dark mode works,” but “theme behavior is predictable under refresh, navigation, multi-tab usage, and accessibility constraints.”
What to define before you test
A good checklist starts with behavior rules, not screenshots. Before validating a theme switcher, document the product decisions that should remain stable across the application.
1) Decide the source of truth
Clarify which input wins when multiple signals exist:
- User-selected theme in the app UI
- Operating system or browser color-scheme preference
- Default theme chosen by the product
- Per-tenant or role-based theme settings, if your product supports them
Common precedence models include:
- User choice overrides everything, which is the simplest for users and often the least surprising.
- System preference is the default until the user chooses otherwise, which is common for SaaS products.
- Tenant policy overrides user choice, which can happen in enterprise applications, but should be explicit.
If the precedence is unclear, bugs will be reported as defects even when the implementation matches an undocumented rule.
2) Define persistence scope
State can be persisted in several places:
localStorage- cookies
- server-side user profile settings
- URL parameters, less common but sometimes useful for previews or demos
- in-memory state only, which should not be used for a persistent preference
Each choice has testing implications. localStorage is easy to inspect and test, but it is origin-scoped and unavailable in some privacy modes. Server-side persistence survives device changes but requires sync behavior and API testing. Cookies can help with SSR, but introduce expiry and domain considerations.
3) Define what “theme” actually changes
A theme might affect more than background and text colors. Your checklist should account for:
- Typography color tokens
- Surfaces, borders, shadows, and overlays
- Focus states and hover states
- Icons and illustrations
- Charts and status indicators
- Syntax highlighting in code blocks
- Third-party embeds or widgets
- System UI elements mirrored by the app, such as form controls
If theme changes are token-driven, test token propagation. If the app uses ad hoc CSS overrides, test for drift between components.
Core checklist for theme switching
Use this as the base set of checks for manual validation and automation.
1) Toggle the theme from the primary UI control
Verify the main switch works from the location where users expect it.
Checklist:
- The control is visible and operable with mouse, keyboard, and touch
- The label is understandable, for example, “Light theme” or “Use dark mode”
- The selected state is clear
- The theme changes immediately, or with documented delay if it depends on a save action
- No layout jump or broken animation occurs during the change
Edge cases to watch:
- Toggle appears in one layout but not in another
- The control changes appearance but not the actual theme
- The UI briefly flashes the wrong theme before settling
2) Refresh the page after changing theme
A preference that disappears on refresh is not a preference, it is a transient state.
Checklist:
- Theme remains correct after a full browser refresh
- Theme remains correct after hard refresh, if the implementation should survive it
- Initial paint does not flash the default theme before applying the stored preference
- No console errors occur during rehydration or preference loading
This is especially important for server-rendered apps, where theme can be rendered on the server, then reconciled on the client. A common failure mode is a visible flash of the wrong theme because the client reads storage after the first paint.
3) Open a second tab and verify cross-tab behavior
This is where many persisted preferences become real product bugs.
Checklist:
- Changing theme in tab A updates tab B, if real-time sync is expected
- Refreshing tab B after a change picks up the latest stored preference
- Two tabs opened before login and after login behave consistently
- Closing one tab does not reset the preference in another tab
If cross-tab live sync is required, verify the implementation responds to the storage event or an equivalent synchronization mechanism. If live sync is not required, document the latency users should expect.
4) Test navigation across routes and subpages
A theme should not depend on the current page.
Checklist:
- Route changes preserve the selected theme
- Nested pages and modals render with the same tokens
- Theme survives client-side navigation and full page navigation
- Deep links open in the correct theme when preference is already stored
This check catches apps where the root shell remembers the preference, but lazily loaded pages or micro-frontends do not.
5) Verify logout, login, and account switching behavior
If preferences are tied to a profile, state transitions matter.
Checklist:
- Logged-out and logged-in experiences are consistent with the product rule
- Switching accounts loads the right theme for each account
- Logging out does not leak a previous user’s preference into a new session
- Guest preferences behave as intended, if guests can choose a theme
For shared devices, this is a privacy issue as much as a UX issue.
System preference overrides and fallback logic
Theme switching should not ignore prefers-color-scheme, but it should also not treat the OS preference as absolute unless that is the intended design.
1) Test with system set to light and dark
Use browser dev tools or operating system settings to verify both states.
Checklist:
- Default theme matches the system when no stored preference exists
- Dark mode is used when system preference is dark and no user override exists
- Light mode is used when system preference is light and no user override exists
- Stored user choice overrides the system preference when that is the documented rule
If your application supports a three-state control, such as System, Light, Dark, test all three explicitly. In that model, System should follow the OS without storing an accidental hard override.
2) Verify behavior when system preference changes mid-session
This is easy to miss and especially important for design systems.
Checklist:
- App responds appropriately when OS theme changes while the page is open
- The result is stable and does not oscillate between modes
- User override still wins if the product says it should
- No duplicate listener behavior occurs after route changes or hot reloads
A clean implementation should make it obvious whether the app listens to media query changes continuously or only reads the preference at load time.
3) Check no-preference and fallback cases
Not all environments report the same media query behavior.
Checklist:
- App has a stable default when no preference can be determined
- Theme selection does not break on older browsers or embedded webviews
- Loading the page with blocked storage still results in a usable color scheme
- Fallback theme is readable and accessible
A robust fallback is especially important for privacy-restricted modes, headless browsers, and automated test environments.
Storage and persistence checklist
Theme issues often begin as storage issues, so verify the persistence layer directly when possible.
1) Inspect stored values
Checklist:
- Stored value matches the chosen theme exactly, using a stable schema
- Unknown values are ignored or sanitized safely
- Storage key names are documented and consistent
- Expiry behavior is understood if cookies are used
For example, if theme=dark is the stored value, verify the app does not also write contradictory flags like isDarkMode=true in another place unless there is a clear reason.
2) Test storage unavailable scenarios
Some environments block storage, or storage may be cleared between sessions.
Checklist:
- App does not crash when
localStorageaccess fails - Preference can still be used in-session, even if it cannot persist
- Graceful fallback messaging exists if persistence is impossible and should be visible to users
- Rejected storage writes are handled without breaking theme application
This is where UI state testing intersects with resilience testing. The theme can be applied visually even when persistence fails, but the app should not assume permanence.
3) Verify versioning and migration
If theme settings changed over time, check migration logic.
Checklist:
- Old preference values map correctly to new ones
- Deprecated keys are read or ignored intentionally
- Migration does not reset unrelated preferences
- Existing users do not get a surprise theme change after deployment
Design-system teams often forget this when they rename tokens or adjust the available palette set.
Accessibility checklist for dark mode and theme switching
Dark mode is not automatically accessible. Sometimes it improves comfort, sometimes it introduces contrast and focus problems, and sometimes the dark palette is visually polished but operationally difficult.
The WCAG guidelines are the right baseline when evaluating color contrast, focus visibility, and non-color cues.
1) Check color contrast in both themes
Checklist:
- Body text contrast meets target WCAG level in light and dark themes
- Secondary text remains readable, not just barely visible
- Disabled text is distinguishable but not ambiguous
- Buttons, links, and badges retain sufficient contrast against surfaces
Do not assume an inverted palette remains accessible. Many color systems look fine in design mockups but fail in dark mode because saturation and luminance relationships shift.
2) Verify focus states and keyboard navigation
Checklist:
- Focus rings are visible in both themes
- Focus does not disappear on dark surfaces
- Interactive elements retain adequate contrast when focused, hovered, and pressed
- Keyboard order remains stable after theme changes
A subtle but common bug is a custom focus outline that looks good on white backgrounds and becomes invisible on charcoal surfaces.
3) Test icons, charts, and status colors
Checklist:
- Icons remain legible when colors are inverted or tokenized
- Success, warning, and error indicators are not color-only signals
- Charts remain distinguishable with dark backgrounds and grid lines
- Third-party widgets follow the same theme or remain visually integrated enough to avoid jarring contrast
4) Verify reduced motion and transition behavior
Checklist:
- Theme transitions respect motion preferences where applicable
- Long animations do not interfere with reading or navigation
- Theme changes do not cause layout thrash or content reflow that disrupts screen reader usage
A short, deliberate fade can be fine. A broad animated repaint across many components can become distracting, especially when the user is already trying to read.
Browser and device matrix worth testing
Theme behavior can differ across rendering engines and platform conventions.
Minimum matrix
- Chromium desktop
- Firefox desktop
- Safari desktop
- Mobile Safari
- Mobile Chromium
If your audience is broad, include at least one tablet view and one mobile viewport where the control is still reachable and persistent.
Specific scenarios
Checklist:
- Private or incognito mode, where persistence may be restricted
- Browser sync enabled versus disabled
- Multiple monitor setups, if the system preference changes per display or OS session
- Installed PWA or standalone browser window, if supported
- Embedded browser contexts, if your product uses them
Even if your app is mostly responsive, theme switching can surface responsive layout regressions. A control placed in a desktop header may collapse into an overflow menu on smaller screens, and that menu needs the same persistence semantics. If your team is also validating breakpoints, pair this checklist with a responsive testing guide.
Automation strategy for repeatable preference checks
Theme testing is a strong candidate for automation because the behavior is deterministic once the rules are defined.
What to automate first
Start with high-value, low-flake checks:
- Toggle theme and assert the page root has the expected theme attribute or class
- Reload and confirm persistence
- Open a second page or tab context and verify stored preference is visible
- Validate key pages under both light and dark variants
- Run accessibility checks against both variants
Playwright example for storage-backed theme checks
import { test, expect } from '@playwright/test';
test('theme persists across reload', async ({ page, context }) => {
await page.goto('https://example.com');
await page.getByRole('button', { name: /dark mode/i }).click();
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
await page.reload(); await expect(page.locator(‘html’)).toHaveAttribute(‘data-theme’, ‘dark’);
const secondPage = await context.newPage(); await secondPage.goto(‘https://example.com’); await expect(secondPage.locator(‘html’)).toHaveAttribute(‘data-theme’, ‘dark’); });
This kind of test is useful because it validates the behavior users actually care about, not just the implementation detail of a theme toggle component.
When to avoid brittle assertions
Do not overfit tests to CSS details that may change during a redesign. Prefer stable signals such as:
data-themeattribute- root class name
- CSS variables applied to the root
- accessible names for controls
- persisted storage values
Visual regression tests can still help, but they should complement functional checks rather than replace them.
CI considerations
If theme preference is part of your regression suite, make sure the pipeline clears storage between tests unless a test explicitly depends on persisted state. Parallel runs can interfere with shared test users or shared storage if the setup is not isolated. This is the same class of problem discussed in test automation and continuous integration, where determinism matters more than surface coverage.
A practical checklist you can reuse
Use this condensed version as a working checklist during implementation or review.
Theme state
- Theme toggle is discoverable and operable by keyboard
- Theme state updates immediately and predictably
- Current selection is visible in the UI
- Theme change applies to the root and all child surfaces
Persistence
- Theme persists after refresh
- Theme persists after route changes
- Theme persists across tabs, if intended
- Logged-in and logged-out states behave correctly
- Storage failures are handled gracefully
System preference
- Default follows system preference when no user choice exists
- User override wins, if that is the product rule
- Changing OS theme during a session behaves as expected
- No-preference and fallback cases are safe
Accessibility
- Contrast passes in both themes
- Focus styles remain visible
- Icons, charts, and status colors remain legible
- Motion and transition behavior do not hinder use
Cross-browser and responsive behavior
- Chromium, Firefox, and Safari are checked
- Mobile and desktop layouts preserve the control
- Private browsing behavior is verified
- Embedded or PWA contexts are covered where relevant
Common failure patterns to look for
A checklist becomes more useful when you know what failures usually look like.
Flash of incorrect theme on load
This often happens when the app reads storage too late. The fix may involve server-side rendering hints, early inline scripts, or setting a default theme class before hydration.
Theme toggles the page but not modal overlays
If overlays, toasts, or menus are rendered in portals, they may not inherit the same CSS variables unless the theme token is propagated to the right root element.
Dark mode passes on homepage, fails on a secondary route
This usually indicates a component tree or layout shell that loads with a different stylesheet, a missing token import, or an environment-specific code path.
Cross-tab inconsistency
One tab changes the preference, but another tab keeps the old value until refresh. Decide whether that is acceptable. If not, add synchronization tests and confirm the app handles storage events correctly.
Accessibility regressions introduced by “just colors” changes
This is common when teams assume theme work is cosmetic. In practice, changing palettes can alter contrast, focus visibility, and semantic affordance at the same time.
How to make this checklist part of your release process
The fastest way to keep theme testing from becoming an afterthought is to tie it to existing release gates.
- Add the checklist to design-system acceptance criteria
- Include theme persistence in smoke tests for app shells
- Run accessibility checks on both light and dark variants
- Record the precedence rule for system versus user preference in your product docs
- Re-test after token refactors, storage migrations, and framework upgrades
If your team wants a repeatable browser-state workflow without building every check from scratch, Endtest is one possible alternative for validating preference-driven UI variants, especially when you want agentic AI-assisted, low-code flows with editable steps and built-in accessibility checks. For teams that need to verify WCAG-related issues as part of the same web test, its accessibility checks can help keep theme variants honest in a repeatable way.
Final takeaway
Theme switching is a compact feature with surprisingly broad test coverage. A solid dark mode testing checklist should confirm that the preference is chosen correctly, persisted correctly, reflected consistently across the UI, and still accessible under real browser conditions. When you treat theme behavior as UI state, not just styling, you catch the bugs users actually notice, the ones that show up after refresh, in another tab, or in the dark at the worst possible time.