Skip to content
Yuzur1ha Open

Component referenceVersion Draft

Form

Form composes native validation, labels, controls and messages without duplicating field ownership.

Design notes

Combine FormLabel and FormControl asChild with Input or TextareaInput. Non-native controls such as Select need their own validation and focus handling.

Playground

Playgrounddemo.tsx

Contact preferences

Contact preferences
Demo only. Try taken@example.test to see a simulated error.
Optional. Up to 160 characters.
Delivery preferences
Delivery schedule
Unavailable settings
No changes submitted.

Support request

Local example. Nothing is sent to support.

Request details
Do not include passwords, codes or configuration credentials.
No sample request submitted.

content/components/form/demo.tsx

import { useRef, useState } from "react"
import {
  Button,
  Checkbox,
  Radio,
  Select,
  Switch,
  Input,
  TextareaInput,
  Form,
  FormField,
  FormLabel,
  FormControl,
  FormDescription,
  FormMessage,
  FormFieldset,
  FormLegend,
  FormActions,
  FormSubmit,
} from "@yuzur1ha/ui"
import { DemoResult } from "../../examples/result"
export function FormDemo() {
  const [serverInvalid, setServerInvalid] = useState(false)
  const [result, setResult] = useState("No changes submitted.")
  return (
    <>
      <section className="form-demo-group" aria-label="Contact form example">
        <h2>Contact preferences</h2>
        <Form
          onClearServerErrors={() => setServerInvalid(false)}
          onReset={() => setResult("No changes submitted.")}
          onSubmit={(event) => {
            event.preventDefault()
            const data = new FormData(event.currentTarget)
            const email = data.get("email")
            if (email === "taken@example.test") {
              setServerInvalid(true)
              return
            }
            const delivery =
              data.get("delivery") === "daily" ? "daily" : "immediate"
            setResult(
              `Saved locally: ${typeof email === "string" ? email : ""} · ${delivery} · notifications ${data.has("notifications") ? "on" : "off"} · product updates ${data.has("updates") ? "on" : "off"}`,
            )
          }}
        >
          <FormFieldset>
            <FormLegend>Contact preferences</FormLegend>
            <FormField name="email" serverInvalid={serverInvalid}>
              <FormLabel>Email address</FormLabel>
              <FormControl asChild>
                <Input
                  type="email"
                  required
                  autoComplete="email"
                  placeholder="you@example.test"
                  onChange={() => setServerInvalid(false)}
                />
              </FormControl>
              <FormDescription>
                Demo only. Try taken@example.test to see a simulated error.
              </FormDescription>
              <FormMessage match="valueMissing">
                Enter your email address.
              </FormMessage>
              <FormMessage match="typeMismatch">
                Enter a valid email address.
              </FormMessage>
              {serverInvalid && (
                <FormMessage>This email is already in use.</FormMessage>
              )}
            </FormField>
            <FormField name="bio">
              <FormLabel>About you</FormLabel>
              <FormControl asChild>
                <TextareaInput
                  maxLength={160}
                  placeholder="A few words about yourself."
                />
              </FormControl>
              <FormDescription>Optional. Up to 160 characters.</FormDescription>
            </FormField>
          </FormFieldset>
          <FormFieldset>
            <FormLegend>Delivery preferences</FormLegend>
            <Switch
              label="Email notifications"
              name="notifications"
              defaultChecked
            />
            <fieldset className="form-demo-choices">
              <legend className="demo-label">Delivery schedule</legend>
              <Radio
                label="Immediately"
                name="delivery"
                value="immediate"
                defaultChecked
              />
              <Radio label="Daily summary" name="delivery" value="daily" />
            </fieldset>
            <Checkbox label="Include product updates" name="updates" />
          </FormFieldset>
          <FormFieldset disabled>
            <FormLegend>Unavailable settings</FormLegend>
            <FormField name="organization">
              <FormLabel>Organization</FormLabel>
              <FormControl defaultValue="Personal workspace" />
            </FormField>
          </FormFieldset>
          <FormActions>
            <FormSubmit>Save preferences</FormSubmit>
            <Button appearance="secondary" type="reset">
              Reset
            </Button>
          </FormActions>
        </Form>
        <DemoResult>{result}</DemoResult>
      </section>
      <SupportRequestDemo />
    </>
  )
}

function SupportRequestDemo() {
  const topicControl = useRef<HTMLButtonElement>(null)
  const [topic, setTopic] = useState("")
  const [topicError, setTopicError] = useState("")
  const [failed, setFailed] = useState(false)
  const [result, setResult] = useState("No sample request submitted.")
  return (
    <section className="form-demo-group" aria-label="Support form example">
      <h2>Support request</h2>
      <p className="demo-label">Local example. Nothing is sent to support.</p>
      <Form
        aria-label="Sample support request"
        onReset={() => {
          setTopic("")
          setTopicError("")
          setFailed(false)
          setResult("No sample request submitted.")
        }}
        onSubmit={(event) => {
          event.preventDefault()
          if (!topic) {
            setTopicError("Choose a request topic.")
            topicControl.current?.focus()
            return
          }
          const data = new FormData(event.currentTarget)
          if (data.has("simulateFailure")) {
            setFailed(true)
            setResult("No sample request submitted.")
            return
          }
          setFailed(false)
          const subject = data.get("subject")
          setResult(
            `Saved locally: ${typeof subject === "string" ? subject : ""} · ${topic} · follow-up ${data.has("followUp") ? "on" : "off"}`,
          )
        }}
      >
        <FormFieldset>
          <FormLegend>Request details</FormLegend>
          <Select
            ref={topicControl}
            label="Request topic"
            placeholder="Choose a topic"
            name="topic"
            value={topic}
            onValueChange={(value) => {
              setTopic(value)
              setTopicError("")
            }}
            {...(topicError ? { error: topicError } : {})}
            options={[
              { value: "connection", label: "Connection & setup" },
              { value: "account", label: "Account access" },
              { value: "general", label: "General question" },
            ]}
          />
          <FormField name="subject">
            <FormLabel>Subject</FormLabel>
            <FormControl asChild>
              <Input required placeholder="What do you need help with?" />
            </FormControl>
            <FormMessage match="valueMissing">Enter a subject.</FormMessage>
          </FormField>
          <FormField name="details">
            <FormLabel>Request details</FormLabel>
            <FormControl asChild>
              <TextareaInput
                required
                placeholder="Describe the issue using sample information."
              />
            </FormControl>
            <FormDescription>
              Do not include passwords, codes or configuration credentials.
            </FormDescription>
            <FormMessage match="valueMissing">Describe the issue.</FormMessage>
          </FormField>
          <Checkbox
            label="Email me about this sample request"
            name="followUp"
            defaultChecked
          />
        </FormFieldset>
        <Checkbox label="Simulate submission failure" name="simulateFailure" />
        {failed && (
          <p className="form-demo-error" role="alert">
            Demo submission failed. Your draft is preserved; turn off the
            simulation and retry.
          </p>
        )}
        <FormActions>
          <FormSubmit>Submit sample request</FormSubmit>
          <Button appearance="secondary" type="reset">
            Reset request
          </Button>
        </FormActions>
      </Form>
      <DemoResult>{result}</DemoResult>
    </section>
  )
}

API

Form

Radix form root using native constraint validation. Use FormField, FormLabel and FormControl for native controls, not a nested TextField or non-native Select.

FormProps
PropertyTypeRequiredDefaultDescription
onClearServerErrors(() => void)No—Clear previous server errors before the submit callback and after an uncancelled reset. Editing alone does not invoke it.
onSubmitSubmitEventHandler<HTMLFormElement>No—Called after native validation permits form submission. Prevent default for client-side handling.
onResetReactEventHandler<HTMLFormElement>No—Native reset handler. Prevent default to cancel reset and server-error clearing.
namestringNo—Native form submission field name.
targetstringNo—Browsing context for the linked destination, such as _self or _blank.
actionstring | ((formData: FormData) => void | Promise<void>)No—Native form submission destination or React form action.
methodstringNo—Native form submission method.
noValidatebooleanNo—Skip built-in constraint validation during form submission.
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
asChildbooleanNo—Render through one child element instead of the default element. The child must forward its ref and accept the supplied props.
refRef<HTMLFormElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormField

Groups one named native field with its label and validation messages. Native div attributes are forwarded.

FormFieldProps
PropertyTypeRequiredDefaultDescription
namestringYes—Field name shared by its native control, label and validation messages.
serverInvalidbooleanNo—Mark the field invalid from a server response.
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
asChildbooleanNo—Render through one child element instead of the default element. The child must forward its ref and accept the supplied props.
refRef<HTMLDivElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormLabel

Label associated with a field by name or the surrounding FormField. Native label attributes are forwarded.

