shadcn.io is an independent project built on shadcn/ui
Username needs 3 characters, email must be an address, bio needs 10 characters, and website must be a valid URL. All four start empty. Save Profile checks them together.
Use it for a first-time profile, not an edit of one that already exists. A saved profile should open prefilled in each Input. In React, Base UI does not remember a profile.
Installation
Usage
<form className="w-full max-w-md" onSubmit={form.handleSubmit(onSubmit)}>
<FieldGroup>
<Controller
control={form.control}
name="username"
render={({ field, fieldState }) => (
<Field invalid={fieldState.invalid}>
<FieldLabel htmlFor="four-field-profile-form-username">
Username
</FieldLabel>
<Input
{...field}
id="four-field-profile-form-username"
placeholder="johndoe"
/>
<FieldDescription>Your unique username.</FieldDescription>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
/>
<Controller
control={form.control}
name="email"
render={({ field, fieldState }) => (
<Field invalid={fieldState.invalid}>
<FieldLabel htmlFor="four-field-profile-form-email">
Email
</FieldLabel>
<Input
{...field}
id="four-field-profile-form-email"
{/* The rest of the file is under Installation. */}FAQ
What does each profile field check?
username is z.string().min(3, …), email is z.email(…), bio is z.string().min(10, …) and website is z.url(…). Their messages: "Username must be at least 3 characters.", "Please enter a valid email address.", "Bio must be at least 10 characters." and "Please enter a valid URL."
Is the bio a textarea?
No, it's a single-line Input with the placeholder Tell us about yourself. For longer bios, swap in a Textarea and keep the same Controller.
What website string passes?
z.url() needs a full URL with a scheme, such as https://example.com, which is also the placeholder. example.com alone fails. The input is type="url".
How are email and username different on the input?
Email sets type="email"; username is a text input with the placeholder johndoe and the description "Your unique username." Each label's htmlFor matches its input's id, such as four-field-profile-form-username.
API Reference
Field
Sets invalid={fieldState.invalid}, htmlFor="four-field-profile-form-username", htmlFor="four-field-profile-form-email", htmlFor="four-field-profile-form-bio", htmlFor="four-field-profile-form-website" and orientation="horizontal". The rest is on the Field page.
Input
Sets field, id="four-field-profile-form-username", placeholder="johndoe", id="four-field-profile-form-email", placeholder="john@example.com" and type="email". The rest is on the Input page.
Button
Sets type="submit". The rest is on the Button page.





