
Implementando Dark Mode com Vite, Tailwind CSS e TypeScript
Neste tutorial, vamos aprender como implementar o Dark Mode em um projeto criado com Vite, utilizando Tailwind CSS e TypeScript. Vamos configurar um sistema de temas dinâmicos que permite ao usuário alternar entre o modo claro, escuro e seguir a preferência do sistema operacional. Também vamos utilizar variáveis CSS para facilitar a gestão de cores no tema.
Requisitos
- Node.js instalado em sua máquina.
- Conhecimentos básicos de React, TypeScript e Tailwind CSS.
Passo 1: Configurando o Projeto com Vite e TypeScript
- Crie um novo projeto com Vite: No terminal, execute:
npm create vite@latest my-dark-mode-app -- --template react-ts
cd my-dark-mode-app
- Instale as dependências necessárias:
npm install
Passo 2: Instalando e Configurando o Tailwind CSS
- Instale o Tailwind CSS e suas dependências:
npm install -D tailwindcss postcss autoprefixer
- Inicialize o Tailwind CSS:
npx tailwindcss init -p
- Configure o arquivo tailwind.config.js: Edite o arquivo para adicionar o modo escuro baseado em classe e configurar o conteúdo:
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'class', // Habilita o modo escuro via classe 'dark'
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
// Definindo cores personalizadas usando variáveis CSS
'custom-neutral': {
50: 'var(--50)',
100: 'var(--100)',
200: 'var(--200)',
300: 'var(--300)',
400: 'var(--400)',
500: 'var(--500)',
600: 'var(--600)',
700: 'var(--700)',
800: 'var(--800)',
900: 'var(--900)',
950: 'var(--950)',
},
},
},
},
plugins: [],
};
Passo 3: Configurando as Variáveis CSS para Cores Dinâmicas
- Edite o arquivo CSS principal (src/index.css ou src/globals.css):
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Cores para o modo claro */
--950: theme('colors.neutral.50');
--900: theme('colors.neutral.100');
--800: theme('colors.neutral.200');
--700: theme('colors.neutral.300');
--600: theme('colors.neutral.400');
--500: theme('colors.neutral.500');
--400: theme('colors.neutral.600');
--300: theme('colors.neutral.700');
--200: theme('colors.neutral.800');
--100: theme('colors.neutral.900');
--50: theme('colors.neutral.950');
}
.dark {
/* Cores para o modo escuro */
--950: theme('colors.neutral.950');
--900: theme('colors.neutral.900');
--800: theme('colors.neutral.800');
--700: theme('colors.neutral.700');
--600: theme('colors.neutral.600');
--500: theme('colors.neutral.500');
--400: theme('colors.neutral.400');
--300: theme('colors.neutral.300');
--200: theme('colors.neutral.200');
--100: theme('colors.neutral.100');
--50: theme('colors.neutral.50');
}
}
Passo 4: Criando o Provedor de Tema com Context API
- Crie o arquivo src/theme-provider.tsx e adicione o seguinte código:
import React, { createContext, useContext, useEffect, useState } from 'react';
type Theme = 'dark' | 'light' | 'system';
type ThemeProviderProps = {
children: React.ReactNode;
defaultTheme?: Theme;
storageKey?: string;
};
type ThemeProviderState = {
theme: Theme;
toggleTheme: (theme: Theme) => void;
};
const initialState: ThemeProviderState = {
theme: 'system',
toggleTheme: () => null,
};
const ThemeContext = createContext<ThemeProviderState>(initialState);
export function ThemeProvider({
children,
defaultTheme = 'system',
storageKey = 'ui-theme',
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(() => {
const storedTheme = localStorage.getItem(storageKey) as Theme;
return storedTheme || defaultTheme;
});
const toggleTheme = (newTheme: Theme) => {
localStorage.setItem(storageKey, newTheme);
setTheme(newTheme);
};
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');
if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
root.classList.add(systemTheme);
} else {
root.classList.add(theme);
}
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
Passo 5: Configurando o AppContext como Wrapper.
- Crie o arquivo src/AppContext.tsx e adicione o seguinte código:
import React from 'react';
import { ThemeProvider } from './theme-provider';
interface AppContextProps {
children: React.ReactNode;
}
export function AppContext({ children }: AppContextProps) {
return <ThemeProvider>{children}</ThemeProvider>;
}
- Atualize o arquivo src/main.tsx para utilizar o AppContext:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { AppContext } from './AppContext';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<AppContext>
<App />
</AppContext>
</React.StrictMode>
);
Passo 6: Criando o Componente para Alternar o Tema
- No componente onde deseja adicionar a opção de alternar o tema, importe o useTheme e crie os botões:
import React from 'react';
import { useTheme } from './theme-provider';
export default function ThemeSwitcher() {
const { theme, toggleTheme } = useTheme();
return (
<div className="flex space-x-2">
<button
onClick={() => toggleTheme('light')}
className="p-2 bg-custom-neutral-200 dark:bg-custom-neutral-800 rounded"
>
Light Mode
</button>
<button
onClick={() => toggleTheme('dark')}
className="p-2 bg-custom-neutral-200 dark:bg-custom-neutral-800 rounded"
>
Dark Mode
</button>
<button
onClick={() => toggleTheme('system')}
className="p-2 bg-custom-neutral-200 dark:bg-custom-neutral-800 rounded"
>
System Default
</button>
</div>
);
}
Passo 7: Utilizando as Cores Personalizadas nos Componentes
- Exemplo de utilização das cores personalizadas:
export function ExampleComponent() {
return (
<div className="p-4 bg-custom-neutral-100 dark:bg-custom-neutral-900 text-custom-neutral-900 dark:text-custom-neutral-100">
<h1 className="text-xl font-bold">Título</h1>
<p>Este é um exemplo de componente que muda de aparência com o tema.</p>
</div>
);
}
Passo 8: Mencionando o Uso de Cores do Radix (Opcional)
Embora este tutorial utilize o sistema de cores do Tailwind CSS, é possível utilizar as cores do Radix UI para obter uma paleta de cores mais rica e consistente. O Radix UI fornece um conjunto de cores com shaders que podem ser facilmente integrados ao Tailwind CSS. Eles também fornecem orientações sobre onde utilizar cada shader, o que facilita o desenvolvimento de componentes estilizados.
Para saber mais sobre as cores do Radix UI, visite a documentação oficial.
Nota: Neste tutorial, não instalamos o Radix UI, apenas mencionamos como ele pode ser uma alternativa para a gestão de cores em seu projeto.
Conclusão
- Configurar um projeto com Vite, React e TypeScript.
- Instalar e configurar o Tailwind CSS para suportar o modo escuro baseado em classe.
- Utilizar variáveis CSS para definir cores que mudam dinamicamente com o tema.
- Criar um provedor de tema com Context API para gerenciar o estado do tema.
- Implementar botões que permitem ao usuário alternar entre os temas claro, escuro e sistema.
- Aplicar cores personalizadas nos componentes que respondem às mudanças de tema.
Com essa implementação, você oferece uma melhor experiência aos usuários, permitindo que escolham o tema de sua preferência ou sigam a configuração do sistema operacional. Além disso, o uso de variáveis CSS e o sistema de cores do Tailwind ou Radix facilita a manutenção e escalabilidade do design da aplicação.