FormLabelProps
PropertyTypeRequiredDefaultDescription
namestringNo—Target field name; inherited from the surrounding FormField when omitted.
htmlForstringNo—ID of the native control labelled by this element.
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
asChildbooleanNo—Render through one child element instead of the default element. The child must forward its ref and accept the supplied props.
refRef<HTMLLabelElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormControl

Native input validation integration. Use asChild with a single Input or TextareaInput; this does not add native validation to Select.

FormControlProps
PropertyTypeRequiredDefaultDescription
asChildbooleanNo—Compose with one native-input-compatible child, such as Input or TextareaInput. Select is not a native validation control.
typeHTMLInputTypeAttributeNo—Native control type.
namestringNo—Native form submission field name.
valuestring | number | readonly string[]No—Current native control value. Pair with onChange for controlled input.
defaultValuestring | number | readonly string[]No—Initial value used when the native control is uncontrolled.
onChangeChangeEventHandler<HTMLInputElement, HTMLInputElement>No—Native input change handler. Read value or checked from event.currentTarget.
disabledbooleanNo—Disables the native control.
requiredbooleanNo—Requires a non-empty native control value during form validation.
readOnlybooleanNo—Keeps the current value readable and focusable while preventing editing.
placeholderstringNo—Text shown when the control has no value.
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
refRef<HTMLInputElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormMessage

Conditional validation feedback for a named field. Renders when match succeeds or forceMatch is true.

FormMessageProps
PropertyTypeRequiredDefaultDescription
childrenReactNodeYes—Content rendered inside the component.
namestringNo—Target field name; inherited from the surrounding FormField when omitted.
match"badInput" | "patternMismatch" | "rangeOverflow" | "rangeUnderflow" | "stepMismatch" | "tooLong" | "tooShort" | "typeMismatch" | "valid" | "valueMissing" | (((value: string, formData: FormData) => boolean) | ((value: string, formData: FormData) => Promise<boolean>))No—Native ValidityState key or custom matcher deciding when the message appears.
forceMatchbooleanNo—Show the message independently of the current validation result.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
asChildbooleanNo—Render through one child element instead of the default element. The child must forward its ref and accept the supplied props.
refRef<HTMLSpanElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormDescription

Persistent field guidance associated with the native control. Does not accept match or forceMatch.

FormDescriptionProps
PropertyTypeRequiredDefaultDescription
childrenReactNodeYes—Content rendered inside the component.
namestringNo—Target field name; inherited from the surrounding FormField when omitted.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
asChildbooleanNo—Render through one child element instead of the default element. The child must forward its ref and accept the supplied props.
refRef<HTMLSpanElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormFieldset

A native fieldset for grouping related form controls, including group-level disabled state.

FormFieldsetProps
PropertyTypeRequiredDefaultDescription
disabledbooleanNo—Disables the native control.
namestringNo—Native form submission field name.
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
refRef<HTMLFieldSetElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormLegend

A native legend naming a FormFieldset.

FormLegendProps
PropertyTypeRequiredDefaultDescription
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
refRef<HTMLLegendElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormActions

A div arranging form action controls. Native div attributes and ref are forwarded.

FormActionsProps
PropertyTypeRequiredDefaultDescription
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
refRef<HTMLDivElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormSubmit

A Radix submit button styled as a primary action. Use asChild to compose a custom submit control.

FormSubmitProps
PropertyTypeRequiredDefaultDescription
asChildbooleanNo—Render through one child element instead of the default element. The child must forward its ref and accept the supplied props.
type"button" | "submit" | "reset"No"submit"Native button behavior. With asChild, ensure the child retains type="submit".
childrenReactNodeNo—Content rendered inside the component.
disabledbooleanNo—Disables the native control.
onClickMouseEventHandler<HTMLButtonElement>No—Native activation handler.
namestringNo—Native form submission field name.
valuestring | number | readonly string[]No—Current native control value. Pair with onChange for controlled input.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
refRef<HTMLButtonElement>No—Ref to the component’s documented DOM element.

No custom imperative methods. Event callbacks are listed as properties.

FormValidityState

Render-prop access to a native field’s validity state. Does not render an extra DOM element.

FormValidityStateProps
PropertyTypeRequiredDefaultDescription
namestringNo—Target field name; inherited from the surrounding FormField when omitted.
children(validity: ValidityState | undefined) => ReactNodeYes—Render function receiving the native ValidityState or undefined before the control is available.

No custom imperative methods. Event callbacks are listed as properties.