Skip to content
Authoring Wireframe Themes
AutoXXS (320px)XS (375px)SM (640px)MD (768px)LG (1024px)XL (1280px)XXL (1536px)
SketchMaterialiOSTamagui
DataInjectionKeyPatternsServiceTransactionProcessResearchProductQualityPerformanceSpecDomainFunctionTechnologyArchitectureConfigMiddlewareDataDatabaseDrizzleMigrationModelop-sqliteSchemaSQLState ManagementDraftKeystoneMergePatchPatchesPersistenceReactiveRedoStoreUndoTestingDeviceFactoryIsolationTypeScriptZodTopicsCommunicationBidsNVCDesignDesign ImplicationsEducationPedagogyFoundationsPsychologyAttachmentFloodingRelatingAuthentic RelatingUIEditorReact Native

Authoring Wireframe Themes

Wireframe themes control the visual appearance of the low-fidelity UI preview area — the palette, radii, and typography that make a wf-* component look like Material, iOS, or Tamagui. They are separate from Web Awesome site themes (--wa-* tokens); do not confuse the two layers.

What a wireframe theme controls

  • --wf-color-* variables — brand, background, text, border, and semantic colors for all wf-* components
  • --wf-radius-* variables — corner radii from buttons to modals
  • --wf-font-* variables — typeface family for body, headings, and code
  • --wf-spacing-gap — base rhythm for stacks and rows
  • Optional component overrides.wf-{component} class rules scoped to the theme

The wf-* components in the wireframe preview area are intentionally low-fidelity. Do not replace them with wa-* components.

Active themes

IDLabelCSS fileLightDark
""Sketchwireframe.css (base, always loaded)
muiMaterialwireframe-mui.css
iosiOSwireframe-ios.css
tamaguiTamaguiwireframe-tamagui.css

How dark/light mode works

The site runs three co-operating axes simultaneously:

Starlight: data-theme="light"|"dark" on <html> (user-toggled)
↓ Head.astro bridge (inline script, MutationObserver)
WA: .wa-light / .wa-dark on <html> (synced from Starlight)
independent ↓
Wireframe: data-wf-theme="{id}" on <html> (ThemeDropdown → localStorage)

Both wa-dark/wa-light and data-wf-theme live on the same <html> element, making compound selectors the correct pattern. The bridge in Head.astro is correct as-is — no modifications needed.

Selector pattern

Both wa-dark/wa-light and data-wf-theme are set on the same <html> element. Themes must define variables in two compound selector blocks so they cooperate with Web Awesome’s dark/light class mechanism:

/* ── Light (default) ──────────────────────────────────────── */
/* Applies when wa-light is present, and also before any WA */
/* class is set (prevents flash of unstyled content at paint). */
:root[data-wf-theme="{id}"],
:root.wa-light[data-wf-theme="{id}"] {
--wf-color-primary: #…;
--wf-color-primary-text: #…;
--wf-color-bg: #…;
--wf-color-bg-light: #…;
--wf-color-bg-elevated: #…;
--wf-color-text: #…;
--wf-color-text-muted: #…;
--wf-color-border: #…;
--wf-color-border-dark: #…;
--wf-color-success: #…;
--wf-color-warning: #…;
--wf-color-danger: #…;
--wf-radius-card: …px;
--wf-radius-button: …px;
--wf-border-width: 1px;
--wf-spacing-gap: 1rem;
/* Optional: --wf-font-body, --wf-font-heading, --wf-font-mono */
}
/* ── Dark ─────────────────────────────────────────────────── */
/* Overrides when wa-dark is present on <html>. */
:root.wa-dark[data-wf-theme="{id}"] {
--wf-color-primary: #…; /* often a lighter tint than light mode */
--wf-color-bg: #…;
--wf-color-bg-light: #…;
--wf-color-bg-elevated: #…;
--wf-color-text: #…;
--wf-color-text-muted: #…;
--wf-color-border: #…;
--wf-color-border-dark: #…;
}
/* ── Component overrides ─────────────────────────────────── */
/* Scope to the theme to avoid polluting other themes. */
/* Use :root[data-wf-theme="{id}"] .wf-{component} for */
/* overrides that apply in both modes; narrow further with */
/* .wa-light / .wa-dark when mode-specific shapes are needed. */
:root[data-wf-theme="{id}"] .wf-card { … }
:root.wa-dark[data-wf-theme="{id}"] .wf-card { … }

Specificity notes

SelectorSpecificityWhen it wins
:root[data-wf-theme="{id}"](0,1,1)Default / light mode
:root.wa-light[data-wf-theme="{id}"](0,2,1)Explicit light mode (same result)
:root.wa-dark[data-wf-theme="{id}"](0,2,1)Dark mode — beats the single-selector default

The dark block wins by specificity alone — no !important or load-order tricks required. The plain :root[data-wf-theme] block handles the moment before any WA class is applied (pre-paint flash prevention).

Dark palette guidelines

When authoring dark-mode values, follow the design system’s own dark palette rather than inventing arbitrary darks:

ThemeLight primaryDark primaryDark bgDark surfaceDark text
Sketch#4a5568#718096#1a1e2e#252b3b#e2e8f0
Material#1976d2#90caf9#121212#1e1e1ergba(255,255,255,0.87)
iOS#007aff#0a84ff#000000#1c1c1e#ffffff
Tamagui#3a9e50#32d74b#111111#1a1a1a#ebebeb

