Form, Label, Field, and Input Group
Form, Label, Field, and Input Group: anatomy, keyboard behavior, state, and theming in Askr.
Example
import { Button, Field, FieldHint, FieldLabel, Input } from '@askrjs/themes/components';
<Field>
<FieldLabel htmlFor="project-name">Project name</FieldLabel>
<Input id="project-name" name="name" required />
<FieldHint>Shown to everyone in the workspace.</FieldHint>
<Button type="submit">Save project</Button>
</Field>Published props
Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.
FormAsChildProps
Import from @askrjs/ui/form.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSX.Element; | — | — |
ref | ref?: Ref<Element>; | — | — |
FormProps
Import from @askrjs/ui/form.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLFormElement>; | — | — |
LabelAsChildProps
Import from @askrjs/ui/label.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
LabelLabelProps
Import from @askrjs/ui/label.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
htmlFor | htmlFor?: string | undefined; | — | — |
ref | ref?: Ref<HTMLLabelElement>; | — | — |
LabelOwnProps
Import from @askrjs/ui/label.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
LabelProps
Import from @askrjs/ui/label.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: boolean | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: ((value: Element | null) => void) | { current: Element | null; } | ((value: HTMLLabelElement | null) => void) | { current: HTMLLabelElement | null; } | null | undefined; | — | — |
Purpose
This page groups four small, composable pieces that don't stand well alone: `Form` (a semantic `<form>` wrapper from `@askrjs/ui/form`), `Label` (an accessible label with no visual opinion, from `@askrjs/ui/label`), and the themes-only `Field` and `InputGroup` layout primitives that give a labeled input row consistent spacing and error/hint slots. None of these own form validation logic — they're structural and accessibility building blocks you compose around whatever validation approach you're already using.
Install and import
`Form` and `Label` come from `@askrjs/ui/form` and `@askrjs/ui/label`; `Field` and `InputGroup` are themes-only and come from `@askrjs/themes/field` and `@askrjs/themes/input-group` since there's no headless behavior to separate out — they're pure layout components. All four are also re-exported from `@askrjs/themes/components` if you're already importing your styled catalog from there.
Live examples
The examples build up a typical labeled field: a `Field` wrapping a `Label`, an `InputGroup` containing an `Input`, and optionally a `FieldHint` or `FieldError` beneath it, all inside a `Form`. Each piece is shown both alone and composed, so you can see what each one actually contributes to the final markup.
Anatomy
`Form` is just a `<form>` (or, via `asChild`, whatever element you provide) with a `data-slot="form"` attribute — it doesn't intercept submission or manage field state. `Label` renders a native `<label>` by default. `Field` is a `<div>` that accepts an `invalid` flag and optionally contains `FieldHint`/`FieldError` paragraph children. `InputGroup` is a `<div>` that visually attaches its children (e.g. an input and a leading icon) via its `attached` prop.
State model
None of these four hold state themselves — `Form` doesn't track field values, and `Field`'s `invalid` prop is something you pass in based on validation you're already running elsewhere, not something the component derives. This is intentional: form state and validation belong to whatever data layer or form library you're using, and these pieces only handle structure and semantics around it.
Keyboard and accessibility
`Label`'s accessibility contract prioritizes `aria-label` over `textContent` for computing the accessible name, and its `for`/`htmlFor` association follows native label semantics — clicking the label focuses the associated control the same way a native `<label for>` does. Neither `Field` nor `InputGroup` changes tab order or keyboard behavior; they're layout-only, so keyboard behavior is entirely inherited from whatever control you place inside them.
Styling and tokens
`Field`'s `invalid` boolean prop sets a real `data-invalid="true"` attribute on the root — but the default theme's `field.css` has no rule keyed to `[data-invalid]`, so setting it doesn't change `Field`'s own appearance out of the box. It's there as a stable styling hook for your own CSS or a custom theme to key off, not a treatment `@askrjs/themes` ships pre-wired; any visible invalid state you see in practice comes from styling the control inside the field (e.g. Input's own `aria-invalid` border swap), not from `Field` itself. `InputGroup` takes an `orientation` (`horizontal`/`vertical`) and an `attached` boolean that removes the gap and shared borders between grouped children, which is the look you want for a search box with a leading icon or a trailing button.
API
`Form` takes only `children` plus standard `BoxProps<'form'>`. `Label` takes `children` and, in its non-`asChild` form, `htmlFor` plus the rest of native label props. `Field` takes `invalid` and native `div` props; `FieldHint` and `FieldError` take native paragraph props — the error text itself is whatever you render as children, but `FieldError` isn't purely presentational the way `FieldHint` is: it renders with `role="alert"` baked in, so its content gets announced to assistive tech as soon as it mounts. `InputGroup` takes `attached` and `orientation`; `InputGroupText` renders inline decorative or label text inside the group and supports `asChild` like most other parts in the library.
Edge cases
Because `Field`, `FieldHint`, `FieldError`, and `InputGroup` don't manage any state, there's no automatic wiring between `invalid` on `Field` and the appearance of `FieldError` — if you want the error message to only show when the field is invalid, that's a condition you write yourself in JSX. Note also that the broader `Field*` family you'll see in the styled catalog (`FieldContent`, `FieldGroup`, `FieldLegend`, `FieldSet`, `FieldTitle`, and so on) lives in `@askrjs/themes/components` as additional composition helpers beyond the core `Field`/`FieldHint`/`FieldError` trio documented here.
Related pages
For the individual controls you'll typically place inside a `Field`, see Input, Textarea, Checkbox, Select, and Switch. For native browser form fallbacks and OTP-style multi-box input, see Native Select and Input OTP under experimental controls.