Theming

ShellDocs is designed to be re-skinnable via CSS variables : the shipped tokens.css sets every color, radius, spacing, and font stack, and the entire chrome cascades from there. Override the tokens, and every primitive follows.

The token file

ShellDocs.Tokens ships one CSS file : _content/ShellDocs.Tokens/tokens.css. It declares two :root blocks : the light-mode defaults and the :root.dark overrides that kick in when the theme toggle is set to dark.

Where the tokens come from

Each variable value is set via hsl(...) or oklch(...) for perceptual uniformity. The shadcn-compatible naming (--background, --foreground, --primary, --muted, …) means any component library that speaks the same vocabulary drops in without extra mapping.

Overriding tokens

Add a stylesheet after tokens.css in your App.razor:

html
<link rel="stylesheet" href="_content/ShellDocs.Tokens/tokens.css" />
<link rel="stylesheet" href="brand.css" />

Then override any token in brand.css:

css
:root {
    --primary: oklch(0.55 0.22 240);
    --radius: 0.75rem;
    --font-sans: "Söhne", ui-sans-serif, system-ui, sans-serif;
}

:root.dark {
    --primary: oklch(0.75 0.18 240);
}

The cascade takes care of the rest : buttons, links, focus rings, the sidebar highlight, and every callout all pull from --primary and update immediately.

The full token surface

Prop Type Default Description
--background color Page background.
--foreground color Default text color.
--card color Card / elevated surface background.
--card-foreground color Text on card surfaces.
--popover color Popover / floating menu background.
--popover-foreground color Text inside popovers.
--primary color Primary action color : buttons, links, highlights.
--primary-foreground color Text on primary surfaces.
--secondary color Secondary surface / tag background.
--secondary-foreground color Text on secondary surfaces.
--muted color Subtle background : hover states, code blocks.
--muted-foreground color Secondary text, hints, timestamps.
--accent color Focus rings, hover accents. Semi-transparent.
--accent-foreground color Text on accent surfaces.
--border color Default border color.
--border-strong color Prominent border : dividers between sections.
--input color Form input background.
--ring color Focus-ring outline color.
--info color Info callout accent.
--warning color Warning callout accent.
--error color Error / danger callout accent.
--success color Success / tip callout accent.
--radius length Base border-radius. Chrome uses `calc(var(--radius) + N)` for varied roundness.
--sidebar-width length Sidebar column width.
--toc-width length Right-rail table-of-contents width.
--header-height length Top header height (TopNav variant).
--font-sans stack Sans-serif stack for prose + UI.

Dark mode

Dark mode is toggled by adding .dark to :root (<html>). The theme toggle in the header does this at click time and persists to localStorage under the key shelldocs-theme.

An inline script in App.razor reads that key on page load and applies the class before first paint : this is what prevents the light-mode flash on hard refresh. The script is added by shelldocs init automatically.

To only override dark values, scope your rules:

css
:root.dark {
    --primary: oklch(0.75 0.18 240);
    --background: hsl(220, 15%, 8%);
}

Set via ShellDocsOptions.LogoSvg (inline SVG, uses currentColor : recolors with the theme) or LogoLight / LogoDark (URL pair for pre-rasterized bitmaps or full-color SVGs).

csharp
// Inline SVG that recolors with theme
o.LogoSvg = File.ReadAllText(Path.Combine(builder.Environment.WebRootPath, "brand/logo.svg"));

// Or theme-specific images
o.SetLogo("/img/logo-light.svg", "/img/logo-dark.svg", alt: "MyBrand");
Prefer inline SVG

An inline LogoSvg with stroke="currentColor" swaps between light and dark for free : one file, both themes. Reach for SetLogo only when your brand needs specific hex colors that shouldn't shift.

Layout variant

ShellDocsOptions.LayoutVariant picks between the two chrome layouts:

Prop Type Default Description
TopNav default Classic header spanning the full width, sidebar below. Best for wide catalog-style docs.
Sidebar alt Floating sidebar card with brand + search + collapse baked in. No top header. Best for narrower, chapter-style docs. This site uses it.
csharp
o.LayoutVariant = DocsLayoutVariant.Sidebar;

Both layouts share the same token surface : switching is a one-line change.

Custom fonts

--font-sans and --font-mono are the two font tokens. Load your webfont via @font-face in a stylesheet loaded after tokens.css, then override the token:

css
@font-face {
    font-family: "MySansFont";
    src: url("/fonts/my-sans.woff2") format("woff2");
    font-weight: 400 900;
    font-display: swap;
}

:root {
    --font-sans: "MySansFont", ui-sans-serif, system-ui, sans-serif;
}

Every ShellDocs primitive picks it up automatically : no per-component override needed.

The --theme CLI flag

shelldocs init --theme <shadcn|fuma|nextra> records a theme preset in the scaffold. Currently informational : all presets ship the same tokens.css. Future work: fuma and nextra presets will ship as additional stylesheet drops.

Overriding component styles directly

Every component ships with a scoped .razor.css : Blazor CSS isolation guarantees selectors don't leak. To override, add a global stylesheet after tokens.css and target the primitive's class:

css
/* Slightly rounder callouts */
.callout {
    border-radius: calc(var(--radius) + 4px);
}

Prefer token overrides where possible : they cascade universally and keep future ShellDocs updates from clobbering your look.

Interoperating with ShellUI (or any Tailwind-shaped design system)

The whole point of splitting ShellDocs.Tokens out of ShellDocs.Components : third-party design systems can adopt the same token surface. If you're layering ShellUI on top, its components read the same --primary, --background, --muted variables. One brand.css retunes both frameworks in lockstep.

See also