Theming

The widget ships its own palette inside a shadow root, so your page styles never leak in and its styles never leak out. You retune it with a color scheme and CSS token overrides.

Color scheme

colorScheme is "light", "dark", or "system" (the default), which follows the OS setting live.

<AstralBeamChat colorScheme="dark" />

Theme tokens

theme overrides the widget's shadcn/ui tokens per scheme. light is the base applied in both, and dark layers on top when the resolved scheme is dark.

<AstralBeamChat
  theme={{
    light: { "--primary": "oklch(0.55 0.2 260)", "--radius": "0.5rem" },
    dark: { "--primary": "oklch(0.7 0.15 260)" },
  }}
/>
  • Keys are CSS custom property names (--background, --primary, --radius, --font-sans, ...).
  • Values apply immediately on prop or update changes.
  • Because the shadow root cannot read your stylesheet, tokens must be passed here, not inherited.

Custom CSS

Because chat renders inside a Shadow DOM, pass application-owned CSS through customCss to customize its layout and controls. Let's give the composer a larger input:

<AstralBeamChat customCss={`textarea { min-height: 5rem; }`} />

The same option works with mountAstralBeamChat. Call handle.update({ customCss }) or change the React prop to apply styles without resetting the conversation or draft. Set it to undefined to restore SDK styles. Pass ordinary, trusted application CSS, not uncompiled Tailwind classes or user-supplied content. Prefer theme for colors and fonts. Directories support the same option.

Widget renders

Your own widgets (see Tools and widgets) render in the host page's light DOM, not the shadow root.

  • They inherit your page's typography and CSS custom properties automatically.
  • Your own selectors match a render like any other element. Target [slot^="astralbeam-widget-"] for all of them.
  • Inherited properties are read from the mount target's parent, so rules on the target itself are missed.
  • Tokens declared only in a cross-origin stylesheet cannot be read.
  • The bridge pins -webkit-text-fill-color to currentColor on projected content, so your color rules always drive text paint.