21 KiB
Design System — EMI / Falck
Sistema de diseño derivado de la identidad visual de EMI (Grupo Falck). Toda la implementación CSS sigue la metodología BEM (
block__element--modifier) y un enfoque mobile-first responsive.
1. Filosofía de diseño
EMI es una marca de atención médica de urgencia 24/7. El sistema visual debe transmitir:
| Atributo | Cómo se traduce en UI |
|---|---|
| Confianza | Tipografía sólida, alto contraste, espaciado generoso |
| Urgencia | Rojo coral como acento dominante en CTAs |
| Profesional | Vinotinto profundo (burgundy) como color institucional |
| Cercanía | Bordes redondeados (pill shape en botones), iconografía amable |
| Accesible | Contrastes AA/AAA, áreas táctiles ≥ 44×44px, foco visible |
Regla de oro: el rojo coral se usa con moderación, solo para acciones primarias. El vinotinto domina secciones institucionales. El blanco respira.
2. Paleta de colores
2.1. Colores de marca (Brand)
| Token | HEX | RGB | Uso |
|---|---|---|---|
--color-brand-primary |
#E63946 |
230, 57, 70 |
CTAs principales, botones "Conoce más" |
--color-brand-primary-hover |
#C92A3B |
201, 42, 59 |
Hover state del CTA primario |
--color-brand-primary-active |
#A8202F |
168, 32, 47 |
Active/pressed state |
--color-brand-secondary |
#6B1F2A |
107, 31, 42 |
Vinotinto institucional, top bar, fondos |
--color-brand-secondary-dark |
#4D1620 |
77, 22, 32 |
Variante oscura del vinotinto |
--color-brand-accent |
#D32F2F |
211, 47, 47 |
Rojo del logo, detalles iconográficos |
2.2. Colores de soporte (Support)
| Token | HEX | Uso |
|---|---|---|
--color-success |
#25D366 |
Botón WhatsApp, estados completados |
--color-success-dark |
#1EA952 |
Hover del verde |
--color-warning |
#F59E0B |
Alertas no críticas |
--color-danger |
#DC2626 |
Errores, eliminaciones, validaciones falladas |
--color-info |
#0EA5E9 |
Mensajes informativos |
2.3. Neutrales (Scale)
| Token | HEX | Uso típico |
|---|---|---|
--color-white |
#FFFFFF |
Fondos de cards, textos sobre vinotinto |
--color-gray-50 |
#F9FAFB |
Fondo de página, secciones suaves |
--color-gray-100 |
#F3F4F6 |
Hovers sutiles, dividers |
--color-gray-200 |
#E5E7EB |
Bordes de inputs, separadores |
--color-gray-300 |
#D1D5DB |
Bordes activos, placeholders |
--color-gray-400 |
#9CA3AF |
Texto deshabilitado, iconos secundarios |
--color-gray-500 |
#6B7280 |
Texto secundario, labels |
--color-gray-600 |
#4B5563 |
Texto de cuerpo |
--color-gray-700 |
#374151 |
Texto principal en fondos claros |
--color-gray-800 |
#1F2937 |
Headings sobre blanco |
--color-gray-900 |
#111827 |
Texto de máximo contraste |
2.4. Tokens semánticos
Estos tokens referencian los anteriores. Los componentes consumen estos, no los HEX directos.
:root {
/* Backgrounds */
--bg-page: var(--color-gray-50);
--bg-surface: var(--color-white);
--bg-elevated: var(--color-white);
--bg-inverse: var(--color-brand-secondary);
--bg-overlay: rgba(17, 24, 39, 0.6);
/* Text */
--text-primary: var(--color-gray-900);
--text-secondary: var(--color-gray-600);
--text-muted: var(--color-gray-400);
--text-inverse: var(--color-white);
--text-link: var(--color-brand-primary);
--text-link-hover: var(--color-brand-primary-hover);
/* Borders */
--border-subtle: var(--color-gray-200);
--border-default: var(--color-gray-300);
--border-strong: var(--color-gray-400);
--border-focus: var(--color-brand-primary);
/* States */
--state-error: var(--color-danger);
--state-success: var(--color-success);
--state-warning: var(--color-warning);
}
2.5. Dark mode (opcional, recomendado)
@media (prefers-color-scheme: dark) {
:root {
--bg-page: #0F0A0B;
--bg-surface: #1A1214;
--bg-elevated: #241719;
--text-primary: #F5F5F5;
--text-secondary: #B8B8B8;
--border-subtle: #2D1F22;
/* El rojo y vinotinto se mantienen, son colores de marca */
}
}
3. Tipografía
3.1. Stack de fuentes
:root {
/* Display: para titulares grandes (Hero) — peso fuerte, condensada */
--font-display: 'Sora', 'Plus Jakarta Sans', system-ui, sans-serif;
/* Body: legibilidad para párrafos y UI */
--font-body: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
/* Mono: código, números tabulares */
--font-mono: 'JetBrains Mono', 'Fira Code', Menlo, monospace;
}
Nota: Sora se usa porque tiene el peso visual robusto que se observa en los titulares de EMI ("Somos atención médica…"). Si se requiere licencia comercial sin Google Fonts, alternativa: Manrope o Plus Jakarta Sans.
3.2. Escala tipográfica (mobile-first, escala fluida)
| Token | Mobile | Desktop | Uso |
|---|---|---|---|
--fs-xs |
12px | 12px | Captions, labels pequeños |
--fs-sm |
14px | 14px | Texto secundario, helper text |
--fs-base |
16px | 16px | Cuerpo de texto (default) |
--fs-md |
18px | 18px | Subtítulos, lead paragraph |
--fs-lg |
20px | 24px | H4, destacados |
--fs-xl |
24px | 32px | H3 |
--fs-2xl |
30px | 40px | H2 (ej: "Nuestros planes") |
--fs-3xl |
36px | 56px | H1 hero (ej: "Somos atención…") |
--fs-4xl |
44px | 72px | Display, posters |
:root {
--fs-xs: 0.75rem;
--fs-sm: 0.875rem;
--fs-base: 1rem;
--fs-md: 1.125rem;
--fs-lg: clamp(1.25rem, 1rem + 1vw, 1.5rem);
--fs-xl: clamp(1.5rem, 1.2rem + 1.5vw, 2rem);
--fs-2xl: clamp(1.875rem, 1.5rem + 2vw, 2.5rem);
--fs-3xl: clamp(2.25rem, 1.8rem + 3vw, 3.5rem);
--fs-4xl: clamp(2.75rem, 2rem + 4vw, 4.5rem);
}
3.3. Pesos
:root {
--fw-regular: 400;
--fw-medium: 500;
--fw-semibold: 600;
--fw-bold: 700;
--fw-extrabold: 800; /* Para hero titles */
}
3.4. Line heights
:root {
--lh-tight: 1.1; /* Headings grandes */
--lh-snug: 1.25; /* Headings medianos */
--lh-normal: 1.5; /* Body */
--lh-relaxed: 1.65; /* Párrafos largos */
}
3.5. Letter spacing
:root {
--tracking-tighter: -0.04em; /* Hero titles */
--tracking-tight: -0.02em; /* H2-H3 */
--tracking-normal: 0; /* Body */
--tracking-wide: 0.05em; /* Eyebrows, uppercase labels */
}
4. Espaciado (Spacing Scale)
Sistema basado en múltiplos de 4px, alineado con buenas prácticas de Material/Tailwind:
:root {
--space-0: 0;
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
--space-32: 8rem; /* 128px */
}
Regla de aplicación:
- Espaciado dentro de un componente (padding interno):
--space-2a--space-6. - Espaciado entre componentes:
--space-8a--space-16. - Espaciado entre secciones:
--space-20a--space-32.
5. Radios de borde
:root {
--radius-none: 0;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-2xl: 24px;
--radius-pill: 9999px; /* Botones tipo "Conoce más" */
--radius-full: 50%;
}
Identidad EMI: los CTAs primarios usan
--radius-pill(forma de cápsula). Es un sello distintivo de la marca y debe respetarse.
6. Sombras
:root {
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
/* Sombra de marca (tinte rojo sutil) para CTAs elevados */
--shadow-brand: 0 10px 25px -5px rgba(230, 57, 70, 0.35);
}
7. Breakpoints responsive (Mobile-first)
:root {
--bp-xs: 360px; /* Mobile pequeño */
--bp-sm: 480px; /* Mobile estándar */
--bp-md: 768px; /* Tablet */
--bp-lg: 1024px; /* Desktop pequeño */
--bp-xl: 1280px; /* Desktop estándar */
--bp-2xl: 1536px; /* Desktop grande */
}
Convención de uso (mobile-first SIEMPRE):
.task-list {
// Default: mobile
display: grid;
grid-template-columns: 1fr;
gap: var(--space-4);
// Tablet en adelante
@media (min-width: 768px) {
grid-template-columns: repeat(2, 1fr);
gap: var(--space-6);
}
// Desktop en adelante
@media (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr);
gap: var(--space-8);
}
}
Regla estricta: nunca usar max-width como base. Siempre escalar de pequeño a grande.
Container widths
:root {
--container-sm: 640px;
--container-md: 768px;
--container-lg: 1024px;
--container-xl: 1200px;
--container-2xl: 1440px;
}
.container {
width: 100%;
max-width: var(--container-xl);
margin-inline: auto;
padding-inline: var(--space-4);
@media (min-width: 768px) {
padding-inline: var(--space-8);
}
}
8. Z-index Scale
:root {
--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
--z-floating-cta: 900; /* Botón flotante WhatsApp */
}
9. Transiciones y animaciones
:root {
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);
/* Easings nombrados */
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
Accesibilidad: respetar siempre prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
10. Metodología BEM — Convenciones obligatorias
10.1. Estructura
.block { } /* Bloque independiente */
.block__element { } /* Parte interna del bloque */
.block--modifier { } /* Variante del bloque */
.block__element--modifier { } /* Variante de un elemento */
10.2. Reglas
- No anidar selectores en CSS más de 1 nivel (el nombre BEM ya da la jerarquía).
- No usar selectores de tipo (
div,span) dentro de un bloque BEM. - Nombres en kebab-case:
task-card__action-button--disabled. - Un componente = un bloque: si reutilizas algo, conviértelo en su propio bloque.
- Modifiers booleanos:
--active,--disabled,--loading. - Modifiers de valor:
--size-lg,--variant-primary.
10.3. Ejemplo correcto
<article class="task-card task-card--completed">
<header class="task-card__header">
<h3 class="task-card__title">Revisar contratos</h3>
<span class="task-card__badge task-card__badge--success">Completado</span>
</header>
<p class="task-card__description">Lorem ipsum…</p>
<footer class="task-card__footer">
<button class="task-card__action task-card__action--edit">Editar</button>
<button class="task-card__action task-card__action--delete">Eliminar</button>
</footer>
</article>
.task-card {
background-color: var(--bg-surface);
border-radius: var(--radius-lg);
padding: var(--space-4);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--transition-base);
&--completed {
opacity: 0.7;
border-left: 4px solid var(--color-success);
}
&:hover {
box-shadow: var(--shadow-md);
}
}
.task-card__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-3);
}
.task-card__title {
font-family: var(--font-display);
font-size: var(--fs-lg);
font-weight: var(--fw-semibold);
color: var(--text-primary);
margin: 0;
}
.task-card__badge {
font-size: var(--fs-xs);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-pill);
&--success {
background-color: var(--color-success);
color: var(--color-white);
}
}
.task-card__description {
font-size: var(--fs-sm);
color: var(--text-secondary);
line-height: var(--lh-normal);
}
.task-card__footer {
display: flex;
gap: var(--space-2);
margin-top: var(--space-4);
@media (min-width: 768px) {
justify-content: flex-end;
}
}
.task-card__action {
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
font-size: var(--fs-sm);
font-weight: var(--fw-medium);
cursor: pointer;
transition: background-color var(--transition-fast);
&--edit {
background-color: var(--color-brand-primary);
color: var(--color-white);
&:hover {
background-color: var(--color-brand-primary-hover);
}
}
&--delete {
background-color: transparent;
color: var(--color-danger);
border: 1px solid var(--color-danger);
&:hover {
background-color: var(--color-danger);
color: var(--color-white);
}
}
}
10.4. Lo que NUNCA debe hacerse
/* ❌ MAL: anidamiento excesivo */
.task-card {
.header {
.title { ... }
}
}
/* ❌ MAL: selector de tipo */
.task-card h3 { ... }
/* ❌ MAL: doble guion bajo encadenado */
.task-card__header__title { ... }
/* ❌ MAL: modifier sin bloque padre */
.--disabled { ... }
11. Componentes base (referencia de marca EMI)
11.1. Botón primario (CTA "Conoce más" / "Afíliate en línea")
<button class="btn btn--primary btn--lg">Conoce más</button>
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-family: var(--font-body);
font-weight: var(--fw-semibold);
border: none;
cursor: pointer;
transition: all var(--transition-base);
text-decoration: none;
min-height: 44px; /* Accesibilidad: área táctil mínima */
&:focus-visible {
outline: 2px solid var(--border-focus);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Variantes */
&--primary {
background-color: var(--color-brand-primary);
color: var(--color-white);
border-radius: var(--radius-pill);
box-shadow: var(--shadow-brand);
&:hover:not(:disabled) {
background-color: var(--color-brand-primary-hover);
transform: translateY(-1px);
}
}
&--outline {
background-color: transparent;
color: var(--color-brand-primary);
border: 2px solid var(--color-brand-primary);
border-radius: var(--radius-pill);
&:hover:not(:disabled) {
background-color: var(--color-brand-primary);
color: var(--color-white);
}
}
&--whatsapp {
background-color: var(--color-success);
color: var(--color-white);
border-radius: var(--radius-pill);
&:hover:not(:disabled) {
background-color: var(--color-success-dark);
}
}
/* Tamaños */
&--sm { padding: var(--space-2) var(--space-4); font-size: var(--fs-sm); }
&--md { padding: var(--space-3) var(--space-6); font-size: var(--fs-base); }
&--lg { padding: var(--space-4) var(--space-8); font-size: var(--fs-md); }
}
11.2. Input de formulario
<div class="form-field">
<label class="form-field__label" for="name">Nombre y apellido *</label>
<input class="form-field__input" id="name" type="text" required />
<span class="form-field__error">Este campo es obligatorio</span>
</div>
.form-field {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-bottom: var(--space-4);
&--error {
.form-field__input {
border-color: var(--state-error);
}
}
&__label {
font-size: var(--fs-sm);
font-weight: var(--fw-medium);
color: var(--text-secondary);
}
&__input {
padding: var(--space-3) var(--space-4);
font-size: var(--fs-base);
font-family: var(--font-body);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
background-color: var(--bg-surface);
color: var(--text-primary);
transition: border-color var(--transition-fast);
min-height: 44px;
&:focus {
outline: none;
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(230, 57, 70, 0.15);
}
}
&__error {
font-size: var(--fs-xs);
color: var(--state-error);
}
}
11.3. Card
.card {
background-color: var(--bg-surface);
border-radius: var(--radius-lg);
padding: var(--space-4);
box-shadow: var(--shadow-sm);
@media (min-width: 768px) {
padding: var(--space-6);
}
&--elevated {
box-shadow: var(--shadow-lg);
}
&--bordered {
border: 1px solid var(--border-subtle);
box-shadow: none;
}
&--inverse {
background-color: var(--bg-inverse);
color: var(--text-inverse);
}
}
12. Accesibilidad (no negociable)
- Contraste mínimo AA: 4.5:1 para texto normal, 3:1 para texto grande.
- Foco visible: usar
:focus-visible, nunca remover el outline sin reemplazarlo. - Áreas táctiles: mínimo 44×44 px en cualquier elemento interactivo.
- Roles ARIA: usar
role,aria-label,aria-describedbycuando el elemento no sea semántico nativo. - Skip links:
<a href="#main" class="skip-link">Saltar al contenido</a>. - Lang en el HTML:
<html lang="es">. - Imágenes: siempre con
altdescriptivo oalt=""si son decorativas.
13. Checklist antes de hacer commit
- Todos los colores vienen de tokens CSS, nunca hex directos en componentes.
- Clases siguen BEM (
block__element--modifier). - Sin selectores anidados más allá de 1 nivel.
- Mobile-first: el CSS por defecto es para mobile, los
@mediaescalan hacia arriba. - Botones interactivos tienen
:hover,:focus-visible,:disabled. - Áreas táctiles ≥ 44px de altura.
prefers-reduced-motionrespetado en animaciones.- Contraste verificado con WebAIM Contrast Checker.
- Layout probado en 360px, 768px, 1024px, 1440px.
- Sin
!importantsalvo en utilidades.
14. Estructura recomendada de archivos SCSS
src/styles/
├── abstracts/
│ ├── _variables.scss # CSS custom properties (tokens)
│ ├── _mixins.scss # Mixins (responsive, focus, etc.)
│ └── _functions.scss
├── base/
│ ├── _reset.scss # Normalize / reset moderno
│ ├── _typography.scss # Estilos base de h1-h6, p, etc.
│ └── _global.scss # html, body, *
├── components/
│ ├── _button.scss
│ ├── _card.scss
│ ├── _form-field.scss
│ └── _task-card.scss
├── layouts/
│ ├── _header.scss
│ ├── _footer.scss
│ └── _container.scss
├── utilities/
│ └── _helpers.scss # .sr-only, .visually-hidden, etc.
└── main.scss # Punto de entrada
15. Mixin útil para media queries
// abstracts/_mixins.scss
@mixin respond-to($breakpoint) {
@if $breakpoint == sm {
@media (min-width: 480px) { @content; }
} @else if $breakpoint == md {
@media (min-width: 768px) { @content; }
} @else if $breakpoint == lg {
@media (min-width: 1024px) { @content; }
} @else if $breakpoint == xl {
@media (min-width: 1280px) { @content; }
}
}
// Uso:
.task-list {
grid-template-columns: 1fr;
@include respond-to(md) {
grid-template-columns: repeat(2, 1fr);
}
@include respond-to(lg) {
grid-template-columns: repeat(3, 1fr);
}
}
Última actualización: 2026-05-13 Mantenedor: equipo de frontend Versión: 1.0.0