Vista del componente
Un saludo, despues de mucho tiempo dejo esta publicacion randon por aqui 🙂, en esta publicación mostraré cómo construir un componente reutilizable para alternar entre tema claro y oscuro usando Stimulus, Bootstrap y Bootstrap Icons.
📦 Dependencias necesarias
- Bootstrap CSS – para los estilos base y el sistema de temas (
data-bs-theme). - Bootstrap Icons – para los iconos de sol (
bi-sun) y luna (bi-moon-stars). - Stimulus y ES Module Shims – para la interactividad y la carga de módulos.
Estas son las cosas necesarias para que nuestro componente formado por una pequeña porción de codigo html y una controller de estimulus (codigo javascript) funcione,
son herramientas que necesitamos que esten precentes junto con nuestro componente para que funcione.
- bootstrap estiliza, como se ve el componente
- bootstrap icon, para mostrar los dos iconos del componente
- stimulus para interactividad, eje: cambiar de icono en funcion del tema
Son cosas que se pueden importar en cualquier simplemente con una linea de codigo desde un servidor externo(CDN)
🧠 El controlador Stimulus
Creamos un archivo theme_controller.js con la siguiente lógica:
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = ['icon']
connect() {
this.applyTheme()
}
toggle() {
const currentTheme = document.documentElement.getAttribute('data-bs-theme')
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
document.documentElement.setAttribute('data-bs-theme', newTheme)
localStorage.setItem('theme', newTheme)
this.updateIcon()
}
applyTheme() {
const savedTheme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
document.documentElement.setAttribute('data-bs-theme', savedTheme)
this.updateIcon()
}
updateIcon() {
if (!this.hasIconTarget) return
const theme = document.documentElement.getAttribute('data-bs-theme')
// Cambia el icono según el tema
this.iconTarget.className = theme === 'dark' ? 'bi bi-moon-stars' : 'bi bi-sun'
}
}
Esto será un archivo que ejecuta JavaScript y será el que haga todo el trabajo pesado. Este y el código HTML a continuación serán lo único que se necesite crear prácticamente para que el componente funcione en cualquier web.
🧩 El HTML del componente
El botón con los atributos de Stimulus y las clases de Bootstrap:
<button data-controller="theme"
data-action="click->theme#toggle"
class="btn btn-info text-white border border-info rounded-circle d-flex align-items-center fs-5">
<i data-theme-target="icon" class="bi bi-sun"></i>
</button>
Este es el código HTML que podrás copiar y pegar en cualquier lugar de tu web. Al hacerlo, aparecerá un botoncito que, si tienes importada la controladora anterior que creamos y las dependencias, ya funcionará al cliquearlo.
🔧 Configuración en el <head>
Para que funcione, necesitas incluir los siguientes recursos en tu página:
<!-- ES Module Shims (necesario para import maps) -->
<script async src="https://cdn.jsdelivr.net/npm/es-module-shims@2.8.0/dist/es-module-shims.js"></script>
<!-- Import Map con Stimulus y la ruta a tu controlador -->
<script type="importmap">
{
"imports": {
"@hotwired/stimulus": "https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.2/+esm",
"theme_controller": "/ruta/a/theme_controller.js"
}
}
</script>
<!-- Registrar el controlador -->
<script type="module">
import { Application } from '@hotwired/stimulus'
import ThemeController from 'theme_controller'
const application = Application.start()
application.register('theme', ThemeController)
</script>
<!-- Bootstrap CSS e Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
⚡ Evitar el destello al cargar
Si notas un breve destello del tema claro antes de aplicar el guardado, coloca este script en el <head>:
<script>
const savedTheme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
document.documentElement.setAttribute('data-bs-theme', savedTheme)
</script>
📝 Ejemplo completo
Aquí tienes una página HTML funcional con todo lo necesario (sin Turbo):
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi Página con Tema</title>
<!-- Script anti-destello -->
<script>
const savedTheme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
document.documentElement.setAttribute('data-bs-theme', savedTheme)
</script>
<!-- Bootstrap CSS e Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<!-- ES Module Shims -->
<script async src="https://cdn.jsdelivr.net/npm/es-module-shims@2.8.0/dist/es-module-shims.js"></script>
<!-- Import Map -->
<script type="importmap">
{
"imports": {
"@hotwired/stimulus": "https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.2/+esm",
"theme_controller": "/theme_controller.js"
}
}
</script>
<!-- Registrar controlador -->
<script type="module">
import { Application } from '@hotwired/stimulus'
import ThemeController from 'theme_controller'
const application = Application.start()
application.register('theme', ThemeController)
</script>
</head>
<body>
<div class="container mt-5">
<h1>Mi Página</h1>
<!-- El componente -->
<button data-controller="theme"
data-action="click->theme#toggle"
class="btn btn-info text-white border border-info rounded-circle d-flex align-items-center fs-5">
<i data-theme-target="icon" class="bi bi-sun"></i>
</button>
</div>
</body>
</html>
A modo general consiste en copiar un trozo de html en tu pagina, crear un archivo con el javascript de la controller de stimulus y cargarlo o importarlo en la misma pagina donde copiaste el html al igual que las dependecias que hablamos al principio y ya todo funcionara.
Hay que tener en cuenta que bootstraps es un framework que estilizara tu web entonces deberas usarlo en tu web por coherencia, se podria usar el componente solamente con el html y la controller y funcionaria solo que deberas estilizarlo con estilos propios, modificar la controller para que se adapte y manejar el Tema de tu pagina cosa que bootatrap ya lo trata por defecto.
Podria parecer poco practico usar todo una framework y demas para solo algo tan sencillo, pero en realidad no seria tan sencillo pero ese no es el motivo sino que voy a seguir haciendo componentes que integrare en un pequeño proyecto poco a poco por lo que estas dependencias para el proyecto en general son practicas y ahoran tiempo.
Deje esto por aqui y tal ves a alguien le sirva o ayude en algo.
Nota: compartir el codigo por lo menos en ecency se vuelve complicado ya que el lo elimina si detecta html o lo embebe supongo lo que hace que desaparesca por lo que hay que poner caracteres o escapes extraños mediante un script que lo automatize o pidiendoselo a una IA mucho mas sencillo,
Si alguien conoce una forma eficiente de hacer esto sin tener que convertir parte del codigo con simbolitos extraños para que los fron no los elimine estaria muy agradecido.
Un saludo y hasta el proximo componente 🙂