Form
A native form element with consolidated error handling.
import { createSignal } from 'solid-js';
import { Field } from 'base-ui-solid/field';
import { Form } from 'base-ui-solid/form';
import { Button } from 'base-ui-solid/button';
import styles from './index.module.css';
export default function ExampleForm() {
const [errors, setErrors] = createSignal({});
const [loading, setLoading] = createSignal(false);
return (
<Form
class={styles.Form}
errors={errors()}
onSubmit={async (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const value = formData.get('url') as string;
setLoading(true);
const response = await submitForm(value);
const serverErrors = {
url: response.error,
};
setErrors(serverErrors);
setLoading(false);
}}
>
<Field.Root name="url" class={styles.Field}>
<Field.Label class={styles.Label}>Homepage</Field.Label>
<Field.Control
type="url"
required
defaultValue="https://example.com"
placeholder="https://example.com"
pattern="https?://.*"
class={styles.Input}
/>
<Field.Error class={styles.Error} />
</Field.Root>
<Button type="submit" disabled={loading()} focusableWhenDisabled class={styles.Button}>
Submit
</Button>
</Form>
);
}
async function submitForm(value: string) {
// Mimic a server response
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
try {
const url = new URL(value);
if (url.hostname.endsWith('example.com')) {
return { error: 'The example domain is not allowed' };
}
} catch {
return { error: 'This is not a valid URL' };
}
return { success: true };
}
Anatomy
Form is composed together with Field. Import the components and place them together:
Anatomy
import { Field } from 'base-ui-solid/field';
import { Form } from 'base-ui-solid/form';
<Form>
<Field.Root>
<Field.Label />
<Field.Control />
<Field.Error />
</Field.Root>
</Form>;
Examples
Submit with a Server Function
Use a native onSubmit handler to collect FormData, call your server function, and update the returned field errors and loading state with Solid signals.
import { createSignal } from 'solid-js';
import { Field } from 'base-ui-solid/field';
import { Form } from 'base-ui-solid/form';
import { Button } from 'base-ui-solid/button';
import styles from './index.module.css';
interface FormState {
serverErrors?: Form.Props['errors'];
}
export default function ActionStateForm() {
const [state, setState] = createSignal<FormState>({});
const [loading, setLoading] = createSignal(false);
async function formAction(event: SubmitEvent) {
event.preventDefault();
const form = event.currentTarget as HTMLFormElement;
const data = new FormData(form);
setLoading(true);
try {
setState(await submitForm(state(), data));
} finally {
setLoading(false);
// React resets a form passed to `action` once the action completes.
form.reset();
}
}
return (
<Form errors={state().serverErrors} onSubmit={formAction} class={styles.Form}>
<Field.Root name="username" class={styles.Field}>
<Field.Label class={styles.Label}>Username</Field.Label>
<Field.Control
type="text"
autocomplete="username"
required
defaultValue="admin"
placeholder="e.g. alice132"
class={styles.Input}
/>
<Field.Error class={styles.Error} />
</Field.Root>
<Button type="submit" disabled={loading()} focusableWhenDisabled class={styles.Button}>
Submit
</Button>
</Form>
);
}
// Mark this as a Server Function with `'use server'` in a supporting framework like Next.js
async function submitForm(_previousState: FormState, formData: FormData) {
// Mimic a server response
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
try {
const username = formData.get('username') as string | null;
if (username === 'admin') {
return { success: false, serverErrors: { username: "'admin' is reserved for system use" } };
}
// 50% chance the username is taken
const success = Math.random() > 0.5;
if (!success) {
return {
serverErrors: { username: `${username} is unavailable` },
};
}
} catch {
return { serverErrors: { username: 'A server error has occurred' } };
}
return {};
}
Submit form values as a JavaScript object
You can use onFormSubmit instead of the native onSubmit to access form values as a JavaScript object. This is useful when you need to transform the values before submission, or integrate with 3rd party APIs.
Submission using onFormSubmit
<Form
onFormSubmit={async (formValues: { id: string; quantity: number }) => {
const payload = {
product_id: formValues.id,
order_quantity: formValues.quantity,
};
const response = await fetch('https://api.example.com', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
}}
/>
When used, preventDefault is called on the native submit event.
Using with Zod
When parsing the schema using schema.safeParse(), the z.flattenError(result.error).fieldErrors data can be used to map the errors to each field’s name.
import { createSignal } from 'solid-js';
import { z } from 'zod';
import { Field } from 'base-ui-solid/field';
import { Form } from 'base-ui-solid/form';
import { Button } from 'base-ui-solid/button';
import styles from './index.module.css';
const schema = z.object({
name: z.string().min(1, 'Name is required'),
age: z.coerce.number('Age must be a number').positive('Age must be a positive number'),
});
async function submitForm(formValues: Form.Values) {
const result = schema.safeParse(formValues);
if (!result.success) {
return {
errors: z.flattenError(result.error).fieldErrors,
};
}
return {
errors: {},
};
}
export default function Page() {
const [errors, setErrors] = createSignal({});
return (
<Form
class={styles.Form}
errors={errors()}
onFormSubmit={async (formValues) => {
const response = await submitForm(formValues);
setErrors(response.errors);
}}
>
<Field.Root name="name" class={styles.Field}>
<Field.Label class={styles.Label}>Name</Field.Label>
<Field.Control placeholder="Enter name" class={styles.Input} />
<Field.Error class={styles.Error} />
</Field.Root>
<Field.Root name="age" class={styles.Field}>
<Field.Label class={styles.Label}>Age</Field.Label>
<Field.Control placeholder="Enter age" class={styles.Input} />
<Field.Error class={styles.Error} />
</Field.Root>
<Button type="submit" class={styles.Button}>
Submit
</Button>
</Form>
);
}
API reference
Prop
Type
Default
errorsErrors—
Validation errors returned externally, typically after submission by a server or a form action.
This should be an object where keys correspond to the
name attribute on <Field.Root>,
and values correspond to error(s) related to that field.ErrorsactionsRefRefObject<Form.Actions | null>—
A ref to imperative actions.
validate: Validates all fields when called. Optionally pass a field name to validate a single field.RefObject<Form.Actions | null>onFormSubmitfunction—
Event handler called when the form is submitted.
preventDefault() is called on the native submit event when used.((formValues: Record<string, any>, eventDetails: Form.SubmitEventDetails) => void)validationModeForm.ValidationMode'onSubmit'
Determines when the form should be validated.
The
validationMode prop on <Field.Root> takes precedence over this. onSubmit (default): validates the field when the form is submitted, afterwards fields will re-validate on change.onBlur: validates a field when it loses focus.onChange: validates the field on every change to its value.Form.ValidationModeclassfunction—
CSS class applied to the element, or a function that
returns a class based on the component’s state.
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
Style applied to the element, or a function that
returns a style object based on the component’s state.
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
Replace the default element with a tag name, component, or render function.
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Form.State
type FormState = {};Form.Actions
type FormActions = { validate: (fieldName?: string) => void };Form.SubmitEventDetails
type FormSubmitEventDetails = {
/** The reason for the event. */
reason: 'none';
/** The native event associated with the custom event. */
event: Event;
};Form.SubmitEventReason
type FormSubmitEventReason = 'none';Form.ValidationMode
type FormValidationMode = 'onSubmit' | 'onBlur' | 'onChange';Form.Values
type FormValues = Record<string, any>;