---
title: "Form"
description: "Extends the native form element with server error display and submit-time validation gating."
canonical_url: "https://vinicunca.dev/akar/components/form"
---
# Form

> Extends the native form element with server error display and submit-time validation gating.

```vue [index.vue]
<script setup lang="ts">
import { FieldControl, FieldError, FieldLabel, FieldRoot, FormRoot } from 'akar'
import { ref } from 'vue'
import './styles.css'

const serverErrors = ref<Record<string, string>>({})
const submittedUsername = ref('')

function onFormSubmit(values: Record<string, unknown>) {
  submittedUsername.value = ''
  const username = values.username

  // Pretend this came back from an API call.
  if (username === 'taken') {
    serverErrors.value = { username: 'That username is already taken.' }
  }
  else {
    serverErrors.value = {}
    submittedUsername.value = String(username)
  }
}
</script>

<template>
  <div class="akar-demo akar-demo-Form">
    <FormRoot
        class="FormRoot"
        :errors="serverErrors"
        @form-submit="onFormSubmit"
      >
        <FieldRoot
          class="FieldRoot"
          name="username"
          required
          validation-mode="onBlur"
        >
          <FieldLabel class="FieldLabel">
            Username
          </FieldLabel>
          <FieldControl
            placeholder="Try 'taken'"
            class="FieldControl"
          />
          <FieldError class="FieldError" />
        </FieldRoot>

        <button
          type="submit"
          class="SubmitButton"
        >
          Submit
        </button>

        <p
          v-if="submittedUsername"
          class="SuccessMessage"
        >
          Submitted as "{{ submittedUsername }}".
        </p>
      </FormRoot>
  </div>
</template>
```

```css [styles.css]
@scope (.akar-demo-Form) {
.FormRoot {
  display: flex;
  width: 256px;
  flex-direction: column;
  gap: 12px;
}

.FieldRoot {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.FieldLabel {
  font-size: 14px;
  font-weight: 500;
  color: white;
}

.FieldControl {
  border-radius: 6px;
  border: 1px solid #404040;
  background-color: #171717;
  padding: 8px 12px;
  font-size: 14px;
  color: white;
  outline: none;
}

.FieldControl:focus {
  box-shadow: 0 0 0 2px #10b981;
}

.FieldControl[data-invalid] {
  border-color: #ef4444;
}

.FieldError {
  font-size: 12px;
  color: #ef4444;
}

.SubmitButton {
  border-radius: 6px;
  background-color: #047857;
  padding: 8px 12px;
  font-size: 14px;
  font-weight: 500;
  color: white;
  border: none;
  cursor: pointer;
}

.SubmitButton:hover {
  background-color: #065f46;
}

.SuccessMessage {
  font-size: 12px;
  color: #10b981;
}
}
```

## Features

- Runs every Field's validation on submit and focuses the first invalid control instead of submitting.
- Displays server-side errors by field name, cleared automatically once the user edits that field.
- Hands you the value of every named field through `formSubmit`, or leaves a valid form to submit natively.
- Sets a default `validationMode` for every Field inside it.
- Native `reset` clears every Field's touched/dirty/error state.

## Installation

Install the component from your command line.

```bash [pnpm]
pnpm add akar
```

```bash [yarn]
yarn add akar
```

```bash [npm]
npm install akar
```

```bash [bun]
bun add akar
```

## Anatomy

