DEV Community

Cover image for Usando Modo oscuro en tu aplicación de react! 🌙
Franklin Martinez
Franklin Martinez

Posted on

Usando Modo oscuro en tu aplicación de react! 🌙

El modo oscuro es una de las funciones que se vería excelente implementado en tu aplicación, ya que mejoraría la experiencia de usuario dentro de tu aplicación.

Por lo que en esta ocasión, te enseñare como implementar el modo oscuro con React y sin alguna otra librería externa!

🚨 Nota: Este post requiere que sepas las bases de React con TypeScript (hooks básicos).

Cualquier tipo de Feedback es bienvenido, gracias y espero disfrutes el articulo.🤗

 

Tabla de contenido.

📌 Tecnologías a utilizar.

📌 Creando el proyecto.

📌 Primeros pasos.

📌 Creando el componente Switch.

📌 Agregando unos cuantas tarjetas.

📌 Estilos para los temas.

📍 Configurando las variables para el tema light.

📍 Configurando las variables para el tema dark.

📍 Usando las variables en nuestros estilo.

📌 Agregando la lógica para cambiar entre temas.

📍 Controlando el estado del switch.

📌 Refactorizando la lógica en un custom hook.

📌 Conclusión.

📍 Demostración en vivo.

📍 Código fuente.

 

