Skip to content

Form ​

Wraps your fields and buttons in a <form> that handles the repeated parts: the required note, focus on a failed submit, a second submit while saving, and the spacing.

vue
<script setup lang="ts">
import { FsButton, FsField, FsForm, FsInput } from '@fivespark/ui'
import { ref } from 'vue'

const naam = ref('')

async function save() {
  await new Promise(resolve => setTimeout(resolve, 1000))
}
</script>

<template>
  <FsForm class="w-full max-w-md" @submit="save">
    <FsField label="Naam">
      <FsInput v-model="naam" />
    </FsField>
    <template #actions>
      <FsButton type="submit" variant="primary" label="Opslaan" />
    </template>
  </FsForm>
</template>

Usage ​

vue
<script setup lang="ts">
import { FsButton, FsField, FsForm, FsInput } from '@fivespark/ui'
import { ref } from 'vue'

const naam = ref('')

function save() {}
</script>

<template>
  <FsForm @submit="save">
    <FsField label="Naam"><FsInput v-model="naam" /></FsField>
    <template #actions>
      <FsButton type="submit" variant="primary" label="Opslaan" />
    </template>
  </FsForm>
</template>

@submit is the handler; the browser's own submit is cancelled while it is set. Form does not check values: you decide each Field's error. For a question with two answers, use a Dialog.

Examples ​

Required fields ​

required-note says once what the asterisk means. Add it to every form with a required field.

vue
<script setup lang="ts">
import { FsButton, FsField, FsForm, FsInput } from '@fivespark/ui'
import { ref } from 'vue'

const naam = ref('')
const telefoon = ref('')

function save() {}
</script>

<template>
  <FsForm required-note class="w-full max-w-md" @submit="save">
    <FsField label="Naam" required>
      <FsInput v-model="naam" />
    </FsField>
    <FsField label="Telefoonnummer">
      <FsInput v-model="telefoon" type="tel" />
    </FsField>
    <template #actions>
      <FsButton type="submit" variant="primary" label="Opslaan" />
    </template>
  </FsForm>
</template>

Validation ​

Set the errors in your handler. When a submit leaves a field with an error, focus moves to the first one, and its message is read out.

vue
<script setup lang="ts">
import { FsButton, FsField, FsForm, FsInput } from '@fivespark/ui'
import { reactive } from 'vue'

const values = reactive({ naam: '', email: '' })
const errors = reactive<Record<string, string | undefined>>({})

function save() {
  errors.naam = values.naam.trim() ? undefined : 'Vul een naam in'
  errors.email = values.email.includes('@') ? undefined : 'Vul een e-mailadres in, zoals naam@bedrijf.nl'
}
</script>

<template>
  <FsForm required-note class="w-full max-w-md" @submit="save">
    <FsField label="Naam" required :error="errors.naam">
      <FsInput v-model="values.naam" />
    </FsField>
    <FsField label="E-mailadres" required :error="errors.email">
      <FsInput v-model="values.email" type="email" />
    </FsField>
    <template #actions>
      <FsButton type="submit" variant="primary" label="Opslaan" />
    </template>
  </FsForm>
</template>

Saving ​

Return a promise from the handler. pending is true until it settles, and a second submit is ignored meanwhile. Pass it to the submit button's loading.

vue
<script setup lang="ts">
import { FsButton, FsField, FsForm, FsInput } from '@fivespark/ui'
import { ref } from 'vue'

const naam = ref('Jansen Bouw B.V.')

async function save() {
  await new Promise(resolve => setTimeout(resolve, 2000))
}
</script>

<template>
  <FsForm class="w-full max-w-md" @submit="save">
    <FsField label="Naam">
      <FsInput v-model="naam" />
    </FsField>
    <template #actions="{ pending }">
      <FsButton type="submit" variant="primary" :loading="pending" label="Opslaan" loading-text="Klant opslaan…" />
      <FsButton label="Annuleren" to="/klanten" />
    </template>
  </FsForm>
</template>

Without a handler ​

Without @submit, the browser submits the form to its action. Give each control a name.

vue
<script setup lang="ts">
import { FsButton, FsField, FsForm, FsInput } from '@fivespark/ui'
</script>

<template>
  <FsForm action="/zoeken" method="get" class="w-full max-w-md">
    <FsField label="Offertenummer">
      <FsInput name="nummer" />
    </FsField>
    <template #actions>
      <FsButton type="submit" variant="primary" label="Zoeken" />
    </template>
  </FsForm>
</template>

API Reference ​

FsForm ​

PropTypeDefaultDescription
requiredNotebooleanfalseShows "Velden met een * zijn verplicht." or "Fields marked * are required." first. Set it on a form with a required field.
onSubmit((event: SubmitEvent) => unknown)The submit handler, written `@submit`. The browser's submit is cancelled, a second submit is ignored while its promise is pending, and afterwards the first invalid field is focused. Without it, the browser submits the form to its `action`.
SlotPropsDescription
default{ pending: boolean; }The fields. `pending` is true while the submit handler's promise is.
actions{ pending: boolean; }The buttons, in a row after the fields: the submit button first, with `:loading="pending"`.