> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cskn.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Theme rendering

> Render the owner's selected theme safely, and give them controls to tune it.

A theme decides how your app's user page looks. It never runs on the page that holds the visitor's session: `ThemeHost` runs the theme's bundle in a sandbox, receives a declarative page tree, and draws it with @cskn/ui primitives. There is no raw HTML, style string or script in that tree, so a third-party theme cannot reach the visitor's cookies.

Get the data from [`useThemeSelection`](/sdk/themes) in the SDK.

## ThemeHost

```tsx theme={null}
import { useThemeSelection } from '@cskn/sdk';
import { ThemeHost } from '@cskn/ui';

const t = useThemeSelection(username);

<ThemeHost
  bundleUrl={t.bundleUrl}
  bundleHash={t.selection?.theme.bundleHash}
  declaredTokens={t.selection?.theme.tokens ?? []}
  settings={t.selection?.settings}
  profile={t.selection?.context?.profile ?? { name: username, username }}
  slots={{ content: <Posts />, about: <About /> }}
  fallback={<BuiltInDesign />}
  pending={<NeutralFrame />}
  prerendered={t.prerendered}
  onThemeResult={t.reportResult}
/>
```

<ParamField path="bundleUrl" type="string | null" required>
  The version-pinned bundle URL from `useThemeSelection`. `null` renders `fallback` without starting a sandbox.
</ParamField>

<ParamField path="bundleHash" type="string">
  The published SHA-256. The sandbox refuses bytes that do not match.
</ParamField>

<ParamField path="declaredTokens" type="string[]" required>Token paths the theme declared.</ParamField>
<ParamField path="settings" type="Record<string, string | number>">The owner's token overrides.</ParamField>

<ParamField path="profile" type="ThemeProfile" required>
  The owner's `name`, `username`, `bio`, `avatar`, `location` and `headline`. A theme places these fields without seeing their values; the host fills them in.
</ParamField>

<ParamField path="slots" type="ThemeSlots" required>
  Your content for the regions a theme can reserve: `hero`, `content`, `about`, `footer`. The theme chooses where they go, never what they contain.
</ParamField>

<ParamField path="fallback" type="ReactNode" required>Drawn when no theme applies or a theme fails.</ParamField>

<ParamField path="pending" type="ReactNode">
  Drawn while the theme loads. Keep it different from `fallback`: drawing your built-in design and then swapping it for the theme reads as a glitch. Defaults to `fallback`.
</ParamField>

<ParamField path="prerendered" type="object | null">The tree the edge already produced. When it matches, the first paint needs no sandbox.</ParamField>
<ParamField path="accent" type="string">Color for the `accent` token.</ParamField>
<ParamField path="timeoutMs" type="number">How long to wait for the theme.</ParamField>
<ParamField path="onThemeResult" type="(result) => void">Outcome and timing of each render attempt. Pass `useThemeSelection().reportResult`.</ParamField>
<ParamField path="onThemeError" type="(message) => void">Called when a theme could not be used.</ParamField>

### Outcomes

`rendered` and `prerendered` are successes. A failure falls back to `fallback` and reports one of: `timeout`, `fetch-failed`, `hash-mismatch`, `no-export`, `threw`, `too-large`, `malformed`, `invalid-document`.

## ThemeSettings

Controls for the tokens a theme lets the owner tune.

```tsx theme={null}
import { useThemeSelection } from '@cskn/sdk';
import { ThemeSettings } from '@cskn/ui';

const { selection, selectTheme, isSaving } = useThemeSelection(username);

{selection && (
  <ThemeSettings
    declaredTokens={selection.theme.tokens}
    settings={selection.settings}
    onSave={(next) => selectTheme(selection.theme.slug, next)}
    isSaving={isSaving}
  />
)}
```

<ParamField path="declaredTokens" type="string[]" required>The theme's tunable token paths.</ParamField>
<ParamField path="settings" type="Record<string, string | number>">Current overrides.</ParamField>
<ParamField path="defaults" type="Record<string, string | number>">The theme's own token values, shown as each control's default. Without them the controls show @cskn/ui's neutral defaults.</ParamField>
<ParamField path="onSave" type="(settings) => void | Promise<void>" required>Save handler.</ParamField>
<ParamField path="isSaving" type="boolean">Disable while saving.</ParamField>
<ParamField path="labels" type="object">Text overrides.</ParamField>

## Variants inside slots

A theme can ask the host for named component variants (for example a card style). Read them inside your slot content:

```tsx theme={null}
import { useThemeVariant } from '@cskn/ui';

function PostCard(props: Props) {
  const style = useThemeVariant('card', ['plain', 'framed', 'image'] as const, 'plain');
  return <Card className={`post post--${style}`} {...props} />;
}
```

Unknown names and values fall back to the default you pass. `useThemeVariants()` returns all of them.

## Lower-level exports

`runThemeInSandbox`, `ThemeSandboxError`, `ThemeTree`, `ThemeNode`, `isThemeDocument`, `themeTokenControls`, `themeOverrideFromSettings`, `themeStyle`, `safeTokenValue`, `googleFontsHref`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.