Skip to main content

Starwind UI v3.0 is now available! Migration guide

Starwind UI components support light and dark themes out of the box. Dark styles use Tailwind CSS’s dark: variant and become active when the dark class is present on the <html> element.

Starwind’s shared theme controller manages that class, persists the user’s preference, follows system color-scheme changes, and keeps theme controls synchronized.

Add Theme Initialization

Astro

Render ThemeInitScript in your main layout’s <head>. It applies the saved or system theme before the page becomes visible, preventing a flash of the wrong theme.

src/layouts/Layout.astro
---
import { ThemeInitScript } from "@starwind-ui/astro/theme";
---
<!doctype html>
<html lang="en">
<head>
<!-- ... other head elements -->
<ThemeInitScript />
</head>
<body>
<slot />
</body>
</html>

By default, ThemeInitScript:

  • reads and writes the colorTheme key in localStorage;
  • accepts light, dark, or system as the saved preference;
  • uses system when no preference has been saved; and
  • toggles the dark class on <html> based on the resolved theme.

It also reapplies the theme after Astro view transitions, so no additional astro:after-swap listener is needed.

Info

system remains the saved preference while the resolved theme follows window.matchMedia("(prefers-color-scheme: dark)"). Once the shared theme controller is active, it updates the page when the operating system preference changes.

Vite React

Vite React uses getThemeInitScript from @starwind-ui/react/theme. The Starwind CLI adds a small Vite transformIndexHtml plugin that places the script at the start of the document head. This applies the theme before React renders. See the Vite React placement and config.

Next.js

The App Router renders ThemeInitScript in app/layout.* or src/app/layout.*. The Pages Router renders it in pages/_document.* or src/pages/_document.*; Starwind creates _document when it is absent. Both use ThemeInitScript from @starwind-ui/react/theme and set suppressHydrationWarning on the root HTML element. See the Next.js App Router and Next.js Pages Router sections.

TanStack Start

TanStack Start renders ThemeInitScript in src/routes/__root.*, immediately before HeadContent. Keep one prepaint script in the root document. Initialization recognizes an existing Starwind initializer and avoids adding a duplicate. See the TanStack Start theme setup.

React Router

React Router framework mode renders ThemeInitScript in the <head> owned by app/root.*. The root <html> element receives suppressHydrationWarning. See the React Router root setup.

Customize the defaults

Pass the same settings to the initializer and any controller you initialize manually. The defaults work for most Starwind projects.

<ThemeInitScript storageKey="colorTheme" defaultTheme="system" className="dark" />
PropDefaultDescription
storageKey"colorTheme"The localStorage key used for the saved preference.
defaultTheme"system"The preference used when no valid saved value exists.
className"dark"The class applied to <html> when the resolved theme is dark.

Add a theme control

Use Starwind UI’s Theme Toggle component for a ready-to-use light/dark control. It initializes the shared controller, updates the saved preference and <html> class, and synchronizes other theme controls on the page.

---
import { ThemeToggle } from "@/components/starwind/theme-toggle";
---
<ThemeToggle ariaLabel="Toggle theme" />

The toggle switches between light and dark. Use the lower-level theme controller when your UI needs an explicit three-way light, dark, and system picker or needs to change the theme imperatively.

import { initThemeController } from "@starwind-ui/astro/theme";
const theme = initThemeController();
theme.setTheme("system");
theme.setTheme("dark");

Tip

You can find advanced production-ready theme switchers on Starwind Pro.