Skip to content

Field

The parts every field is made of: Label, Description, FieldError and FieldHelp (the description, replaced by the error while invalid). Field wraps a control that is not a React Aria field with them, and nativeFieldBox styles a native select or text area like the rest; FieldGroup holds an input and the buttons that act on it in one box.

Language
Appearance

Plain children

The short form: an Input or TextArea inside is tied to the label, the help and the error by itself.

The patient sees it in the reminder.
Write at most 500 characters.
import { Field, Input, TextArea } from '@arbol-ai/ui';

<div className="grid max-w-xs gap-4">
  <Field description="The patient sees it in the reminder." label="Reason">
    <Input defaultValue="Follow-up visit" />
  </Field>
  <Field errorMessage="Write at most 500 characters." isInvalid label="Note for the front desk">
    <TextArea defaultValue="Bring the authorization." />
  </Field>
</div>

A native select or text area

nativeFieldBox gives a native control the look of the fields beside it, from the theme tokens; the Field’s id goes on the control.

Only active agendas are listed.
import { Field, nativeFieldBox } from '@arbol-ai/ui';

<div className="grid max-w-xs gap-4">
  <Field description="Only active agendas are listed." id="example-agenda" label="Agenda">
    <select className={nativeFieldBox()} defaultValue="ruiz" id="example-agenda">
      <option value="ruiz">Dr. Ruiz</option>
      <option value="room-2">Room 2</option>
    </select>
  </Field>
  <Field id="example-instructions" label="Instructions">
    <textarea className={nativeFieldBox({ control: 'textarea' })} id="example-instructions" />
  </Field>
</div>

A control of your own

Field gives the control the ids that tie it to its label, help and error. Here an input with suggestions validates what was typed.

Like 30m, 1h or 1h30.
import { useId, useState } from 'react';
import { Field, Input } from '@arbol-ai/ui';

const USUAL_DURATIONS = ['15m', '30m', '45m', '1h', '1h30', '2h'];

const DurationExample = () => {
  const [text, setText] = useState('30m');
  const suggestions = useId();
  const isValid = text === '' || /^\d+(?:m|h(?:\d+)?)$/u.test(text);
  return (
    <div className="max-w-xs">
      <Field
        description="Like 30m, 1h or 1h30."
        errorMessage="Write the length like 30m, 1h or 1h30."
        isInvalid={!isValid}
        label="Appointment length"
      >
        {(control) => (
          <>
            <Input
              {...control}
              list={suggestions}
              onChange={(event) => {
                setText(event.target.value);
              }}
              value={text}
            />
            <datalist id={suggestions}>
              {USUAL_DURATIONS.map((duration) => (
                <option key={duration} value={duration}>
                  {duration}
                </option>
              ))}
            </datalist>
          </>
        )}
      </Field>
    </div>
  );
};

<DurationExample />

An input with a button

FieldGroup draws one box around the input and its button, with one focus ring.

At least 12 characters.
import { EyeIcon, EyeSlashIcon } from '@phosphor-icons/react';
import { useState } from 'react';
import { TextField as AriaTextField } from 'react-aria-components';
import { Button, FieldGroup, FieldGroupInput, FieldHelp, Label } from '@arbol-ai/ui';

const PasswordExample = () => {
  const [isVisible, setIsVisible] = useState(false);
  const label = 'Password';
  return (
    <AriaTextField className="flex max-w-xs flex-col gap-1.5" defaultValue="clinic-2026">
      <Label>{label}</Label>
      <FieldGroup>
        <FieldGroupInput type={isVisible ? 'text' : 'password'} />
        <Button
          aria-label={isVisible ? 'Hide password' : 'Show password'}
          className="me-0.5"
          onPress={() => {
            setIsVisible((visible) => !visible);
          }}
          size="icon-sm"
          variant="ghost"
        >
          {isVisible ? <EyeSlashIcon aria-hidden /> : <EyeIcon aria-hidden />}
        </Button>
      </FieldGroup>
      <FieldHelp description="At least 12 characters." />
    </AriaTextField>
  );
};

<PasswordExample />

Source

The examples above are src/components/field/field.examples.tsx, which the library’s tests render too.

Type to search…

↑↓ navigate↵ selectEsc close