Form Groups
Form groups are a way to structure related fields in a form. They allow you to group fields together and slice forms into smaller parts. This can be useful for organizing forms with many fields or for creating reusable group components that may be added to multiple forms.
Their main purpose is to slice forms into manageable parts. They are not nested forms; they do not submit data on their own. They are just a way to group fields together in a parent form.
Features
- Multi-layered validation with both HTML attributes or Standard Schemas.
- Aggregated state for validation, dirty, touched, and more.
- Supports either
fieldset
andlegend
elements or any other HTML elements. - Automatic field name prefixing for organizing submitted data structure.
Anatomy
Creating a Form Group
To create a form group, you can use the useFormGroup
composable. Just like fields, the useFormGroup
composable returns values that should be bound to the anatomy elements.
Typically, you need to create a FormGroup
component that you can use to structure your form fields. You can use fieldset
as a base element for your FormGroup
component or any other element that you prefer.
Form groups require a name
prop, which will be used to nest the fields’ values in the form data. In the next examples, fill out the data and submit the form to see how the form data are structured.
With a fieldset element
<template> <fieldset v-bind="groupProps"> <legend v-bind="labelProps">{{ label }}</legend>
<slot /> </fieldset></template>
<script setup lang="ts">import { type FormGroupProps, useFormGroup } from '@formwerk/core';
const props = defineProps<FormGroupProps>();
const { labelProps, groupProps } = useFormGroup(props);</script>
<style>fieldset { border: 1px solid #e0e0e0; margin: 10px 0; padding: 8px; border-radius: 6px;
legend { font-size: 16px; font-weight: 600; color: #333; }}</style>
Naturally, styling is entirely up to you. This can be much easier with other HTML elements like div
, as shown in the next section.
With any HTML element
You can use any HTML element to create a form group. Formwerk’s useFormGroup
bindings automatically change based on the element that is bound to it to ensure users get a consistent experience regardless of what element you choose to use.
Here’s an example using a div
element:
<template> <div v-bind="groupProps"> <h3 v-bind="labelProps">{{ label }}</h3>
<slot /> </div></template>
<script setup lang="ts">import { type FormGroupProps, useFormGroup } from '@formwerk/core';
const props = defineProps<FormGroupProps>();
const { labelProps, groupProps } = useFormGroup(props);</script>
Validation
The form guide mentioned briefly that groups can have their own validation Standard Schema just like forms do. They do not override the parent form schema; they are just an extension of it.
Formwerk prioritizes the validation sources in the following order:
- HTML Constraints are checked first; only if they are valid, continue to the next step.
- Field-level’s Standard Schema is checked next; only if it is valid, continue to the next step.
- Group-level’s Standard Schema is checked next; only if it is valid, continue to the next step.
- Form-level’s Standard Schema is checked last.
<script setup lang="ts">import { useForm } from '@formwerk/core';import TextField from './TextField.vue';import FormGroup from './FormGroup.vue';import { z } from 'zod';
const shippingSchema = z.object({ address: z.string().min(5), city: z.string().min(3), zip: z.string().length(5),});
const schema = z.object({ email: z.string().email(),});
const { handleSubmit } = useForm({ schema,});
const onSubmit = handleSubmit((data) => { alert(JSON.stringify(data.toObject(), null, 2));});</script>
<template> <form @submit="onSubmit" novalidate> <TextField name="email" label="Email" />
<FormGroup name="shipping" label="Shipping information" :schema="shippingSchema" > <TextField name="address" label="Address" /> <TextField name="city" label="City" /> <TextField name="zip" label="ZIP" /> </FormGroup>
<button type="submit">Submit</button> </form></template>
Group state
The group tracks and aggregates the fields’ state that are part of it. The following states are an aggregation of the fields that are part of the group:
Name | Type | Description |
---|---|---|
isDirty | Boolean | Indicates whether any field within the group has been modified. |
isValid | Boolean | Indicates whether all fields within the group pass their validation checks. |
isTouched | Boolean | Indicates whether any field within the group has been interacted with. |
It also exposes the following getters and functions:
Name | Type | Description |
---|---|---|
getErrors | () => IssueCollection[] | Returns all errors within the group. |
getValues | () => Record<string, any> | Returns all values within the group. |
getError | (name: string) => string | undefined | Returns the error for a given field within the group. |
displayError | (name: string) => string | undefined | Displays the error for a given field within the group if the field was touched. |
validate | () => Promise | Validates all fields within the group. |
Group Names and Nested Paths
A group can accept a name
prop, which will prefix all the field names nested under it with that same name.
This means that when submitting the form, the data will be nested under the group name.
<script setup lang="ts">import { useForm } from '@formwerk/core';import TextField from './TextField.vue';import FormGroup from './FormGroup.vue';
const { handleSubmit } = useForm();
const onSubmit = handleSubmit((data) => { alert(JSON.stringify(data.toObject(), null, 2));});</script>
<template> <form @submit="onSubmit" novalidate> <TextField name="field" label="Not Nested" />
<FormGroup name="group-1" label="Group 1"> <TextField name="field" label="Field 1 - Group 1" /> </FormGroup>
<FormGroup name="group-2" label="Group 2"> <TextField name="field" label="Field 1 - Group 2" /> </FormGroup>
<button type="submit">Submit</button> </form></template>
API
Props
These are the properties that can be passed to the useFormGroup
composable.
Name | Type | Description |
---|---|---|
disabled | Whether the form group is disabled. | |
disableHtmlValidation | Whether HTML5 validation should be disabled for this form group. | |
label | The label for the form group. | |
name | The name/path of the form group. | |
schema | The validation schema for the form group. |
Returns
These are the properties in the object returned by the useFormGroup
composable.
Name | Type | Description |
---|---|---|
displayError | Displays an error for a given field. | |
groupEl | Reference to the group element. | |
groupProps | Props for the group element. | |
isDirty | Whether the form group is dirty. | |
isDisabled | Whether the form group is disabled. | |
isTouched | Whether the form group is touched. | |
isValid | Whether the form group is valid. | |
labelProps | Props for the label element. | |
validate | () => Promise<GroupValidationResult<TOutput> & { type: "GROUP"; }> | Validates the form group. |