Penpot / already-dark designs: If the source design is inherently dark, treat its dark values as the dark block and infer a light variant. Do not hard-code dark backgrounds with no light alternative — this creates a clash when the user has Starlight set to light mode.

Required variables

Every StarSpec wireframe theme defines CSS variables in two scope blocks — one for light mode and one for dark mode:

/* Light (default) */
:root[data-wf-theme="id"],
:root.wa-light[data-wf-theme="id"] { … }
/* Dark */
:root.wa-dark[data-wf-theme="id"] { … }

Variables marked required must be defined in both blocks. Variables marked optional extend the base and are only needed when a theme diverges from the Sketch defaults.

Brand Colors (required)

VariableDescriptionTypical LightTypical Dark
--wf-color-primaryMain accent — buttons, active states, focus rings#5E6AD2lighter tint of light value
--wf-color-primary-textText on primary background (must contrast primary)#FFFFFF#000000 or #FFFFFF
--wf-color-bgPage/wireframe background#FFFFFF#111111#1E1E1E
--wf-color-bg-lightCard and input field background#F7FAFCslightly lighter than bg
--wf-color-bg-elevatedActive segment, hover, raised surface#FFFFFFslightly lighter than bg-light
--wf-color-textPrimary text color#1A202Crgba(255,255,255,0.87)
--wf-color-text-mutedSecondary text, hints, placeholders#718096rgba(255,255,255,0.60)
--wf-color-borderDefault container border#E2E8F0rgba(255,255,255,0.12)
--wf-color-border-darkInteractive element border (inputs, focus)#CBD5E0rgba(255,255,255,0.23)

Semantic Colors (required)

VariableDescriptionTypical Value
--wf-color-successSuccess states, progress bars#38A169
--wf-color-warningWarning states, progress bars#DD6B20
--wf-color-dangerError states, destructive actions#E53E3E

Extended Semantic (optional)

VariableDescriptionUsage
--wf-color-danger-bgSubtle danger background fillDanger callout rows
--wf-color-danger-borderDanger border for callout containersBorders on danger cards

Geometry & Spacing (required)

VariableDescriptionTypical Value
--wf-radius-cardCorner radius for cards and modals12px
--wf-radius-buttonCorner radius for buttons and inputs8px
--wf-border-widthThickness of borders1px
--wf-spacing-gapBase gap for stacks and rows1rem

Extended Radii (optional)

Use when a theme needs component-level radius control beyond the two base values.

VariableDescriptionTypical Value
--wf-radius-smSmall radius (badges, tags)4px
--wf-radius-mdMedium radius (inputs, buttons)8px
--wf-radius-lgLarge radius (cards, modals)12px
--wf-radius-pillFull pill (toggle buttons, chips)100px
--wf-radius-inputInput field radius6px
--wf-radius-modalModal/dialog radius12px
--wf-radius-badgeBadge/chip radius4px
--wf-radius-tabsTab container radius8px

Font (optional)

Override fonts when the design system uses a custom typeface. Load the font family via @import above the variable blocks.

VariableDescriptionDefault
--wf-font-bodyBody and label textinherit (Starlight site font)
--wf-font-headingHeading elementsinherit
--wf-font-monoCode, values, monospaced textui-monospace, monospace

Step-by-step: creating a new theme

1. Optional — create src/styles/{id}.theme.toml

A theme.toml anchor file lets you declare known values that StarVision must respect during visual extraction. See Theme TOML Specification for the schema. Partial files are valid — only declare what you know.

[theme]
id = "my-theme"
label = "My Theme"
[brand]
primary = "#5E6AD2"
bg = "#FFFFFF"
[brand.dark]
primary = "#818CF8"
bg = "#111111"
[geometry]
radius_card = "12px"
radius_button = "8px"

2. Create src/styles/wireframe-{id}.css

Use the selector template above. Required checklist:

  • Light block: all --wf-color-* required variables defined
  • Dark block: all --wf-color-* required variables defined (typically inverted values)
  • Both blocks: --wf-radius-card, --wf-radius-button, --wf-border-width, --wf-spacing-gap
  • Font @import above the variable blocks if [font] is declared in theme.toml
  • No hardcoded hex values inside component rules — reference --wf-* variables only
  • Component overrides scoped with :root[data-wf-theme="{id}"] .wf-{component}

3. Register in src/themes.json

{ "id": "{id}", "label": "{Label}" }

astro.config.ts auto-discovers wireframe-{id}.css via fs.existsSync(). No manual config change needed. The theme will appear in the header dropdown immediately.

4. Verify

  • Toggle Starlight to dark with the new theme active — wireframe area must update
  • Toggle Starlight to light — wireframe must return to light values (no hardcoded dark remains)
  • Select the new theme, then switch to each other theme — no residual variables from the new theme
  • Run pnpm compile — no validation errors
  • Check the header dropdown lists the new theme with the correct label

Using --sl-color-* tokens in theme CSS

Several theme files use Starlight’s --sl-color-* tokens inside wf-* rules. This is intentional only when the goal is to match the page chrome (e.g., a hairline card border that blends with the Starlight sidebar). Use --wf-color-* variables whenever the intent is to represent the design system being previewed, not the documentation site surrounding it.