Starlight Theming
Starlight exposes its entire visual design through CSS custom properties. Override any variable to change colors, typography, and layout — with or without Tailwind.
When to Use
- Changing the color palette (accent color, grays, backgrounds)
- Swapping fonts or adjusting text sizes
- Applying a consistent brand identity
- Integrating Tailwind CSS into a Starlight project
When Not to Use
- Replacing entire UI sections (header, footer, sidebar) — use
starlight-custom-componentinstead - Per-page layout changes without global theming intent
Mindset
Starlight styles live in CSS cascade layers. Unlayered CSS always wins.
- CSS custom properties are the API. Set
--sl-*variables on:rootto change every element that reads them. See css-variables-reference.md for the full list. - Cascade layers determine priority. Any CSS you add without an
@layerblock is unlayered and automatically takes precedence over Starlight's named layers. - Tailwind requires different wiring. The
@astrojs/starlight-tailwindcompatibility package must be imported before Tailwind's own CSS or Starlight's styles break.
Approach 1: Custom CSS File
Step 1 — Create the file:
/* src/styles/custom.css */
:root {
--sl-color-accent-low: #1a1a4e;
--sl-color-accent: #3d52d5;
--sl-color-accent-high: #b4bffe;
--sl-font: 'Inter', sans-serif;
--sl-content-width: 50rem;
}
:root[data-theme='dark'] {
--sl-color-bg: #0f172a;
}
Step 2 — Register in astro.config.mjs:
starlight({
customCss: ['./src/styles/custom.css'],
})
Approach 2: Tailwind CSS Integration
npx astro add tailwind
npm install @astrojs/starlight-tailwind
Replace src/styles/global.css with:
@layer base, starlight, theme, components, utilities;
@import '@astrojs/starlight-tailwind';
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);
@theme {
--font-sans: 'Inter';
--color-accent-500: var(--color-indigo-500);
/* Map full accent and gray scales — see references for complete example */
}
Update astro.config.mjs:
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
integrations: [starlight({ customCss: ['./src/styles/global.css'] })],
vite: { plugins: [tailwindcss()] },
});
See css-variables-reference.md for the full Tailwind @theme color scale mapping.
Anti-Patterns
NEVER override Starlight styles without the customCss array
WHY: CSS files not registered in customCss are excluded from the Starlight build.
BAD: Add a <style> block in a layout component.
GOOD: Register a CSS file in customCss in astro.config.mjs.
Consequence: Styles are silently ignored.
NEVER add raw Tailwind imports without the compatibility layer
WHY: Tailwind's Preflight reset conflicts with Starlight's base styles. Consequence: Visual regressions, broken dark mode toggle.
BAD: Import tailwindcss without @astrojs/starlight-tailwind first.
GOOD: @import '@astrojs/starlight-tailwind' before Tailwind imports.
NEVER use .dark class selectors for dark mode
WHY: Starlight uses data-theme="dark" on <html>, not a .dark class. Consequence: Dark mode styles never activate.
BAD: .dark:bg-gray-900 (never matches in Starlight)
GOOD (plain CSS):
:root[data-theme='dark'] { --sl-color-bg: #0f172a; }
Use dark: Tailwind variants only after installing @astrojs/starlight-tailwind.
NEVER mix --sl-* variables with Tailwind @theme overrides
WHY: The two systems map to each other through @astrojs/starlight-tailwind. Setting both creates conflicting sources of truth.
BAD: Set --sl-color-accent in CSS while also defining --color-accent-* in @theme.
GOOD: Choose one approach and apply all overrides through it.
Consequence: Colors partially apply or behave differently in light vs. dark mode.
NEVER forget font-display: swap in custom @font-face
WHY: Without it, browsers may block rendering until the font downloads.
BAD: @font-face { font-family: 'X'; src: url('...'); }
GOOD: Add font-display: swap; to every @font-face.
Consequence: Flash of invisible text (FOIT) on slower connections.