Theme Customization

The theme chrome is a set of Solid components mounted onto placeholder DOM: #ap-nav (navbar), #ap-sidebar (sidebar), #ap-toc (table of contents), #ap-content (content container). Visual customization goes through CSS variables; no component changes needed.

Design tokens --c-*

src/client/styles/theme.css defines light and dark token sets: light in :root, dark in html[data-theme='dark']. Custom islands, site-level components, and injected styles read only these variables, so anything rendered anywhere on the page picks up both light and dark values automatically:

Variable Purpose
--c-accent Accent color: links, active states, hover
--c-bg Page and component background
--c-bg-soft One level softer: toolbars, table headers, collapsed blocks
--c-bg-mute Softest level: disabled fills, scrollbars, progress tracks
--c-text Primary text
--c-text-2 Secondary text: helper copy, meta, grayscale icons
--c-border Borders and dividers
--c-code-bg Inline code and code block background
--c-shadow Card/figure shadow (full box-shadow value)

There are also layout size tokens --ap-nav-h / --ap-sidebar-w / --ap-toc-w / --ap-footer-h, plus two component-level tokens: --ap-container-c (the theme color of each container type, overridden internally per type) and --ap-ln-gap (gap between code line numbers and code text; the line-number width --ap-ln-w is inlined by the renderer based on digit count).

Usage example:

css
/* Styles for a site-provided island / component */
.my-widget {
  color: var(--c-text-2);
  background-color: var(--c-bg-soft);
  border: 1px solid var(--c-border);
  border-radius: 0.5rem;
}

.my-widget__button {
  color: var(--c-accent);
  transition: color 140ms ease-out;
}

Rules:

DOM mount points and class hooks

The page shell is generated at build time, and the theme chrome mounts onto fixed placeholder DOM — site CSS and scripts can rely on this structure (produced by the renderer, consumed by the theme; do not rename):

html
```html
<div id="ap-nav" class="ap-nav"></div>
<!-- navbar -->
<aside id="ap-sidebar" class="ap-sidebar"></aside>
<!-- sidebar -->
<main id="ap-content" class="ap-main">…</main>
<!-- content (full HTML already rendered at build time) -->
<div id="ap-toc" class="ap-toc"></div>
<!-- table of contents -->
<script type="application/json" id="__AP_DATA__">
  …
</script>
<!-- page payload -->

The ids are mount anchors for scripts; site CSS should key on the sibling classes (ap-nav / ap-sidebar / ap-main / ap-toc / ap-footer, plus ap-chrome on the client-injected theme chrome). Framework styles only use class selectors: id specificity is so strong that a site rule hanging off one survives any future framework default.

This site's navbar carries a live example of that hook: hover "Guide" and the overview row in the panel's first row has ap-nav-index-row; same for nested folders with an index page, whose panel also leads with an overview row.

When overriding these hooks' styles, keep both light and dark themes and mobile usable.

--vp-c-* compatibility aliases

Vuepress variable aliases are kept for migration, mapping one-to-one onto --c-* (by reference, so light/dark stays in sync):

vuepress alias Points to Note
--vp-c-accent --c-accent
--vp-c-bg --c-bg
--vp-c-bg-alt / --vp-c-bg-soft --c-bg-soft
--vp-c-bg-mute --c-bg-mute
--vp-c-text / --vp-c-text-1 --c-text
--vp-c-text-2 / --vp-c-text-3 --c-text-2 vuepress's tertiary text converges into secondary text
--vp-c-border / --vp-c-divider / --vp-c-gutter --c-border
--vp-c-code-bg --c-code-bg

Old component styles can be dropped in unchanged; write new components against --c-* directly.

Dark mode mechanics

Theme toggle button

The navbar toggle button follows "icon shows the current theme, label describes the action":

The sidebar is generated automatically from the directory tree (data in src/node/build/pages.ts). Interaction contract:

This site's guide/advanced/deep is a live example of multi-level nesting.

Cascade layers

Every stylesheet on the page lives in a cascade layer: winners are decided by layer order, independent of selector strength or load order. The order is declared once in each page head:

css
@layer properties, theme, base, preflights, shortcuts, ap-base, ap-prose, default, ap-chrome;
Layer Owner Contents
properties / theme / base / preflights / shortcuts uno base is the reset, preflights holds global base rules, shortcuts are utility macros
ap-base framework design tokens, body basics, anchor offsets
ap-prose framework markdown body defaults (zero specificity — any explicit style overrides them)
default uno utilities
ap-chrome framework components, islands, layout
(unlayered) your site your CSS

Site CSS left out of every layer beats all framework styles — an override only needs the same selector (or a weaker one), no html prefixes, !important, or ids. Two caveats:

ts
// uno.config.ts
outputToCssLayers: {
  cssLayerName: layer => (layer === 'preflights' ? null : layer),
},

When overriding --c-* tokens, provide both light and dark values (tokens live inside a layer, so an unlayered site :root also shadows the dark block — see the override example below).

Override example

A small CSS block reskins the site (shared by all pages; give dark mode its own values too):

css
:root {
  --c-accent: #2f6fed;
}

html[data-theme='dark'] {
  --c-accent: #6ea1ff;
}

Animation conventions

Responsive

After changing the theme, check both light and dark plus mobile — this is one of the framework's ground rules.