1. Components
  2. Forms and inputs
  3. Form

Form

Forms allow users to enter and submit data, and provide them with feedback along the way.

Please enter your full name.
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  /* Handle form submission */
};

return (
  <Form onSubmit={handleSubmit}>
    <TextField
      name="name"
      label="Name"
      description="Please enter your full name."
      minLength={2}
      isRequired
    />
    <Button variant="primary" type="submit">
      Submit
    </Button>
  </Form>
);

Installation

npx dotui-cli@latest add form

React Aria

Label & help text

Accessible forms start with clear, descriptive labels for each field. All dotUI form components support labeling using the Label component or the label prop, which is automatically associated with the field via the id and for attributes on your behalf.

In addition, the components support help text, which associates additional context with a field such as a description or error message. The label and help text are announced by assistive technology such as screen readers when the user focuses the field.

import {TextField, Label, Input, Text} from 'react-aria-components';
 
<TextField type="password">
  <Label>Password</Label>
  <Input />
  <Text slot="description">Password must be at least 8 characters.</Text>
</TextField>c

Built-in validation

All React Aria form components integrate with native HTML constraint validation. This allows you to define constraints on each field such as required, minimum and maximum values, text formats such as email addresses, and even custom regular expression patterns. These constraints are checked by the browser when the user commits changes to the value (e.g. on blur) or submits the form.

Register

Gender
Birth Date
How did you hear about us?
"use client";

import React from "react";
import { Button } from "@/components/dynamic-core/button";
import { Checkbox } from "@/components/dynamic-core/checkbox";
import { Combobox } from "@/components/dynamic-core/combobox";
import { DatePicker } from "@/components/dynamic-core/date-picker";
import { RadioGroup, Radio } from "@/components/dynamic-core/radio-group";
import { Select, SelectItem } from "@/components/dynamic-core/select";
import { TextField } from "@/components/dynamic-core/text-field";
import { Form } from "@/registry/core/form_basic";

export default function Demo() {
  return (
    <div className="w-sm bg-bg-muted space-y-4 rounded-lg border p-8">
      <h1 className="text-xl font-bold">Register</h1>
      <Form
        onSubmit={(e) => {
          e.preventDefault();
          const data = Object.fromEntries(new FormData(e.currentTarget));
          alert(JSON.stringify(data, null, 2));
        }}
        className="space-y-4"
      >
        <TextField
          name="name"
          label="Name"
          minLength={2}
          isRequired
          className="w-full"
        />
        <TextField
          name="email"
          label="Email"
          type="email"
          isRequired
          className="w-full"
        />
        <RadioGroup
          name="gender"
          label="Gender"
          isRequired
          orientation="horizontal"
        >
          <Radio value="male">Male</Radio>
          <Radio value="female">female</Radio>
          <Radio value="other">Other</Radio>
        </RadioGroup>
        <DatePicker
          label="Birth Date"
          name="birth-date"
          isRequired
          className="w-full"
        />
        <Combobox
          name="language"
          label="Preferred language"
          items={languages}
          isRequired
          className="w-full"
        >
          {(item) => (
            <SelectItem key={item.value} id={item.value}>
              {item.label}
            </SelectItem>
          )}
        </Combobox>
        <Select
          name="referral"
          label="How did you hear about us?"
          isRequired
          variant="outline"
          className="w-full"
        >
          <SelectItem id="linkedin">LinkedIn</SelectItem>
          <SelectItem id="x">X</SelectItem>
        </Select>
        <Checkbox isRequired>I agree to the terms and conditions</Checkbox>
        <div className="flex justify-end">
          <Button type="submit">Register</Button>
        </div>
      </Form>
    </div>
  );
}

const languages = [
  { label: "English", value: "en" },
  { label: "French", value: "fr" },
  { label: "German", value: "de" },
  { label: "Spanish", value: "es" },
  { label: "Portuguese", value: "pt" },
  { label: "Russian", value: "ru" },
  { label: "Japanese", value: "ja" },
  { label: "Korean", value: "ko" },
  { label: "Chinese", value: "zh" },
] as const;

React Hook Form

In most cases, uncontrolled forms with the builtin validation features are enough. However, if you are building a truly complex form, a separate form library such as React Hook Form may be helpful.

Anatomy

const formSchema = z.object({
  // Define your form schema
});
 
const Demo = () => {
  const { control, handleSubmit } = useForm();
  const onSubmit = (values: z.infer<typeof formSchema>) => {
    /* Handle form submission */
  };
 
  return (
    <Form onSubmit={handleSubmit(onSubmit)}>
      <FormControl
        control={control}
        name="..."
        render={(props) => {
          // Your form component
        }}
      />
    </Form>
  );
};

Example

Register

Gender
Birth Date
How did you hear about us?
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { parseDate } from "@internationalized/date";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/dynamic-core/button";
import { Checkbox } from "@/components/dynamic-core/checkbox";
import { Combobox } from "@/components/dynamic-core/combobox";
import { DatePicker } from "@/components/dynamic-core/date-picker";
import { RadioGroup, Radio } from "@/components/dynamic-core/radio-group";
import { Select, SelectItem } from "@/components/dynamic-core/select";
import { TextField } from "@/components/dynamic-core/text-field";
import { Form, FormControl } from "@/registry/core/form_react-hook-form";

const FormSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  gender: z.enum(["male", "female", "other"]),
  "birth-date": z.string(),
  referral: z.string({
    required_error: "Please select a method.",
  }),
  language: z.string({
    required_error: "Please select a language.",
  }),
  terms: z.boolean().refine((val) => val === true, {
    message: "You must accept the terms and conditions",
  }),
});

export default function Demo() {
  const { handleSubmit, control } = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
  });

  return (
    <div className="w-sm bg-bg-muted space-y-4 rounded-lg border p-8">
      <h1 className="text-xl font-bold">Register</h1>
      <Form
        onSubmit={handleSubmit((data) => {
          alert(JSON.stringify(data, null, 2));
        })}
        className="space-y-4"
      >
        <FormControl
          name="name"
          control={control}
          render={(props) => (
            <TextField label="Name" className="w-full" {...props} />
          )}
        />
        <FormControl
          name="email"
          control={control}
          render={(props) => (
            <TextField label="Email" className="w-full" {...props} />
          )}
        />
        <FormControl
          name="gender"
          control={control}
          render={(props) => (
            <RadioGroup label="Gender" orientation="horizontal" {...props}>
              <Radio value="male">Male</Radio>
              <Radio value="female">female</Radio>
              <Radio value="other">Other</Radio>
            </RadioGroup>
          )}
        />
        <FormControl
          name="birth-date"
          control={control}
          render={({ value, onChange, ...props }) => (
            <DatePicker
              label="Birth Date"
              value={value ? parseDate(value) : undefined}
              onChange={(val) => onChange(val?.toString())}
              className="w-full"
              {...props}
            />
          )}
        />
        <FormControl
          name="language"
          control={control}
          render={({ value, onChange, ...props }) => (
            <Combobox
              label="Preferred language"
              items={languages}
              inputValue={value}
              onSelectionChange={onChange}
              className="w-full"
              {...props}
            >
              {(item) => (
                <SelectItem key={item.value} id={item.value}>
                  {item.label}
                </SelectItem>
              )}
            </Combobox>
          )}
        />
        <FormControl
          name="referral"
          control={control}
          render={({ value, ...props }) => (
            <Select
              label="How did you hear about us?"
              selectedKey={value}
              variant="outline"
              className="w-full"
              {...props}
            >
              <SelectItem id="linkedin">LinkedIn</SelectItem>
              <SelectItem id="x">X</SelectItem>
            </Select>
          )}
        />
        <FormControl
          name="terms"
          control={control}
          render={({ value, ...props }) => (
            <Checkbox isSelected={value} {...props}>
              I agree to the terms and conditions
            </Checkbox>
          )}
        />
        <div className="flex justify-end">
          <Button type="submit">Register</Button>
        </div>
      </Form>
    </div>
  );
}

const languages = [
  { label: "English", value: "en" },
  { label: "French", value: "fr" },
  { label: "German", value: "de" },
  { label: "Spanish", value: "es" },
  { label: "Portuguese", value: "pt" },
  { label: "Russian", value: "ru" },
  { label: "Japanese", value: "ja" },
  { label: "Korean", value: "ko" },
  { label: "Chinese", value: "zh" },
] as const;
dotUI

Bringing singularity to the web.

Built by mehdibha. The source code is available on GitHub.