Web Component Theming

Styling third-party web components with platform design tokens, including live light/dark switching in the shadow DOM

web-componentsthemingdesign-tokens

Web components share the platform's look automatically: the sandbox delivers the active theme's design tokens (daisyUI-style CSS variables) and keeps them in sync live — including light/dark switches — without a reload.

How theme delivery works

  1. The sandbox page applies the active theme before your bundle runs, so components never paint unthemed.
  2. Theme changes (for example the user toggling dark mode) arrive as live bridge messages; the sandbox updates its data-theme attribute and your component can react through the SDK.
  3. Because the sandbox blocks network access, the theme stylesheet is delivered from the sandbox origin — components must adopt it as a <link>, which the SDK handles for you.

Adopting the theme in a shadow DOM

Components render in shadow DOM, which does not inherit document stylesheets. Use the SDK helper once, at mount:

import { connect } from "@smallstack/web-component-sdk";

const client = connect(hostElement);
const stopTheming = client.adoptThemeStylesheets(shadowRoot);
// ...on unmount:
stopTheming();
client.disconnect();

adoptThemeStylesheets appends the theme's stylesheet links into your shadow root and swaps them only when the URLs actually change — a plain light/dark switch keeps the same stylesheet and restyles instantly through the token variables.

Styling with token variables

Write your styles against the platform variables, with fallbacks for standalone rendering:

.card {
  background: var(--color-base-200, transparent);
  color: var(--color-base-content, inherit);
  border-radius: var(--radius-box, 0.5rem);
}
h2 {
  color: var(--color-primary, inherit);
}

Commonly used variables (each theme defines light and dark values):

Variable Purpose
--color-base-100 / --color-base-200 / --color-base-300 Surface backgrounds, lightest to strongest
--color-base-content Default text color on base surfaces
--color-primary / --color-primary-content Brand color and text on it
--color-secondary, --color-accent, --color-neutral Additional palette roles (each with -content pair)
--color-info, --color-success, --color-warning, --color-error Status colors (each with -content pair)
--radius-box, --radius-field, --radius-selector Corner radii for cards, inputs, small controls

Reacting to light/dark in logic

If your component needs to know the mode (not just restyle), subscribe:

client.onTheme((theme) => {
  console.log(theme.name, theme.dark); // e.g. "slate-teal-dark", true
});

The dev harness ships a dark-mode toggle so you can verify both modes locally — npm run dev in any sample workspace.