Skip to content

Form and Field

Form turns a submission into one onSubmit call, and Field gives a control its label, description and error. Press Send with the field empty, then fix the address and send again:

One letter a month, and nothing else.

Not sent yet

FormDemo.tsx
import { Paragraph, View } from '@hozo/core'
import { Button, Field, Form, Input, Stack, useFormSubmit } from '@hozo/ui'
import { useState } from 'react'
// A Button is not a submit button, so it asks the form to submit.
function Submit() {
const submit = useFormSubmit()
return (
<Button tone="accent" onPress={submit}>
Send
</Button>
)
}
export function FormDemo() {
const [email, setEmail] = useState('')
const [tried, setTried] = useState(false)
const [sent, setSent] = useState<string>()
const wrong = tried && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)
return (
<View className="w-full max-w-md gap-3">
<Form
accessibilityLabel="Newsletter"
onSubmit={() => {
setTried(true)
if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) setSent(email)
}}
>
<Field
label="Email"
description="One letter a month, and nothing else."
error={wrong ? 'That does not look like an email address.' : undefined}
required
>
{(control) => (
<Input
{...control}
inputMode="email"
value={email}
onChangeText={setEmail}
placeholder="you@example.com"
/>
)}
</Field>
<Stack direction="row" gap="tight">
<Submit />
</Stack>
</Form>
<Paragraph className="text-sm text-hozo-text-body">
{sent ? `Subscribed ${sent}` : 'Not sent yet'}
</Paragraph>
</View>
)
}
  • onSubmit is called with no event. Enter in a field, a real submit button and useFormSubmit() all arrive there, in one order.
  • A Button submits through useFormSubmit(), because a Button is not a <button type="submit">. Call the hook in a component inside the form, as the example’s Submit does.
  • It does not submit past a control marked aria-invalid. It focuses that control instead, so a screen reader hears which field is wrong, not only a sighted person. focusInvalidOnSubmit={false} turns that off.
  • accessibilityLabel names the form.
  • label labels the control, description says what it is for, and error says what is wrong. The control is described by both and marked aria-invalid while there is an error.
  • children is a function that is given control: the id and the ARIA attributes that connect the control to the label, the description and the error. Spread it onto Input, TextArea or NativeSelect.
  • required marks the control aria-required and shows a marker beside the label. It does not add the browser’s own required, so the browser does not show its own message.