Websites & practice

Light and dark: designing consistent appearances

Consistent content, readable surfaces and an understandable personal choice, with source examples and a review plan for both appearances.

AI-generated material illustration: matching light and dark ceramic panels share a red accent and a circle divided into light and dark halves.

A dark presentation adds another design task. Text, cards, forms and images still need to work together. Simply exchanging black and white can blur surface levels or make brand accents difficult to read on their new backgrounds.

This guide develops a small system for a fictional consultancy. It combines colour roles, a system default and a direct appearance control. The source is educational material and needs integration with the actual components, security rules and loading architecture before production use.

1. Establish consistency through meaning

Presentation can change while a component's task remains. A primary action stays primary and an error explains the same state. Navigation does not need different labels merely because its background is now dark.

Inventory the consultancy's roles first: page surface, content surface, main text, supporting text, border, action and focus. Choose light and dark values for each role afterwards. That prevents individual pages from inventing unrelated exceptions.

Hierarchy should remain understandable too. If cards are distinguished through surfaces in light mode, the dark version needs a corresponding relationship. A border may communicate it more clearly than an almost invisible shadow.

Assess complete tasks. A dark specimen can look refined while the contact journey loses orientation in context. Acceptance connects the design with actual interaction.

2. Distinguish the system default from personal choice

Without a stored decision, a page can follow the requested system appearance. The MDN prefers-color-scheme reference describes the relevant media feature. It supplies a preference rather than establishing which presentation is always best for everyone.

A direct control then permits a personal choice. Its first activation switches from the visible version to the other. People do not need to understand a menu of technical settings first. The accessible name describes the next action.

Our example stores that explicit choice. A later system change should not override it unexpectedly. Without a personal selection, the page can respond to system changes. Define the precedence and test both directions.

Initial conditionPresentationNext activation
No personal choice, light systemLightSwitch to dark and store the choice
No personal choice, dark systemDarkSwitch to light and store the choice
Personal choice existsStored versionSwitch directly to the alternative
Storage unavailableChoice for the current pageRemain usable

3. Define colour roles together

A shared role system avoids scattered values. Components request a meaning, such as a content surface or the text on it. Appearance can change those roles without redefining every component's entire design.

:root {
  --page: #f3f0e9;
  --surface: #ffffff;
  --text: #202326;
  --line: #747474;
  --action: #d00000;
  --on-action: #ffffff;
  color-scheme: light;
}
:root[data-theme="dark"] {
  --page: #16191c;
  --surface: #202326;
  --text: #f3f0e9;
  --line: #91979d;
  color-scheme: dark;
}
body { color: var(--text); background: var(--page); }
.content-card { background: var(--surface); }
.primary-action { color: var(--on-action); background: var(--action); }

These colours form a specific teaching example. Review every combination used in the actual project. The red action retains white text here. Small red text on anthracite needs another appropriate treatment because one colour is not equally legible on every surface.

4. Include browser-rendered components

Some surfaces come from the browser rather than custom styling. These can include native controls and other interface parts. The MDN color-scheme reference explains how supported schemes can be declared. That does not define all the project's custom colours.

Inspect actual forms in both modes. An input can acquire a background different from the expected one. Placeholder text, selection and error messages still need to remain understandable. A dark page background does not demonstrate that the entire input interface changes appropriately.

Selection controls deserve their own review. The indicator, label and internal spacing must remain apparent. A custom treatment should be assessed through its actual states rather than inferred solely from a padding value.

Browsers may render details differently. Record which combinations were actually inspected. One screenshot from one browser does not establish universal acceptance of every native control.

5. Make the appearance control unambiguous

Our example uses a compact button containing a circle divided into light and dark halves. It switches directly between the two presentations. A usable activation area, visible keyboard focus and a meaningful accessible name belong to the component.

<button id="appearance-toggle" type="button" hidden
        aria-label="Use dark appearance">
  <span class="appearance-symbol" aria-hidden="true"></span>
</button>

The symbol is decorative because the button already has an accessible name. Update that name after every change. “Appearance” alone describes the area less specifically than the next action performed by this immediate control.

The function should remain reachable without a mouse. Do not restrict the actual target to the small visible circle. A larger surrounding button can support activation without dominating the header.

6. Implement the choice transparently

The JavaScript sketch runs after the button exists. It demonstrates precedence between an explicit choice and the system. Storage failures do not interrupt the current interaction. Its storage key is illustrative and should follow the project's conventions.

