Theming & Styling

Customize theme, dark mode, and typography.

Styling is Tailwind CSS v4 with semantic CSS variables, shadcn/ui (new-york style) components, and Geist fonts. Light and dark themes are driven by next-themes.

Important files

FileRole
src/app/globals.cssTailwind import, theme tokens (:root / .dark)
src/components/theme-provider.tsxnext-themes provider
src/app/[locale]/layout.tsxGeist font setup + <html> class
components.jsonshadcn config (style, aliases)
src/lib/utils.tscn() class-merging helper

Color tokens

The palette is defined as CSS variables in globals.css — a :root block for light and a .dark block for dark — then exposed to Tailwind through @theme inline. Components reference semantic tokens, not raw colors, so classes like bg-background, text-foreground, border-border, and bg-primary automatically adapt to the active theme.

src/app/globals.css
:root {
  --background: #f8fafc;
  --foreground: #0f172a;
  --primary: #3b76f6;        /* brand blue */
  --border: #e2e8f0;
  /* …card, popover, muted, accent, ring, chart-*, sidebar-* */
}

.dark {
  --background: #090807;     /* neutral near-black */
  --foreground: #f8fafc;
  --primary: #3b76f6;
  /* … */
}

To recolor the app, change the variables — every component that uses the semantic class follows. The template also defines design-system extras (--sky, --brand-subtle, --success, --warning) and a soft elevation shadow scale.

Tailwind v4 is CSS-first: there is no tailwind.config.js. Theme tokens, the @theme inline mapping, and custom utilities all live in src/app/globals.css.

Dark mode

next-themes toggles a .dark class on <html>:

src/components/theme-provider.tsx
<NextThemesProvider
  attribute="class"
  defaultTheme="light"
  storageKey="theme"
  disableTransitionOnChange
>

The root layout sets suppressHydrationWarning on <html> so the server/client class mismatch from theme detection doesn't warn, and disableTransitionOnChange prevents color-fade flicker when the theme flips.

Typography

Fonts are loaded with next/font in the locale layout and exposed as CSS variables consumed by the --font-sans / --font-mono theme tokens:

src/app/[locale]/layout.tsx
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });

// <html className={`${geistSans.variable} ${geistMono.variable}`}>

Geist Sans is the UI typeface; Geist Mono is used for code, labels, and metrics.

shadcn/ui components

components.json configures the new-york style with RSC enabled, lucide icons, and path aliases (@/components, @/components/ui, @/lib, @/lib/utils). Add a component with:

pnpm dlx shadcn@latest add <component>

It lands in src/components/ui/, already wired to the theme tokens above. Compose class names with the cn() helper (clsx + tailwind-merge):

src/lib/utils.ts
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Where to look

DirectoryContains
src/components/uishadcn primitives (button, card, dialog, …)
src/components/layoutHeader, footer, sidebar, nav
src/components/motionAnimation helpers (quiet, reduced-motion aware)

On this page