Theming

Dark, light, system themes and accent color customization.

The full-featured desktop starter has two persisted appearance controls: a light/dark/system mode and an independent accent color. Both are applied inside the webview through a root class or data attribute; they do not cross a custom Rust command boundary.

What ships

SurfaceFileRole
Appearance UIsrc/routes/settings/sections/AppearanceSection.tsxMode buttons and accent swatches
Theme helperssrc/utils/theme-settings.tsPersists preferences and applies dark / data-accent
Settings schemasrc/lib/settings/schema.tsValid theme modes and ACCENT_COLORS
Design tokenssrc/styles/globals.cssTailwind v4 mappings, shadcn/ui variables, dark tokens, and accent overrides
Native launch colorsrc-tauri/tauri.conf.json and tauri.macos.conf.jsonFixed window background shown around webview startup

The defaults in src/lib/settings/store.ts are dark and default. The versioned settings store restores both at startup.

Theme modes

type ThemePreference = 'light' | 'dark' | 'system'

applyThemePreference() resolves system with window.matchMedia('(prefers-color-scheme: dark)') and toggles the root dark class. Tailwind's custom dark variant applies to that class:

@import 'tailwindcss';
@variant dark (&:where(.dark, .dark *));
@import 'tw-animate-css';

The current helper reads the OS color scheme when the preference is applied, but does not install a media-query change listener. If the app is set to system while it is open, an OS theme switch does not update the webview until the preference is applied again or the app restarts. Add that listener if live system following matters to your product.

Accent colors

ACCENT_COLORS contains default, blue, violet, green, rose, and amber. default removes the root attribute and keeps the brand primary; another selection sets document.documentElement.dataset.accent.

:root[data-accent='violet'] {
  --primary: hsl(262 83% 58%);
  --primary-foreground: hsl(0 0% 100%);
  --ring: hsl(262 83% 58%);
}

Accent rules override only primary, primary foreground, and focus ring. Stable semantic tokens such as background, card, muted, border, and destructive still come from the light or dark palette.

To change the product palette:

  1. Update the default --primary, --primary-foreground, and --ring values in :root and .dark.
  2. Keep ACCENT_SWATCHES in AppearanceSection.tsx visually aligned with the CSS selectors.
  3. When adding or removing a choice, update ACCENT_COLORS and accentColorSchema, the CSS selector, settings translations, and tests.
  4. Add a settings-store migration when a removed value may already exist on user devices.

Tailwind v4 and shadcn/ui

globals.css maps semantic CSS variables into Tailwind v4 through @theme. shadcn/ui components consume the same tokens with classes such as bg-background, text-foreground, border-border, and bg-primary. tw-animate-css provides the animation utilities used by generated primitives.

components.json is configured for the new-york style, React without RSC, TypeScript, CSS variables, the neutral base color, Lucide icons, and the @/components/ui alias. Add components from the template root:

pnpm dlx shadcn@latest add dialog dropdown-menu

Review generated files before committing; they should land in src/components/ui/ and use the existing aliases and variables.

Native window chrome

Windows and Linux use decorations: false, so the visible titlebar and window buttons are React components and naturally follow the webview tokens. macOS uses titleBarStyle: "Overlay" with native traffic lights managed by the OS.

There is no programmatic native-chrome theme synchronization today. The Conveyor compatibility method windowSetTitlebarDark() is a no-op, and backgroundColor is statically #0e0e10 in both Tauri config files. Treat that value as a dark startup/fallback color; adjust it if your product defaults to a light first frame.

The tray is separate from the app theme: macOS uses an OS-tinted template icon, while Windows/Linux choose light or dark tray assets from the OS color scheme and listen for OS changes.

Verify

  • Switch between light, dark, and system in Settings → Appearance.
  • Try every accent and check buttons, links, focus rings, and contrast.
  • Restart and confirm both preferences persist.
  • Inspect startup frames and titlebar controls on every target OS.
  • Confirm tray art remains readable in light and dark system themes.
pnpm lint:check && pnpm typecheck && pnpm test

On this page