Tailwind dark mode
CSS
React
Tailwind
Radix

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

  1. 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
  1. Instale as dependências necessárias:
npm install

Passo 2: Instalando e Configurando o Tailwind CSS

  1. Instale o Tailwind CSS e suas dependências:
npm install -D tailwindcss postcss autoprefixer
  1. Inicialize o Tailwind CSS:
npx tailwindcss init -p
  1. 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

  1. 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

  1. 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.

  1. 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>;
}
  1. 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

  1. 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

  1. 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.

Implementando Dark Mode com Vite, Tailwind CSS e TypeScript | pr.dev