Form
Build structured and accessible forms with support for inputs, validation, and flexible layouts.
Component
Architecture
The Form component provides a clean and consistent layout structure for building forms.
It works with the FormRow, FormFieldGroup, and FormActions components to help organize fields and actions intuitively.
<template>
<Form
class="max-w-[600px]"
@submit="handleSubmit"
@reset="resetForm"
>
<FormRow>
<InputField
id="full-name"
v-model="formData.fullName"
v-model:error="formErrors.fullName"
label="Full name"
placeholder="Enter full name"
required
:validator="validateField"
/>
</FormRow>
<FormRow>
<InputField
id="email"
v-model="formData.email"
v-model:error="formErrors.email"
label="Email"
placeholder="Enter email"
:validator="validateEmail"
type="email"
required
/>
</FormRow>
<FormFieldGroup title="Additional Information">
<FormRow>
<InputField
id="phone"
v-model="formData.phone"
label="Phone"
placeholder="Enter phone number"
/>
<InputField
id="address"
v-model="formData.address"
label="Address"
placeholder="Enter address"
/>
</FormRow>
</FormFieldGroup>
<FormActions>
<ActionButton
type="submit"
text="Submit"
:styleType="ButtonStyleType.PRIMARY_BRAND_FILLED"
/>
<ActionButton
type="reset"
text="Reset"
/>
</FormActions>
</Form>
</template>
<script setup lang="ts">
// Initialize toast
const { $toast } = useNuxtApp()
// States
const formData = reactive({
fullName: '',
email: '',
phone: '',
address: '',
})
// Validation
const { formErrors, resetForm, validateFormFields } = useForm({
formData,
requiredFields: [
'fullName',
'email',
],
validators: {
fullName: validateField,
email: validateEmail,
},
})
// Methods
const handleSubmit = () => {
const isValid = validateFormFields()
const hasErrors = !isValid
if (hasErrors) {
$toast.error('Some fields contain errors', {
toastId: 'form-error',
})
return
}
$toast.success('Form submitted successfully', {
toastId: 'form-success',
})
resetForm()
}
</script>
Components
This set of components are used to create the layout of a form.
| Name | Description |
|---|---|
<FormRow> | Wraps form field components, ensuring proper spacing and alignment.On smaller screens, fields are automatically stacked vertically for better readability. |
<FormFieldGroup> | Groups related form fields under a common title. |
<FormActions> | Groups form action buttons, such as Submit or Cancel. On mobile devices, buttons stack vertically and their order can be customized via props. |
Usage
FormRow
The FormRow component is used to wrap form field components, ensuring proper spacing and alignment.
<template>
<FormRow>
<InputField
id="first-name"
v-model="formData.firstName"
label="Full name"
/>
</FormRow>
<FormRow>
<InputField
id="full-name"
v-model="formData.lastName"
label="Full name"
/>
</FormRow>
</template>
| Props | Default | Type |
|---|---|---|
spaced | false | boolean |
spaced
Adds some extra vertifcal padding to the row.
<template>
<FormRow spaced>
...
</FormRow>
</template>
FormFieldGroup
The FormFieldGroup component groups related form fields under a common title.
<template>
<FormFieldGroup title="Personal Information">
<FormRow>
<InputField
id="first-name"
v-model="formData.firstName"
label="First name"
/>
<InputField
id="last-name"
v-model="formData.lastName"
label="Last name"
/>
</FormRow>
</FormFieldGroup>
</template>
| Props | Default | Type |
|---|---|---|
title | 'Group title' | string |
titleClass | — | string |
headingTag | 'h3' | HeadingTag |
title
Sets the title of the form field group.
<template>
<FormFieldGroup title="Personal Information">
...
</FormFieldGroup>
</template>
FormActions
The FormActions component groups form action buttons, such as Submit or Cancel. On mobile devices, buttons stack vertically and their order can be customized via props.
<template>
<FormActions>
<ActionButton
type="submit"
text="Submit"
:styleType="ButtonStyleType.PRIMARY_BRAND_FILLED"
/>
<ActionButton
type="reset"
text="Reset"
/>
</FormActions>
</template>
| Props | Default | Type |
|---|---|---|
reverseOnMobile | false | boolean |
reverseOnMobile
Reverses the order of the buttons on mobile devices.
<template>
<FormActions reverseOnMobile>
...
</FormActions>
</template>
Triggers
submit
To submit a form, there two options:
Via @submit
<template>
<Form @submit="handleSubmit">
<FormRow>
<InputField
id="full-name"
v-model="formData.fullName"
label="Full name"
/>
</FormRow>
<FormActions>
<ActionButton
type="submit"
text="Submit"
:styleType="ButtonStyleType.PRIMARY_BRAND_FILLED"
/>
</FormActions>
</Form>
</template>
<script setup lang="ts">
// Methods
const handleSubmit = () => {
// Submit logic here
}
</script>
Via button @click
@submit emitter can be omitted when the submit function is being set directly to the submit button @click.
In this case, the button does not require the type prop.
<template>
<Form>
<FormRow>
<InputField
id="full-name"
v-model="formData.fullName"
label="Full name"
/>
</FormRow>
<FormActions>
<ActionButton
text="Submit"
:styleType="ButtonStyleType.PRIMARY_BRAND_FILLED"
@click="handleSubmit"
/>
</FormActions>
</Form>
</template>
<script setup lang="ts">
// Methods
const handleSubmit = () => {
// Submit logic here
}
</script>
reset
To reset a form, there two options:
Via @reset
<template>
<Form @reset="resetForm">
<FormRow>
<InputField
id="full-name"
v-model="formData.fullName"
label="Full name"
/>
</FormRow>
<FormActions>
<ActionButton
type="reset"
text="Reset"
/>
</FormActions>
</Form>
</template>
<script setup lang="ts">
// States
const formData = reactive({
fullName: '',
})
// Composables
const { resetForm } = useForm({
formData,
})
</script>
Via button @click
@reset emitter can be omitted when the submit function is being set directly to the submit button @click.
In this case, the button does not require the type prop.
<template>
<Form>
<FormRow>
<InputField
id="full-name"
v-model="formData.fullName"
label="Full name"
/>
</FormRow>
<FormActions>
<ActionButton
text="Reset"
@click="resetForm"
/>
</FormActions>
</Form>
</template>
<script setup lang="ts">
// States
const formData = reactive({
fullName: '',
})
// Composables
const { resetForm } = useForm({
formData,
})
</script>
Validation
To properly validate a form, consider the following aspects:
formData
A reactive object named formData is required to store the form’s field values. This object should be defined inside the <script> block.
Form field requirements
Must use this two props:
Validation composable
Use the useForm composable to manage validation logic:
const { formErrors, resetForm, validateFormFields } = useForm({
formData,
requiredFields: [
'fullName',
'email',
],
validators: {
fullName: validateField,
email: validateEmail,
},
validateOn: FormValidationMode.SUBMIT // Optional (Default: Submit)
})
Usage example
<template>
<Form @submit="handleSubmit">
<FormRow>
<InputField
id="full-name"
v-model="formData.fullName"
v-model:error="formErrors.fullName"
label="Full name"
placeholder="Enter full name"
required
:validator="validateField"
/>
</FormRow>
<FormActions>
<ActionButton
type="submit"
text="Submit"
:styleType="ButtonStyleType.PRIMARY_BRAND_FILLED"
/>
</FormActions>
</Form>
</template>
<script setup lang="ts">
// Initialize toast
const { $toast } = useNuxtApp()
// States
const formData = reactive({
fullName: '',
})
// Validation
const { formErrors, resetForm, validateFormFields } = useForm({
formData,
requiredFields: [
'fullName',
],
validators: {
fullName: validateField,
},
})
// Methods
const handleSubmit = () => {
const isValid = validateFormFields()
if (!isValid) {
$toast.error('Some fields contain errors', {
toastId: 'form-error',
})
return
}
$toast.success('Form submitted successfully', {
toastId: 'form-success',
})
resetForm()
}
</script>
Key takeaways
Validation functions
validateField
Checks if the field value has a value and is not empty.
validators: {
field: validateField,
}
validateEmail
Validates whether a given value is a properly formatted email address and not empty.
validators: {
email: validateEmail,
}
validatePasswordMatch
Validates that the repeated password matches the original one.
validators: {
passwordRepeat: value => validatePasswordMatch(formData.password, value),
}
validateUrl
Validates whether the value is a valid URL and is not empty.
validators: {
url: validateUrl,
}
validateBooleanField
Validates that a boolean field (checkbox or switch) is true.
validators: {
acceptConditions: validateBooleanField,
}
validateArrayField
Validates that the field is a non-empty array, optionally checking minimum and/or maximum item counts.
validators: {
technologies: validateArrayField,
}
validateDateRange
Validates that the end date is after or equal to the start date. If either date is missing or invalid, it returns an error message.
validators: {
startDate: value => validateField(value),
endDate: value => validateDateRange(formData.startDate, value),
}
composeArrayValidators
Composes multiple array validators and returns the first error found.
const validateRules = composeArrayValidators([
validateRuleCompleteness,
validateAtLeastOneRule,
])
validators: {
rules: validateRules,
}
validateRuleCompleteness
Validates that rule rows are not partially completed. Empty rows are allowed; partially filled rows are rejected.
validators: {
rules: validateRuleCompleteness,
}
validateAtLeastOneRule
Validates that at least one complete rule exists.
validators: {
rules: validateAtLeastOneRule,
}
validateMaxRules
Validates that a rules array does not exceed a maximum length.
validators: {
rules: value => validateMaxRules(value, 10, 'You can add up to 10 rules.'),
}
createRulesFieldValidator
Creates a ready-to-use validator for RulesField, combining:
const validateRules = createRulesFieldValidator({
required: true,
maxRules: 10,
requiredFieldMessage: 'Add at least one complete rule.',
incompleteRuleMessage: 'Complete all rule fields before continuing.',
maxRulesMessage: 'You can add up to 10 rules.',
})
const { formErrors, validateFormFields } = useForm({
formData,
requiredFields: ['rules'],
validators: {
rules: validateRules,
},
})