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

FileRole
app/assets/css/tailwind.cssTailwind imports, tokens, light/dark variables, typography
nuxt.config.tsGlobal CSS, Tailwind Vite plugin, color mode, icons, images, shadcn
components.jsonshadcn-vue style, aliases, base color, CSS path
app/lib/utils.tscn() class merging helper
app/components/common/ThemeToggle.vueLight/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 theme storage 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.

On this page