Beta, expect API change, and bugs. Hit one? Tell us.

Skip to content

Form

A validated form.

<script setup lang="ts">
import { Button } from '@shardsui/vue/button'
import { Field } from '@shardsui/vue/field'
import { Form, type FormErrors } from '@shardsui/vue/form'
import { shallowRef } from 'vue'

const errors = shallowRef<FormErrors>({})
const loading = shallowRef(false)

async function joinWaitlist(email: string): Promise<FormErrors> {
  await new Promise((resolve) => setTimeout(resolve, 1000))
  if (email.endsWith('@example.com')) {
    return { email: 'This email is already on the waitlist' }
  }
  return {}
}

async function onSubmit(event: SubmitEvent) {
  event.preventDefault()
  const email = new FormData(event.currentTarget as HTMLFormElement).get('email')
  loading.value = true
  errors.value = await joinWaitlist(String(email))
  loading.value = false
}
</script>

<template>
  <Form class="flex w-full max-w-64 flex-col gap-4" :errors="errors" @submit="onSubmit">
    <Field.Root name="email" class="flex flex-col items-start gap-1">
      <Field.Label class="text-sm font-semibold text-gray-900">Email</Field.Label>
      <Field.Control
        type="email"
        required
        placeholder="you@company.com"
        class="h-8 w-full rounded-md border border-gray-200 px-2 text-sm font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-gray-950 any-pointer-coarse:text-base"
      />
      <Field.Error class="text-sm text-red-800" />
    </Field.Root>
    <Button
      type="submit"
      :disabled="loading"
      class="font-inherit m-0 flex h-8 items-center justify-center gap-2 rounded-md border border-gray-200 bg-gray-50 px-3 text-sm/6 font-normal text-nowrap text-gray-900 outline-0 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-disabled:text-gray-500 hover:data-disabled:bg-gray-50"
    >
      Join waitlist
    </Button>
  </Form>
</template>

Anatomy

Pair Form with Field:

<script setup>
import { Form } from '@shardsui/vue/form'
import { Field } from '@shardsui/vue/field'
</script>

<template>
  <Form>
    <Field.Root>
      <Field.Label />
      <Field.Control />
      <Field.Error />
    </Field.Root>
  </Form>
</template>

Examples

Validating from code

A template ref on the component exposes validate() — call it with a field name to run just that one.

<script setup>
import { useTemplateRef } from 'vue'
import { Form } from '@shardsui/vue/form'

const form = useTemplateRef('form')
</script>

<template>
  <Form ref="form">
    <!-- fields -->
    <button type="button" @click="form?.validate('email')">Check email</button>
  </Form>
</template>

API reference

Renders a <form> element. Submitting validates every field first; if one fails, the native submit is cancelled and focus moves to the first invalid control, selecting its text when it is an <input>.

PropTypeDefault

errors is a one-way prop: the form copies it into its own state, and there is no update:errors emit to bind to.

A template ref on the component exposes:

MethodDescription
validate(fieldName?: string)Runs validation. With fieldName, only the field registered under that name. Without it, every field.

Form is generic over FormValues extends Record<string, unknown>, which defaults to Record<string, unknown>. Annotate the parameter of the formSubmit handler to type the object it receives.

Other standard <form> attributes (method, action, target, enctype) pass through.