Design System y fundamentos de UX/UI
Base visual del storefront: tokens, biblioteca de componentes, reglas responsive y
línea base de accesibilidad. Cada tabla lista el estado actual y la regla que rige de
acá en más.
Base técnica
| Pieza |
Estado actual |
Regla |
| Motor de estilos |
Tailwind CSS 3.4 · PostCSS · autoprefixer |
Sin CSS-in-JS ni hojas por componente |
| Preset base |
@medusajs/ui-preset |
Los tokens del preset son la base; lo propio se agrega por theme.extend |
| Biblioteca de primitivas |
@medusajs/ui |
Primera opción para todo control estándar |
| Primitivas headless |
@headlessui/react 2.2 · @radix-ui/react-accordion · tailwindcss-radix |
Solo para lo que @medusajs/ui no cubre |
| Modo oscuro |
darkMode: "class" declarado, 0 usos de dark: |
Fuera de alcance: no escribir variantes dark: hasta que se defina la paleta |
| CSS global |
src/styles/globals.css, 184 líneas |
Solo utilidades transversales y overrides de terceros |
Tokens de color
| Token |
Valor |
Contraste s/ blanco |
Regla |
brand.DEFAULT |
#ED0000 |
4.56:1 |
Único rojo de acción. Texto blanco encima: 4.56:1, cumple AA |
brand.hover |
#B80000 |
6.91:1 |
Solo estado hover/pressed |
brand.light |
#FF4D4D |
3.27:1 |
No apto para texto. Solo superficies, bordes y badges |
brand.soft |
#FFF1F1 |
1.1:1 |
Solo fondo. Texto encima con text.primary (15.83:1) |
brand.foreground |
#FFFFFF |
— |
Texto sobre superficies de marca |
text.primary |
#1A1A1A |
17.4:1 |
Texto por defecto |
text.secondary |
#374151 |
10.31:1 |
Texto de apoyo |
text.muted |
#9CA3AF |
2.54:1 |
No apto para texto. Solo placeholders y estados deshabilitados |
background.DEFAULT |
#FFFFFF |
— |
Fondo base |
background.subtle |
#F5F5F5 |
— |
Superficies elevadas y separadores de sección |
grey.0…grey.90 |
11 pasos, #FFFFFF → #111827 |
— |
Escala cerrada. grey.40 y menores no se usan para texto (≤2.54:1) |
| Variables Algolia |
--brand-r/g/b + 14 --aa-* en :root |
Tema del autocomplete. Cambiar el rojo acá y en brand.DEFAULT a la vez |
|
Reglas transversales de color:
- Prohibido el hex literal en componentes: todo color sale de un token.
- Umbral mínimo: 4.5:1 en texto normal, 3:1 en texto grande y en bordes de controles.
- Ningún estado se comunica solo por color (error, seleccionado, disponible).
Tipografía
Conviven dos sistemas de clases. Ambos están vivos y no son intercambiables.
| Sistema |
Origen |
Clases |
Usos |
Regla |
txt-* |
@medusajs/ui-preset |
20 (txt-xsmall … txt-xlarge-plus, variantes compact) |
83 |
Sistema vigente. Todo componente nuevo usa estas |
text-*-regular / -semi |
Definidas localmente en globals.css |
14 (text-xsmall-regular … text-3xl-semi) |
103 |
Heredadas del starter. No usar en código nuevo; migrar al tocar un componente |
| Aspecto |
Estado actual |
Regla |
| Familia |
Inter declarada en fontFamily.sans, sin next/font ni @font-face |
Cargar por next/font o aceptar el fallback de sistema de forma explícita |
| Fallback |
-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Ubuntu, sans-serif |
Se mantiene |
| Escala |
Tailwind por defecto, con 3xl sobrescrito a 2rem |
No agregar tamaños sueltos: usar la escala de clases |
| Tamaño mínimo |
text-xsmall-regular = 10px |
10px solo en etiquetas no esenciales; nunca en texto de lectura |
| Aspecto |
Estado actual |
Regla |
| Contenedor |
.content-container = max-w-[1440px] w-full mx-auto px-6, 29 usos |
Contenedor único de página. No definir anchos de página por componente |
| Anchos sueltos |
10 max-w-sm, 4 max-w-4xl, 4 max-w-3xl, y 8 variantes más |
Permitido dentro de un componente; nunca para el ancho de la página |
maxWidth.8xl |
100rem |
Token disponible, sin uso actual |
| Radios |
none 0 · soft 2px · base 4px · rounded 8px · large 16px · circle 9999px |
Escala cerrada; prohibido rounded-[Npx] arbitrario |
| Espaciado |
Escala por defecto de Tailwind |
Sin valores arbitrarios p-[13px] |
Reglas responsive
Dos escalas de breakpoints activas al mismo tiempo, con dos valores duplicados.
| Breakpoint |
Valor |
Origen |
Usos |
Regla |
2xsmall |
320px |
propio |
0 |
Disponible |
xsmall |
512px |
propio |
2 |
Disponible |
small |
1024px |
propio |
68 |
Escala vigente |
medium |
1280px |
propio |
3 |
Escala vigente |
large |
1440px |
propio |
0 |
Escala vigente |
xlarge |
1680px |
propio |
0 |
Escala vigente |
2xlarge |
1920px |
propio |
0 |
Escala vigente |
sm |
640px |
Tailwind |
57 |
No usar en código nuevo |
md |
768px |
Tailwind |
21 |
No usar en código nuevo |
lg |
1024px |
Tailwind |
51 |
Duplica small — no usar en código nuevo |
xl |
1280px |
Tailwind |
1 |
Duplica medium — no usar en código nuevo |
2xl |
1536px |
Tailwind |
0 |
No usar |
| Regla |
Detalle |
| Punto de partida |
Mobile first: los estilos sin prefijo son los de móvil |
| Corte principal |
small (1024px) separa móvil/tablet de escritorio |
| Ancho mínimo soportado |
320px |
| Componentes con variante móvil dedicada |
mobile-actions, mobile-filter, side-menu, cart-dropdown |
| Imágenes responsive |
next/image con sizes declarado |
| Contenido ancho |
Tablas y grillas con scroll propio; el body no scrollea en horizontal |
Biblioteca de componentes
Primitivas de @medusajs/ui en uso
| Primitiva |
Usos |
Primitiva |
Usos |
Text |
33 |
Badge |
3 |
clx |
27 |
Toaster |
2 |
Heading |
22 |
Input |
2 |
Button |
22 |
RadioGroup · Checkbox · IconButton · IconBadge · useToggleState · toast |
1 c/u |
Container |
9 |
|
|
Table |
8 |
|
|
Label |
4 |
|
|
Regla: no reimplementar una primitiva que exista en @medusajs/ui. Si hace falta
variar su apariencia, se envuelve; no se copia.
Componentes compartidos propios — modules/common/components
| Componente |
Tipo |
input · checkbox · radio · native-select · filter-radio-group |
Formulario |
modal · divider · interactive-link · localized-client-link · CTA |
Estructura y navegación |
product-card · ProductGrid · Hero · HeroSlider · PageHero · RichText |
Contenido |
cart-totals · line-item-options · line-item-price · line-item-unit-price · delete-button |
Comercio |
WhatsAppButton |
Flotante |
| Categoría |
Cantidad |
Regla |
| Componentes compartidos |
21 |
Todo lo que use más de un módulo vive acá |
| Componentes por módulo |
172 .tsx en 15 módulos |
Solo si el uso es de ese dominio |
| Skeletons |
15 |
Una carga con layout propio exige su skeleton |
| Iconos propios |
18 en common/icons |
Primero buscar en @medusajs/icons (19 importaciones) |
| Overlays |
Dialog de Headless UI en 6 componentes |
Todo overlay usa Dialog: aporta foco atrapado y cierre por Esc |
Movimiento
| Aspecto |
Estado actual |
Regla |
| Animaciones definidas |
9 (ring, fade-in-right, fade-in-top, fade-out-top, accordion-open, accordion-close, enter, leave, slide-in) |
Escala cerrada; no agregar keyframes por componente |
| Duraciones |
150ms – 300ms, salvo ring 2.2s y slide-in 1.2s |
Transiciones de UI ≤ 300ms |
prefers-reduced-motion |
0 usos |
Toda animación no esencial se desactiva con motion-reduce: |
Línea base de accesibilidad
Objetivo de conformidad: WCAG 2.1 nivel AA.
| Señal |
Medición actual |
Regla |
| Idioma del documento |
<html lang="en"> con contenido en castellano |
lang="es" — corrección bloqueante |
aria-label |
24 |
Todo control sin texto visible lleva nombre accesible |
role= |
7 |
Solo cuando el elemento nativo no alcanza |
sr-only |
3 |
Texto alternativo para íconos y contexto de enlaces repetidos |
htmlFor |
4, frente a 21 componentes de formulario |
Todo input con <label> asociado |
tabIndex |
0 |
No se altera el orden de tabulación; tabIndex={-1} solo para foco programático |
focus: |
41 |
— |
focus-visible: |
3 |
Todo interactivo necesita indicador de foco visible con focus-visible: |
outline-none |
21 |
Prohibido sin reemplazo visible en la misma clase |
onClick |
97 |
Solo sobre button o a; nunca sobre div o span |
<img> nativo |
9 en 7 archivos |
Migrar a next/image (4 archivos ya lo usan) |
alt= |
19 |
Obligatorio: descriptivo, o alt="" si es decorativo |
| Contraste |
Ver tokens de color |
4.5:1 texto · 3:1 UI |
data-testid |
331 |
Se mantiene: base de las pruebas end-to-end |
| Criterio WCAG 2.1 AA |
Estado |
Acción |
| 1.1.1 Contenido no textual |
Parcial |
Auditar los 9 <img> y los íconos sin sr-only |
| 1.4.3 Contraste mínimo |
Parcial |
Retirar text.muted y brand.light de texto |
| 2.1.1 Teclado |
Sin verificar |
Recorrido completo de catálogo y checkout con teclado |
| 2.4.7 Foco visible |
No cumple |
21 outline-none contra 3 focus-visible: |
| 3.1.1 Idioma de la página |
No cumple |
lang="es" |
| 3.3.2 Etiquetas o instrucciones |
Parcial |
Asociar label en todos los formularios |
| 4.1.2 Nombre, función, valor |
Parcial |
Revisar los 97 onClick sobre elementos no interactivos |
Conflictos abiertos
| Conflicto |
Alcance |
Regla de convergencia |
| Dos escalas de breakpoints |
129 usos en la escala de Tailwind |
Migrar a la escala propia al tocar cada componente |
| Dos sistemas de tipografía |
103 usos de las clases locales |
Migrar a txt-* al tocar cada componente |
| Dos plantillas de tienda |
algolia-store-template · instantsearch-store-template |
Definir cuál queda antes de tocar la grilla |
darkMode: "class" sin paleta |
Config declarada, 0 usos |
Quitar la config o definir la paleta oscura |
| Sucursales y teléfono fijos en código |
WhatsAppButton, disponibilidad, promesa de entrega |
Fuera del design system: sale a configuración |