Submit component values, label controls, and show validation errors.

Give a control a name to include its value in a form. This works for Select and Combobox as well as inputs and checkboxes.

Submit a Value

<script setup lang="ts">
import { Checkbox } from "@ui-sdk/vue/checkbox"
</script>

<template>
  <form action="/preferences" method="post">
    <label
      ><Checkbox name="updates" value="yes" default-checked /> Email me
      updates</label
    >
    <button type="submit">Save</button>
    <button type="reset">Reset</button>
  </form>
</template>

The checkbox submits updates=yes when checked. Reset restores its default value.

Reset a Controlled Value

A reset also goes through @checked-change and v-model. Keep the binding in place so your ref follows the reset:

<script setup lang="ts">
import { Checkbox, type CheckedState } from "@ui-sdk/vue/checkbox"
import { ref } from "vue"

const checked = ref<CheckedState>(true)
const reason = ref("No change yet")
</script>

<template>
  <form>
    <label>
      <Checkbox
        v-model:checked="checked"
        name="updates"
        default-checked
        @checked-change="(_next, details) => (reason = details.reason)"
      />
      Email me updates
    </label>
    <button type="reset">Reset</button>
    <output>{{ reason }}</output>
  </form>
</template>

Uncheck the box, then press Reset. default-checked supplies the reset value. The event receives true with reason "reset", and v-model updates checked. The event runs when reset requests a different value; it does not run if the checkbox is already at its default.

Label and Validate a Field

Field connects a control to its label, description, and error message:

<script setup lang="ts">
import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@ui-sdk/vue/field"
import { Input } from "@ui-sdk/vue/input"
</script>

<template>
  <Field name="email">
    <FieldLabel>Email</FieldLabel>
    <Input type="email" required />
    <FieldDescription>We'll only use it for receipts.</FieldDescription>
    <FieldError />
  </Field>
</template>

You can use a Textarea, Checkbox, Switch, Radio Group, or Select in place of the Input. Field shows how they connect.

On controls that support them, required, disabled, read-only, and form follow native conventions. Disabled fields are left out of submission; read-only fields remain focusable and submitted.

Handle a Submission

On a normal submit, the browser checks constraints such as required and type="email" before running your handler.

Your server may still reject a valid-looking value. Pass invalid to the field and show the server’s message. When the user edits the field, clear the message and return invalid to undefined so the field uses native validation again. This example expects a /api/preferences endpoint:

<script setup lang="ts">
import { Field, FieldError, FieldLabel } from "@ui-sdk/vue/field"
import { Input } from "@ui-sdk/vue/input"
import { ref } from "vue"

const error = ref("")

async function save(event: SubmitEvent) {
  const body = new FormData(event.currentTarget as HTMLFormElement)
  const response = await fetch("/api/preferences", { method: "POST", body })
  error.value = response.ok ? "" : "That email is already taken."
}
</script>

<template>
  <form @submit.prevent="save">
    <Field name="email" :invalid="error ? true : undefined">
      <FieldLabel>Email</FieldLabel>
      <Input type="email" required @value-change="error = ''" />
      <FieldError v-slot="{ state }">{{ error || state.message }}</FieldError>
    </Field>
    <button type="submit">Save</button>
  </form>
</template>

Without a handler, the form submits through its action URL.