Skip to content

Starlight setup

@static-bloom/starlight is a Starlight plugin. It restyles every Starlight surface (header, search, sidebar, table of contents, content, asides, cards, tabs, steps, file trees, pagination, hero and code blocks), replaces the theme picker with a flavor picker, and generates code themes from the palette. This site uses it with the default options.

  1. Install the plugin next to Starlight. It needs astro 7 and @astrojs/starlight 0.42.

    Terminal window
    pnpm add @static-bloom/starlight
  2. Add it to Starlight’s plugins.

    astro.config.mjs
    import { defineConfig } from 'astro/config'
    import starlight from '@astrojs/starlight'
    import staticBloomStarlight from '@static-bloom/starlight'
    export default defineConfig({
    integrations: [
    starlight({
    title: 'My docs',
    plugins: [staticBloomStarlight()],
    }),
    ],
    })

When Starlight sets up, the plugin:

  • puts @static-bloom/starlight/styles.css first in customCss, so your own stylesheets load after it and win;
  • overrides the ThemeProvider component and, unless flavorSelect is off, the ThemeSelect component, keeping every other override you configured;
  • sets the Expressive Code themes and style overrides;
  • hands the configured flavor runtime to its ThemeProvider through a Vite virtual module.

If you already override ThemeProvider or ThemeSelect, your component is kept and the plugin logs a warning. Static Bloom’s ThemeSelect needs Static Bloom’s ThemeProvider.

staticBloomStarlight({
flavors: { dark: 'blackout', light: 'daybreak' },
accent: 'ion',
flavorSelect: true,
expressiveCode: true,
})
Option Values Default
flavors.dark 'dusk', 'midnight', 'blackout' 'midnight'
flavors.light 'daybreak' 'daybreak'
accent any of the 12 accents 'plasma'
flavorSelect true, false true
expressiveCode true, false true

flavors.dark is the flavor Auto uses on a dark system, and the one Starlight’s “Dark” choice maps to. accent re-points the primary color: links, the active sidebar entry, focus rings and glow. Invalid values or unknown keys throw a StaticBloomStarlightOptionsError when the plugin is created.

The ThemeProvider override inlines the flavor runtime in <head>, so the stored flavor applies before the first paint. It sets data-flavor, data-theme (which Starlight and Expressive Code read), data-accent and color-scheme on <html>, keeps <meta name="theme-color"> on the active flavor, and exposes window.staticBloom. Astro pages and Vue islands on the same origin share the choice, stored under static-bloom:flavor.

The ThemeSelect override is Starlight’s own select with Auto and the four flavors; the header and mobile-menu copies stay in sync. Its labels come from Starlight’s UI strings, so you can rename it through the i18n collection. This site calls it “Select flavor”:

src/content.config.ts
import { defineCollection } from 'astro:content'
import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders'
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),
}
src/content/i18n/en.json
{
"themeSelect.accessibleLabel": "Select flavor"
}

themeSelect.auto renames the Auto option the same way.

With flavorSelect: false, Starlight’s dark, light and auto picker stays and keeps working: its choices map to flavors.dark, flavors.light and Auto. The plugin mirrors the flavor into Starlight’s starlight-theme storage key, and adopts a visitor’s earlier Starlight choice once, if no flavor was stored yet. window.StarlightThemeProvider.updatePickers() stays available for code that calls it.

Unless expressiveCode is false on the plugin, or Expressive Code is turned off in Starlight, the plugin configures code blocks:

  • Themes. A dark and a light VS Code theme generated from the palette’s syntax roles: keywords in ultraviolet, strings in acid, functions in ion, comments in italic overlay2. The dark theme uses the accents all dark flavors share; the light theme uses Daybreak.
  • Style overrides. Code backgrounds, borders, frames, title bars, the copy button and text markers point at CSS variables (var(--sb-canvas-sunken), var(--sb-line), var(--sb-panel), …), so every block follows the active flavor and accent without a rebuild.
  • Contrast. minSyntaxHighlightingColorContrast is 4.5. The palette already meets WCAG AA on the code background, so token colors render as designed.
flavor.ts
import type {} from '@static-bloom/astro'
window.staticBloom?.setFlavor('blackout') // persists and applies

Your own Expressive Code options win: themes replace the generated ones, and styleOverrides merge over the plugin’s, group by group for frames and textMarkers. The themes are exported for other Expressive Code or Shiki setups:

import {
createExpressiveCodeTheme,
staticBloomDark,
staticBloomLight,
} from '@static-bloom/starlight'
const blackoutTheme = createExpressiveCodeTheme('blackout')

To use the Tailwind theme and the Vue components on a Starlight site, add a stylesheet to customCss. The plugin’s stylesheet still loads first.

src/styles/global.css
@layer base, starlight, theme, components, utilities;
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);
@import '@static-bloom/tailwind';
@import '@static-bloom/vue/tailwind.css';
@layer base {
*,
::before,
::after {
border: 0 solid;
}
}
astro.config.mjs
starlight({
plugins: [staticBloomStarlight()],
customCss: ['./src/styles/global.css'],
})

Tailwind’s preflight is left out because Starlight ships its own reset; the border reset is the one piece Tailwind’s border utilities need. Register @tailwindcss/vite in vite.plugins. @astrojs/starlight-tailwind is compatible but not required.

These docs also render the framework-free CSS components inside pages. They import the component and utility layers after the lines above:

src/styles/global.css
@import '@static-bloom/css/components.css';
@import '@static-bloom/css/utilities.css';

and wrap every live example in an element with the not-content class, which keeps Starlight’s content typography (margins, heading and link styles) out of it:

<div class="not-content">
<button class="sb-btn">Connect</button>
</div>

Add @astrojs/vue to the integrations and use the components as islands. Do not add the @static-bloom/astro integration to a Starlight site: the plugin’s ThemeProvider already installs the runtime.

The plugin’s stylesheet declares the page’s layer order:

@layer base, starlight, theme, components, utilities;
/* starlight: base, reset, core, content, components, utils, static-bloom */
  • Starlight’s color, shadow and font variables (--sl-color-*, --sl-shadow-*, --sl-font, --sl-font-mono) are mapped to Static Bloom tokens in unlayered CSS. Override one in your own unlayered CSS, which loads later and wins.
  • The component restyles live in @layer starlight.static-bloom. Any unlayered rule, or a rule in a later layer such as Tailwind’s utilities, beats them.
  • To recolor Starlight and the Static Bloom components together, change the tokens (--sb-primary, --sb-canvas, …) rather than the --sl-* variables.
  • A nested data-flavor or data-accent attribute restyles one region, as on the home page flavor strip.
  • Printing uses the Daybreak palette. Decorative effects respect prefers-reduced-motion, and the background grain is removed in print and in forced-colors mode.