Theming
Light and dark modes, design tokens, density and the flat appearance.
Light and dark
Every color role is a light-dark() pair, so a theme follows the color-scheme of its element. tp-theme-switcher stores the choice and applies it to the document root, which is how the switcher in this site’s header themes every demo on the page.
To avoid a flash of the wrong scheme, apply the stored preference from <head>:
Tokens
Components read inherited --tp-* custom properties. Override a few roles on any subtree for a brand region, or replace the full set for a new theme.
Spacing is the exception: the space-* roles, control heights and icon sizes derive from the spacing seed, so a region changes its spacing through Density, not by setting --tp-spacing on the subtree.
Content colors
A separate set of roles paints colors that user content names, such as a highlight, colored text or a table cell background in the text editor, and the swatches that offer them. Each of nine hues, gray, brown, orange, yellow, green, blue, purple, pink and red, has a surface role, --tp-content-<hue>, for highlights and cell fills, and a foreground role, --tp-content-<hue>-foreground, for colored text, in light and dark. Keep them for content: interface controls use the status and accent roles.
A replacement theme that defines the full token set must define these 18 roles too.
Motion
Motion has its own token roles: three durations, --tp-duration-fast (200ms) for feedback, --tp-duration-normal (280ms) for surfaces and layout moves and --tp-duration-slow (560ms) for reveals, and three easings, --tp-easing-standard for changes within the page, --tp-easing-enter for arrivals and --tp-easing-exit for departures. Popups and dialogs open on the enter easing and close on the exit easing. Every finite animation is multiplied by --tp-motion-scale, which reduced motion sets to 0. Override the roles on any subtree to retime everything inside it.
Density
The token set has two density modes. default uses the theme’s spacing seed; compact uses a smaller spacing seed (0.1875rem against the default 0.25rem) and a smaller type scale (0.9 against the default 1), so padding, gaps, control heights, icon sizes and text sizes all tighten together. Set density="compact" on any element to make it and everything inside it compact, and density="default" to return part of a compact region to the default density. Regions nest in any order, so one document can mix both:
spacingspace-2control-height-xs … lgicon-size-xs … lgtype-scaletext-xs, text-sm, text-baseLibrary elements also take density as an attribute or a property (element.density = 'compact', or density in the framework bindings), which works inside other shadow roots where the default stylesheet’s document rules do not reach. Popups opened from a region, such as a Select list or a Menu, keep the region’s density, also when they are portaled to the document body, and changing density on a region updates surfaces that are already open.
Density and a component’s size combine: size picks a step of the control scale, and density sets how large that scale is. A compact region of size="sm" controls is smaller still.
A theme sets the seeds of each mode on :root: --tp-spacing and --tp-type-scale for the default mode, --tp-spacing-compact and --tp-type-scale-compact for the compact mode. Every derived role follows in each region, so a theme never restates the space-*, control, icon or text-* roles:
Every preview on this site has a Default and Compact selector that sets density on that preview; the choice carries across previews and pages.
Status colors
Status variants, such as a destructive Button or a success Badge, paint the role’s tinted fill, its text tone and its edge: --tp-destructive-subtle, --tp-destructive-subtle-foreground and --tp-destructive-border. The default and secondary variants keep their solid fill and foreground. An Alert paints its severity the same way: the tinted fill, the edge, and the text tone for its title, description and icon; informational uses the primary role. Error messages and destructive menu commands use the destructive text tone, so they stay readable on any surface.
Lines and edges
Every line a component draws belongs to one kind, and each kind takes exactly one color role, so a theme or an appearance mode changes a kind everywhere at once:
edgehairlineinputcontrol-border<role>-borderborderIn a joined group (Button group, Toggle group, Field group) the seam between members takes the member’s own edge, or cuts through the fills where the member has none, such as two secondary Buttons. In the flat appearance every seam is a cut, joined-edge-width wide. A Separator inside a joined group is part of that cut.
Flat appearance
The token set has two appearance modes. default draws an edge around components and panels: cards, popups, fields, outline controls and status variants. flat removes those edges and separates components by fill instead: panels take a fill a step from the page, fields and outline controls a step from panels, and status variants keep their tint without an edge. Checkboxes and radios lose their boundary too and show as filled marks; a chosen radio is a plain primary fill with no dot (choice, choice-foreground). Dividers, focus rings, shadows and corner radii stay the same.
Set appearance="flat" on any element to make it and everything inside it flat, and appearance="default" to return part of a flat region to the default look. Regions nest in any order and combine with density:
edge, inputborder colorcontrol-borderchoice, choice-foreground<role>-bordercardcontrolcontrol-stronginput colorLibrary elements also take appearance as an attribute or a property (element.appearance = 'flat', or appearance in the framework bindings), which works inside other shadow roots. Popups opened from a region keep its appearance, also when they are portaled to the document body.