Import all parts and piece them together. See the [Field](https://vinicunca.dev/akar/components/field) docs for `FieldRoot` and its parts.

```vue
<script setup>
import { FieldControl, FieldError, FieldLabel, FieldRoot, FormRoot } from 'akar';
</script>

<template>
  <FormRoot
    :errors="serverErrors"
    @form-submit="onFormSubmit"
  >
    <FieldRoot
      name="email"
      required
    >
      <FieldLabel>Email</FieldLabel>
      <FieldControl type="email" />
      <FieldError />
    </FieldRoot>
  </FormRoot>
</template>
```

## API Reference

### Root

Extends the native `form` element. Provides context that every descendant `FieldRoot` optionally reads for server errors, submit-time validation, and reset handling. Renders with `novalidate`, so the browser's own validation popups don't pre-empt Field errors.

Exposes a `validate(name?)` method through a template ref, which validates every field (or only the named one) and returns whether they're valid.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"form"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `errors` |  | `Record<string, string \| string[]>` | A map of field `name` to server-side error message(s). Displayed by the<br>matching `FieldRoot`'s `FieldError` until the user edits that field, or a<br>new value for that key is provided. |
| `validationMode` | `"onSubmit"` | `"onBlur" \| "onChange" \| "onSubmit"` | When fields validate. The `validationMode` of a `FieldRoot` takes precedence over this.<br>- `onSubmit`: when the form is submitted, then on every change after that.<br>- `onBlur`: when a control loses focus.<br>- `onChange`: on every change to a control's value. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `formSubmit` | `values: Record<string, unknown>, event: SubmitEvent` | Emitted after `submit` with the values of every named field. Listening<br>to it prevents the native submission. |
| `submit` | `event: SubmitEvent` | Emitted with the native submit event once every field has validated<br>successfully. When any field is invalid, the native submission is<br>prevented and this isn't emitted. Otherwise the form submits natively<br>(e.g. to its `action`) unless you call `event.preventDefault()`, or<br>listen to `formSubmit`. |

**Methods**

| Method | Type | Description |
| --- | --- | --- |
| `validate` | `(fieldName?: string \| undefined) => boolean` | Validates every field, or only the field with the given `name`. Returns whether they're all valid. |

[FormRoot.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Form/FormRoot.vue)

## Examples

### Server-side errors

Pass an `errors` map keyed by field `name`. The matching `FieldRoot`'s `FieldError` displays it until the user edits that field, or a new value for that key is provided. When errors arrive in response to a submit, focus moves to the first field they invalidate.

```vue
<script setup>
import { FieldControl, FieldError, FieldLabel, FieldRoot, FormRoot } from 'akar'
import { ref } from 'vue'

const serverErrors = ref({})

async function onFormSubmit(values) {
  const result = await api.createAccount(values)
  if (result.error)
    serverErrors.value = { email: result.error }
}
</script>

<template>
  <FormRoot :errors="serverErrors" @form-submit="onFormSubmit">
    <FieldRoot name="email">
      <FieldLabel>Email</FieldLabel>
      <FieldControl type="email" />
      <FieldError v-slot="{ errors }">
        {{ errors[0] }}
      </FieldError>
    </FieldRoot>
  </FormRoot>
</template>
```

### Submit gating

On submit, `FormRoot` runs every field's validation. If any field is invalid, it prevents the native submission and focuses the first invalid field's control, in document order. Otherwise it emits `submit` with the native event, then:

- With a `formSubmit` listener, it prevents the native submission and emits `formSubmit` with the value of every named field.
- Without one, the form submits natively (for example to its `action`), unless you call `event.preventDefault()` in your `submit` handler.

Only synchronous results gate the submission. An async `validate` runs, and shows its errors once it resolves, but doesn't hold the submission back in `onSubmit` mode.

```vue
<template>
  <FormRoot @form-submit="onFormSubmit">
    <FieldRoot name="email" required>
      <FieldLabel>Email</FieldLabel>
      <FieldControl type="email" />
      <FieldError match="valueMissing">
        Email is required
      </FieldError>
    </FieldRoot>
  </FormRoot>
</template>
```

### Validation timing

Set `validation-mode` on `FormRoot` to change when every field inside it validates. A `FieldRoot`'s own `validation-mode` takes precedence.

```vue
<template>
  <FormRoot validation-mode="onBlur">
    <!-- ... -->
  </FormRoot>
</template>
```

## Accessibility

Renders a native `form` element. Focus moves to the first invalid field's control when a submit is blocked, so keyboard and screen-reader users land directly on the field that needs attention.

---

- [GitHub](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Form)
- [npm](https://www.npmjs.com/package/akar)


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
