Wrapper component for form validation and submission handling
Import
import { Form } from "heroui-solid";Usage
import { Check } from "gravity-icons-solid"
import {
Button,
Description,
FieldError,
Form,
Input,
Label,
TextField
} from "heroui-solid"
import { createSignal } from "solid-js"
export function Basic() {
const [email, setEmail] = createSignal("")
const [password, setPassword] = createSignal("")
const validateEmail = (value: string) => {
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return "Please enter a valid email address"
}
return null
}
const validatePassword = (value: string) => {
if (value.length < 8) {
return "Password must be at least 8 characters"
}
if (!/[A-Z]/.test(value)) {
return "Password must contain at least one uppercase letter"
}
if (!/[0-9]/.test(value)) {
return "Password must contain at least one number"
}
return null
}
const emailError = () => (email() ? validateEmail(email()) : null)
const passwordError = () => (password() ? validatePassword(password()) : null)
const onSubmit = (e: SubmitEvent) => {
e.preventDefault()
const formData = new FormData(e.currentTarget as HTMLFormElement)
const data: Record<string, string> = {}
// Convert FormData to plain object
formData.forEach((value, key) => {
data[key] = value.toString()
})
alert(`Form submitted with: ${JSON.stringify(data, null, 2)}`)
}
return (
<Form class="flex w-96 flex-col gap-4" onSubmit={onSubmit}>
<TextField
isInvalid={!!emailError()}
isRequired
name="email"
onChange={setEmail}
value={email()}
>
<Label>Email</Label>
<Input placeholder="john@example.com" type="email" />
<FieldError>{emailError()}</FieldError>
</TextField>
<TextField
isInvalid={!!passwordError()}
isRequired
name="password"
onChange={setPassword}
value={password()}
>
<Label>Password</Label>
<Input
minLength={8}
placeholder="Enter your password"
type="password"
/>
<Description>
Must be at least 8 characters with 1 uppercase and 1 number
</Description>
<FieldError>{passwordError()}</FieldError>
</TextField>
<div class="flex gap-2">
<Button type="submit">
<Check />
Submit
</Button>
<Button type="reset" variant="secondary">
Reset
</Button>
</div>
</Form>
)
}Anatomy
Import all parts and piece them together.
import { Form, Button } from "heroui-solid";
export default () => (
<Form>
{/* Form fields go here */}
<Button type="submit" />
<Button type="reset" />
</Form>
);Styling
Passing Tailwind CSS classes
import { Form, TextField, Label, Input, FieldError, Button } from "heroui-solid";
function CustomForm() {
return (
<Form class="w-full max-w-md space-y-4 rounded-lg border border-border bg-surface-secondary p-6">
<TextField>
<Label class="text-sm font-medium">Email</Label>
<Input class="rounded-full border-border/60" placeholder="Enter your email" />
<FieldError />
</TextField>
<Button type="submit" class="w-full">
Submit
</Button>
</Form>
);
}API Reference
Form Props
The Form component renders a native <form> element and forwards all of its
attributes. It accepts every prop of an HTML <form>, including:
| Prop | Type | Default | Description |
|---|---|---|---|
action | string | - | The URL to submit the form data to |
method | 'get' | 'post' | - | The HTTP method to use when submitting the form |
encType | string | - | The encoding type for form data submission |
target | '_self' | '_blank' | '_parent' | '_top' | - | Where to display the response after submitting |
onSubmit | (event: SubmitEvent) => void | - | Handler called when the form is submitted |
onReset | (event: Event) => void | - | Handler called when the form is reset |
class | string | - | Tailwind CSS classes applied to the form element |
children | JSX.Element | - | Form content (fields, buttons, etc.) |
Form Validation
Unlike HeroUI React (which wires React Aria's validation system into the form),
the Solid port renders a plain <form>. Drive validation yourself:
- Use native HTML5 validation attributes on
Input(required,minLength,pattern, …) - Compute each field's
isInvalidfrom your own signals - Display the message through a
FieldErrorchild (see the Usage example)
Validation Behavior
There is no validationBehavior prop. Native HTML validation still applies when
you set attributes like required on the Input; realtime, ARIA-style messages
are produced by driving isInvalid and FieldError from state as the user types.
Form Submission
Forms can be submitted in several ways:
- Traditional submission: set the
actionprop to submit to a URL - JavaScript handling: use the
onSubmithandler to process form data - FormData API: access form data using the FormData API in your submit handler
Example with FormData:
function handleSubmit(e: SubmitEvent) {
e.preventDefault();
const formData = new FormData(e.currentTarget as HTMLFormElement);
const data = Object.fromEntries(formData);
console.log("Form data:", data);
}Integration with Form Fields
The Form component works with HeroUI's form field components:
- TextField: for text inputs with labels and validation
- Input / TextArea: the field controls themselves
- Label / Description / FieldError: field satellites
- Button: for form submission and reset actions
Place them inside the <Form> and they submit as part of its FormData.
Accessibility
- Native
<form>element semantics - Form landmark creation with
aria-labeloraria-labelledby - Labels associate with their controls through
TextField - Field errors are announced through
FieldError
Advanced Usage
For richer flows — server-side validation, form-context providers, or integration
with a third-party form library — compose those around the plain <form>; the
Form component intentionally stays a thin native wrapper.
Last updated: 7/19/26, 3:27 AM