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.
📌 Refactorizando la lógica en un custom hook.
📌 Conclusión.
💡 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
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
Luego instalamos las dependencias.
npm install
Después abrimos el proyecto en un editor de código (en mi caso VS code).
code .
💡 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>
)
}
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
Debería de verse así 👀:
💡 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>
)
}
Debería de verse así 👀:
💡 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>
)
}
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>
)
}
Debería de verse algo como esto 👀:
💡 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 {
}
Dentro colocamos las variables que vamos a usar. Para definir variables utilizamos esta sintaxis
--background: #f2f2f2;
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;
}
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;
}
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>
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>
🟡 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;
}
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);
}
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>
)
}
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>
)
}
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);
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>
)
}
Y listo, ya quedaría la funcionalidad para cambiar entre temas. 🥳
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])
}
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]
}
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]
}
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>
)
}
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.
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!.
Features ⚙️
- Tema Light
- Dark Theme
- Switch between themes
Tecnologies 🧪
- React JS
- TypeScript
- Vite JS
- Vanilla CSS 3
Installation 🧰
- Clone the repository (you need to have Git installed).
git clone https://github.com/Franklin361/dark-light-app.git
- Install dependencies of the project.
npm install
- Run the project.
npm run dev
Note: For running the tests, use the following command
npm run test
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)
Increible articulo, me gusto!
Muy bien explicado, muy completo, detallado. Y no hace uso de ninguna libreria lo cual en mi opinión es una ventaja...
Que bueno que sigan habiendo alternativas a las librerias y frameworks... Gracias.