Component reference
Field
A labelled form control with optional help text and an error message.
Preview
Field has no story of its own; this one draws it as part of Input.
Read-only, errors and long content
Read-only content can still be selected and copied; disabled content cannot be edited. Explicit errors, composed fields and long content keep a clear association.
render: () => (
<div className="workshop-composition-columns">
<section className="workshop-stack">
<Field
label="Project path (read-only)"
htmlFor="readonly-path"
description="Can be selected and copied; it cannot be changed here."
>
<Input
id="readonly-path"
readonly
value="/workspace/meowland/packages/meowtail/components/long-project-reference"
/>
</Field>
<Field label="Search files" htmlFor="search-files">
<Input id="search-files" type="search" placeholder="Enter a file name…" />
</Field>
<Field
label="Notification email"
htmlFor="invalid-email"
invalid
error="Enter a complete email address."
>
<Input id="invalid-email" type="email" value="meow@" />
</Field>
<Field label="Access passphrase (disabled)" htmlFor="disabled-password">
<Input id="disabled-password" type="password" value="demo-only" disabled />
</Field>
<Field
label="Check range"
description="For composed fields, the caller associates the label and description separately."
descriptionId="range-description"
>
<div className="workshop-foundation-range">
<label>
From{" "}
<Input
aria-label="Start line"
aria-describedby="range-description"
type="number"
value="1"
min="1"
/>
</label>
<label>
To{" "}
<Input
aria-label="End line"
aria-describedby="range-description"
type="number"
value="80"
min="1"
/>
</label>
</div>
</Field>
</section>
<section className="workshop-stack">
<Field label="Delivery notes (editable)" htmlFor="long-notes">
<Textarea id="long-notes" rows={6}>
First, confirm every component stays readable on narrow screens. Second, keep user input
while switching themes. Third, open a dialog with the keyboard, cancel it and return.
Long content should wrap naturally; never drop the evidence for checks to keep the
screen tidy.
</Textarea>
</Field>
<Field label="Last check record (read-only)" htmlFor="readonly-notes">
<Textarea id="readonly-notes" rows={3} readonly>
The record is kept for checking. Read-only does not mean inaccessible.
</Textarea>
</Field>
<Field label="Waiting for the connection to return" htmlFor="disabled-notes">
<Textarea id="disabled-notes" rows={3} disabled>
Continue editing once the connection returns.
</Textarea>
</Field>
</section>
</div>
)Installation
bun add meowtail
Usage
Taken from the examples on this page.
import { Field } from "meowtail";
<Field
label="Project path (read-only)"
htmlFor="readonly-path"
description="Can be selected and copied; it cannot be changed here."
>
<Input
id="readonly-path"
readonly
value="/workspace/meowland/packages/meowtail/components/long-project-reference"
/>
</Field>Examples
Other stories that draw Field.
Manual entry and host browsing
render: () => {
const value = signal("/Users/demo/Projects/my-project");
let result: "selected" | "cancelled" | "failed" = "selected";
return (
<Stack gap="normal" className="workshop-constrained">
<Field label="Project name" htmlFor="picker-name">
<Input id="picker-name" value="Personal website" />
</Field>
<Field
label="Local directory"
htmlFor="picker-directory"
description="The demo picker only returns a fixed path; it does not read files or grant access."
>
<PathPicker
id="picker-directory"
label="Local directory"
value={value}
kind="directory"
onValueChange={(next) => {
value.value = next;
}}
onChoose={async () => {
await new Promise((resolve) => setTimeout(resolve, 450));
if (result === "failed") throw new Error("Picker unavailable");
return result === "cancelled" ? null : "/Users/demo/Projects/selected-project";
}}
/>
</Field>
<Stack gap="compact" align="start">
<Button
variant="quiet"
onClick={() => {
result = "selected";
}}
>
Simulate a successful choice
</Button>
<Button
variant="quiet"
onClick={() => {
result = "cancelled";
}}
>
Simulate a canceled choice
</Button>
<Button
variant="quiet"
onClick={() => {
result = "failed";
}}
>
Simulate a failed choice
</Button>
</Stack>
<Field label="File path" htmlFor="picker-file">
<PathPicker
id="picker-file"
label="File path"
value="/tmp/report.md"
onValueChange={() => {}}
onChoose={async () => null}
readOnly
/>
</Field>
<Field label="Waiting to save" htmlFor="picker-disabled">
<PathPicker
id="picker-disabled"
label="Waiting to save"
value="/tmp/project"
kind="directory"
onValueChange={() => {}}
onChoose={async () => null}
disabled
/>
</Field>
<Field
label="Enter a directory manually"
htmlFor="picker-manual"
description="When the host offers no picker, keep entering an absolute path manually."
>
<PathPicker
id="picker-manual"
label="Enter a directory manually"
value={value}
onValueChange={(next) => {
value.value = next;
}}
/>
</Field>
</Stack>
);
}Legend and whole-group disabled
Uses native fieldset/legend for fields in one group; when the group is unavailable, inputs and selects follow form semantics together.
render: () => (
<Measure width="narrow">
<form>
<Stack gap="relaxed">
<FieldGroup
legend="Project notifications"
description="Choose how to receive alerts when work completes."
descriptionId="notification-help"
>
<Field label="Delivery address" htmlFor="notification-email">
<Input id="notification-email" type="email" placeholder="you@example.com" />
</Field>
<Field label="Alert frequency" htmlFor="notification-frequency">
<Select id="notification-frequency">
<option>Only when attention is needed</option>
<option>Every time work completes</option>
</Select>
</Field>
</FieldGroup>
<Separator />
<FieldGroup
legend="Organization-managed settings"
description="The organization manages this group; it cannot be changed now."
disabled
>
<Field label="Organization name" htmlFor="managed-name">
<Input id="managed-name" value="Meowland" />
</Field>
<Field label="Visibility" htmlFor="managed-visibility">
<Select id="managed-visibility">
<option>Project members only</option>
</Select>
</Field>
</FieldGroup>
</Stack>
</form>
</Measure>
)API reference
Props
| Prop | Type | Default |
|---|---|---|
labelRequired | Child | – |
description | Child | – |
error | Child | – |
htmlFor | string | – |
descriptionId | string | – |
errorId | string | – |
invalidExplicit state takes precedence over the live error message. | MaybeSignal<boolean> | – |
Also takes className, children and the attributes and events of its root element.
DOM parts
rootlabeldescriptionerrorcontrol
Registry
The component is registered as field in the meowtail component registry.