Nuxt Styling and Theming — CSS, Tailwind CSS, and Dark Mode
In this tutorial, you will learn about Nuxt Styling and Theming. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Nuxt styling and theming — configure Tailwind CSS, write global and scoped styles, implement dark mode, and create a consistent design system.
In this lesson, you'll understand how to add CSS frameworks, organize styles, implement theme switching, and build reusable design components in Nuxt.
What You'll Learn
How to integrate Tailwind CSS with Nuxt, use global and scoped styles, implement dark mode with the color-mode module, create CSS variables for theming, and build a consistent design system across your application.
Why It Matters
A consistent visual theme improves user trust, reduces cognitive load, and speeds up development. Proper styling architecture prevents CSS conflicts and makes theme changes manageable across hundreds of components.
Real-World Use
A multi-tenant SaaS platform uses Nuxt with CSS custom properties and color-mode to let each customer customize colors and switch between light and dark themes without redeploying the application.
flowchart TD
A[Nuxt Styling] --> B[Tailwind CSS]
A --> C[Global Styles]
A --> D[Scoped Styles]
A --> E[CSS Variables]
B --> F[Utility Classes]
C --> G[Base Reset]
D --> H[Component Styles]
E --> I[Theme System]
I --> J[Light Mode]
I --> K[Dark Mode]
style A fill:#00dc82,color:#fff
Installing Tailwind CSS
The easiest way to add Tailwind is with the Nuxt Tailwind module:
npm install @nuxtjs/tailwindcss
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss'],
tailwindcss: {
config: {
content: [
'components/**/*.vue',
'pages/**/*.vue',
'layouts/**/*.vue'
],
theme: {
extend: {
colors: {
primary: {
50: '#f0fdf4',
500: '#00dc82',
900: '#14532d'
}
}
}
}
}
}
});
Create app/assets/css/tailwind.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
Global Styles
Add global CSS that applies to every page:
// nuxt.config.ts
export default defineNuxtConfig({
css: ['~/assets/css/global.css']
});
/* assets/css/global.css */
/* Base reset */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-thumb {
background: #00dc82;
border-radius: 4px;
}
/* Focus styles */
:focus-visible {
outline: 2px solid #00dc82;
outline-offset: 2px;
}
Scoped Styles in Components
Vue's scoped styles apply only to the current component:
<template>
<div class="card">
<h3 class="card-title">{{ title }}</h3>
<p class="card-body"><slot /></p>
</div>
</template>
<style scoped>
.card {
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 1.5rem;
transition: box-shadow 0.2s;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.card-body {
color: #64748b;
line-height: 1.6;
}
</style>
Expected output: A styled card component whose styles don't leak to other components on the page.
Dark Mode with @nuxtjs/color-mode
Install the color-mode module for automatic dark mode support:
npm install @nuxtjs/color-mode
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/color-mode'],
colorMode: {
classSuffix: '', // Add class without suffix
preference: 'system', // Follow OS preference
fallback: 'light'
}
});
Create a theme toggle component:
<template>
<button @click="toggleColorMode" class="theme-toggle">
{{ colorMode === 'dark' ? 'Light Mode' : 'Dark Mode' }}
</button>
</template>
<script setup>
const colorMode = useColorMode();
function toggleColorMode() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark';
}
</script>
Expected output: A toggle button that switches between light and dark modes, respecting the user's OS preference by default.
CSS Custom Properties for Theming
Define theme-agnostic CSS variables:
/* assets/css/themes.css */
:root {
--color-bg: #ffffff;
--color-text: #1a202c;
--color-primary: #00dc82;
--color-border: #e2e8f0;
--color-card-bg: #f8fafc;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.dark {
--color-bg: #0f172a;
--color-text: #e2e8f0;
--color-primary: #00dc82;
--color-border: #334155;
--color-card-bg: #1e293b;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
}
Use them in components:
<template>
<div class="themed-card">
<h3>{{ title }}</h3>
<p><slot /></p>
</div>
</template>
<style scoped>
.themed-card {
background: var(--color-card-bg);
color: var(--color-text);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm);
padding: 1.5rem;
border-radius: 8px;
}
</style>
Expected output: A themed component that automatically updates colors when the user switches between light and dark mode.
Using Tailwind for Dark Mode
Tailwind's dark mode variants work seamlessly with color-mode:
<template>
<div class="bg-white dark:bg-slate-800 text-gray-900 dark:text-gray-100
border border-gray-200 dark:border-gray-700
shadow-sm dark:shadow-slate-900/50
p-6 rounded-lg transition-colors duration-200">
<h3 class="text-lg font-semibold">{{ title }}</h3>
<p class="mt-2"><slot /></p>
</div>
</template>
Expected output: A component that uses Tailwind's dark: prefix to swap styles, with a smooth color transition.
Common Mistakes
Using
@applyexcessively in component styles: Tailwind's@applydirective in<style>blocks works but can bloat your CSS. Prefer utility classes in templates for smaller CSS output.Forgetting the
scopedattribute: Withoutscoped, component styles become global and can conflict with other components. Always addscopedunless you intentionally need global styles.Mixing CSS variable naming conventions: Use
--color-*naming consistently. Inconsistent variable names (some--color-, some--clr-) make theming confusing and error-prone.Not providing a fallback color scheme: Without a fallback, users with JavaScript disabled see no styles. Add an inline
<script>in yourapp.htmlthat sets the class before the page renders.Overriding Tailwind base styles incorrectly: Modifying Tailwind's
baselayer or preflight styles can break utility classes. Extend rather than override when possible.
Practice Questions
How does the
scopedattribute prevent style conflicts? Answer: Vue adds a unique data attribute to the component's elements and modifies the CSS selector to match only those elements, preventing styles from leaking.What does
colorMode.preference = 'system'do? Answer: It tells the color-mode module to follow the user's operating system setting, automatically switching between light and dark based on the OS preference.How do you define a CSS custom property for theming? Answer: Use
--name: valueinside:rootfor the default (light) theme and:root.darkor.darkfor the dark theme override.What is the purpose of the transition-colors utility? Answer: It applies a smooth color transition when theme-specific properties change, preventing jarring instant color swaps during theme switching.
Challenge
Build a complete design system with: custom color palette and typography scale as CSS custom properties, light and dark themes using color-mode, Tailwind configuration that maps custom properties to utility classes, reusable button, card, and input components with theme support, and a theme playground page demonstrating all components.
Mini Project
Create a themed portfolio site with: a hero section that adapts to light/dark mode, Tailwind utility classes for responsive layout, CSS variables for primary and accent colors, a theme toggle with system preference detection, and animated transitions between themes using CSS transitions.
FAQ
What's Next
Learn about Nuxt Internationalization (i18n) to add multi-language support to your Nuxt application with the @nuxtjs/i18n module.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro