Forms model
BeeUI gives you the presentation and accessibility wiring of a form — label, control, description, error, grouping and keyboard-aware layout — while values, validation rules and submission stay entirely in your application.
Prerequisites: read State model first — this page assumes you already know which values a component holds versus reports back.
Why the concept exists
Section titled “Why the concept exists”Form libraries are opinionated by nature: they decide when validation runs, what an error is, how async checks resolve and what a submit lifecycle looks like. Those are product decisions, and a UI library that made them would force every consumer onto one form architecture.
So BeeUI stops at a line that is easy to state: it renders invalid; it does not decide what invalid means. Everything on your side of that line composes with any form library, or with plain useState.
The composition
Section titled “The composition”FormGroup legend, group-level description/error (optional — related fields)└── Field label, description, error, invalid, required, disabled └── control Input · Textarea · PasswordInput · SearchInput Select · Checkbox · Switch · RadioGroup DatePicker · DateTimePicker · OTPInput ▲ field context ─────┘ the control reads label id, invalid and disabled from Field
FormMessage / HelperText standalone message surfaces, for text outside a FieldKeyboardAwareScreen the screen shell: scrolling, keyboard avoidance, measureField is the association boundary. It renders the label, wires the control’s accessible name to it through context, and renders either the error (when invalid and error are both set) or the description underneath — announced politely when it is an error.
import * as React from 'react';import { Button, Field, Input, KeyboardAwareScreen } from '@beemvp/beeui-ui';
type EmailFormProps = { onSubmit: (email: string) => void;};
export function EmailForm({ onSubmit }: EmailFormProps) { const [email, setEmail] = React.useState(''); const [error, setError] = React.useState<string | undefined>();
// Your rule, your timing, your error string. const submit = () => { const message = email.includes('@') ? undefined : 'Enter a valid email address.'; setError(message); if (!message) onSubmit(email); };
return ( <KeyboardAwareScreen contentWidth="sm" safeAreaEdges={['top', 'bottom']}> <Field error={error} invalid={error !== undefined} label="Email" description="We use this for receipts only." required > <Input autoComplete="email" inputMode="email" onChangeText={setEmail} value={email} /> </Field> <Button onPress={submit}>Continue</Button> </KeyboardAwareScreen> );}Note what BeeUI did and did not do. It associated the label, exposed the required state, rendered the error as a polite live region and kept the focused input above the keyboard. It never looked at the value, never decided when to validate, and never owned submission.
Rules and invariants
Section titled “Rules and invariants”Fieldneeds alabel. It is a required prop, because an unlabelled control is not a form field.invalidanderrorwork together.Fieldshows the error only wheninvalidis true anderroris a string; otherwise it shows the description. Setting one without the other is the most common reason an error “does not appear”.- Controls inherit
invalidanddisabledfromField. Text inputs and the date pickers read field context, so you set the state once on theFieldrather than repeating it on the control. - One control per
Field. UseFormGroupwith alegendwhen several related controls — a radio set, a group of checkboxes — need a shared label and a shared error. - Values are always yours. Text inputs follow React Native
TextInputsemantics, and the selection controls follow the shapes in the state model. BeeUI ships no form engine. KeyboardAwareScreenowns viewport composition, not form state. It handles scrolling, keyboard avoidance, tap-through and a bounded content measure. It does not validate.- Error text is announced, so write it as a sentence. It reaches assistive technology as a live region, not as decoration.
Consequences for application code
Section titled “Consequences for application code”- Map your form library to props, not to internals. Whatever produces your errors — Zod, Yup, a resolver, a server response — the integration is the same three props:
invalid,error, and the control’s value/callback pair. - Decide validation timing yourself, and be consistent. On blur, on submit, or on change after the first submit — BeeUI renders whichever you choose.
- Server errors are just errors. A failed mutation sets the same
errorprop; there is no separate channel. - Bound the measure on wide viewports.
contentWidth="sm"keeps a long-line form readable on tablet and Web. - Set
safeAreaEdgesdeliberately.KeyboardAwareScreenleaves it unset by default precisely so a screen composed under a shell that already owns insets does not double them — see Ownership model. - Use
FormMessageandHelperTextfor text outside aField— a form-level failure summary, or a note attached to a section rather than to one control.
Common misconception
Section titled “Common misconception”“
Fieldvalidates for me.”
It does not, and the symptom is a form where nothing ever turns red: error was set but invalid stayed false, or validation was never run at all because the component was expected to run it. Field is a renderer of validation results.
The related anti-pattern is putting a whole group of controls inside a single Field to get one label. That produces a control whose accessible name points at several inputs at once. Use FormGroup for grouping and keep one control per Field.
Where to go next
Section titled “Where to go next”- State model — controlled and uncontrolled controls.
- Composition model — why the parts must stay together.
- Accessibility model — label, error and announcement obligations.
- Field · Input · Select · Keyboard-aware screen — exact props and types.
- Date & time guide — timezone, locale and storage rules for date fields.
- Patterns — sign-in, sign-up and settings forms already composed.
Source authority
Section titled “Source authority”packages/ui/src/components/field.tsx— the association and message contract.packages/ui/src/components/keyboard-aware-screen.tsx— screen-level keyboard composition.docs/components.md— the canonical behavior catalog.