Ir al contenido

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.

Svelte
<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.

Svelte
<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.

.arche.app

Usa solo minúsculas, números y guiones.

Es la dirección pública del proyecto.

Svelte
<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.

Svelte
<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.

Los campos con * son obligatorios.

Enviaremos las facturas a esta dirección.

.arche.app

Opcional. La ve todo el equipo.

Svelte
<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.

Props de Field
PropDescripció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 falseCampo obligatorio: el control recibe required y la etiqueta, un asterisco visual.
disabled? boolean Por defecto falseDeshabilita 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 propio aria-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 required es visual (aria-hidden); el control anuncia «obligatorio» por el atributo nativo required. 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 id en 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.