01Tokens: la ley que la app copia
globals.css · color-variables.css · mogos-brand.css · apps/client/app/globals.cssAntes de un solo componente, la fila cero de la galería muestra los valores: el sistema semántico de shadcn tal como lo pinta el client (--primary #3765c0, no el gris neutro del package), la capa de marca mogos-* que ya viste el 17 % de las pantallas, las tres familias con su reparto real, la escala de radios derivada de --radius 10 px y las dos sombras que conviven en la web. Nada de esto es del estudio: es lo que hay.
--primary es del client, no del package
packages/uideja--primaryen gris neutro;apps/client/app/globals.css:8-11lo repinta a #3765c0 junto con--ring. Todo lo que leabg-primary(switch, radio, tooltip, día seleccionado) es azul producto.mobile-uiya tienebrand.500 #3765c0: se conserva.Tres azules en el mismo formulario
El botón primario es brand-700 #213e83 (
button.tsx:14), el checkbox marcado es brand-600 #274da2 (checkbox.tsx:17) y el foco/switch es #3765c0. La app los replica los tres; no se «unifican».El reparto tipográfico real
Geist en el
body(next/font,layout.tsx:72-90); Poppins en cada componente que llevafont-family-sans(257 usos); Lemon Milk Pro enfont-family-display: números y títulos de cards, títulos de diálogo y de página (97 usos). Ni «Geist en todo» ni «Lemon Milk en todo» como hacemobile-clienthoy.Radios: un paso más abierto que mobile-ui
La web deriva de
--radius 10 px: sm 6 · md 8 · lg 10 · xl 14.mobile-uiva un paso más cerrado (lg 8, xl 10): se corrige la escala entera entheme/radii.ts.Dos sombras conviven
Los primitivos shadcn llevan
shadow-xsde Tailwind (negro al 5 %, un hairline); las superficies elevadas de mogos ya van tintadas: menúrgba(2,27,84,.10), FABrgba(33,62,131,.45), barrasrgba(15,23,42,.04). En RN, un soloshadowColorpor preset; ver decisión 4.Light-only, tokens por esquema
userInterfaceStyle: light(DECISIONS #3/#21). Los tokens se estructuranlight / darkdesde el inicio; dark queda vacío hasta que la web lo active.
02Texto y botón
button.tsx:7-37 · presets de TextSiete variantes en la web (default, destructive, outline, secondary, tertiary, ghost, link), no cinco: el brief contaba las de shadcn y la casa añadió tertiary y cambió secondary a blanco con borde. Cuatro tamaños (40 / 32 / 48 / icono 36), el disabled del primario es gris explícito, y el foco es un anillo de 3 px al 50 % del ring. Antes, los siete presets de Text que espejan el reparto tipográfico real.
Llega al puerto en 35–45 días. Te avisamos cuando pase por aduana.
Siete variantes, no cinco
A shadcn la casa le añadió tertiary (
bg-gray-100) y cambió secondary a blanco con bordeinput(button.tsx:18-20). Las siete entran enmobile-ui/Button;linkse vuelve unPressablecon texto subrayado a 4 px.Primario = brand-700, disabled gris explícito
bg-brand-700 … disabled:bg-gray-100 disabled:text-gray-500: el disabled del primario no es opacidad, es gris. Los demás usanopacity-50.mobile-uiya usabrand[700]en el botón: DECISIONS #3 lo daba por error; leído el cva, es correcto — lo que corrige MF-01 es el foco/ring (#3765c0) y la tipografía.Texto Poppins 14/600, altura 40, radio 8
font-family-sans text-sm font-semibold rounded-md h-10 px-4: el botón es Poppins semibold sobre radio md 8, no lg 10. Icono 16 con gap 8; con icono el padding se queda en 16.Foco y press
Foco:
border-ring ring-ring/50 ring-[3px]→ borde #3765c0 + anillo 3 px al 50 %. Press: la web no tieneactive:enButton; el shell móvil sí usaactive:scale-95con 150 ms. En RN, press = scale .95, 160 ms para todo botón (sección 10) — la única libertad que se toma, y va en motion, no en el sistema.Loading
La web no tiene prop
loading: cada pantalla mete unLoader2 animate-spin.mobile-ui/Buttonlo formaliza:loadingreemplaza el icono por el spinner de 16 y bloquea el press sin cambiar el color.Presets de Text
display(Lemon Milk 24/1.25),num(Lemon Milk 30/500 tabular),heading(Poppins 18/600),body(Geist 16/24),sm(Poppins 14/500),muted(Poppins 14 #667085),mono(Geist Mono 12). Cada preset apunta a la clase web que espeja; PARITY.md lo lista.
03Campos: Input, Label, Field, Textarea, sufijo y OTP
input.tsx:10 · label.tsx:16 · field.tsx · numeric-input-with-suffix.tsx · otp-input-group.tsx:87El input mide 40 px, radio 8, borde #d0d5dd, texto Poppins 16 (evita el zoom de iOS), foco con anillo de 2 px (el input es la excepción: botón y textarea usan 3). El error pinta el borde destructivo y el label. El OTP que usa la app es el de auth: seis cajas separadas de 44 × 48 con tinta marino y foco celeste, no el agrupado de shadcn.
Anatomía del input
h-10 rounded-md border-input bg-base-white px-3 text-base font-family-sans: 40 px, radio 8, borde #d0d5dd, Poppins 16. Placeholdertext-muted-foreground#667085. En RN,TextInputconfontSize 16ylineHeight 24: en iOS evita el zoom automático y en Android alinea la línea base.Foco de 2 px, no de 3
focus-visible:ring-2enInputySelect;ring-[3px]en Button, Textarea, Checkbox, Radio, Switch.mobile-ui/Inputpinta borde #3765c0 + anillorgba(55,101,192,.5)de 2 px cononFocus; sinoutlinenativo.Error = borde + label + mensaje
aria-invalid:border-destructiveen el control ydata-[invalid=true]:text-destructiveen elField(field.tsx:58): el label también se pinta.FieldError14 px destructivo;FieldDescription14 px muted. Gap interno 6 px (gap-1.5).Sufijo numérico
Un solo borde envolvente con
focus-within, el input pierde su borde, el sufijo va sobrebg-mutedconborder-ly Poppins 14/500. Se usa en peso y dimensiones del envío y en la calculadora.Dos OTP, uno por uso
/verify-otpy/verify-whatsapp-otprenderizanotp-input-group.tsx: seis cajas 44 × 48, radio 11, texto marino 20/500 tabular, fococeleste-deepcon halorgba(95,185,216,.18)y-translate-y-px, inválido rojo con halorgba(201,49,60,.14). ElInputOTPagrupado de shadcn (36 × 36) queda como segundo primitivo para códigos fuera de auth. La app implementa los dos; MF-02 usa el primero.Teclado
Sin
KeyboardHintBar(no aplica). El patrónKeyboardAvoidingdePageFramedesplaza el campo activo 16 px por encima del teclado; en iOS elinputAccessoryViewnativo trae «Listo».
04Selección: Select, MultiSelect, Checkbox, Radio, Switch
select.tsx:46,116 · multi-select.tsx · checkbox.tsx:17 · radio-group.tsx:30 · switch.tsx:16-24El Select comparte anatomía con el input (40, radio 8, borde input) y abre un popover blanco con sombra md; en móvil ese popover se convierte en bottom sheet con las mismas filas. Checkbox 16 px con radio 6 y relleno brand-600; radio de 16 con punto de 8 en primary; switch 32 × 18, pista input → primary.
Select = input con chevron
h-10 rounded-md border-input px-3 py-2 text-base+ chevron 16 al 50 %. Abierto:data-[state=open]:ring-2, mismo anillo que el foco. Placeholder muted. En RN el trigger es idéntico; la lista abre en bottom sheet (sección 8) con las filas deSelectItem: 14 px, padding 6/8, radio 6, check a la derecha,bg-accenten la resaltada.MultiSelect = botón outline + chips neutral
Trigger
Button variant=outlinecon peso normal y muted cuando está vacío; lo elegido sale comoBadge neutralcon una X de 12 en un hit-area de 28 (multi-select.tsx:175). Las opciones llevan un cuadro de 16 con borde primary que se rellena al marcar — distinto al Checkbox (brand-600). Se replica tal cual.Checkbox brand-600, borde primary
size-4 rounded-sm border-input→ 16 px, radio 6 (la escala custom:rounded-sm= 10 − 4). Marcado:bg-brand-600 border-primarycon check de 14 blanco. Sin animación de check en la web (transition-none): en RN tampoco.Radio y switch beben de --primary
Punto de 8
fill-primary= #3765c0; switchdata-[state=checked]:bg-primarycon pistainputapagada, thumb 16 blanco que viajacalc(100% − 2px). En RN se dibuja propio (no elSwitchnativo: iOS lo pinta verde y de 51 × 31).Filas de opción de 44
El control mide 16 pero la fila tocable mide 44 (
min-h-11del shell móvil): label Poppins 14/500secondary-foregroundcon gap 12. Disabled:opacity-50en control y label (label.tsx:16).