Plantilla base de desarrollo frontend siguiendo la metodología BEMIT (BEM + ITCSS). Lista para clonar, extender y desplegar.
Este proyecto es el complemento práctico del artículo: La guía definitiva para dejar de adivinar cómo nombrar tus clases CSS
Si aún no lo has leído, te recomiendo comenzar por ahí. El artículo explica los fundamentos teóricos de BEMIT que dan origen a cada decisión de arquitectura de este starter.
BEMIT combina dos metodologías complementarias:
- BEM (Block Element Modifier) — convención de nomenclatura CSS que genera clases predecibles, reutilizables y sin colisiones.
- ITCSS (Inverted Triangle CSS) — arquitectura que organiza los estilos de menor a mayor especificidad en capas, siguiendo la forma de un triángulo invertido.
El resultado es una base de código CSS escalable, mantenible y fácil de incorporar en equipos de cualquier tamaño.
Estructura
Estilos
Funcionalidad
Build & Herramientas
Control de versiones
# 1. Clonar el repositorio
git clone https://github.com/Osvalam86/bemit-starter.git
cd bemit-starter
# 2. Instalar dependencias
pnpm install
# 3. Iniciar el servidor de desarrollo
pnpm devAbre http://localhost:5173 en tu navegador.
pnpm dev # Servidor de desarrollo con HMR en localhost:5173
pnpm build # Genera la carpeta dist/ optimizada para producción
pnpm preview # Sirve dist/ localmente para verificar el buildbemit-starter/
├── public/ # Archivos estáticos (copiados tal cual a dist/)
├── src/
│ ├── assets/
│ │ ├── fonts/ # Tipografías locales
│ │ └── images/ # Imágenes del proyecto
│ ├── data/
│ │ └── site.json # Datos globales del sitio (nombre, nav, lang…)
│ ├── js/
│ │ ├── main.js # Entry point — inicializa todos los módulos
│ │ └── modules/
│ │ └── header.js # Módulo del menú de navegación
│ ├── layouts/
│ │ └── base.hbs # Layout HTML base compartido por todas las páginas
│ ├── pages/ # Una carpeta por página → URL limpia
│ │ ├── index.html # → /
│ │ ├── nosotros/
│ │ │ └── index.html # → /nosotros/
│ │ └── contacto/
│ │ └── index.html # → /contacto/
│ ├── partials/
│ │ ├── header.hbs # Cabecera del sitio
│ │ └── footer.hbs # Pie de página
│ └── styles/
│ ├── main.scss # Punto de entrada de estilos
│ ├── 01-settings/ # Variables, tokens, breakpoints
│ ├── 02-tools/ # Mixins y funciones Sass
│ ├── 03-generic/ # Reset y normalize
│ ├── 04-elements/ # Estilos de elementos HTML sin clase
│ ├── 05-objects/ # Patrones de layout reutilizables
│ ├── 06-components/ # Componentes UI específicos
│ ├── 07-utilities/ # Clases de utilidad (overrides)
│ └── vendors/ # CSS de terceros
├── vite.config.js
├── postcss.config.js
└── package.json
Los estilos siguen el triángulo invertido: cada capa tiene mayor especificidad y menor alcance que la anterior.
| Capa | Prefijo | Descripción |
|---|---|---|
01-settings |
— | Tokens CSS (colores, espaciado, tipografía, z-index…) |
02-tools |
— | Mixins respond-to(), focus-ring(), visually-hidden() |
03-generic |
— | Reset + Normalize. Sin clases |
04-elements |
— | Estilos base de body, img, a, tipografía, formularios |
05-objects |
.o- |
Patrones de layout agnósticos: wrapper, grid, flex, stack… |
06-components |
.c- |
Componentes UI completos: header, button, card… |
07-utilities |
.u- |
Clases de utilidad con !important: spacing, visibilidad… |
Definidos en src/styles/01-settings/_tokens.scss como custom properties CSS:
/* Colores */
--color-primary, --color-secondary
--color-neutral-100 … --color-neutral-900
--color-success, --color-warning, --color-error
/* Espaciado */
--space-2xs, --space-xs, --space-sm, --space-md, --space-lg, --space-xl, --space-2xl
/* Tipografía */
--text-xs … --text-4xl
--font-body, --font-heading, --font-mono
/* Utilidades */
--radius-sm/md/lg/full
--shadow-sm/md/lg
--z-dropdown, --z-modal, --z-toast
--transition-fast, --transition-base, --transition-slowDefinidos en src/styles/01-settings/_breakpoints.scss:
| Clave | Valor |
|---|---|
xs |
360px |
sm |
576px |
md |
768px |
lg |
1024px |
xl |
1440px |
Uso en Sass:
@use "../02-tools/mixins" as mixins;
.mi-componente {
font-size: var(--text-base);
@include mixins.respond-to("md") {
font-size: var(--text-lg);
}
}| Clase | Archivo | Descripción |
|---|---|---|
.o-wrapper |
_o-wrapper.scss |
Contenedor centrado, max-width 75rem |
.o-grid |
_o-grid.scss |
Grid de 12 columnas con modificadores responsive |
.o-flex |
_o-flex.scss |
Contenedor flex configurable |
.o-stack |
_o-stack.scss |
Apilado vertical con gap uniforme |
.o-cluster |
_o-cluster.scss |
Agrupación horizontal con wrapping |
.o-media |
_o-media.scss |
Patrón media object (imagen + texto) |
El entry point src/js/main.js inicializa los módulos en orden:
import { initHeader } from "./modules/header.js";
const initApp = () => {
initHtmlJsFlag(); // Reemplaza clase no-js → js en <html>
initHeader(); // Activa el toggle del menú de navegación
};La clase .js se agrega al elemento <html> en cuanto carga el script. Los estilos que dependen de JavaScript usan este selector como prefijo:
// Oculto en móvil solo cuando JS está disponible
.js .c-header__nav-list {
display: none;
}Sin JavaScript: la navegación siempre es visible (accesible para todos los usuarios).
- Crear
src/js/modules/mi-modulo.js:
export const initMiModulo = () => {
const el = document.querySelector(".js-mi-elemento");
if (!el) return; // guard: solo actúa si el elemento existe
// lógica del módulo
};- Importar e inicializar en
main.js:
import { initMiModulo } from "./modules/mi-modulo.js";
const initApp = () => {
initHtmlJsFlag();
initHeader();
initMiModulo(); // ← agregar aquí
};- Crear la carpeta y archivo HTML:
src/pages/mi-pagina/index.html
- Usar el layout base:
{{#> base pageTitle="Mi Página — BEMIT Starter" pageDescription="Descripción de
la página"}}
<section class="o-wrapper">
<h1>Mi Página</h1>
</section>
{{/base}}- Agregar la ruta a
src/data/site.jsonsi debe aparecer en la navegación:
{ "name": "Mi Página", "url": "/mi-pagina/" }La página estará disponible en http://localhost:5173/mi-pagina/ sin ninguna configuración adicional.
| Variable Handlebars | Descripción | Fallback |
|---|---|---|
{{pageTitle}} |
Título de la pestaña | site.siteName |
{{pageDescription}} |
Meta description | site.description |
{{site.siteName}} |
Nombre del sitio | — |
{{site.lang}} |
Atributo lang del HTML | — |
{{year}} |
Año actual (generado en build) | — |
El starter incluye por defecto:
- Skip link visible al recibir foco — navega al contenido principal (
#main-content) aria-expandeden el botón hamburguesa sincronizado con el estado real del menúaria-hiddenen SVG decorativos con texto alternativo en<span class="u-sr-only">focus-visiblecon anillo de foco accesible (WCAG 2.1 AA) en todos los elementos interactivos- Touch targets mínimos de 44×44px (WCAG 2.5.5) en el botón hamburguesa
El contenido de dist/ es un sitio estático puro. Compatible con cualquier servidor o servicio de hosting:
pnpm build # genera dist/| Plataforma | Instrucción |
|---|---|
| Netlify / Vercel | Conectar repo, comando: pnpm build, directorio: dist |
| GitHub Pages | Subir contenido de dist/ a la rama gh-pages |
| FTP / cPanel | Subir contenido de dist/ a la raíz del dominio |
| Servidor propio | Apuntar el document root a dist/ |
Nota: Las rutas son absolutas (
/assets/…). Si el sitio vive en un subdirectorio (ej.tudominio.com/proyecto/), configurabase: '/proyecto/'envite.config.jsantes del build.
- Haz fork del repositorio
- Crea una rama:
git checkout -b feature/mi-mejora - Haz commit de tus cambios:
git commit -m 'Add: descripción del cambio' - Abre un Pull Request
MIT
Osvaldo Ocampo