theme-dark-mode-setup
This skill sets up dark mode / theme switching with persistence and system preference detection. Supports React (Context API, use-dark-mode), Vue 3 (Composition API), Tailwind CSS, CSS variables. Creates theme provider, toggle component, localStorage persistence, system preference detection (prefers-color-scheme). Activate when user says "dark mode", "theme switcher", "light/dark toggle", or needs theme switching.
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
name: theme-dark-mode-setup
description: |
This skill sets up dark mode / theme switching with persistence and system preference detection.
Supports React (Context API, use-dark-mode), Vue 3 (Composition API), Tailwind CSS, CSS variables.
Creates theme provider, toggle component, localStorage persistence, system preference detection (prefers-color-scheme).
Activate when user says "dark mode", "theme switcher", "light/dark toggle", or needs theme switching.
Output: Complete theme system with toggle, persistence, system detection, and smooth transitions.
---
# Theme/Dark Mode Setup
> **Purpose**: Set up dark mode with persistence and system preference detection
---
## When to Use
- ✅ Add dark mode to application
- ✅ Theme switcher (light/dark/auto)
- ✅ Persist user preference
- ✅ User says: "dark mode", "theme switcher", "light/dark"
---
## React + Tailwind CSS
```tsx
// contexts/ThemeContext.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
type Theme = 'light' | 'dark' | 'system';
interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
actualTheme: 'light' | 'dark';
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>(() => {
return (localStorage.getItem('theme') as Theme) || 'system';
});
const [actualTheme, setActualTheme] = useState<'light' | 'dark'>('light');
useEffect(() => {
// Save to localStorage
localStorage.setItem('theme', theme);
// Get actual theme
const root = window.document.documentElement;
if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
setActualTheme(systemTheme);
root.classList.toggle('dark', systemTheme === 'dark');
} else {
setActualTheme(theme);
root.classList.toggle('dark', theme === 'dark');
}
}, [theme]);
// Listen for system theme changes
useEffect(() => {
if (theme !== 'system') return;
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (e: MediaQueryListEvent) => {
const systemTheme = e.matches ? 'dark' : 'light';
setActualTheme(systemTheme);
document.documentElement.classList.toggle('dark', e.matches);
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme, actualTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
```
**Theme Toggle Component**:
```tsx
// components/ThemeToggle.tsx
import { useTheme } from '../contexts/ThemeContext';
import { Sun, Moon, Monitor } from 'lucide-react';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<div className="flex gap-2 p-1 bg-gray-100 dark:bg-gray-800 rounded-lg">
<button
onClick={() => setTheme('light')}
className={`p-2 rounded ${
theme === 'light' ? 'bg-white dark:bg-gray-700 shadow' : ''
}`}
title="Light mode"
>
<Sun className="w-4 h-4" />
</button>
<button
onClick={() => setTheme('dark')}
className={`p-2 rounded ${
theme === 'dark' ? 'bg-white dark:bg-gray-700 shadow' : ''
}`}
title="Dark mode"
>
<Moon className="w-4 h-4" />
</button>
<button
onClick={() => setTheme('system')}
className={`p-2 rounded ${
theme === 'system' ? 'bg-white dark:bg-gray-700 shadow' : ''
}`}
title="System preference"
>
<Monitor className="w-4 h-4" />
</button>
</div>
);
}
```
**Usage**:
```tsx
// App.tsx
import { ThemeProvider } from './contexts/ThemeContext';
import { ThemeToggle } from './components/ThemeToggle';
function App() {
return (
<ThemeProvider>
<div className="min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<header className="p-4 flex justify-between items-center">
<h1>My App</h1>
<ThemeToggle />
</header>
<main className="p-4">
<p>This text changes color based on theme!</p>
</main>
</div>
</ThemeProvider>
);
}
```
**Tailwind Config**:
```javascript
// tailwind.config.js
module.exports = {
darkMode: 'class', // Enable class-based dark mode
theme: {
extend: {
colors: {
primary: {
light: '#3b82f6',
dark: '#60a5fa'
}
}
}
}
};
```
---
## Vue 3 Composition API
```typescript
// composables/useTheme.ts
import { ref, watch, onMounted } from 'vue';
type Theme = 'light' | 'dark' | 'system';
const theme = ref<Theme>('system');
const actualTheme = ref<'light' | 'dark'>('light');
export function useTheme() {
const setTheme = (newTheme: Theme) => {
theme.value = newTheme;
localStorage.setItem('theme', newTheme);
};
const updateActualTheme = () => {
const root = document.documentElement;
if (theme.value === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
actualTheme.value = systemTheme;
root.classList.toggle('dark', systemTheme === 'dark');
} else {
actualTheme.value = theme.value;
root.classList.toggle('dark', theme.value === 'dark');
}
};
watch(theme, updateActualTheme);
onMounted(() => {
// Load from localStorage
const saved = localStorage.getItem('theme') as Theme;
if (saved) {
theme.value = saved;
}
updateActualTheme();
// Listen for system theme changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', updateActualTheme);
});
return {
theme,
actualTheme,
setTheme
};
}
```
**Theme Toggle Component**:
```vue
<script setup lang="ts">
import { useTheme } from '@/composables/useTheme';
const { theme, setTheme } = useTheme();
</script>
<template>
<div class="flex gap-2 p-1 bg-gray-100 dark:bg-gray-800 rounded-lg">
<button
@click="setTheme('light')"
:class="['p-2 rounded', theme === 'light' ? 'bg-white shadow' : '']"
>
☀️
</button>
<button
@click="setTheme('dark')"
:class="['p-2 rounded', theme === 'dark' ? 'bg-white shadow' : '']"
>
🌙
</button>
<button
@click="setTheme('system')"
:class="['p-2 rounded', theme === 'system' ? 'bg-white shadow' : '']"
>
💻
</button>
</div>
</template>
```
---
**Skill Version**: 1.0.0
**Technologies**: React (Context API), Vue 3, Tailwind CSS, CSS variables
**Output**: Dark mode system with toggle, persistence, system detection, smooth transitions