Fase 2 — en progreso

Primitives

Componentes primitivos del design system. Cada uno usa exclusivamente los tokens definidos en globals.css.

2.1Button

Variantes primary (accent, conversión), secondary, tertiary/ghost y destructive. Tamaños sm/md/lg. Icono izq/der o solo icono. Estado loading con spinner interno.

Variantes

Tamaños

Con icono

Estados

Texto largo (alemán, prueba de resistencia)

<Button variant="primary" size="lg" icon={<Search size={18} />}>
  Buscar
</Button>

<Button loading>Enviando…</Button>
<Button icon={<Search size={20} />} aria-label="Buscar" />

2.2Input

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.

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" />

2.3Textarea

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

<Textarea label="Mensaje" maxLength={200} showCount helperText="Máximo 200 caracteres." />

2.4Select

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.

<Select
  label="Tipo de vivienda"
  placeholder="Selecciona un tipo"
  options={[{ value: 'piso', label: 'Piso' }, { value: 'chalet', label: 'Chalet / villa' }]}
/>

2.5Checkbox

Caja visual 20×20px, hit-area de fila completa (min-h-touch). Estado indeterminado no está en el spec — no implementado.

Estados

Label largo (alemán, prueba de resistencia)

<Checkbox label="Acepto la política de privacidad" />
<Checkbox label="Con error" errorMessage="Debes aceptar para continuar" />

2.5Radio

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" />

2.6Switch / Toggle

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) => …} />

2.7Combobox / Autocomplete

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.

<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></>}
/>

2.8Badge / Tag / Pill

Un único componente: shape='rounded' cubre Badge/Pill (informativo o de estado), shape='square' cubre Tag (metadatos de blog). No interactivo.

Variantes (shape=rounded)

NeutralVerificadoDisponible hoyHorario limitadoNo disponibleNuevo

Tag (shape=square)

FontaneríaMantenimiento
<Badge variant="primary">Verificado</Badge>
<Badge shape="square" variant="primary">Fontanería</Badge>

2.9Chip

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>

2.10Avatar

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)

Anke Weber
<Avatar alt="Karl Schmidt" size="lg" />
<Avatar alt="Anke Weber" src="/professionals/anke-weber.jpg" size="lg" />

2.11Divider

hr semántico sin label. Con label, un div (hr no admite children).

Sin label


Con label

o continúa con
<Divider />
<Divider label="o continúa con" />

2.12Skeleton loader

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" />

2.13Spinner

role=status + texto sr-only. Usado internamente por Button en su estado loading.

Tamaños

Cargando…Cargando…Cargando…
<Spinner size={20} />

2.13Progress bar

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" />

2.14Tooltip

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

Verificado por MyCasa Spain el 12 de marzo de 2026Más informaciónSe abre en una pestaña nueva
<Tooltip content="Verificado por MyCasa Spain el 12 de marzo de 2026" placement="top">
  <Button variant="tertiary" size="sm">¿Qué significa "verificado"?</Button>
</Tooltip>