---
title: Label, Help, FieldError
description: The three text pieces around a control, usable on their own when you compose a field by hand.
preview: label-help-error
---
# Label, Help, FieldError

`Field` renders these for you. Import them when you assemble a field yourself, or to change the words without touching the schema.

## Preview
<!-- preview: label-help-error -->

## Usage
```tsx
import { Form, Label, Help, FieldControl, FieldError, Submit } from '@trymirio/mirio';

<Form schema={schema} client={mirio}>
  <div className="flex flex-col gap-1.5">
    <Label name="company">Company name</Label>   {/* children override the schema label */}
    <Help name="company" />
    <FieldControl name="company" />
    <FieldError name="company" />
  </div>
  <Submit />
</Form>
```

## Props

### Label
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | required | The field key. |
| `children` | `ReactNode` | `field.label` | Replaces the label text. |
| `requiredIndicator` | `ReactNode \| null` | from `Form`/`MirioProvider`, else nothing | What to append when `field.required`. |
| `optionalIndicator` | `ReactNode \| null` | from `Form`/`MirioProvider`, else a small muted "Optional" | What to append when the field isn't required; `null` renders nothing. |
| `className` | `string` | `text-sm font-medium leading-none text-mirio-fg` | Merged onto the element. |
| …`<label>` attributes | | | Spread on the `<label>`. |

Renders `<label for="<control id>" id="<label id>" data-mirio="label">`. For grouped controls (`single_choice`, `multiple_choice`, `yes_no`, `rating`) it renders a `<span id data-mirio="label">` instead, because the group is labelled through `aria-labelledby` rather than `for`.

### Help
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | required | The field key. |
| `children` | `ReactNode` | `field.help` | Replaces the help text. Renders nothing when there is none. |
| `className` | `string` | `text-sm text-mirio-muted-fg` | Merged onto the `<p>`. |
| …`<p>` attributes | | | Spread on the `<p>`. |

Renders `<p id="<help id>" data-mirio="help">`; the id is in the control's `aria-describedby`. `Field` places it under the label, and under the checkbox row for `consent` fields.

### FieldError
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | required | The field key. |
| `className` | `string` | `text-sm text-mirio-error` | Merged onto the `<p>`. |
| …`<p>` attributes | | | Spread on the `<p>`. |

Renders `<p id="<error id>" role="alert" data-mirio="error">message</p>` while the field has an error, nothing otherwise. Messages come from Mirio's validator (client and server use the same one), e.g. "This field is required", "Enter a valid email address", "Choose at most 2".

## Variants and states
Required and optional marks (since 0.1.3): by default optional fields get a small, muted "Optional" after the label and required fields get no mark. Most fields on a short form are required, so a mark on each says little; screen readers hear "Optional" with the label, and required controls carry `aria-required`. Set either mark once for every form, or per label:

<!-- preview: required-indicator -->

```tsx
<Form schema={schema} client={mirio}>            // the default
  <Field name="email" />
</Form>

<MirioProvider
  client={mirio}
  requiredIndicator={<span aria-hidden className="ml-0.5 text-mirio-error">*</span>}   // asterisks instead
  optionalIndicator={null}
>
  <Form schema={schema}>…</Form>
</MirioProvider>

<Label name="company" optionalIndicator={null}>Company</Label>   // one label
```

Resolution order for each: the `Label` prop, then the nearest `Form`/`MirioProvider` that set one, then the default (nothing for required, "Optional" for optional). A required `consent` checkbox uses `requiredIndicator`; an optional one has no mark.

## Customization
- Tokens: `--mirio-fg` (label), `--mirio-muted-fg` (help and the "Optional" mark), `--mirio-error` (error).
- `className="text-base font-semibold"` on `Label` replaces the size and weight. `FieldError` with `className="text-xs"` shrinks the message.
- CSS hooks: `[data-mirio="label"]`, `[data-mirio="help"]`, `[data-mirio="error"]`.
- Label and help text are part of the schema (and its hash): changing them is a schema change that `mirio push` records as a new version; overriding with `children` is a display-only change.

## Accessibility
`Label` targets the control with `for` (or labels a group through `aria-labelledby`). `Help` and `FieldError` ids are joined into the control's `aria-describedby`, so screen readers read help on focus and the error with the control. `FieldError` is a live region (`role="alert"`), so a new message is announced when it appears, and Mirio also moves focus to the first invalid control after a failed submit. The default `*` is `aria-hidden`; required state is conveyed by `aria-required` on the control.

## Related
[Field](/docs/components/field.md), [MirioProvider](/docs/components/provider.md), [Consent](/docs/components/consent.md), [Headless: useField](/docs/components/headless.md)
