Field
A label, form control, help text, and error with associated accessible descriptions.
Installation
Import from the Reflow workspace package.
Form
Open demoimport { FieldSet, FieldDescription, Field, Input, Select, Checkbox, Textarea, Button } from '@rtzll/reflow'
import { type Handle, on } from 'remix/component'
export function Example(handle: Handle) {
const options = [{"value":"Pre-seed","label":"Pre-seed"},{"value":"Seed","label":"Seed"},{"value":"Series A","label":"Series A"},{"value":"Series B","label":"Series B"},{"value":"Series C+","label":"Series C+"},{"value":"Public","label":"Public"}]
let errors: Partial<Record<'company' | 'email' | 'conduct', string>> = {}
let sent = false
const submit = on('submit', (event) => {
event.preventDefault()
const data = new FormData(event.currentTarget as HTMLFormElement)
const email = String(data.get('email') ?? '').trim()
errors = {}
if (!String(data.get('company') ?? '').trim()) errors.company = 'Company is required'
if (!email) errors.email = 'Work email is required'
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.email = 'Enter a valid email address'
if (!data.has('conduct')) errors.conduct = 'Required to join the event'
sent = Object.keys(errors).length === 0
handle.update()
})
return () => (
<>
<form noValidate mix={submit} style={{ display: 'grid', gap: '24px', width: 'min(100%, 384px)' }}>
<FieldSet legend="Request to join">
<FieldDescription>We read every request before the event.</FieldDescription>
<Field label="Company *" error={errors.company}>
{(control) => <Input {...control} name="company" required />}
</Field>
<Field label="Work email address *" error={errors.email}>
{(control) => <Input {...control} type="email" name="email" placeholder="you@company.com" required />}
</Field>
<Field label="What stage is your company at?">
{(control) => <Select {...control} label="What stage is your company at?" name="stage" placeholder="Select an option" options={options} />}
</Field>
<Field error={errors.conduct}>
{(control) => <Checkbox {...control} label="I agree to the code of conduct *" name="conduct" required />}
</Field>
<Field label="Is there anything else you'd like us to know?">
{(control) => <Textarea {...control} name="notes" rows={3} />}
</Field>
</FieldSet>
<Button type="submit">Request to Join</Button>
{sent && <p role="status">Request submitted.</p>}
</form>
</>
)
}Input
import { Field, Input } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<Field label="Username" description="Choose a unique username for your account.">
{(control) => <Input {...control} placeholder="ada" />}
</Field>
</>
)
}Textarea
import { Field, Textarea } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<Field label="Feedback" description="It grows with your text.">
{(control) => <Textarea {...control} placeholder="What worked, what didn't?" />}
</Field>
</>
)
}Select
import { Field, Select } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const options = ['Design', 'Engineering', 'Research'].map((label) => ({ value: label, label }))
return () => (
<>
<Field label="Department" description="Where you spend most of your week.">
{(control) => <Select {...control} label="Department" options={options} placeholder="Choose a department" />}
</Field>
</>
)
}Checkbox
import { FieldSet, Field, Checkbox, FieldLabel, FieldContent, FieldDescription } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<FieldSet legend="Notify me about" legendVariant="label">
<Field orientation="horizontal">
<Checkbox id="replies" defaultChecked />
<FieldLabel for="replies">Replies to my comments</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="mentions" />
<FieldContent>
<FieldLabel for="mentions">Mentions</FieldLabel>
<FieldDescription>Only from people in your workspace.</FieldDescription>
</FieldContent>
</Field>
</FieldSet>
</>
)
}Choice card
import { FieldGroup, FieldLabel, Field, Checkbox, FieldContent, FieldTitle, FieldDescription } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<FieldGroup>
<FieldLabel for="kubernetes">
<Field orientation="horizontal">
<Checkbox id="kubernetes" defaultChecked />
<FieldContent>
<FieldTitle>Kubernetes</FieldTitle>
<FieldDescription>Run GPU workloads on a K8s cluster.</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
<FieldLabel for="vm">
<Field orientation="horizontal">
<Checkbox id="vm" />
<FieldContent>
<FieldTitle>Virtual machine</FieldTitle>
<FieldDescription>Access a cluster to run GPU workloads.</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
</FieldGroup>
</>
)
}Fieldset
We need your address to deliver your order.
import { FieldSet, FieldDescription, Field, Input } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<FieldSet legend="Address">
<FieldDescription>We need your address to deliver your order.</FieldDescription>
<Field label="Street address">{(control) => <Input {...control} placeholder="123 Main St" />}</Field>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '16px' }}>
<Field label="City">{(control) => <Input {...control} placeholder="Paris" />}</Field>
<Field label="Postal code">{(control) => <Input {...control} placeholder="75001" />}</Field>
</div>
</FieldSet>
</>
)
}Validation and errors
Enter a valid email address
import { Field, Input } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<Field label="Work email address" error="Enter a valid email address">
{(control) => <Input {...control} type="email" defaultValue="ada@anthropic" />}
</Field>
</>
)
}Horizontal
Used across the app and in emails.
import { FieldGroup, Field, FieldContent, FieldLabel, FieldDescription, Select } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const options = [{ value: 'en', label: 'English' }, { value: 'fr', label: 'Français' }]
return () => (
<>
<FieldGroup>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel for="language">Language</FieldLabel>
<FieldDescription>Used across the app and in emails.</FieldDescription>
</FieldContent>
<Select id="language" label="Language" placeholder="Pick one" options={options} />
</Field>
</FieldGroup>
</>
)
}Separator
Or continue with
import { FieldGroup, Field, Input, FieldSeparator, Button } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<FieldGroup>
<Field label="Work email">{(control) => <Input {...control} type="email" placeholder="you@company.com" />}</Field>
<FieldSeparator>Or continue with</FieldSeparator>
<Button variant="secondary">Continue with SSO</Button>
</FieldGroup>
</>
)
}API Reference
Public props for the Reflow implementation. Native controls also accept their standard HTML attributes and Remix event mixes.
Field
| Prop | Type | Default | Description |
|---|---|---|---|
children* | RemixNode | ((control: FieldControlProps) => RemixNode) | — | Content rendered inside the component. |
controlId | string | — | ID of the associated form control. |
label | RemixNode | — | Accessible name or visible label for the control. |
description | RemixNode | — | Additional explanatory text. |
error | RemixNode | — | Validation message or error state. |
orientation | "horizontal" | "vertical" | "responsive" | vertical | Layout direction. |
FieldSet
| Prop | Type | Default | Description |
|---|---|---|---|
children | RemixNode | — | Child nodes to render inside the element. |
disabled | boolean | — | The `disabled` HTML attribute. |
name | string | — | The `name` HTML attribute. |
legend | RemixNode | — | Legend for a group of form controls. |