Theming & Styling
Tailwind, color mode and shadcn-vue styling in the Nuxt template.
The visual system combines Tailwind CSS v4, CSS-variable design tokens, shadcn-vue primitives, Nuxt Color Mode, Nuxt Icon, and Nuxt Image.
Important files
| File | Role |
|---|---|
app/assets/css/tailwind.css | Tailwind imports, tokens, light/dark variables, typography |
nuxt.config.ts | Global CSS, Tailwind Vite plugin, color mode, icons, images, shadcn |
components.json | shadcn-vue style, aliases, base color, CSS path |
app/lib/utils.ts | cn() class merging helper |
app/components/common/ThemeToggle.vue | Light/dark switch using useColorMode() |
app/assets/icons/ | Custom my-icons collection |
Tailwind CSS v4 and tokens
@tailwindcss/vite is registered as a Vite plugin, while
app/assets/css/tailwind.css is loaded through Nuxt's css option. The file
imports Tailwind and tw-animate-css, defines the class-based dark variant, and
maps semantic tokens such as background, primary, muted, and border to
CSS variables.
Rebrand by changing the :root and .dark variables instead of scattering raw
colors through components. Update both modes and retain readable foreground,
focus-ring, chart, and sidebar contrast.
Color mode
@nuxtjs/color-mode is configured with a light preference and fallback,
classPrefix: "", classSuffix: "", and storageKey: "theme". Dark mode
therefore adds the literal .dark class, matching Tailwind's custom variant.
const colorMode = useColorMode()
colorMode.preference = colorMode.preference === "light" ? "dark" : "light"Use <ColorScheme> for markup that must wait until the client knows the stored
preference. The template also includes a client plugin that suppresses CSS
transitions while the theme changes.
shadcn-vue
components.json selects the new-york style, neutral base color, CSS
variables, and Lucide icons. The Nuxt module writes primitives to
app/components/ui and AI elements to app/components/ai-elements, with no
component prefix.
Use the shadcn-vue CLI when adding a primitive, then review generated code and imports. Compose conditional classes with the included helper:
import { cn } from "@/lib/utils"
const classes = cn("rounded-lg border", active && "border-primary")cn() combines clsx with tailwind-merge, so later utilities resolve
conflicts predictably.
Fonts, icons, and images
Geist and Geist Mono load from a Google Fonts stylesheet in app.head; the CSS
theme maps them to --font-sans and --font-mono. This is a browser stylesheet,
not next/font. For stricter privacy or fewer external requests, self-host the
font files and update both the head links and font variables.
@nuxt/icon supplies Iconify icons, including the installed Lucide collection.
Files under app/assets/icons are exposed with the my-icons prefix, for
example <Icon name="my-icons:resend" />. Use @nuxt/image/<NuxtImg> for
responsive product imagery.
Verify a theme change
- Light and dark surfaces, borders, text, focus rings, and charts are legible.
- Refresh preserves the choice in the
themestorage key. - No color-mode hydration warning appears.
- Generated shadcn components use semantic tokens rather than fixed colors.
- Fonts have an acceptable fallback and loading behavior.
- Custom icons and optimized images render in production builds.
