---
title: Web Component Theming
description: Styling third-party web components with platform design tokens, including live light/dark switching in the shadow DOM
date: 2026-07-22
order: 5
tags:
  - web-components
  - theming
  - design-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:

```ts
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:

```css
.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:

```ts
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.

## Related

- [Building Web Components](/web-components) — the authoring guide
- [Security model](./security.md) — why stylesheets are adopted, not fetched
