Skip to content
ShineUI
Install

ThemePicker

Presets, a custom pair, and the kit in a toolbar.

<sui-theme-picker>MiscStable

About

Theme control for a header or a settings panel. It writes four data-sui-* attributes on a target node, so one pick restyles everything under it: preset, light, dark or the system's mode, brand or semantic status, and code panes. A custom primary or accent builds a derived stylesheet scoped to the same target.

Usage

<sui-theme-picker show-preset-name target="#tp-compact"></sui-theme-picker> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-theme-picker"); el.addEventListener("suiThemeChange", (e) => console.log(e.detail)); </script>
import { SuiThemePicker } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiThemePicker showPresetName target="#tp-compact" onSuiThemeChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiThemePicker } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiThemePicker show-preset-name target="#tp-compact" @sui-theme-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiThemePicker } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiThemePicker], template: `<sui-theme-picker show-preset-name target="#tp-compact" (suiThemeChange)="onChange($event)"></sui-theme-picker>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The compact trigger shows two live dots, the preset name with show-preset-name set, and a chevron; it opens a dialog with the full kit. Host aria-label names the trigger and the dialog; the default is Theme.

<div id="tp-compact" style="background:var(--canvas);color:var(--ink);padding:16px;border-radius:10px;border:1px solid var(--border)"> <sui-theme-picker show-preset-name target="#tp-compact"></sui-theme-picker> </div>

Panel

appearance="panel" lays the same kit out in flow, with no trigger and no dialog, for a settings page.

<div id="tp-panel" style="background:var(--canvas);color:var(--ink);padding:16px;border-radius:10px;border:1px solid var(--border);max-width:320px"> <sui-theme-picker appearance="panel" target="#tp-panel"></sui-theme-picker> </div>

Preset and target

preset picks a shipped palette and target names the node that receives data-sui-preset, data-sui-theme and data-sui-status, so this box turns Evergreen and the page does not. Press the sun button to see mode="dark" written to the same wrapper.

<div id="tp-evergreen" style="background:var(--canvas);color:var(--ink);padding:16px;border-radius:10px;border:1px solid var(--border);display:flex;flex-direction:column;gap:12px"> <sui-theme-picker preset="evergreen" show-preset-name target="#tp-evergreen"></sui-theme-picker> <sui-button variant="brand" label="Save"></sui-button> </div>

Semantic status

status="semantic" writes data-sui-status="semantic", which turns the status badges green, gold and red; brand removes the attribute and keeps the badges on the brand and accent hues. Errors are red in both.

<div id="tp-status" style="background:var(--canvas);color:var(--ink);padding:16px;border-radius:10px;border:1px solid var(--border);display:flex;flex-direction:column;gap:10px"> <sui-theme-picker appearance="panel" status="semantic" target="#tp-status"></sui-theme-picker> <sui-button variant="danger" label="Delete"></sui-button> </div>

Following the system

mode="system" reads prefers-color-scheme, writes light or dark to the target and follows the OS while connected; suiThemeChange reports mode "system" and the resolvedMode, and again when the OS changes. It is a starting state: the sun-or-moon switch shows what is painted and a press picks light or dark outright.

<div id="tp-system" style="background:var(--canvas);color:var(--ink);padding:16px;border-radius:10px;border:1px solid var(--border);display:flex;flex-direction:column;gap:12px"> <sui-theme-picker mode="system" target="#tp-system"></sui-theme-picker> <sui-button variant="brand" label="Save"></sui-button> </div>

Disabled

disabled blocks every pick and keeps the current theme applied.

<div id="tp-disabled" style="background:var(--canvas);color:var(--ink);padding:16px;border-radius:10px;border:1px solid var(--border)"> <sui-theme-picker show-preset-name disabled target="#tp-disabled"></sui-theme-picker> </div>

Methods and events

setTheme() applies a partial choice, and a primary that matches no shipped pair leaves the preset as custom and writes a derived stylesheet scoped to the target; reset() returns to ShineUI, light and brand and removes that sheet. suiThemeChange fires after each with the resolved choice, and does not bubble.

No change yet.

<div id="tp-methods" style="background:var(--canvas);color:var(--ink);padding:16px;border-radius:10px;border:1px solid var(--border);display:flex;flex-direction:column;gap:10px"> <sui-theme-picker appearance="panel" target="#tp-methods"></sui-theme-picker> <div style="display:flex;gap:8px;flex-wrap:wrap"> <sui-button id="tp-plum" variant="outline" size="sm" label="setTheme({ primary: plum })"></sui-button> <sui-button id="tp-reset-btn" variant="outline" size="sm" label="reset()"></sui-button> </div> <p id="tp-methods-out" style="margin:0;font:550 12px var(--font-sans);color:var(--ink-muted)">No change yet.</p> </div>
// setup(root): properties and events setup(root) { const out = root.querySelector('#tp-methods-out'); const el = root.querySelector('sui-theme-picker'); el.addEventListener('suiThemeChange', (e) => { const d = e.detail; out.textContent = `${d.preset} / ${d.mode} / ${d.status} / ${d.neutral} ยท ${d.primary} / ${d.accent}`; }); /* sui-audit-allow */ root.querySelector('#tp-plum').addEventListener('click', () => el.setTheme({ primary: '#3D1D50' })); root.querySelector('#tp-reset-btn').addEventListener('click', () => el.reset()); }

Keyboard

KeyAction
Tab Shift+TabMoves through the open dialog. Past its last control, closes it and moves on to the control after the trigger; before its first, closes it and stops on the trigger.
EscapeCloses the color editor if one is open, else the open Preset or Surface menu, else the dialog, and returns focus to the trigger.
TabIn the panel appearance, from a color well whose editor is open, moves into the editor. Past the end of the editor, closes it and moves on from the well.

API

<sui-theme-picker>

Properties

PropertyAttributeTypeDefaultDescription
accentColoraccent-colorstringstring | undefinedAccent hex. Used when accentMode is custom. Named accentColor because accent is a boolean tone flag on six other elements; the theme vocabulary shared with setTheme() and the suiThemeChange detail still calls the hue accent.
accentModeaccent-modeSuiThemeAccentMode"auto" | "custom" | "primary"'custom'Suggested chips, match primary, or a custom accent well. UI labels: Suggested | Primary | Custom.
appearanceappearance"compact" | "panel"'compact'compact is an outline trigger (two live dots, the preset name with label, a chevron) that opens a non-modal dialog, as a popover does; panel lays the same kit out in flow. The kit is a Preset menu, Swap, Surprise and Reset buttons and a light-or-dark switch that shows the mode painted (under mode="system" too; a press picks the other one outright), then Primary, Accent, Surface, Status and Code rows. The compact dialog, the Preset and Surface lists and the color editor paint in the browser's top layer while open, so a transformed, contained or clipping ancestor cannot move them or cut them off.
codecodeSuiThemeCode"auto" | "dark" | "light"'dark'Code panes and consoles: dark (default) or light, written as data-sui-code. Independent of mode, because the light set is computed from the theme rather than from the site's light or dark, so a dark site with a light code pane is a supported pair. Unrecognised values become dark.
disableddisabledbooleanfalseBlocks picks. The current theme stays applied.
modemodeSuiThemeMode"dark" | "light" | "system"'light'light, dark, or system, which follows the operating system's prefers-color-scheme for as long as the element is connected and writes whichever it resolves to. It is the starting state: the switch in the kit shows what is painted and, pressed, picks light or dark outright, so mode="system" follows the OS until the reader chooses. Unrecognised values become light. Light removes data-sui-theme.
neutralneutralSuiThemeNeutral"gray" | "mauve" | "mist" | "olive" | "sage" | "sand" | "slate" | "stone" | "tint" | "tintA" | "white" | "zinc"'tint'Neutral family. Unrecognised values become tint.
persistpersistbooleanfalseWhen true, read and write the full choice to localStorage under sui-theme.
presetpresetSuiThemePreset"achroma" | "aubergine" | "charcoal" | "cranberry" | "custom" | "espresso" | "evergreen" | "forest" | "graphite" | "harbor" | "indigo" | "midnight" | "ocean" | "olive" | "plum" | "royal" | "sage" | "shineui" | "slate" | "steel" | "terracotta" | "wine"'shineui'Named palette or custom. Unrecognised values become shineui.
primaryprimarystringstring | undefinedPrimary hex. Used when the choice leaves a shipped pair.
showPresetNameshow-preset-namebooleanfalseCompact trigger shows the preset display name (ShineUI, Evergreen, Custom and so on). It is a switch, not a string, which is why it is not called label. The trigger's accessible name is the host aria-label, default "Theme".
statusstatusSuiThemeStatus"brand" | "custom" | "semantic"'brand'brand (default) keeps the status badges on the brand and accent hues; semantic makes them green, gold and red and sets data-sui-status="semantic". Errors are red in both.
targettargetstring'html'CSS selector for the node that receives the theme attributes. Empty, unmatched or invalid falls back to html. The selector only finds the node: a custom sheet is scoped by the node's id or by a data-sui-scope the picker gives it, escaped, so the text of target never reaches CSS.

Events

EventDetailReactVueDescription
suiThemeChangeSuiThemeChoiceonSuiThemeChange@sui-theme-changeFires after attributes and any custom sheet are applied, from a user pick, setTheme(), reset(), or the operating system's color scheme changing under mode="system"; detail is { preset, mode, resolvedMode, status, code, primary, accent, neutral }. The only event: there is no suiClick. A state report, not a field's value: the picker applies the theme itself and posts nothing, so a form around it never hears it. Does not bubble; listen on the picker, or on the document in the capture phase.

Methods

MethodSignatureDescription
reset()reset() => Promise<void>Returns to ShineUI, light, brand, dark code panes, the default hex pair and the tint neutral. Removes the custom sheet, data-sui-status and data-sui-code.
setFocus()setFocus() => Promise<void>Compact: focus the trigger. Panel: focus the preset button.
setTheme()setTheme(partial?: Partial<SuiThemeChoice>) => Promise<void>Applies a theme. Omitted fields keep the current resolved value. Attributes (and the custom sheet, if any) are written before suiThemeChange fires.

Types

SuiThemeAccentMode type
No members.
SuiThemeChoice interfaceCurrent theme choice after attributes (and a custom sheet, when needed) are applied.
presetSuiThemePreset
modeSuiThemeMode
resolvedModeSuiThemeResolvedModeThe mode in force: mode itself, or the operating system's answer under system. Reported only; setTheme() ignores it.
statusSuiThemeStatus
codeSuiThemeCode
primarystring
accentstring
neutralSuiThemeNeutral
SuiThemeCode typeThe code pane's own light or dark, independent of the site's, or auto to follow the site's mode. dark is the default.
No members.
SuiThemeCode typeThe code pane's own light or dark, independent of the page's.
No members.
SuiThemeMode typelight, dark, or system, which follows the operating system's prefers-color-scheme.
No members.
SuiThemeMode typeLight, dark, or system: the operating system's prefers-color-scheme, followed while it changes.
No members.
SuiThemeNeutral typeA neutral family for canvases, borders and inks; the theme module's SuiThemeNeutral.
No members.
SuiThemeNeutral type
No members.
SuiThemePreset typeA shipped preset id, or custom for any other pair; the theme module's SuiThemePreset.
No members.
SuiThemePreset typeA shipped preset id, or custom for any other pair.
No members.
SuiThemePresetName type
No members.
SuiThemeResolvedMode typeWhat mode paints as: itself, or the operating system's answer under system.
No members.
SuiThemeResolvedMode typeWhat a mode paints as. system resolves to one of these.
No members.
SuiThemeStatus typebrand keeps the status badges on the brand and accent hues; semantic makes them green, gold and red; custom is three explicit colors, entered the moment any status color is edited. Errors (invalid fields, error text, destructive buttons) are red in every mode.
No members.
SuiThemeStatus typebrand keeps the status badges on the brand and accent hues; semantic makes them green, gold and red. Errors are red in both.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

Color19

--accentthe one chart highlight: the flagged series, down candles, the peak cell--bordercard + table outer borders--brand--control-borderthe resting edge of every field and control: 3:1 on its grounds--danger--disabled-bg--disabled-ink--focus-halo--focus-ring--ink--ink-faintmetadata beside body ink: 4.8:1 on the page grounds (5.4:1 in dark)--ink-mutedsecondary text: 7:1 on the page grounds and 1.5 times faint--ink-muted-on-washthe muted ink a selected, current or pressed row paints: 4.5:1 on that wash--ink-placeholdera field's placeholder, not a text tier: 4.5:1 on the page grounds--surfacecards, tables, inputs--surface-hovera hovered row or quiet control on any ground: the ink, translucent--surface-raisedmenus, popovers, toasts: the card in light, a step above it in dark--surface-selecteda selected row or option: the primary's hue, always heavier than a hover--surface-sunkentable headers, wells

Typography5

Ag--font-monocode, IDs, terminal onlyAg--font-sansAg--w-boldAg--w-mediumAg--w-semibold

Spacing and size7

42--control-lgform-page inputs34--control-mdDEFAULT toolbar/filter controls12--control-pad-x28--control-sm12--space-3700--z-dropdown800--z-overlay

Radius4

--radius-lgcards, tables, panels, dialogs--radius-mdALL controls: inputs, buttons, selects--radius-pillstatus pills, tabs-as-pills, company chip--radius-smlegend keys, small chips

Elevation2

--focus-shadow--shadow-popdropdowns, popovers, toasts

Motion3

--duration-fasthovers, presses, every exit--duration-overlayevery entrance: palette, popover, dialog, drawer, toast, menu, select list--ease-out

Icon2

14--icon-size-md12--icon-size-sm

Every name above is listed with its value and its role in Foundations.