const root = document.documentElement;
const toggle = document.querySelector("#appearance-toggle");
const system = matchMedia("(prefers-color-scheme: dark)");
let choice = null;
try {
  const stored = localStorage.getItem("appearance");
  if (["light", "dark"].includes(stored)) choice = stored;
} catch {}
const current = () => choice ?? (system.matches ? "dark" : "light");
function apply() {
  const mode = current();
  root.dataset.theme = mode;
  toggle.setAttribute("aria-label", mode === "dark"
    ? "Use light appearance" : "Use dark appearance");
}
toggle.addEventListener("click", () => {
  choice = current() === "dark" ? "light" : "dark";
  try { localStorage.setItem("appearance", choice); } catch {}
  apply();
});
system.addEventListener("change", () => {
  if (choice === null) apply();
});
apply();
toggle.hidden = false;

The MDN matchMedia documentation explains querying and observing media changes. This sketch deliberately addresses a bounded flow. Additional windows, application states and storage strategies need further project decisions.

7. Provide a usable state without JavaScript

Content should remain legible when a script does not execute. The base palette already provides a usable light view. CSS can also follow the system while the document has no explicit appearance attribute.

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --page: #16191c;
    --surface: #202326;
    --text: #f3f0e9;
    --line: #91979d;
    color-scheme: dark;
  }
}

The illustrated switch itself requires JavaScript. Do not expose a nonfunctional button as an available action. It becomes visible after successful initialisation or can be replaced with another genuinely functioning fallback.

Content presentation is a separate concern. Losing the switching function should not make text unreadable or forms invisible. Review the distinction with scripting disabled or blocked.

8. Keep initial rendering stable

If the stored choice is applied late, the wrong appearance can show first. That visible change can be distracting. Initialisation timing belongs to architecture rather than being left as the final visual correction.

The teaching example stays simple by placing behaviour after its control. A production implementation can establish the initial appearance earlier in the document head and bind the button later. Integrate that decision with the project's actual security and script policies.

Inspect an empty cache and delayed resources. A fully warmed-up page can conceal the problem. The relevant evidence covers visible states from navigation until the final presentation.

Avoid unnecessary page-wide transitions. A colour change should not unexpectedly animate every element or make input unstable. Treat any intended motion as a separate design feature requiring its own review.

9. Assess contrast in every state

A suitable light pair does not automatically become a suitable dark pair. Review body text, supporting information, links, actions, selection and focus on the surface actually behind each one. A placeholder can be weaker than the entered text.

The W3C text contrast explanation describes the relevant requirement and conditions. Assess each actual use. Choosing a dark background alone says nothing about all the other combinations.

AreaLight reviewDark review
Main textPage and content surfacesEvery dark surface level
ActionText, background and focusBrand surface and readable label
FormEntry, help and errorNative and custom surfaces together
Image or logoTransparency and contrastNo vanished wordmark or distorted representation

10. Treat images and documents appropriately

An actual application screenshot represents a particular interface. Indiscriminate inversion can distort its colours and meaning. Preserve the represented content and place it within a suitable surrounding surface if necessary.

A logo may need approved light and dark variants. Its form remains consistent with the brand. Do not replace one file with an arbitrary bright version; inspect the actual approved sources.

Embedded documents and third-party material may have independent colour treatments. The surrounding page should communicate that transition clearly. It cannot automatically control every internal detail of an external document.

Review editorial graphic labels on their actual background too. Transparent artwork containing dark text may work in light mode and disappear in dark mode. An appropriate opaque surface can be a more reliable choice.

11. Test the complete switching sequence

Start without a stored choice. Use a light system and then a dark system, then activate the control. The visible presentation and the next accessible action name should agree.

Reload and confirm that the explicit choice remains where storage is available. A later system change should not displace it. If storage is deliberately blocked, the current page should still switch.

Include keyboard interaction, narrow views and form error states. The home page alone does not cover every component. Add later features to the same test matrix.

Document actual browsers, viewport sizes and inspected states. Emulation should not be described as physical-device acceptance. The limitations of the review should be as understandable as its outcome.

12. Treat the symbol button as a complete component

The control consists of more than its small icon. The surrounding button supplies the actual activation area. Border, surface and focus must be apparent in both modes. The following CSS complements the illustrated HTML and JavaScript.

#appearance-toggle {
  display: inline-grid;
  place-items: center;
  min-inline-size: 44px;
  min-block-size: 44px;
  padding: .5rem;
  border: 1px solid var(--line);
  border-radius: .75rem;
  color: var(--text);
  background: var(--surface);
}
#appearance-toggle[hidden] { display: none; }
#appearance-toggle:focus-visible { outline: 3px solid currentColor;
  outline-offset: 3px; }
