Form

Wrapper component for form validation and submission handling

Usage

<script setup lang="ts">
import { Form } from '@hareui/vue'
</script>
Must be at least 8 characters with 1 uppercase and 1 number
<script setup lang="ts">import { Icon } from '@iconify/vue'import { Button, Form, TextField } from '@hareui/vue'function onSubmit(e: Event) {  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)}`)}function validateEmail(value: string | number) {  if (!/^[\w.%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(String(value)))    return 'Please enter a valid email address'  return null}function validatePassword(value: string | number) {  const password = String(value)  if (password.length < 8)    return 'Password must be at least 8 characters'  if (!/[A-Z]/.test(password))    return 'Password must contain at least one uppercase letter'  if (!/\d/.test(password))    return 'Password must contain at least one number'  return null}</script><template>  <Form class="flex w-96 flex-col gap-4" @submit="onSubmit">    <TextField      required      name="email"      type="email"      label="Email"      placeholder="[email protected]"      :validate="validateEmail"    />    <TextField      required      minlength="8"      name="password"      type="password"      label="Password"      placeholder="Enter your password"      description="Must be at least 8 characters with 1 uppercase and 1 number"      :validate="validatePassword"    />    <div class="flex gap-2">      <Button type="submit">        <Icon icon="gravity-ui:check" />        Submit      </Button>      <Button type="reset" variant="secondary">        Reset      </Button>    </div>  </Form></template>

Anatomy

<script setup lang="ts">
import { Button, Form } from '@hareui/vue'
</script>

<template>
  <Form>
    <!-- Form fields go here -->
    <Button type="submit" />
    <Button type="reset" />
  </Form>
</template>

Examples

Render Function

Must be at least 8 characters with 1 uppercase and 1 number
<script setup lang="ts">import { Icon } from '@iconify/vue'import { Button, Form, TextField } from '@hareui/vue'function onSubmit(e: Event) {  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)}`)}function validateEmail(value: string | number) {  if (!/^[\w.%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(String(value)))    return 'Please enter a valid email address'  return null}function validatePassword(value: string | number) {  const password = String(value)  if (password.length < 8)    return 'Password must be at least 8 characters'  if (!/[A-Z]/.test(password))    return 'Password must contain at least one uppercase letter'  if (!/\d/.test(password))    return 'Password must contain at least one number'  return null}</script><template>  <!-- Attributes fall through to the native <form> element (HeroUI's `render` prop) -->  <Form class="flex w-96 flex-col gap-4" data-custom="foo" @submit="onSubmit">    <TextField      required      name="email"      type="email"      label="Email"      placeholder="[email protected]"      :validate="validateEmail"    />    <TextField      required      minlength="8"      name="password"      type="password"      label="Password"      placeholder="Enter your password"      description="Must be at least 8 characters with 1 uppercase and 1 number"      :validate="validatePassword"    />    <div class="flex gap-2">      <Button type="submit">        <Icon icon="gravity-ui:check" />        Submit      </Button>      <Button type="reset" variant="secondary">        Reset      </Button>    </div>  </Form></template>

Attributes and listeners such as action, method, @submit, @reset and data-* fall through to the native <form> element.

Customization

Tailwind CSS

<script setup lang="ts">import { Button, Form, Input, TextField } from '@hareui/vue'</script><template>  <Form    class="flex w-80 flex-col gap-3 rounded-xl border border-border/80 bg-surface p-4 shadow-sm"    @submit.prevent  >    <TextField required name="email" type="email" label="Work email">      <Input class="bg-field" placeholder="[email protected]" />    </TextField>    <Button class="w-full" type="submit">      Continue    </Button>  </Form></template>

Global CSS

Form renders a native <form> with no theme slots. Style it with class, or with a project-level class under @layer components.

@layer components {
  .form-layout {
    @apply flex flex-col gap-4 rounded-xl border border-border bg-surface p-4 shadow-sm;
  }
}
<Form class="form-layout" @submit="onSubmit">
  <!-- TextField, Input, Button, etc. -->
</Form>

For individual controls, see Customization on TextField, Input, Label and FieldError.

Styling Reference

Form has no ui keys and no data-slot. Field appearance and validation states come from child components such as TextField, Input, Label, Description and FieldError.

API Reference

Props

PropTypeDefaultDescription
validationBehaviorValidationBehavior"native"Validation behavior for the fields inside. native blocks submission and shows errors on change or submit; aria shows errors in realtime and never blocks submission. A field's own validationBehavior wins.
validationErrorsValidationErrors-Server-side errors keyed by field name. Shown immediately; a field's error clears once the user changes it.

Slots

SlotPropsDescription
defaultanyFields and buttons.

Form Validation

Fields inside a Form support:

  • Built-in HTML validation attributes (required, minlength, pattern, min, max, …)
  • A custom validate function on TextField, Input, NumberField, Checkbox, Switch and RadioGroup. It returns an error message (or an array of them) when the value is invalid, and true, null or undefined when it is valid.
  • Server-side errors through the validation-errors prop, keyed by field name. They show immediately and clear once the user changes that field.
  • Error display through each field's built-in FieldError. The #error slot receives { isInvalid, validationErrors, validationDetails }.

A field's invalid prop, when set, overrides all of the above.

Validation Behavior

The validation-behavior prop controls how errors are displayed:

  • native (default): native constraint validation. Submission is blocked while a field is invalid, and errors show once the user commits a value (on change, on blur, or on submit). The first invalid field is focused.
  • aria: errors show in realtime as the user types, are exposed through aria-invalid, and never block submission. The <form> gets novalidate.

Set it on the form, or override it on an individual field.

Form Submission

Forms can be submitted in several ways:

  • Traditional submission: set the action attribute to submit to a URL
  • JavaScript handling: listen to @submit and process the data
  • FormData API: read the values with FormData in the submit handler
function onSubmit(e: Event) {
  e.preventDefault()
  const formData = new FormData(e.currentTarget as HTMLFormElement)
  const data = Object.fromEntries(formData)
  console.log('Form data:', data)
}

A reset button (or form.reset()) restores every field's defaultValue and clears displayed errors.

Accessibility

  • Native <form> element semantics
  • Form landmark with aria-label or aria-labelledby
  • Focus moves to the first invalid field when submission is blocked
  • aria-invalid and aria-describedby (pointing to the error) on each invalid control

On this page

No Headings