supafast-ui — sistema de diseño rápido y limpio
Lenguaje visual destilado de apps/performancetv. Objetivo: UI que se monta rápido,
se ve limpia y profesional (monocromo neutro + acentos semánticos), y es consistente
con el resto de fenix sin reinventar nada.
Regla de oro #1 — NO crear controles sueltos. Prohibido escribir un
<button>,<input>,<select>o cualquier control con clases ad-hoc. Antes de crear CUALQUIER elemento de UI (sobre todo un botón) es OBLIGATORIO revisar el inventario de componentes (abajo) y reutilizar el que exista. Un<button className="...">a pelo en una página es un error de revisión.Si necesitas un botón (o control) que crees que no existe: NO lo improvises. Primero (1) confirma en el inventario y en
src/shared/components/ui/que de verdad no está; (2) mira si es una variante de uno existente (entonces se añade a sucva, no se crea nada nuevo); (3) si realmente falta, consulta antes de crearlo — propón añadirlo aui/siguiendo el idiom base-ui, no lo metas inline en la pantalla.Regla de oro #2 — tokens, no valores ad-hoc. No inventes tokens, radios, sombras ni colores. Todo sale de los tokens semánticos (
bg-background,text-muted-foreground,rounded-lg…). Si crees que falta uno, casi siempre ya existe el equivalente.
El stack exacto (no sustituir piezas)
- React 19 + Vite + react-router v7 + TanStack Query + Zustand.
- Tailwind v4 CSS-first: tokens en
:root, mapeo en@theme inlinedentro desrc/index.css. No haytailwind.config.js. No usar sintaxis v3. - shadcn estilo
base-nova(vercomponents.json), baseColor neutral, iconos lucide-react. - @base-ui/react como primitiva headless — NO Radix. Esto cambia los idioms:
useRender+mergePropsen vez deasChild; estadosdata-checked:/data-unchecked:en vez dedata-[state=checked]. - CVA (
class-variance-authority) para variantes +cn()(clsx+tailwind-merge). - Geist Variable (sans) + Geist Mono, base
font-size: 15px.
Decisiones de diseño que definen el look
| Tema | Decisión |
|---|---|
| Color | Monocromo neutro (oklch grises). Acentos solo para significado: emerald=éxito, amber=aviso, destructive=error, rose=delta negativo/quitar, --chart-series=dato de gráfica. Fondos tenues (-50,/10) + texto saturado (-600/700). Nada de gradientes ni color de marca random. Detalle completo en references/colors.md. |
| Radio | rounded-lg para controles (botones, inputs, nav items), pill rounded-4xl para badges, rounded-2xl para paneles. Tokens: --radius-control/-surface/-panel. |
| Sombras | Mínimas: shadow-xs/shadow-sm solo en switch/elementos flotantes. Las superficies se separan con border border-border, no con sombra. |
| Tipografía | Títulos de sección text-xl font-semibold; cuerpo text-sm; metadatos text-xs text-muted-foreground. Pesos font-medium/font-semibold, rara vez bold. |
| Foco | Siempre focus-visible:ring-[3px] focus-visible:ring-ring/50 (+ focus-visible:border-ring). |
| Espaciado | Secciones con space-y-4; grupos con gap-1.5/gap-2/gap-3. Densidad media, generoso pero no aireado. |
| Motion | transition-colors/transition-all con duration-200 y curvas ease-out/ease-in-out. Animaciones decorativas vía keyframes en @theme (ver logo). |
Inventario de componentes (CONSULTA OBLIGATORIA antes de crear nada)
Antes de maquetar cualquier control, comprueba aquí (y en src/shared/components/ui/) si ya existe.
Para botones: SIEMPRE Button de @/shared/components/ui/button con su variant/size. Nunca un <button> suelto.
Primitivas @/shared/components/ui/:
badge · button · calendar · chart · dialog · dropdown-menu · field · input ·
password-input · popover · select · separator · sheet · switch · tabs · textarea ·
toggle · toggle-group
Compuestos reutilizables (@/shared/components/...):
data-table/EntityDataTable (tablas) · data-table/EntityTableToolbar (orden/filtros) ·
data-table/EntityTableSearch · SectionBlock (secciones) · Sidebar · Logo.
Mapa rápido “necesito X → usa Y”:
- ¿un botón / CTA / acción / icon-button? →
Button(variant: default/outline/secondary/ghost/destructive/link;size: xs/sm/default/lg/icon…). Polimórfico conrender={<NavLink/>}. - ¿campo de texto? →
Input; ¿multilínea? →Textarea; ¿contraseña? →PasswordInput(con ojo). - ¿label+control+error? →
Field/FieldLabel/FieldError. - ¿elegir una opción / ordenar / filtrar? → menú (
EntityTableToolbaroDropdownMenu), nunca<select>. Verreferences/menus-selects.md. - ¿tabla/listado? →
EntityDataTable. ¿on/off? →Switch. ¿etiqueta de estado? →Badge. - ¿modal? →
Dialog; ¿panel lateral? →Sheet; ¿flotante? →Popover; ¿pestañas? →Tabs.
Cómo trabajar (flujo)
- PARA. Consulta el inventario de arriba. ¿Existe el componente? → reúsalo tal cual. Esto es obligatorio antes de escribir una sola etiqueta de UI; vale especialmente para botones.
- ¿Existe pero te falta un look? → es una variante: añádela al
cva(...)del componente (p.ej.buttonVariants), nunca clases sueltas en el call site ni un control nuevo a pelo. - ¿De verdad no existe? → no lo improvises inline. Consúltalo / propón crearlo en
ui/siguiendo el idiom de@base-ui/react(useRender+mergeProps) — verreferences/components.md. Sin Radix. - Usa siempre tokens semánticos y
cn()para componer clases. Nunca colores hex inline si hay token. - Lucide para iconos, tamaño
size-4por defecto (size-3dentro de badges).
Sub-skills (referencias — léelas según la tarea)
Esta master enruta a fichas especializadas. Carga solo la que toque:
references/stack.md— stack tecnológico completo: versiones exactas (React 19, Vite 6, Tailwind v4, @base-ui, React Query, Zustand…), scripts, alias@/, config de Vite y organización del código. Léela al configurar deps, añadir librerías o dudar de qué pieza usar.references/base-components.md— ⭐ la base: código fuente verbatim de Button, Input, Textarea, PasswordInput (con ojo) y Field + ejemplos de uso (botones, campos de formulario, estados de error). Empieza aquí al construir botones, inputs o formularios; cópialos y extiéndelos.references/tables.md— ⭐ tablas conEntityDataTable(dirigido por columnas), código verbatim, render por celda, hover/selección, vacío, skeleton. Léela al montar cualquier tabla/listado.references/detail-popover.md— ⭐ ampliar detalle: cuando quieres expandir algo resumido (celda de tabla, valor truncado) NO se abre un modal, se despliega unPopoveranclado al elemento, con tarjeta key-value (labels en mayúsculas, valores mono, secretos enmascarados, botón Copiar). Código verbatim (ObjectiveTechnicalCredentialsCell,CopyValueButton,Popover). Léela para “ver detalle” desde una tabla o cualquier resumen clave→valor copiable.references/colors.md— ⭐ cómo se aplica el color: roles semánticos (emerald=éxito, amber=aviso,destructive=error, rose=delta negativo/quitar,--chart-series=dato) con uso real y tabla de decisión. Léela siempre que vayas a colorear algo (estados, deltas, badges, charts).references/tokens.md— paleta oklch cruda, escala de radios, tipografía Geist, el bloque@theme inline. Léela al tocarindex.css, declarar tokens o radios.references/menus-selects.md— ⭐ NO usamos<select>nativo: para elegir opciones se usa un menú (triggerButton outline+ chevron + ítems con check). Código verbatim del “Ordenar” (EntityTableToolbar) y delDropdownMenubase-ui. Léela al hacer selects, orden, filtros o menús de acciones.references/interaction-patterns.md— ⭐ idioms transversales que el usuario corrige siempre a la misma forma: pills de filtro/vista sueltas estilo Linear (activasecondary/ inactivaghost,rounded-full, bajo el titular, sin track), ⓘ + Tooltip en cards (elabsoluteva en el wrapper del Tooltip), alturas fijas (dialog fijo +invisiblepara reservar hueco), iconos de estado solo cuando comunican, segmented controlw-fit, CTA de crearprimary, y toasts con «Deshacer» en acciones reversibles. Léela al montar filtros, vistas, cards, toasts o menús de acciones.references/components.md— el idiom base-ui (useRender/mergeProps), patrón CVA, anatomía de button/badge/switch, estadosdata-checked, focus rings. Léela al crear/editar cualquier componente deui/.references/layout.md— sidebar colapsable,NavLinkactivo/inactivo,SectionBlock, cards con borde, grids y densidad. Léela al montar páginas, navegación o estructura.references/motion.md— transiciones, duraciones, keyframes en@theme, animaciones de logo/shimmer. Léela al animar algo.
Anti-patrones (no hacer)
- ❌ Botones/controles sueltos:
<button className="…">o<input>a pelo en una pantalla. UsaButton/Input/etc. del inventario. Si falta algo, consulta antes de crearlo, no lo improvises inline. - ❌ Radix (
@radix-ui/*),asChild,data-[state=...]. Aquí es base-ui. - ❌
tailwind.config.jso sintaxis Tailwind v3. - ❌ Colores de marca, gradientes, sombras grandes, glassmorphism. Esto es sobrio y neutro.
- ❌ Clases sueltas repetidas en vez de extender el
cvadel componente. - ❌ Hex inline (
#3080ff) cuando existe el token (text-blue-500/semántico). - ❌ Tamaños de fuente arbitrarios; usa la escala (
text-xs/sm/base/lg/xl). - ❌
<select>nativo o elSelectde shadcn por defecto para elegir opciones → usa un menú (trigger outline + ítems con check). Verreferences/menus-selects.md. - ❌ Escribir
<table>/<thead>a mano por pantalla → usaEntityDataTable+ columnas. Verreferences/tables.md. - ❌
<Input type="password">pelado → usaPasswordInput(lleva el toggle del ojo). Verreferences/base-components.md.