Componentes · Ola 1 · Formularios
Field
Da a un campo su etiqueta, su texto de ayuda y su mensaje de error, y los conecta con el control. Envuelve un Input, un Textarea, un Select, un Combobox o un DatePicker y, por contexto,
les pasa el id al que apunta la etiqueta, aria-describedby, aria-invalid, required y disabled. El control no necesita saber nada: se escribe igual
que suelto.
import { Field, Input } from '@archeblack/ui';
// o Textarea, Select, Combobox o DatePicker, según el control
Ejemplos
Etiqueta
Lo mínimo: una etiqueta y el control. Un clic en la etiqueta enfoca el campo.
<script lang="ts">
import { Field, Input } from '@archeblack/ui';
let name = $state('Portal de clientes');
</script>
<div style:width="min(100%, 20rem)">
<Field label="Nombre del proyecto">
<Input bind:value={name} />
</Field>
</div> Texto de ayuda
hint explica el dato o su formato. Se lee junto con el control.
Enviaremos las facturas a esta dirección.
<script lang="ts">
import { Field, Input } from '@archeblack/ui';
let email = $state('');
</script>
<div style:width="min(100%, 20rem)">
<Field label="Correo de facturación" hint="Enviaremos las facturas a esta dirección.">
<Input type="email" bind:value={email} placeholder="nombre@empresa.com" />
</Field>
</div> Error
error marca el control como inválido y muestra el mensaje con un ícono. Escribe un subdominio válido para ver cómo se va.
Usa solo minúsculas, números y guiones.
Es la dirección pública del proyecto.
<script lang="ts">
import { Field, Input } from '@archeblack/ui';
let subdomain = $state('portal clientes');
// El mensaje dice qué hacer, no solo qué está mal.
const error = $derived(
/^[a-z0-9-]*$/.test(subdomain) ? undefined : 'Usa solo minúsculas, números y guiones.'
);
</script>
<div style:width="min(100%, 20rem)">
<Field label="Subdominio" {error} hint="Es la dirección pública del proyecto.">
<Input bind:value={subdomain} suffix=".arche.app" />
</Field>
</div> Obligatorio y deshabilitado
required suma el asterisco y el atributo nativo; disabled deshabilita el control y apaga la etiqueta. La ayuda se sigue leyendo: es el lugar para explicar por qué está deshabilitado.
No se puede cambiar: se asigna al crear el proyecto.
<script lang="ts">
import { Field, Input } from '@archeblack/ui';
</script>
<div style:display="grid" style:gap="var(--arche-spacing-6)" style:width="min(100%, 20rem)">
<Field label="Nombre del equipo" required>
<Input autocomplete="organization" />
</Field>
<Field
label="ID del proyecto"
hint="No se puede cambiar: se asigna al crear el proyecto."
disabled
>
<Input value="prj_7f3a92c1" />
</Field>
</div> Formulario completo
Los tres controles dentro de Field. Los errores aparecen al enviar y el foco va al primer campo con error.
<script lang="ts">
import { tick } from 'svelte';
import { Button, Field, Input, Select, Textarea } from '@archeblack/ui';
let name = $state('');
let email = $state('');
let subdomain = $state('');
let region = $state('');
let description = $state('');
let submitted = $state(false);
let saved = $state(false);
// Los errores aparecen después del primer envío, no mientras la persona escribe por primera vez.
const errors = $derived({
name: name.trim() ? undefined : 'Escribe un nombre para el proyecto.',
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
? undefined
: 'Escribe un correo con el formato nombre@empresa.com.',
subdomain: /^[a-z0-9-]+$/.test(subdomain)
? undefined
: 'Usa solo minúsculas, números y guiones.',
region: region ? undefined : 'Elige dónde se alojan los datos.'
});
/** El error de un campo, solo después del primer envío. */
const errorOf = (field: keyof typeof errors) => (submitted ? errors[field] : undefined);
async function submit(event: SubmitEvent) {
event.preventDefault();
const form = event.currentTarget as HTMLFormElement;
submitted = true;
saved = !Object.values(errors).some(Boolean);
if (!saved) {
// Espera a que los campos muestren su error y lleva el foco al primero.
await tick();
form.querySelector<HTMLElement>('[aria-invalid="true"]')?.focus();
}
}
</script>
<form
novalidate
onsubmit={submit}
style:display="grid"
style:gap="var(--arche-spacing-6)"
style:width="min(100%, 26rem)"
>
<p style:margin="0" style:color="var(--arche-color-text-muted)">
Los campos con * son obligatorios.
</p>
<Field label="Nombre del proyecto" required error={errorOf('name')}>
<Input bind:value={name} autocomplete="off" />
</Field>
<Field
label="Correo de facturación"
hint="Enviaremos las facturas a esta dirección."
required
error={errorOf('email')}
>
<Input type="email" bind:value={email} placeholder="nombre@empresa.com" autocomplete="email" />
</Field>
<Field label="Subdominio" required error={errorOf('subdomain')}>
<Input bind:value={subdomain} suffix=".arche.app" autocomplete="off" />
</Field>
<Field label="Región" required error={errorOf('region')}>
<Select bind:value={region} placeholder="Elige una región">
<option value="sa">Sudamérica (São Paulo)</option>
<option value="us">EE. UU. Este (Virginia)</option>
<option value="eu">Europa (Irlanda)</option>
</Select>
</Field>
<Field label="Descripción" hint="Opcional. La ve todo el equipo.">
<Textarea bind:value={description} rows={3} />
</Field>
<div style:display="flex" style:align-items="center" style:gap="var(--arche-spacing-4)">
<Button type="submit" variant="primary">Crear proyecto</Button>
<span role="status" style:color="var(--arche-color-text-muted)">
{saved ? 'Proyecto listo para crear.' : ''}
</span>
</div>
</form> Props
Un control propio del producto se conecta igual que Input con getFieldContext(): devuelve id, describedBy, invalid, required y disabled del Field más cercano, o undefined si el control está suelto.
| Prop | Descripción |
|---|---|
label string | Etiqueta visible. Es el nombre accesible del control. |
hint? string | Texto de ayuda debajo del control: el formato esperado, para qué se usa el dato. Describe al control (aria-describedby). |
error? string | Mensaje de error. Si no está vacío, el control queda con aria-invalid="true", su línea toma el color de peligro y el mensaje aparece con un ícono, antes de la ayuda. |
required? boolean Por defecto false | Campo obligatorio: el control recibe required y la etiqueta, un asterisco visual. |
disabled? boolean Por defecto false | Deshabilita el control que envuelve. La etiqueta y el asterisco pasan a text-disabled, igual que si el control se deshabilita por su cuenta. La ayuda queda en text-muted, porque suele explicar por qué el campo está deshabilitado. |
id? string | Id del control. Si falta, Field genera uno estable (también en el servidor). Dentro de un Field el id va aquí, no en el control. |
children Snippet | Un solo control: un Input, Textarea o Select. En desarrollo, un segundo control dentro del mismo Field avisa por consola. |
class? ClassValue | Clases del producto; se suman a arche-field. |
...rest HTMLAttributes<HTMLDivElement> | Cualquier otro atributo va al <div> raíz. |
Accesibilidad
- La etiqueta es un
<label for>que apunta al id del control: nombra al campo y un clic en ella lo enfoca. - El error y la ayuda describen al control con
aria-describedby, en ese orden: el lector de pantalla lee el nombre, el valor y después el error y la ayuda. Si el control ya tenía su propioaria-describedby, se conserva. - Con error, el control lleva
aria-invalid="true". El error nunca es solo color: la línea cambia y el mensaje lleva ícono y texto. - El asterisco de
requiredes visual (aria-hidden); el control anuncia «obligatorio» por el atributo nativorequired. Explica el asterisco al inicio del formulario. - El mensaje de error no es una región viva: al enviar, lleva el foco al primer campo inválido y el lector leerá su error. Anunciar cada tecla sería ruido.
- Los ids salen de
$props.id(): son iguales en el servidor y en el navegador.
Qué evitar
- Un placeholder como única etiqueta. Siempre
label. El placeholder desaparece al escribir y es texto sutil: sirve solo para un ejemplo del formato. - Mostrar errores mientras la persona escribe por primera vez. Valida al enviar o al salir del campo; después, corrige el mensaje en vivo mientras lo arregla.
- Un error que solo dice qué está mal («Subdominio inválido»). Dile qué hacer: «Usa solo minúsculas, números y guiones».
- Poner el
iden el Input dentro de un Field. Ponlo en Field (<Field id="email">): la etiqueta necesita conocerlo. - Dos controles dentro de un Field (nombre y apellido juntos). Un Field por control: la etiqueta, la ayuda y el error apuntan a un solo id. En desarrollo, el segundo control avisa por consola.
- Un Field para un grupo de casillas o radios. Un
<fieldset>con<legend>, o el grupo de Radio, que ya nombra al conjunto.