.appearance-symbol {
  inline-size: 1.1rem;
  aspect-ratio: 1;
  border: 1px solid currentColor;
  border-radius: 50%;
  background: linear-gradient(90deg, #fff 50%, #202326 50%);
}

The initially hidden state avoids a nonfunctional control when scripting is unavailable. Successful initialisation reveals it. The symbol does not need to change shape after every activation; the accessible name and visible page together communicate the next change.

Review placement within the entire header. Language selection, contact and navigation also require room. The new component should not clip another essential function in a narrow view.

13. Migrate an existing site in bounded steps

Inventory scattered fixed colours first and map them to actual roles. Similar greys may serve different purposes or may have drifted apart accidentally. Decide from content and components rather than an automatic string replacement alone.

Move a bounded area into the role system, such as a service overview. Check normal state, focus and long content in both presentations before applying the rules elsewhere.

Preserve functioning special cases. An embedded screenshot may need its own light surface and a technical diagram may have been designed for a defined background. Treat those cases deliberately instead of changing them all through global filters.

Repeat complete visitor tasks after migration. A functioning card collection does not establish that later form confirmation is readable. Finish with a task-based review and a documented recovery path for the files actually changed.

14. Bind new components to the agreed roles

Long-term consistency depends on maintenance. Establish the meaning of a new information region first, then apply existing text, surface and border roles or define a justified additional role.

A role description records intended backgrounds and states. Supporting text needs checking on both page and raised-card surfaces, for example. Documentation should not imply permission to use one colour on arbitrary future backgrounds without review.

Record which images and components need separate variants. A later contributor should understand why a logo changes while a screenshot remains intact. That distinction is a meaningful design decision within the system.

Include less prominent states too: selected table rows, entered search terms and disabled actions. These may use different roles from the main view. Explain whether the state remains apparent and which actions are possible; a collection of dark screenshots alone does not describe those differences.

Include an invalid stored value in the review. The example accepts only the explicit strings “light” and “dark”; an older or unrelated value falls back to the system. That bounded interpretation avoids allowing arbitrary stored content to become a presentation state. The storage key should also be chosen deliberately when several applications share an origin, so that one application's setting does not unexpectedly replace another's preference.

Another useful case begins with a dark system and an explicit light choice. Navigate to another page, reload and change the system setting. The interface should remain light until the person activates the appearance control again. Describe that expected behaviour in the component contract, alongside the case with no personal choice. The result then remains assessable when the implementation is refactored or another team member introduces a new page template.

A consistent-appearance checklist

  • Colour roles and functional meaning are defined centrally.
  • Both versions have understandable hierarchy.
  • The system is considered before a personal choice exists.
  • The first activation switches from the visible version.
  • The accessible name describes the next action.
  • An explicit choice survives later system changes.
  • Unavailable storage does not interrupt current interaction.
  • Content remains legible without scripting.
  • Initial rendering has been checked with delayed resources.
  • Forms, focus, logos and content images are reviewed separately.
  • New components repeat the same complete matrix.

Common light and dark mode questions

Must both versions use identical values?

Their meanings should be consistent. Actual values can differ to work on the relevant backgrounds.

Is dark mode inherently better?

A universal claim is not helpful. Preferences and situations vary. A clear choice and carefully developed alternatives provide a practical foundation.

Can the system replace an explicit choice?

It is a useful starting point. Someone who deliberately selects another appearance should be able to retain that decision understandably.

Review each new content component in both appearances before adding it to the shared library. Examples include a high-priority notice, an illustrated reference card and a form field with a longer error message. Approval should identify the actual roles and states examined. Do not reuse a red background merely because that red was checked elsewhere. Text, surface and meaning must work together in this new combination as well.

For images, distinguish the content of the asset from the surrounding interface. A transparent logo can become difficult to read against a different background. A photograph should not automatically have its colours transformed simply because the theme changes. Editors check its purpose and designers choose an appropriate presentation. Image provenance and usage permissions remain separate responsibilities rather than following automatically from a colour decision.

The stored choice in this example belongs to the browser context being used. It is not an account-wide setting across every device. This boundary matters for support and testing. If storage is blocked, direct switching should still work even though the choice may not persist after a reload. The application should not infer successful permanent storage from a successful visual change. Examine those situations using an explicit test setup rather than assuming that one browser run establishes all persistence behaviour.

Record embedded content separately too. An external interface may use its own appearance rules and may not inherit the host page's colour roles. Check whether it remains readable and clearly bounded in both surroundings. A dark page surface does not automatically make every embedded component appropriate. Document those dependencies and review the visible combination again when either side changes. This keeps component maintenance tied to what visitors actually encounter rather than only to the values in a central token file.

Sources and related foundations

The linked MDN references and W3C WAI explanation were checked on 9 November 2026. The role model, state matrix and source examples are original educational material.

Continue with Business website quality and Meaningful HTML structure and interaction.

Let’s move forward

What would you like to change?

An idea, a specific challenge or your next step with AI. We’re listening.

Let’s talk