COMPONENTS / MOLECULES / al-theme-switcher← ALL COMPONENTS

Theme Switcher

<al-theme-switcher><ALThemeSwitcher> REACTbeta

Reference generated from the custom elements manifest — 0 properties, 0 slots, 1 events.

PLAYGROUND0 CONTROLS
PREVIEW / al-theme-switcher / ALTITUDE
Loading preview
Hello world
CODE — HTML / REACT

Guidance

AUTHORED · CITED TO SOURCE

The control that changes the active brand and mode. It is a popover holding a menu of brand+mode entries, and its real job is to find the nearest <al-theme> ancestor and set brand and mode attributes on it — a scoped change, not a global one, so several themes can coexist in one document.

WHEN TO USE

  • A product lets the user choose between light and dark, or between brands, and the choice should apply to a region of the page rather than to the document.
  • You are demoing or documenting the system and want a visible way to switch brands without reloading.
  • You need to react to the change — onThemeSwitcherChange reports the resolved theme, the logo, and whether the change was applied scoped or through the deprecated global path.

WHEN NOT TO USE

  • You want the theme applied and never changed by the user. Wrap the region in the theme host and set its attributes directly — the switcher is the UI for changing it, not the mechanism for applying it.USE THEME →
  • You need a general-purpose menu of options anchored to a button. The switcher is a specific control with a fixed, data-driven brand list.USE POPOVER →
  • The choice is any other binary setting. Nothing here generalises beyond brand and mode.USE TOGGLE →

DO

  • Put an <al-theme> ancestor above the switcher. That is the supported path: the switcher walks up to the nearest one and sets its attributes, which scopes the change instead of mutating the document.
  • Set scopedOnly when you want the deprecated global stylesheet swap to never run, even if the theme host is missing. It turns a silent fallback into a no-op you can notice.
  • Read scoped from the onThemeSwitcherChange detail if you need to know which path ran. It is true when a theme ancestor was found and updated, false when the legacy swap was used.
  • Add a brand by adding an entry to the component's brand table rather than by branching in consumer code — the list is data, and the component does not change when it grows.

DON’T

  • Don't depend on the global swap. It removes and re-appends a <style id="al-tokens-sheet"> element in the document head, is marked deprecated with a removal target of 3.0.0, and exists only because one in-repo fixture still uses the switcher with no theme wrapper.
  • Don't put two switchers over the same theme host expecting them to stay in step. Each one sets attributes on the host; neither reads the other's state.
  • Don't assume changing the theme re-renders the whole page cheaply when the page is server-rendered. Each themed host serialises its brand and mode blocks into its own declarative shadow DOM template, which is a real cost when many themed islands are rendered on the server.
  • Don't expose a brand switch to end users when the brand is a property of the product rather than a preference. Mode is a preference; brand usually is not.

ACCESSIBILITY IN PRACTICE

  • The switcher composes a button, a popover and a menu, so it inherits their keyboard behaviour rather than defining its own — the trigger is a real button and the entries are real menu items.
  • A theme change is a visual change with no announcement. If the switch also changes meaning — a high-contrast mode, say — say so in text near the control, because nothing here tells a screen-reader user what changed.
  • Both light and dark entries have to clear contrast on their own. A switcher is not a substitute for either mode being accessible.

CONTENT GUIDELINES

  • Label the entries with what the user gets, not with the token layer behind it: "Dark", "Light", the brand's name.
  • Keep the trigger's accessible name about the setting, not about the widget — "Change theme" rather than "Settings", which is what the gear icon alone implies.

WHERE THIS CAME FROM

  • libs/al-web-components/components/theme-switcher/theme-switcher.tsThe scoped path: the nearest theme ancestor is found and its brand and mode attributes are set, with no global mutation.
  • libs/al-web-components/components/theme-switcher/theme-switcher.tsThe legacy document-level stylesheet swap, why it is still present, and its removal target.
  • libs/al-web-components/components/theme-switcher/theme-switcher.tsThe change event, including the `scoped` flag that reports which of the two paths ran.
  • MIGRATION.mdThe migration note covering the deprecated global swap and what replaces it.
  • .altitude/SSR.mdThe measured server-rendering cost of a themed host — every brand and mode block is serialised into each host's declarative shadow DOM template.

Each line above names a file and a literal string that must still appear in it.pnpm gate:guidance re-reads them from this built page, so guidance that has stopped being true fails a check rather than misleading you.

NOT TRACKEDAltitude Design System

Deliberately not represented in Figma.

DECIDED BY
NEVER SYNCED
FIGMA SIDE
NEVER OBSERVED
LAST CONFIRMED SYNC
NEVER
PUBLIC SURFACE
0 ATTR · 0 SLOT · 1 EVENT · 0 PART

The two sides have never been confirmed equal, so there is no baseline to compare against.

Owns no pixels — an al-button + al-popover + al-menu composition; a Figma set would duplicate three components (altitude-figma-sync: NOT_COMPONENTS).

CHECKEVIDENCEDETAILRESULT
Default stateAXEtheme-switcher--DefaultPASS
Advanced statesAXE0 further storiesPASS
Screen readerMANUALNo screen-reader pass has been recorded for this component.NOT RECORDED
Keyboard navigationINTERACTION TESTSNo interaction tests and no manual keyboard pass.NOT RECORDED

MEASURED 2026-09-08 · 1 STORIES · AXE WCAG2A + WCAG2AA + WCAG21A + WCAG21AA + WCAG22AA

COLOR CONTRAST0 RULES FAILING

The CI gate disables color-contrast globally (libs/al-web-components/story-fixture/src/main.ts), so its result never reaches a test run. It is enabled for this measurement, and reported here whichever way it comes out.

NO CONTRAST VIOLATIONS ACROSS THIS COMPONENT’S 1 STORIES.

import '@southleft/al-web-components/components/theme-switcher';
import { ALThemeSwitcher } from '@southleft/al-react';

THIS COMPONENT DECLARES NO PUBLIC PROPERTIES IN THE MANIFEST.

THIS COMPONENT DECLARES NO SLOTS IN THE MANIFEST.

EVENTDESCRIPTIONTYPE
onThemeSwitcherChangeFired when the theme changes. Detail: `{ currentTheme, currentLogo, scoped }` — `scoped` is `true` when an ancestor `<al-theme>` was found and updated, `false` when the deprecated global swap was used.CustomEvent
METHODDESCRIPTION
setTheme()Set theme by key. The data-driven implementation: 1. Walk up the DOM until we find an `<al-theme>` ancestor (the scoped target). When present, set `brand` and `mode` attributes on it — no global mutation. 2. If no `<al-theme>` ancestor exists AND `scopedOnly` is false, perform the legacy `<style id="al-tokens-sheet">` swap. DEPRECATED, removal target 3.0.0 — kept because `apps/web-components/index.html` uses the switcher with no wrapper. See MIGRATION.md § "Legacy fallback". 3. Dispatch `onThemeSwitcherChange` with the resolved attrs.
setStyles()Backward-compatible alias. Delete once T6.1 codemods consumers.

SOURCE — components/theme-switcher/theme-switcher.ts · Molecules/Theme Switcher