cn() utility for composing class names, and shadcn/ui’s CSS variable system for component theming. There is no separate CSS framework, no CSS Modules, and no styled-components.
Tailwind CSS v4 setup
Tailwind v4 integrates directly as a Vite plugin — notailwind.config.js file needed:
vite.config.ts
src/index.css using Tailwind’s @theme directive instead of a JavaScript config file.
The template’s src/index.css sets up three things:
src/index.css
@import "tailwindcss"— replaces the three@tailwinddirectives from v3.@import "tw-animate-css"— pulls in animation utility classes.@custom-variant dark— defines how the.darkclass activates dark mode (class-based, notprefers-color-scheme).@theme inline— bridges CSS custom properties to Tailwind utility names (e.g.,bg-backgroundmaps to--background).:root/.dark— define the neutral color palette using OKLCH values.
The cn() utility
src/lib/utils.ts exports a single helper that merges Tailwind classes safely:
src/lib/utils.ts
clsx— handles conditional class expressions (objects, arrays, falsy values).tailwind-merge— resolves Tailwind class conflicts by keeping only the last class in a group (for example,p-2andp-4together become justp-4).
Using cn() in components
All shadcn/ui primitives accept a className prop and pass it through cn(). Here is how button.tsx applies it:
src/components/ui/button.tsx
className prop — twMerge ensures conflicting utilities are resolved correctly:
shadcn/ui theming
shadcn/ui components are styled with Tailwind utility classes and a set of CSS variables defined insrc/index.css. The configuration is managed by components.json:
components.json
Style variant: new-york
The new-york variant uses slightly more opinionated defaults than the default variant:
- Tighter padding and smaller border radii on form elements.
- Solid default button backgrounds instead of ghost-style.
- Sharper visual hierarchy overall.
Base color: neutral
The neutral base color seeds the CSS variable palette with gray-based values. Variables like --color-background, --color-foreground, and --color-primary are generated from this palette and injected into src/index.css.
To change the theme color, update baseColor in components.json and re-run npx shadcn init — or edit the CSS variables in src/index.css directly.
CSS variables and cssVariables: true
With cssVariables: true, all component colors reference CSS variables instead of hardcoded Tailwind color names. This means you can switch the entire color scheme by updating the variables in one place, including full dark mode support.
Animation utilities
The template includestw-animate-css as a dev dependency, which provides a set of animation utility classes (fade, slide, zoom, etc.) compatible with Tailwind’s syntax. Import the animations you need directly in your component class strings:
Best practices
Use Tailwind utilities, not ad-hoc CSS
Use Tailwind utilities, not ad-hoc CSS
Write all styles as Tailwind classes in JSX. Keep
src/index.css for base layer resets, CSS variable declarations, and @theme customizations only. Avoid writing one-off selectors like .my-button { padding: 8px }.Use cn() for all className composition
Use cn() for all className composition
Never concatenate class strings with template literals (
\base $`). Always pass them through cn()` so that conflicting Tailwind utilities are resolved correctly and falsy values are filtered out.Extend shadcn/ui components, don't fork them
Extend shadcn/ui components, don't fork them
Pass a
className prop to override individual styles. Only edit files in src/components/ui/ when you need to change the default behavior for every usage across the app — not for one-off adjustments.Add new components via the shadcn CLI
Add new components via the shadcn CLI
Run
npx shadcn add <component> to add new primitives. The CLI reads components.json and drops the file into src/components/ui/ with the correct imports already set up.