Theming & Styling
Customize Tailwind CSS v4 tokens, dark mode, and shadcn/ui in the TanStack template.
The template uses Tailwind CSS v4, semantic CSS variables, next-themes, and
shadcn/ui. Despite its package name, next-themes is used here as a framework-
agnostic React theme controller; this is a TanStack Start application.
Styling stack
| File | Role |
|---|---|
vite.config.ts | Registers @tailwindcss/vite |
src/styles.css | Tailwind import, dark variant, design tokens, Fumadocs mapping, typography, animation, layout utilities |
src/components/theme-provider.tsx | Configures next-themes class mode, system default, and theme storage key |
src/routes/__root.tsx | Runs the pre-hydration theme script and mounts ThemeProvider |
src/components/common/ThemeToggle.tsx | Theme control in marketing UI |
components.json | shadcn style, aliases, icon library, and CSS entrypoint |
src/lib/utils.ts | cn() class merge helper |
Tailwind is a Vite plugin, so there is no Tailwind config file in the default
setup. src/styles.css imports Tailwind and exposes semantic tokens through
@theme inline.
Semantic colors
Components use roles such as bg-background, text-foreground, bg-card,
text-muted-foreground, border-border, and ring-ring. The corresponding
variables live in :root and .dark.
:root {
--background: #f8fafc;
--foreground: #0f172a;
--primary: #3b76f6;
--border: #e2e8f0;
}
.dark {
--background: #090807;
--foreground: #f8fafc;
--primary: #3b76f6;
}Change token values before replacing semantic utilities in individual
components. src/styles.css also maps the Fumadocs --color-fd-* variables to
the same system so docs and application surfaces stay coherent.
components.json declares the shadcn new-york style with a Zinc base and CSS
variables. The shipped stylesheet has since customized the visible palette with
slate surfaces, brand blue, and a warm dark canvas; treat the semantic variables
as the current source of truth.
Dark mode without a flash
ThemeProvider stores light, dark, or system under the theme key and
applies the resolved light/dark class to <html>. The inline script in
__root.tsx reads that value before React hydrates, resolves system
preference, sets colorScheme, and records the raw preference in data-theme.
The root <html> uses suppressHydrationWarning because the script may change
the class before React attaches. Fumadocs' own theme controller is disabled so
there is a single theme owner.
Keep the inline script and ThemeProvider settings synchronized: storage key,
accepted values, default theme, class attribute, and system behavior must
agree. A mismatch produces a flash or hydration warning.
Add a shadcn component
The project is not an RSC app ("rsc": false). shadcn aliases resolve to
#/components, #/components/ui, #/lib, and #/hooks.
pnpm dlx shadcn@latest add alert-dialogReview generated imports and keep reusable primitives in src/components/ui.
Compose feature UI in the domain directories (auth, home, dashboard,
layout, ai) and animations in motion.
Use cn() for conditional utilities and conflict resolution:
import { cn } from "#/lib/utils"
<div className={cn("rounded-lg border", active && "border-primary")} />Code style
Biome formats TypeScript/TSX with tabs and double quotes; generated
src/routeTree.gen.ts and the hand-curated stylesheet are excluded from its
normal formatting scope. Code examples and new configuration should follow the
template's tab indentation and #/* aliases.
Verify a theme change
- Light and dark text, surfaces, borders, inputs, focus rings, charts, and destructive states meet contrast needs.
- Refreshing restores
light,dark, andsystemwithout a visible flash. - The browser console has no hydration warning caused by the theme class.
- Fumadocs pages use the same brand tokens as marketing and dashboard pages.
- New shadcn components use semantic colors and work with keyboard/focus states.
-
pnpm checkandpnpm buildpass after component generation.
