- Prop
form- Type
{ readonly formProps: () => React.FormHTMLAttributes<HTMLFormElement>; } | undefined- Default
- —
- Requirement
- optional
- Description
- Form API props returned by
Docs Components Uncategorized
Form
React example follows below.
Vue example follows below.
Angular example follows below.
Svelte example follows below.
Solid example follows below.
- parts
- —
- nodes
- —
- size
- —
- part
- —
ts
import { useForm } from "@grassroot/form-react";import { Button, Form, Input } from "@grassroot/ui-react";import * as React from "react"; const validateEmail = (value: unknown) => String(value).includes("@") ? [] : [{ path: "email", message: "Enter a valid email address." }]; export default function FormDemo() { const [savedEmail, setSavedEmail] = React.useState(""); const form = useForm({ defaultValues: { email: "" }, validators: { email: validateEmail }, validationPolicy: { shouldValidate: (event, hasError = false) => event === "blur" || (event === "change" && hasError), }, onSubmit: (values) => setSavedEmail(String(values.email ?? "")), }); const email = form.useField("email"); const formBinding = { formProps: () => ({ noValidate: true, onSubmit: (event: React.SubmitEvent<HTMLFormElement>) => form.formProps().onSubmit(event.nativeEvent), }), }; return ( <div className="w-full max-w-md" data-form-demo> <Form form={formBinding} data-status={form.service.getSnapshot().context.status}> <Input name={email.inputProps.name} value={email.inputProps.value} onInput={(event) => email.inputProps.onInput(event.nativeEvent)} onBlur={email.inputProps.onBlur} label="Work email" hint="Deployment alerts will be sent here." error={email.error?.[0]?.message} type="email" autoComplete="email" required /> <div className="flex flex-wrap items-center gap-3"> <Button type="submit">Save email</Button> <p className="m-0 min-h-5 text-[12.5px] text-muted" role="status" aria-live="polite"> {savedEmail ? `Saved ${savedEmail} for this preview.` : "No email saved in this preview."} </p> </div> </Form> </div> );}- parts
- —
- nodes
- —
- size
- —
- part
- —
ts
<script setup lang="ts">import { computed, ref } from "vue";import { useForm } from "@grassroot/form-vue";import { Button, Form, Input } from "@grassroot/ui-vue"; const validateEmail = (value: unknown) => String(value).includes("@") ? [] : [{ path: "email", message: "Enter a valid email address." }]; const savedEmail = ref("");const form = useForm(() => ({ defaultValues: { email: "" }, validators: { email: validateEmail }, validationPolicy: { shouldValidate: (event, hasError = false) => event === "blur" || (event === "change" && hasError), }, onSubmit: (values) => { savedEmail.value = String(values.email ?? ""); },}));const email = form.field("email");const emailValue = computed(() => String(form.snapshot.value.context.values.email ?? ""));const emailError = computed(() => { void form.snapshot.value; return email.error()?.[0]?.message;});</script> <template> <div class="w-full max-w-md" data-form-demo> <Form :form="form" :data-status="form.snapshot.value.context.status"> <Input :model-value="emailValue" :name="email.inputProps().name" label="Work email" hint="Deployment alerts will be sent here." :error="emailError" type="email" autocomplete="email" required @input="email.inputProps().onInput($event)" @blur="email.inputProps().onBlur()" /> <div class="flex flex-wrap items-center gap-3"> <Button type="submit">Save email</Button> <p class="m-0 min-h-5 text-[12.5px] text-muted" role="status" aria-live="polite"> {{ savedEmail ? `Saved ${savedEmail} for this preview.` : "No email saved in this preview." }} </p> </div> </Form> </div></template>- parts
- —
- nodes
- —
- size
- —
- part
- —
ts
import "./ensure-compiler";import { Component, computed, signal } from "@angular/core";import { injectForm } from "@grassroot/form-angular";import { GrassrootButton, GrassrootForm, GrassrootInput } from "@grassroot/ui-angular"; const validateEmail = (value: unknown) => String(value).includes("@") ? [] : [{ path: "email", message: "Enter a valid email address." }]; @Component({ selector: "app-form-demo", standalone: true, imports: [GrassrootButton, GrassrootForm, GrassrootInput], host: { style: "display: contents" }, template: ` <div class="w-full max-w-md" data-form-demo> <grassroot-form [form]="form" [attr.data-status]="form.snapshot().context.status"> <grassroot-input [name]="email.inputProps().name" [value]="emailValue()" (valueChange)="inputEmail($event)" (focusout)="blurEmail()" label="Work email" hint="Deployment alerts will be sent here." [error]="emailError()" type="email" autocomplete="email" [required]="true" /> <div class="flex flex-wrap items-center gap-3"> <grassroot-button type="submit">Save email</grassroot-button> <p class="m-0 min-h-5 text-[12.5px] text-muted" role="status" aria-live="polite"> {{ savedEmail() ? "Saved " + savedEmail() + " for this preview." : "No email saved in this preview." }} </p> </div> </grassroot-form> </div> `,})export class FormDemoComponent { readonly savedEmail = signal(""); readonly form = injectForm({ defaultValues: { email: "" }, validators: { email: validateEmail }, validationPolicy: { shouldValidate: (event, hasError = false) => event === "blur" || (event === "change" && hasError), }, onSubmit: (values) => this.savedEmail.set(String(values.email ?? "")), }); readonly email = this.form.field("email"); readonly emailValue = computed(() => String(this.form.snapshot().context.values.email ?? "")); readonly emailError = computed(() => { this.form.snapshot(); return this.email.error()?.[0]?.message; }); inputEmail(value: string): void { this.email.inputProps().onInput({ target: { value } } as unknown as Event); } blurEmail(): void { this.email.inputProps().onBlur(); }} export default FormDemoComponent;- parts
- —
- nodes
- —
- size
- —
- part
- —
ts
<script lang="ts"> import { onDestroy } from "svelte"; import { createForm } from "@grassroot/form-svelte"; import { Button, Form, Input } from "@grassroot/ui-svelte"; const validateEmail = (value: unknown) => String(value).includes("@") ? [] : [{ path: "email", message: "Enter a valid email address." }]; let savedEmail = $state(""); const form = createForm({ defaultValues: { email: "" }, validators: { email: validateEmail }, validationPolicy: { shouldValidate: (event, hasError = false) => event === "blur" || (event === "change" && hasError), }, onSubmit: (values) => { savedEmail = String(values.email ?? ""); }, }); const email = form.field("email"); let snapshot = $state(form.service.getSnapshot()); const unsubscribe = form.service.subscribe( (value) => value, (value) => (snapshot = value), ); let emailValue = $derived(String(snapshot.context.values.email ?? "")); let emailError = $derived.by(() => { snapshot; return email.error()?.[0]?.message; }); function inputEmail(event: Event) { email.inputProps().onInput(event); } onDestroy(() => { unsubscribe(); form.destroy(); });</script> <div class="w-full max-w-md" data-form-demo> <Form {form} data-status={snapshot.context.status}> <Input value={emailValue} name={email.inputProps().name} label="Work email" hint="Deployment alerts will be sent here." error={emailError} type="email" autocomplete="email" required oninput={inputEmail} onblur={email.inputProps().onBlur} /> <div class="flex flex-wrap items-center gap-3"> <Button type="submit">Save email</Button> <p class="m-0 min-h-5 text-[12.5px] text-muted" role="status" aria-live="polite"> {savedEmail ? `Saved ${savedEmail} for this preview.` : "No email saved in this preview."} </p> </div> </Form></div>- parts
- —
- nodes
- —
- size
- —
- part
- —
ts
/** @jsxImportSource solid-js */import { createForm } from "@grassroot/form-solid";import { Button, Form, Input } from "@grassroot/ui-solid";import { createSignal, type JSX } from "solid-js"; const validateEmail = (value: unknown) => String(value).includes("@") ? [] : [{ path: "email", message: "Enter a valid email address." }]; export default function FormDemo(): JSX.Element { const [savedEmail, setSavedEmail] = createSignal(""); const form = createForm({ defaultValues: { email: "" }, validators: { email: validateEmail }, validationPolicy: { shouldValidate: (event, hasError = false) => event === "blur" || (event === "change" && hasError), }, onSubmit: (values) => { setSavedEmail(String(values.email ?? "")); }, }); const email = form.field("email"); const emailError = () => { form.snapshot(); return email.error()?.[0]?.message; }; return ( <div class="w-full max-w-md" data-form-demo> <Form form={form} data-status={form.snapshot().context.status}> <Input name={email.inputProps().name} value={String(form.snapshot().context.values.email ?? "")} onInput={email.inputProps().onInput} onBlur={email.inputProps().onBlur} label="Work email" hint="Deployment alerts will be sent here." error={emailError()} type="email" autocomplete="email" required /> <div class="flex flex-wrap items-center gap-3"> <Button type="submit">Save email</Button> <p class="m-0 min-h-5 text-[12.5px] text-muted" role="status" aria-live="polite"> {savedEmail() ? `Saved ${savedEmail()} for this preview.` : "No email saved in this preview."} </p> </div> </Form> </div> );}Installation
API Reference
Import
import { useForm } from "@grassroot/form-react";
import { Button, Form, Input } from "@grassroot/ui-react";
import * as React from "react";
Props
Prop
Type
Default
Requirement
Description
form{ readonly formProps: () => React.FormHTMLAttributes<HTMLFormElement>; } | undefined—
optional
Form API props returned by
Content regions
children
Native attributes
Standard DOM and ARIA attributes not listed above spread onto the root element.
Import
import { computed, ref } from "vue";
import { useForm } from "@grassroot/form-vue";
import { Button, Form, Input } from "@grassroot/ui-vue";
Props
Prop
Type
Default
Requirement
form{ formProps: () => Record<string, unknown>; } | undefined—
optional
- Prop
form- Type
{ formProps: () => Record<string, unknown>; } | undefined- Default
- —
- Requirement
- optional
Content regions
default
Import
import "./ensure-compiler";
import { Component, computed, signal } from "@angular/core";
import { injectForm } from "@grassroot/form-angular";
import { GrassrootButton, GrassrootForm, GrassrootInput } from "@grassroot/ui-angular";
Props
Prop
Type
Default
Requirement
form{
readonly formProps: () => {
readonly noValidate?: boolean;
readonly onSubmit?: (event: SubmitEvent) => void;
};
}—
optional
- Prop
form- Type
{ readonly formProps: () => { readonly noValidate?: boolean; readonly onSubmit?: (event: SubmitEvent) => void; }; }- Default
- —
- Requirement
- optional
Content regions
default
Import
import { onDestroy } from "svelte";
import { createForm } from "@grassroot/form-svelte";
import { Button, Form, Input } from "@grassroot/ui-svelte";
Props
Prop
Type
Default
Requirement
form{ formProps: () => Record<string, unknown> }—
optional
classstring—
optional
- Prop
form- Type
{ formProps: () => Record<string, unknown> }- Default
- —
- Requirement
- optional
- Prop
class- Type
string- Default
- —
- Requirement
- optional
Content regions
children
Import
import { createForm } from "@grassroot/form-solid";
import { Button, Form, Input } from "@grassroot/ui-solid";
import { createSignal, type JSX } from "solid-js";
Props
Prop
Type
Default
Requirement
form{ readonly formProps: () => JSX.FormHTMLAttributes<HTMLFormElement> }—
optional
- Prop
form- Type
{ readonly formProps: () => JSX.FormHTMLAttributes<HTMLFormElement> }- Default
- —
- Requirement
- optional
Content regions
children
Native attributes
Standard DOM and ARIA attributes not listed above spread onto the root element.