💡 Tecnologías a utilizar.

  • ▶️ React JS (version 18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ CSS vanilla (Los estilos los encuentras en el repositorio al final de este post)

 

💡 Creando el proyecto.

Al proyecto le colocaremos el nombre de: dark-light-app (opcional, tu le puedes poner el nombre que gustes).

npm init vite@latest
Enter fullscreen mode Exit fullscreen mode

Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.

Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

cd dark-light-app
Enter fullscreen mode Exit fullscreen mode

Luego instalamos las dependencias.

npm install
Enter fullscreen mode Exit fullscreen mode

Después abrimos el proyecto en un editor de código (en mi caso VS code).

code .
Enter fullscreen mode Exit fullscreen mode

 

💡 Primeros pasos.

Ahora primero creamos una carpeta src/components y agregamos el archivo Title.tsx que contiene:

export const Title = () => {
    return (
        <h1>Dark - Light Mode </h1>
    )
}
Enter fullscreen mode Exit fullscreen mode

Y ahora, dentro de la carpeta src/App.tsx borramos todo el contenido del archivo y colocamos el titulo que acabamos de crear.

const App = () => {
  return (
    <div className="container">
      <Title />
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Debería de verse así 👀:

Title

 

💡 Creando el componente Switch.

Ahora dentro de la carpeta src/components agregamos el archivo Switch.tsx y colocamos lo siguiente:

export const Switch = () => {
    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Debería de verse así 👀:

Switch

 

💡 Agregando unos cuantas tarjetas.

Nuevamente, dentro de la carpeta src/components
agregamos el archivo Card.tsx.

Primero crearemos el componente Layout que contendrá las tarjetas.

export const LayoutCards = () => {
    return (
        <div className="grid-cards">
            <Card />
            <Card />
            <Card />
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Luego, el componente Card lucirá de esta manera:

 export const Card = () => {
    return (
        <div className="card">
            <div className="card-image"></div>
            <h4 className="card-title">Lorem ipsum dolor sit.</h4>
            <p className="card-description">Lorem ipsum dolor sit amet consectetur adipisicing eli...</p>
            <div className="card-container-buttons">
                <button>Buy</button>
                <button>Show</button>
            </div>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Debería de verse algo como esto 👀:

Cards

 

💡 Estilos para los temas.

La idea es usar las variables con CSS para el tema dark y light.

 

🟡 Configurando las variables para el tema light.

Creamos una carpeta llamada src/styles y creamos el archivo var.css.
Este archivo se encargara de establecer las variables de CSS.

1- Para establecer las variables dentro de CSS usamos pseudo-clase root de la siguiente manera

:root {

}
Enter fullscreen mode Exit fullscreen mode

Dentro colocamos las variables que vamos a usar. Para definir variables utilizamos esta sintaxis

--background: #f2f2f2;
Enter fullscreen mode Exit fullscreen mode

Tenemos que colocar doble guion antes del nombre personalizado de nuestra propiedad, luego, colocamos dos puntos y agregamos el valor de dicha propiedad.
Aquí están las demás variables:

:root {
    --background: #f2f2f2;
    --text-primary: #0f0f0f;
    --text-secondary: #4e4e4e;
    --accent: #dfb017;
    --accent-hover: #cea315; 
    --border: #1f1e1e;
    --shadow: 7px 15px 13px -4px #00000056;
}
Enter fullscreen mode Exit fullscreen mode

Estas variables que acabamos de declarar sin para el tema light

 

🟡 Configurando las variables para el tema dark.

Ahora vamos a definir los variables para el tema oscuro.

Para ello, los nombres de las variables tienen que ser nombradas exactamente igual que las variables anteriores y solo cambiamos su valor después de los dos puntos.

[data-theme='dark'] {
    --background: #05010a;
    --text-primary: #f2f2f2;
    --text-secondary: #a7a4a4;
    --accent: #6a5acd;
    --accent-hover: #5b4cbe; 
    --border: #696969;
    --shadow: 7px 15px 13px -4px #ffffff1b;
}
Enter fullscreen mode Exit fullscreen mode

Nota que para las variables de tema oscuro, ya no usamos la pseudo-clase root, sino que hacemos referencia a un atributo personalizado que estamos definiendo como theme.

Este atributo personalizado, tiene que colocarse en una etiqueta HTML para que el modo oscuro funcione (No coloque el atributo manualmente, esto se hará de forma dinámica, usando react).

Pero no en cualquier etiqueta, sino que, se debe colocar en la etiqueta de mayor jerarquía, como bor ejemplo el body.

Esto es un ejemplo de como debe verse

<body data-theme='dark' >
<!-- content -->
<body>
Enter fullscreen mode Exit fullscreen mode

Si colocamos el atributo data-theme en el otra etiqueta con menos jerarquía, solo el contenido de esa etiqueta usara el modo oscuro.

Por esta razón, hay que colocarla en la etiqueta con mayor jerarquía.

<body>
    <div data-theme='dark' >
        <!-- Dark theme -->
    </div>
    <div>
        <!-- Light theme -->
    </div>
<body>
Enter fullscreen mode Exit fullscreen mode

 

🟡 Usando las variables en nuestros estilos.

Ahora, note que hemos creado un archivo var.css dentro de src/styles. Pero, ¿Donde las importamos?

Bueno, en mi caso me pareció, mejor importarlo en el archivo src/index.css.

Para importar archivos de .css en otro archivo .css usamos @import url() y agregamos la ruta donde se encuentra el archivo a importar.

Esto de separar los archivos CSS es buena practica ya que ayuda a entender mejor el código de los estilos.

Por cierto, debes colocar la importación en el top de tu archivo.

@import url('./styles/var.css');

body{
  font-family: 'Montserrat', sans-serif;
  font-weight: 600;
  transition: all .5s ease-in-out;
}
Enter fullscreen mode Exit fullscreen mode

Bueno, ahora si, usemos las variables.

Para usar las variables, se hace uso de la función var() y dentro le colocamos el nombre de la variable exactamente como la nombramos en nuestro archivo var.css

body{
  background-color: var(--background);
  color: var(--text-primary);
}
Enter fullscreen mode Exit fullscreen mode

Una vez que se hayan colocado las variables en los demás estilos (en las tarjetas, en el switch y el titulo), proseguiremos con agregar la lógica para cambiar entre temas.

 

💡 Agregando la lógica para cambiar entre temas.

Primero, tenemos que controlar el estado el switch para poder obtener cuando esta 'on' / 'off' y dependiendo de esos valores usar un tema u otro.

 

🟡 Controlando el estado del switch.

1- Primero agregamos un estado. Este estado sera de tipo Theme, y solo aceptara el string 'dark' o 'light'.


type Theme = 'dark' | 'light'

export const Switch = () => {

    const [theme, setTheme] = useState<Theme>('light')

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

2- Creamos la función para controlar el evento del switch.

El cual nombramos handleChange, recibe como parámetro el evento que emite por defecto el input.
La función llama al setter setTheme y dentro hace una evaluación:

  • Si la propiedad checked del input esta en true, establece el tema 'dark'.

  • Si la propiedad checked del input esta en false, establece el tema 'light'.

Ahora, la función handleChange se va a ejecutar cuando el input de tipo checkbox tenga un cambio y por eso se lo pasamos a método onChange.

Y la propiedad checked del mismo input, le pasaremos una evaluación, ya que la propiedad checked solo acepta valores booleanos. La evaluación sera:

  • Si el el valor del estado theme es 'dark', el valor de checked sera verdadero.

  • Si el el valor del estado theme es 'light', el valor de checked sera falso.

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

export const Switch = () => {

    const [theme, setTheme] = useState<Theme>('light')

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" onChange={handleChange} checked={theme === 'dark'} />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

3- Y ahora, recuerdan que íbamos a colocar el atributo personalizado data-theme, pues ahora toca hacerlo.

Para ello usamos un efecto, el cual debe ejecutarse cada vez que el valor del estado theme cambie. Es por eso que lo colocamos en su arreglo de dependencias del useEffect.

Después, dentro del useEffect ejecutamos lo siguiente:

document.body.setAttribute('data-theme', theme);
Enter fullscreen mode Exit fullscreen mode

Básicamente, estamos accediendo a la etiqueta body (porque es el punto más alto que encierra toda nuestra aplicación), y le establecemos un nuevo atributo con la función setAttribute

  • setAttribute, recibe en este caso dos parámetros:
    • el nombre del nuevo atributo.
    • el valor para ese nuevo atributo.

Asi que, le establecemos el atributo data-theme con el valor del estado theme.

El código debería de verse como esto:

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

export const Switch = () => {

    const [theme, setTheme] = useState<Theme>('light');

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light');

    useEffect(() => {

        document.body.setAttribute('data-theme', theme);

    }, [theme]);

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" onChange={handleChange} checked={theme === 'dark'} />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Y listo, ya quedaría la funcionalidad para cambiar entre temas. 🥳

switch theme

Pero ahora, tenemos mucha lógica en nuestro archivo, por lo que toca, crear un custom hook! 👀

 

💡 Refactorizando la lógica en un custom hook.

Creamos una nueva carpeta dentro de src/hook creamos el archivo useTheme.ts y cortamos la lógica del archivo Switch.tsx y la pegamos en useTheme.ts.

Hacemos las importaciones necesarias.

import { useEffect, useState } from 'react';

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

export const useTheme = (): => {

    const [theme, setTheme] = useState<Theme>('light')

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    useEffect(() => {
        document.body.setAttribute('data-theme', theme);
    }, [theme])
}
Enter fullscreen mode Exit fullscreen mode

Luego, este hook va a retornar un arreglo con dos elementos:

  • theme: el valor del estado del tema
  • handleChange: la función, que recibe un evento, para cambiar el estado entre temas y no retorna nada.
import { useEffect, useState } from 'react';

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

type useThemeReturn = [ string, (e: ChangeEvent) => void ];

export const useTheme = (): useThemeReturn => {

    const [theme, setTheme] = useState<Theme>('light')

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    useEffect(() => {
        document.body.setAttribute('data-theme', theme);
    }, [theme])

    return [theme, handleChange]
}
Enter fullscreen mode Exit fullscreen mode

Y también, vamos a recibir como parámetro el tema inicial y se lo agregamos al valor inicial del useState.

import { useEffect, useState } from 'react';

type ChangeEvent = React.ChangeEvent<HTMLInputElement>

type Theme = 'dark' | 'light'

type useThemeReturn = [ string, (e: ChangeEvent) => void ];

export const useTheme = (initialTheme:Theme): useThemeReturn => {

    const [theme, setTheme] = useState<Theme>(initialTheme)

    const handleChange = (e: ChangeEvent) => setTheme(e.target.checked ? 'dark' : 'light')

    useEffect(() => {
        document.body.setAttribute('data-theme', theme);
    }, [theme])

    return [theme, handleChange]
}
Enter fullscreen mode Exit fullscreen mode

Ahora, toca llamar nuestro custom hook.
Devuelta en el archivo src/components/Switch.tsx

import { useTheme } from "../hook/useTheme";

export const Switch = () => {

    const [theme, handleChange] = useTheme('dark');

    return (
        <div className="container-switch">
            <span>Change Theme </span>
            <label className="switch">
                <input type="checkbox" onChange={handleChange} checked={theme === 'dark'} />
                <span className="slider"></span>
            </label>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Y ahora definitivamente ya esta mas limpio y fácil de leer nuestro componente! 🥳

 

💡 Conclusión.

Todo el proceso que acabo de mostrar, es una de las formas en que se puede hacer la funcionalidad para crear el modo oscuro y cambiar entre temas, sin usar alguna librería externa. 🌙

Espero haberte ayudado a entender como realizar esta funcionalidad y que logres aplicarla en tus futuros proyectos, muchas gracias por llegar hasta aquí! 🤗❤️

Te invito a que comentes si es que conoces alguna otra forma distinta o mejor de como hacer esta funcionalidad. 🙌

 

🟡 Demostración en vivo.

https://dark-light-theme-app.netlify.app

 

🟡 Código fuente.

GitHub logo Franklin361 / dark-light-app

Switch between dark - light themes without using external libraries. 🌙

Dark Theme React JS 🌘

This time, we are going to implement the dark mode with React and without any other external library!.

 

Image or Gif

 

Features ⚙️

  1. Tema Light
  2. Dark Theme
  3. Switch between themes

 

Tecnologies 🧪

  • React JS
  • TypeScript
  • Vite JS
  • Vanilla CSS 3

 

Installation 🧰

  1. Clone the repository (you need to have Git installed).
    git clone https://github.com/Franklin361/dark-light-app.git
Enter fullscreen mode Exit fullscreen mode
  1. Install dependencies of the project.
    npm install
Enter fullscreen mode Exit fullscreen mode
  1. Run the project.
    npm run dev
Enter fullscreen mode Exit fullscreen mode

Note: For running the tests, use the following command

    npm run test
Enter fullscreen mode Exit fullscreen mode

 

Links ⛓️

Demo of the application 🔥

Here's the link to the tutorial in case you'd like to take a look at it! eyes 👀

  • 🇲🇽 🔗

  • 🇺🇲 🔗

Top comments (3)

Collapse
 
flash010603 profile image
Usuario163

Increible articulo, me gusto!

Collapse
 
duendeintemporal profile image
duendeintemporal

Muy bien explicado, muy completo, detallado. Y no hace uso de ninguna libreria lo cual en mi opinión es una ventaja...

Collapse
 
duendeintemporal profile image
duendeintemporal

Que bueno que sigan habiendo alternativas a las librerias y frameworks... Gracias.