Fase 2 — en progreso
Componentes primitivos del design system. Cada uno usa exclusivamente los tokens definidos en globals.css.
Inline (subrayado permanente, dentro de párrafo), standalone (con flecha animada) y external (icono + aviso para lectores de pantalla).
Inline (en párrafo)
Puedes consultar nuestra política de privacidad antes de continuar.
Standalone
<Link variant="inline" href="/legal/privacidad">política de privacidad</Link>
<Link variant="standalone" href="/servicios">Ver todos los servicios</Link>
<Link variant="standalone" href="https://…" target="_blank">Fuente externa</Link>text, email, tel, number, password. Label siempre visible, helper/error mutuamente excluyentes, icono de contexto opcional.
Tipos
Con icono
Estados
Te llamaremos solo si hace falta.
Introduce un email válido
Label largo (alemán, prueba de resistencia)
<Input label="Email" type="email" icon={<Mail size={18} />} />
<Input label="Teléfono" type="tel" optional />
<Input label="Email" type="email" errorMessage="Introduce un email válido" />Misma anatomía que Input. Resize solo vertical. Contador de caracteres opcional cuando se pasa maxLength + showCount.
Default
Con contador
Máximo 200 caracteres.
0/200
Con error
Este campo es obligatorio
<Textarea label="Mensaje" maxLength={200} showCount helperText="Máximo 200 caracteres." />Nativo estilizado — conserva el picker del sistema operativo. El custom con búsqueda es el Combobox (§2.7), un primitivo aparte.
Default
Estados
Puedes cambiarlo más tarde.
Selecciona un tipo de vivienda
<Select
label="Tipo de vivienda"
placeholder="Selecciona un tipo"
options={[{ value: 'piso', label: 'Piso' }, { value: 'chalet', label: 'Chalet / villa' }]}
/>Caja visual 20×20px, hit-area de fila completa (min-h-touch). Estado indeterminado no está en el spec — no implementado.
Estados
Debes aceptar para continuar
Label largo (alemán, prueba de resistencia)
<Checkbox label="Acepto la política de privacidad" />
<Checkbox label="Con error" errorMessage="Debes aceptar para continuar" />Mismo patrón que Checkbox. La agrupación (name compartido) es responsabilidad del consumidor — no hay RadioGroup a nivel de primitivo.
Grupo (name compartido)
<Radio name="vivienda" label="Piso" defaultChecked />
<Radio name="vivienda" label="Chalet / villa" />Uso restringido a configuraciones binarias inmediatas, nunca en envío de formularios (ahí, Checkbox). role=switch, sin color success/danger a propósito.
Estados
<Switch label="Notificarme por email" defaultChecked onCheckedChange={(v) => …} />Buscador de ubicación/servicio del hero. Patrón ARIA combobox con aria-activedescendant, navegación por teclado (↑↓ Enter Esc) y resaltado de coincidencia.
Escribe “al” o usa las flechas del teclado
Estados
Cubrimos toda la Costa Blanca.
Selecciona una localidad de la lista
<Combobox
label="Localidad"
placeholder="¿En qué localidad?"
options={[{ value: 'alicante', label: 'Alicante' }, …]}
onValueChange={(value) => …}
emptyState={<>No encontramos tu localidad. <Link href="/contacto">Contáctanos</Link></>}
/>Un único componente: shape='rounded' cubre Badge/Pill (informativo o de estado), shape='square' cubre Tag (metadatos de blog). No interactivo.
Variantes (shape=rounded)
Tag (shape=square)
<Badge variant="primary">Verificado</Badge>
<Badge shape="square" variant="primary">Fontanería</Badge>Seleccionable, para filtros. 40px de alto (excepción explícita del spec al touch-target de 48px cuando hay gap≥8px).
Grupo de filtros
<Chip defaultSelected onSelectedChange={(v) => …}>Fontanería</Chip>Tamaños sm/md/lg/xl. Fallback de iniciales (no silueta genérica) cuando no hay foto o falla la carga.
Tamaños (fallback de iniciales)
Con imagen que falla (cae a iniciales)

<Avatar alt="Karl Schmidt" size="lg" />
<Avatar alt="Anke Weber" src="/professionals/anke-weber.jpg" size="lg" />hr semántico sin label. Con label, un div (hr no admite children).
Sin label
Con label
<Divider />
<Divider label="o continúa con" />La geometría (ancho/alto/radio) la controla el className del caller, para replicar el contenido real que sustituye.
Ejemplo: card de servicio cargando
<Skeleton className="h-40 w-full rounded-lg" />
<Skeleton className="h-5 w-3/4" />role=status + texto sr-only. Usado internamente por Button en su estado loading.
Tamaños
<Spinner size={20} />Componente separado del Spinner (§2.13): para pasos de formulario largo. role=progressbar con aria-valuenow/min/max.
Paso 2 de 4
<ProgressBar value={2} max={4} label="Paso 2 de 4" />Solo para información complementaria no crítica. Hover (con delay de 400ms) y foco por teclado (inmediato). En móvil, esa información debe estar disponible por otra vía — el primitivo no lo garantiza por sí solo.
Prueba con ratón y con Tab
<Tooltip content="Verificado por MyCasa Spain el 12 de marzo de 2026" placement="top">
<Button variant="tertiary" size="sm">¿Qué significa "verificado"?</Button>
</Tooltip>