Askr documentation
UI & Components

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/themes/form.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSX.Element;——
refref?: Ref<Element>;——

FormProps

Import from @askrjs/themes/form.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLFormElement>;——

LabelAsChildProps

Import from @askrjs/themes/label.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<Element>;——

LabelLabelProps

Import from @askrjs/themes/label.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
htmlForhtmlFor?: string | undefined;——
refref?: Ref<HTMLLabelElement>;——

LabelOwnProps

Import from @askrjs/themes/label.

PropTypeDefaultDescription
childrenchildren?: unknown;——

LabelProps

Import from @askrjs/themes/label.

PropTypeDefaultDescription
asChildasChild?: boolean | undefined;——
childrenchildren?: unknown;——
refref?: ((value: Element | null) => void) | { current: Element | null; } | ((value: HTMLLabelElement | null) => void) | { current: HTMLLabelElement | null; } | null | undefined;——

FieldErrorProps

Import from @askrjs/themes/field.

PropTypeDefaultDescription
childrenchildren?: unknown;——
refref?: Ref<HTMLParagraphElement>;——

FieldHintProps

Import from @askrjs/themes/field.

PropTypeDefaultDescription
childrenchildren?: unknown;——
refref?: Ref<HTMLParagraphElement>;——

FieldProps

Import from @askrjs/themes/field.

PropTypeDefaultDescription
childrenchildren?: unknown;——
invalidinvalid?: boolean | undefined;——
refref?: Ref<HTMLDivElement>;——

InputGroupProps

Import from @askrjs/themes/input-group.

PropTypeDefaultDescription
attachedattached?: boolean | undefined;——
childrenchildren?: unknown;——
orientationorientation?: InputGroupOrientation | undefined;——
refref?: Ref<HTMLDivElement>;——

InputGroupTextAsChildProps

Import from @askrjs/themes/input-group.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<unknown>;——

InputGroupTextProps

Import from @askrjs/themes/input-group.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLSpanElement>;——

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.

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.