Light ☀️  e Dark 🌙  mode de maneira fácil com Chakra UI, Next.js e Typescript

Light ☀️ e Dark 🌙 mode de maneira fácil com Chakra UI, Next.js e Typescript

🤓 Aprenda em menos de 10 minutos como habilitar light e dark mode de forma global na sua aplicação React usando Next.js e Chakra UI.

Tabela de conteúdos

Introdução

Nesse post você irá aprender como criar uma aplicação web usando Next.js e Chakra UI com light ☀️ e dark 🌙 mode. Além de ser muito mais bonito e estar na moda, o dark mode melhora a acessibilidade do seu site para usuários com sensibilidade nos olhos. A aplicação irá usar:

  • Next.js como um framework React;
  • Chakra UI como biblioteca de componentes React;

O que você vai aprender hoje

  • Iniciar e configurar um projeto Next.js com Typescript;
  • Instalar e configurar Chakra UI no projeto Next.js;
  • Utilizar os React hooks do Chakra UI para lidar e alterar com os colorModes (dark mode e light mode);
  • Criar um componente para alterar o colorMode da sua aplicação compartilhável por todo o app;

Pré-requisitos

O que você precisa para acompanhar bem o post;

  • Conhecimentos básicos de Node.js;
  • Conhecimentos básicos de React;
  • Node.js instalado na sua máquina;

Além disto é bom ter um editor para trabalhar com o código como VS Code.

Iniciando um projeto com Next.js

Para iniciar um projeto com Next.js vamos usar o create-next-app. Já iremos começar usando Typescript para ter toda a ajuda da sua InteliSense.

npx create-next-app@latest <nome-do-projeto> --ts

ou

yarn create next-app <nome-do-projeto> --ts

No meu caso, estou usando yarn e o nome do meu projeto será light-dark-mode-chakraui. Logo irei rodar no meu terminal:

yarn create next-app light-dark-mode-chakraui --ts

Isso irá criar uma pasta chamada light-dark-mode-chakraui dentro do meu diretório atual. Estou usando o VSCode como editor de código, então vou rodar:

# entrar na pasta do projeto
cd light-dark-mode-chakraui

# abrir o projeto no vscode
code .

inital-structure.png Estrutura do projeto que o create-next-app gera pra gente na sua versão 12.0.4, que foi a que eu usei nesse tutorial.

