"Demasiado estándar y sin identidad" tiene diagnóstico técnico: los componentes usan los defaults de shadcn tal cual salieron del generador. La identidad no se logra decorando — se logra con una sola familia de forma repetida con disciplina: la acción es pill, la superficie es 14px con hairline, el campo es 10px quieto, la sombra es noche, el título es Parkinsans y el dato es Inter. Más clean que hoy (menos sombra, menos borde, menos peso) y al mismo tiempo inconfundiblemente Amedi.
Lo quieto ya lo tenemos
Controles h-9 compactos, botones de ícono perfectamente circulares, inputs bordeados sin
drama, shadow-xs. Ese temperamento es el canon de los campos y las barras — solo le
falta la piel de la marca. (Y una purga: el botón de IA del search bar es un
from-violet-500 to-purple-600 que no existe en ninguna paleta.)
La identidad ya está escrita
El manual y las propuestas ya hablan este idioma: CTAs pill, cards de 14px con borde .5pt, pills editoriales en minúscula, tinta #012030, acción #13678A, foco menta. El catálogo de abajo no inventa: traduce ese idioma a cva.
Cuatro radios, cada uno con un significado — nunca por gusto del archivo. Con esto, cualquier componente de los 151 (y los que vengan) se dibuja sin adivinar.
| Rol | Radio | Forma | Ejemplos |
|---|---|---|---|
| Acción | 999px · pill | Petroleo sólido · hover petroleo-deep · Parkinsans 600 · h-10 (h-9 compacta) | button, badge, chips de filtro, paginación activa |
| Superficie | 14px | Borde hairline #D6E2E2 · sombra 0 1px 2px rgba(1,32,48,.06) · título Parkinsans | card, popover, dropdown-menu, toast, calendar |
| Campo | 10px | h-10 · borde #D6E2E2 · foco: borde petroleo + ring menta al 30% · placeholder muted | input, select, textarea, date-picker, multi-select |
| Lámina | 20px | Overlay rgba(1,32,48,.72) · sombra noche lg · header Parkinsans | dialog, sheet, drawer, command palette |
La doctrina del macroproyecto baja al nivel componente con cuatro sitios sutiles: el subrayado del tab activo · el avatar · el relleno de progress y progress-ring · la hairline del elemento protagonista (una card, un KPI). Siempre la venda petroleo→menta→verde, siempre fino (2–3px o un relleno), jamás en botones ni fondos.
Frías y contadas
Toda sombra se tinta a noche rgba(1,32,48,…) y baja un peso respecto a hoy (la
identidad no es elevación, es forma). El foco es uno solo en toda la librería: borde petroleo +
anillo menta-deep al 30%, 3px.
Parkinsans habla, Inter opera
Parkinsans 600–700 en: texto de botón, título de card/dialog/empty, número grande. Inter en:
campos, tablas, menús, badges, metadata — con tnum donde haya cifras. Nada de
UPPERCASE gritado; todo inicia en mayúscula — Sentence case, siempre.
Cada familia con su par: a la izquierda el shadcn de fábrica que tenemos hoy (con el esmeralda actual), a la derecha la propuesta. Debajo, la especificación en una línea y la receta: qué cambia en el cva.
Spec: pill · h-10 (h-9 sm) · px-22 · Parkinsans 600 13.5px · primary petroleo → hover petroleo-deep · secondary outline petroleo · ghost ink-soft · destructive #C0392B · icon-button circular size-9 (el canon del topbar). Receta: en buttonVariants: rounded-md → rounded-full, font-family-sans → font-display, y las variantes de color a tokens (ya retintados por la corrida A).
Spec: campo 10px · h-10 · borde line · foco borde petroleo + ring menta 30% · el search es pill con el botón embebido (el patrón del hero y del topbar, ahora componente oficial search-bar). Receta: input.tsx: rounded-md → rounded-[10px] + focus tokens; nace search-bar.tsx como composición, y el botón violeta de IA muere en el barrido.
Spec: 14px · hairline #D6E2E2 · sombra noche xs · CardTitle en Parkinsans 700 ink · CardDescription Inter ink-soft · horas en 12h minúscula. Receta: card.tsx: rounded-xl shadow-sm → rounded-[14px] shadow-2xs con la sombra tintada vía token, y CardTitle gana font-display.
Spec: pill · Inter 600 11px · Sentence case (inician en mayúscula, jamás EN MAYÚSCULAS) · paleta soft con rol: menta-soft/petroleo-deep (info-activo) · verde-soft/success · ámbar #F7ECCF/#8A5B12 (aviso, AA medido) · danger-soft/#9E2F22 · dot opcional. El violeta del badge actual muere. Receta: badge.tsx: rounded-md → rounded-full + variantes a los pares soft de la paleta; el barrido elimina text-violet-700.
Spec: sin caja ni cápsula · línea base line-soft · activo: tinta + subrayado degradado de 2px (la venda: petroleo→menta→verde — uno de los sitios sutiles del degradado en componentes) · inactivo ink-soft (muted falla AA a 13px: 3.78 — medido) · Inter 600/400 13px. Receta: tabs.tsx: TabsList pierde bg-muted rounded-md; TabsTrigger activo gana bg-[linear-gradient(90deg,…)] bg-bottom bg-no-repeat [background-size:100%_2px] con los tokens. La variante cápsula queda como variant="segmented" para los pocos casos de segmented-control real.
Spec: 20px · overlay rgba(1,32,48,.72) · sombra noche lg · DialogTitle Parkinsans 700 · botones pill · destructive #C0392B (nunca el rojo Tailwind). Receta: dialog.tsx/sheet.tsx/drawer.tsx: rounded-lg → rounded-[20px], overlay a token, título a font-display.
Spec: menú 14px + sombra noche md · ítem 8px, hover petroleo-soft al 40% · seleccionado petroleo-soft sólido con check petroleo · Inter 12.5px. Aplica igual a dropdown-menu, command y multi-select. Receta: content rounded-md → rounded-[14px], item rounded-sm → rounded-[8px] + estados a tokens.
| PACIENTE | ESTADO |
|---|---|
| Maria González | Agendada |
| Pedro Uzcátegui | Atendida |
| Paciente | Estado |
|---|---|
| Maria González | Agendada |
| Pedro Uzcátegui | Atendida |
Spec: header sin fondo, label Inter 600 11.5px Sentence case ink-soft, regla inferior 2px noche (la firma de la tabla) · filas hairline line-soft · hover #F4F9FB · cifras tnum · pills de estado de la familia 04. Ajuste fino sobre el mockup del dashboard: el header noche sólido queda para los documentos PDF; en producto la tabla respira. Receta: data-table.tsx + table.tsx: thead a la regla noche, celdas a tokens.
Spec: fallback con el degradado petroleo→menta (uno de los cuatro sitios sagrados de la doctrina) · inicial Parkinsans 700 · anillo blanco+line cuando se apila. Receta: avatar.tsx: AvatarFallback pierde el gris y gana el degradado vía clase utilitaria tokenizada.
Spec: checked petroleo (la acción, no el éxito — triage R2) · checkbox 5px · switch track petroleo · foco: el anillo menta único de la librería. Receta: tres archivos, solo tokens de estado — la forma ya está bien.
Spec: isotipo discreto (nunca emoji — la regla de Fabrizio hecha componente) · título Parkinsans · texto muted · CTA pill · copy en la voz (calma, sin drama). Receta: empty.tsx gana el slot de isotipo por defecto y el catálogo de copys pasa por voz/03.
Spec: skeleton en paper con el radio del rol que imita (pill si es texto/botón, 14 si es card) · tooltip sobre noche, 8px, máx 3 líneas. Receta: skeleton.tsx a bg-paper; tooltip.tsx a noche + 8px.
Spec: una gramática, dos temperamentos — admin en noche (activo petroleo + rail menta 3px inset, badge menta), consultorio en claro (activo petroleo-soft + rail petroleo); grupos en Parkinsans 700 Sentence case (nunca CAPS trackeadas); ítems 8px de radio; el user card con avatar degradado. Receta: sidebar.tsx ya tokeniza vía --sidebar-*: la gramática entra por los tokens de globals (corrida A) + rail como data-[active=true]:shadow-[inset_3px_0_0].
Spec: track en paper · relleno con la venda completa (el progreso ES curación — el degradado aquí significa) · punta redondeada · cifra Inter tnum. Sitio sutil oficial del degradado, junto al tab y el avatar. Receta: progress.tsx: indicator a bg-[linear-gradient(90deg,…)] con tokens; progress-ring.tsx: stroke a url(#gradient) con los mismos stops.
Spec: superficie 14px + sombra noche md · ícono lucide en chip soft del resultado (éxito=verde-soft, aviso=ámbar, error=danger-soft — el toast comunica el resultado de la acción; la pill comunica el estado del objeto) · nunca emoji · título Parkinsans 700 · detalle con el dato útil (fecha, hora) · acción como link petroleo. Receta: el theme del toast (Sileo) a tokens: radius superficie, sombra noche, tipografías por rol.
Spec: mes en Parkinsans 700 · día elegido como pill petroleo (es una acción) · hoy con anillo hairline · deshabilitados en line · cupos del booking heredan la misma gramática (petroleo-soft → elegido petroleo). Receta: calendar.tsx + date-picker.tsx: day cell rounded-md → rounded-full + estados a tokens.
Todo lo anterior se logra donde shadcn quiere que se logre: en los cva de cada
componente y en los tokens. Ejemplo real — el diff conceptual del botón:
- "… rounded-md text-sm font-semibold font-family-sans …" + "… rounded-full text-sm font-semibold font-display …" - default: 'bg-brand-600 … shadow-xs hover:bg-brand-700' + default: 'bg-brand-600 hover:bg-brand-700' // mismos tokens — la corrida A ya los volvió petroleo; aquí solo cambia la forma + // regla global de foco (una para toda la librería): + focus-visible:border-brand-600 focus-visible:ring-[3px] focus-visible:ring-ring/30
Tokens primero, forma después
Este subproyecto asume las corridas del restyle: la A retinta los tokens (color correcto, forma vieja) y este catálogo cambia la forma (los cva). Son commits distintos y verificables por separado — el color nunca se hardcodea en la forma.
~24 archivos cva, 151 beneficiados
Las 16 familias viven en ~24 archivos de packages/ui/src/components. Los 102
componentes de subcarpetas (layouts, notifications, comunes) heredan por composición — se
revisan con el checklist de la auditoría, no se reescriben.
Antes de declarar el lenguaje cerrado, se retó a sí mismo: cada par de color medido contra WCAG, cada radio contra la doctrina, cada degradado contra su presupuesto. Esto es lo que el reto encontró y cómo quedó resuelto — los números son medidos, no estimados.
| Hallazgo | Medición | Resolución |
|---|---|---|
| El badge ámbar fallaba AA | #B6791A / #F7ECCF → 3.11:1 ❌ | Nace --state-warning-deep #8A5B12 (4.98:1 ✅) como texto sobre warning-soft — token nuevo del sistema, no un parche local. Corregido en cascada en las cuatro piezas del hub. |
| El texto danger sobre danger-soft quedaba corto | #C0392B / #F6DEDA → 4.24:1 ⚠️ | Nace --state-danger-deep #9E2F22 (5.68:1 ✅). El #C0392B queda para fondos sólidos con texto blanco (5.44:1 ✅). |
| El tab inactivo en muted no llegaba | #6E8794 / blanco → 3.78:1 ⚠️ | Inactivo pasa a ink-soft (8.57:1 ✅). Muted queda para metadata y placeholders (exentos), nunca para navegación. |
| Menta y verde se mezclaban semánticamente | toast "agendada" en menta | La regla queda escrita: la pill dice el estado del objeto (Agendada=menta · Atendida=verde) y el toast dice el resultado de la acción (éxito=verde, siempre). El chip del toast se corrigió. |
| Dos direcciones de degradado conviviendo | 90° vs 120° sin regla | 90° en elementos horizontales (tab, progress, hairline) · 120° solo en la marca (cruz, avatar). Ahora es regla, no accidente. |
| Radios fuera de la doctrina de cuatro | ítem 8px · checkbox 5px | Se formaliza el quinto peldaño derivado: r-micro = el radio del padre − 6 (ítem dentro de superficie 14→8 · marca dentro de campo 10→5, el checkbox). La doctrina sigue siendo cuatro roles + una regla de herencia. |
| Tipografías fuera de la escala | 13.5 / 12.5 / 11.5 / 9.5px | Los specs se alinean a la escala del sistema: 11 · 13 · 15. Botón compacto 13, botón default 15, badge 11, grupo de sidebar 10 (text-2xs). Los mockups son ilustrativos; el cva usa tokens de escala. |
| El presupuesto del degradado podía desbordarse | tab + progress + hairline en una vista | Regla de presupuesto: máximo dos apariciones sutiles por vista (sin contar el avatar). Ante conflicto gana el que comunica estado (progress) y la hairline decorativa se omite. |
Pasaron sin tocar: botón primary (6.3:1), destructive (5.44:1), badges info y success (8.17 y 4.86), el badge menta del sidebar con tinta noche (7.82), el header de tabla en ink-soft (8.57). El lenguaje era sólido — ahora además está probado.
| Fase | Qué se toca | Qué cambia |
|---|---|---|
| 1 | Los 4 radios como tokens | --radius-action (pill) · --radius-surface 14 · --radius-field 10 · --radius-panel 20 en globals.css, para que la doctrina sea código. |
| 2 | Acción + campo | button, input, select trigger, badge, search-bar nuevo — las familias 01, 02, 04. El cambio más visible. |
| 3 | Superficies + láminas | card, popover, dropdown, dialog, sheet, drawer, tooltip, toast — familias 03, 06, 07, 12. |
| 4 | Datos + estados | data-table, tabs, avatar, checkbox/radio/switch, empty, skeleton — familias 05, 08–11, 13–16. |
| 5 | QA visual | /cresco:visual-qa por app contra este catálogo + las pantallas del macroproyecto; el checklist de 7 preguntas por componente compuesto. |
Este catálogo cambia forma, no color: depende de que la corrida A (tokens petroleo) esté mergeada, y conviene que B y C hayan barrido las clases crudas para que la forma nueva no conviva con esmeralda hardcodeado. Orden natural: A → B → C → componentes.
Los componentes son la marca en su tamaño más pequeño.
Un botón pill, una card con hairline, una tabla que respira: nadie los mira dos veces, y por eso son la identidad de verdad — la que se siente sin verse. Con cuatro radios con rol, un foco único y dos tipografías haciendo su papel, los 151 componentes dejan de ser un kit de fábrica y se vuelven la casa.
Ninguno funcional. cva variants y tokens; API de componentes intacta.
Fase 2. Botón pill + campo quieto: el 80% de la sensación.
El topbar de /search — ya era el norte; ahora es componente oficial.
Cuatro radios con rol. Todo lo demás se deduce.