Abrindo o terminal (no VS Code o comando é ^ + ` ) e rodando o comando yarn dev (ou npm run dev), poderemos acessar o endereço localhost:3000 e teremos esse resultado:

initial-print-screen.png

Se você tiver algum erro tentando rodar o comando dev, tente atualizar o seu node para a versão lts, isso deve resolver. Esse link tem as instruções. Qualquer outro problema pode deixar nos comentários desse post que eu ajudo

Limpando o projeto

Vamos limpar o projeto para poder começar do zero um novo, com a nossa cara. Os passos serão esses:

  • Remover a pasta ./styles;
  • Remover o arquivo [README.MD](http://README.MD) → depois podemos criar outro com a nossa cara;
  • Remover a pasta ./pages/api → não usaremos nesse tutorial;
  • Criar uma pasta src na raiz do projeto;
    • mkdir src é o comando caso queira criar pelo terminal;
  • Mover a pasta pages para dentro da pasta src;
  • No arquivo _app.tsx, remover a importação dos estilos, ficando assim:

      import type { AppProps } from 'next/app'
    
      function MyApp({ Component, pageProps }: AppProps) {
        return <Component {...pageProps} />
      }
    
      export default MyApp
    
  • Remover todo o conteúdo do arquivo index.tsx, deixando ele assim:

import type { NextPage } from 'next'

const Home: NextPage = () => {
  return (
    <div>
      Hello World!
    </div>
  )
}

export default Home

Agora o seu [http://localhost:3000/](http://localhost:3000/) deve estar com essa cara:

clean-structure-print-screen.png

Instalando e conectando Chakra UI ao projeto

Para instalar o Chakra UI e suas dependências, vamos rodar o seguinte comando:

npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4

ou

yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4

Agora, vamos conectar o Chakra dentro do Next.js. Para isso, vamos no arquivo src/pages/_app.tsx e deixa-lo assim:

import type { AppProps } from 'next/app'
// Importar o ChakraProvider
import { ChakraProvider } from "@chakra-ui/react"

function MyApp({ Component, pageProps }: AppProps) {
  return (
    // Envolver o app com o ChakraProvider
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}

export default MyApp

Agora nós já podemos usar os componentes do Chakra por toda a nossa aplicação.

Criando o nosso primeiro componente com Chakra UI

Vamos criar um componente de Botão (que será o nosso botão de mudança de tema), utilizando o Chakra UI. Para isso, vamos usar o componente IconButton, de dentro do Chakra UI. Você pode ver a documentação dele aqui. Vamos usar também a biblioteca [react-icons](https://react-icons.github.io/react-icons/) para pegarmos os nossos ícones.

Primeiro, vamos instalar o react-icons:

npm install react-icons --save

ou

yarn add react-icons

Agora, vamos criar a pasta components dentro de src. Dentro dela, vamos criar o arquivo ToogleThemeButton.tsx. O código desse arquivo ficará assim:

import { IconButton } from "@chakra-ui/react";
// Importação do icone de dentro do react-icons
import { RiMoonLine } from "react-icons/ri";

export function ToogleThemeButton() {
  return (
    <IconButton
      // Para questões de acessibilidade, explicar o que o elemento faz
      aria-label="Toogle theme"
      // Icone do botão
      icon={<RiMoonLine />}
      // A função que será executada quando o botão for clicado
      onClick={() => console.log('Clicou no botão')}
    />
  );
}

Dica: o pacote react-icons tem várias bibliotecas de pacotes conhecidas. Procure utilizar apenas uma biblioteca por projeto, assim o seu projeto fica mais clean. Nesse projeto usaremos a biblioteca de ícones Remix Icons .

Agora, vamos importar e usar esse componente na nossa home page. O nosso arquivo src/pages/index.tsx ficará assim:

import { Flex } from '@chakra-ui/react'
import type { NextPage } from 'next'
import { ToogleThemeButton } from '../components/ToogleThemeButton'

const Home: NextPage = () => {
  return (
    // Ao invés de usarmos o elemento html div, 
    // usaremos o componente Flex do Chakra UI, que é um container flexbox
    <Flex
      // Altura da container
      height="100vh"
      // Largura do container
      width="100vw" 
      // Alinhamento vertical do container
      align="center" 
      // Alinhamento horizontal do container
      justify="center"
    >
      <ToogleThemeButton />
    </Flex>
  )
}

export default Home

E a nossa página ficará assim:

console-log-gif.webp

Transformando o nosso componente no botão de alterar tema

Para que o nosso componente tenha acesso ao tema da nossa aplicação inteira temos que fazer algumas coisas. A primeira é criar uma pasta styles, dentro de src, para podermos criar o arquivo do nosso tema lá. Então vamos criar o arquivo src/styles/theme.ts. Dentro desse arquivo nós vamos extender o tema padrão do Chakra UI e ir alterando ele com o que queremos. O arquivo ficará assim:

import { extendTheme } from '@chakra-ui/react'

export const theme = extendTheme({
  // Podemos definir cores que ficarão disponíveis para uso em todo o projeto
  colors: {
    success: {
      '100': '#EBFCD8',
      '300': '#B0EF88',
      '500': '#5DCC39',
      '700': '#28921C',
      '900': '#0A610E',
    },
    danger: {
      '100': '#FFE7D9',
      '300': '#FFA48D',
      '500': '#FF4842',
      '700': '#B72136',
      '900': '#7A0C2E',
    },
    warning: {
      '100': '#FFF6CE',
      '300': '#FFDD6D',
      '500': '#FFB80C',
      '700': '#B77906',
      '900': '#7A4802',
    },
  },
  // Podemos definir a fonte do nosso projeto
  fonts: {
    heading: 'Be Vietnam',
    body: 'Be Vietnam',
  },
  // Podemos definir um colorMode inicial (escolhi o dark 🌙 como padrão )
  config: {
    initialColorMode: 'dark',
    useSystemColorMode: false,
  },
})

Para que essas alterações fiquem visíveis em todo o projeto, temos que adicionar o theme ao nosso ChakraProvider, dentro do _app.tsx:

import type { AppProps } from 'next/app'
// Importar o ChakraProvider
import { ChakraProvider } from "@chakra-ui/react"

import { theme } from '../styles/theme';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    // Envolver o app com o ChakraProvider
    <ChakraProvider resetCSS theme={theme}>
      {/* ☝🏽 adicionei o theme a propriedade theme do ChakraProvider */}
      <Component {...pageProps} />
    </ChakraProvider>
  )
}

export default MyApp

Vamos usar algumas coisas que definimos na tema na nossa Home para vermos isso tudo sendo colocado em prática. O arquivo /src/pages/index.tsx ficará assim:

import { Flex, Text } from '@chakra-ui/react'
import type { NextPage } from 'next'
import { ToogleThemeButton } from '../components/ToogleThemeButton'

const Home: NextPage = () => {
  return (
    // Ao invés de usarmos o elemento html div, 
    // usaremos o componente Flex do Chakra UI, que é um container flexbox
    <Flex
      // Altura da container
      height="100vh"
      // Largura do container
      width="100vw" 
      // Alinhamento vertical do container
      align="center" 
      // Alinhamento horizontal do container
      justify="center"
      // Direção do container
      flexDirection="column"
    >
      <Text
        // Essa cor vem direto do nosso tema
        color="success.700"
        // Tamanho da fonte
        fontSize="4xl" 
        // Peso da fonte
        fontWeight="bold"
        // Alinhamento da fonte
        textAlign="center"
      >
        Botão para mudar tema da aplicação
      </Text>
      <ToogleThemeButton />
    </Flex>
  )
}

export default Home

A nossa home deve ficar assim:

project-without-font.png

Repare que a nossa fonte não mudou. Isso é porque precisamos importar a nossa fonte (usarei o Google Fonts para isso) dentro do nosso projeto. Como o carregamento de uma fonte externa é algo que vamos fazer uma única vez na minha aplicação, eu não vou usar o _app.tsx pois ele é recarregado toda a vez que o usuário troca de página. Para fazermos isso, criaremos o arquivo _document.tsx dentro da pasta pages. Ele ficará assim:

import Document, { Html, Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          {/* 👇🏽 definição do favicon */}
          <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🌙</text></svg>" />
          {/* 👇🏽 importação da fonte */}
          <link rel="preconnect" href="https://fonts.gstatic.com" />
          <link
            href="https://fonts.googleapis.com/css2?family=Be+Vietnam:wght@100;300;400;500;600;700;800&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

Agora a nossa fonte foi importada corretamente:

project-with-font.png

Agora podemos finalizar o nosso botão. Vamos importar o React hook useColorMode que Chakra UI disponibiliza pra gente. Desse hook, vamos importar:

  • colorMode → qual o colorMode atual da aplicação;
  • toogleColorMode → altera o colorMode. Se colorMode === 'light' altera o colorMode para dark e vice versa.

Além disso, vamos adicionar uma pequena lógica ao nosso componente. Se o colorMode for light, eu quero mostrar o ícone da uma Lua 🌙, e se for dark eu mostro o ícone do Sol ☀️. O componente ficará assim:

import { IconButton, useColorMode } from "@chakra-ui/react";
// Importação do icone de dentro do react-icons
import { RiMoonLine, RiSunLine } from "react-icons/ri";

export function ToogleThemeButton() {
  const { colorMode, toggleColorMode } = useColorMode();
  return (
    <IconButton
      // Para questões de acessibilidade, explicar o que o elemento faz
      aria-label="Toogle theme"
      // Icone do botão
      icon={colorMode === 'light' ? <RiMoonLine /> : <RiSunLine />}
      // A função que será executada quando o botão for clicado
      onClick={toggleColorMode}
    />
  );
}

Pronto! Tudo está funcionando:

switch-theme-gif.webp

Utilizando o botão em outras páginas

Para vermos que o colorMode é compartilhado pela aplicação toda, vamos criar mais uma página, além de alterar um pouco o index.tsx. Na home, vamos criar um link para a nossa página que vamos criar. Para isso, vamos importar o componente Link do Next.js. O arquivo index.tsx vai ficar assim:

import { Flex, Text } from '@chakra-ui/react'
import type { NextPage } from 'next'
import Link from 'next/link'
import { ToogleThemeButton } from '../components/ToogleThemeButton'

const Home: NextPage = () => {
  return (
    // Ao invés de usarmos o elemento html div, 
    // usaremos o componente Flex do Chakra UI, que é um container flexbox
    <Flex
      // Altura da container
      height="100vh"
      // Largura do container
      width="100vw" 
      // Alinhamento vertical do container
      align="center" 
      // Alinhamento horizontal do container
      justify="center"
      // Direção do container
      flexDirection="column"
    >
      <Text
        // Essa cor vem direto do nosso tema
        color="success.700"
        // Tamanho da fonte
        fontSize="4xl" 
        // Peso da fonte
        fontWeight="bold"
        // Alinhamento da fonte
        textAlign="center"
      >
        Botão para mudar tema da aplicação na Home
      </Text>
      <ToogleThemeButton />
  {/* 👇🏽 por questòes de acessibilidade e SEO */}
      <Link href="/page2"> 
        <a>
            Ir para a página 2
        </a>
      </Link>
  {/* ☝🏽 devemos envolver o elemento filho do componente Link em uma tag 'a' */}
    </Flex>
  )
}

export default Home;

Agora vamos criar o arquivo /src/pages/page2.tsx , que será assim:

import { Flex, Text } from '@chakra-ui/react'
import type { NextPage } from 'next'
import Link from 'next/link'
import { ToogleThemeButton } from '../components/ToogleThemeButton'

const Page2: NextPage = () => {
  return (
    <Flex
      height="100vh"
      width="100vw" 
      align="center" 
      justify="center"
      flexDirection="column"
    >
      <Text
        color="success.700"
        fontSize="4xl" 
        fontWeight="bold"
        textAlign="center"
      >
        Botão para mudar tema da aplicação na Página 2
      </Text>
      <ToogleThemeButton />
      <Link href="/">
        <a>
            Ir para a Home
        </a>
      </Link>
    </Flex>
  )
}

export default Page2;

O resultado será esse:

switch-theme-between-pages-gif.webp

Próximos passos e desafios

Parabéns! Se você chegou até aqui você já sabe criar uma aplicação com light e dark mode! 🎉 🥳

Agora, sugiro que você tente adicionar algumas novas features para aprimorar suas habilidades:

  • Altere o título do site que aparece na aba do navegador;
  • Utilize o tema do Chakra UI para criar componentes que sejam responsivos entre light e dark mode;
  • Crie novas versões (como por exemplo, novos sizes ou variants) para os componentes do Chakra UI;
  • Fazer deploy do projeto (eu indico a Vercel);

Link para documentação do Chakra Ui em como customizar componentes.

Link para documentação do deploy na Vercel

Além disso, não se esqueça de postar o resultado no Github. Lembre-se de caprichar no README do projeto!

Código fonte

Aqui está o código fonte da aplicação criada nesse tutorial.