### VeeValidate and Zod

[VeeValidate](https://vee-validate.logaretm.com/v4/) offer a great set of tools
to validate your forms wich work perfectly with vuero.
We recommand you to use [zod](https://github.com/colinhacks/zod) to
declare schema for VeeValidate forms but you can also use [yup]()
if you already are familiar with it.

<!--code-->

```vue
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { useField, useFieldArray, useForm } from 'vee-validate'
import { z } from 'zod'
import VueScrollTo from 'vue-scrollto'

const notyf = useNotyf()
const { scrollTo } = VueScrollTo

// This is the Zod schema for the form input
// It's used to define the shape that the form data will have
const zodSchema = z
  .object({
    email: z
      .string({
        required_error: 'Enter your email first',
      })
      .email('A valid email address should be provided'),
    rating: z
      .number({
        required_error: 'Enter a valid rating first',
      })
      .gte(1, 'The rating should be at least 1'),
    password: z
      .string({
        required_error: 'Enter your password to sign in',
      })
      .min(8, 'Your password should contains at least 8 characters'),
    passwordCheck: z.string(),
    birthdate: z
      .date({
        invalid_type_error: 'Please enter a valid date',
        required_error: 'Please enter a date',
      })
      .max(new Date(), 'You cannot be born in the future')
      .nullable(),
    agreeTerms: z
      .boolean()
      .refine(value => value, 'You must agree our terms of service'),
    area: z.object({
      timezone: z.string().min(1, 'Please select a timezone'),
    }),
    interests: z
      .string()
      .array()
      .min(2, 'You must select at least 2 center of interest')
      .max(3, 'You can select up to 3 center of interest'),
    allergens: z.string().array().max(4, 'You can select up to 4 allergen'),
    feedback: z
      .array(
        z.object({
          title: z
            .string()
            .min(10, 'Your experience title should be at least 10 characters'),
          rating: z.number().gte(1, 'The rating should be at least 1'),
        }),
      )
      .min(1, 'You must send at least 1 feedback')
      .max(3, 'You can send up to 3 feedbacks'),
    emailOptin: z.boolean(),
  })
  .refine(data => data.password === data.passwordCheck, {
    message: 'The confirmation does not match the password',
    path: ['passwordCheck'],
  })

// Zod has a great infer method that will
// infer the shape of the schema into a TypeScript type
type FormInput = z.infer<typeof zodSchema>

// we need to declare the schema for the form
const validationSchema = toTypedSchema(zodSchema)

// Set initial values for the form
const initialValues = computed<FormInput>(() => ({
  email: '',
  password: '',
  rating: 1,
  passwordCheck: '',
  birthdate: null,
  interests: [],
  allergens: [],
  feedback: [],
  agreeTerms: false,
  emailOptin: false,
}))

// here we create a vee-validate form context that
// will be used in all vuero form components
const { handleSubmit, setFieldError, handleReset, values, errors } = useForm({
  validationSchema,
  initialValues,
})

const { remove, push, fields } = useFieldArray<FormInput['feedback'][0]>('feedback')
const { errorMessage } = useField<FormInput['feedback'][0]>('feedback')

const loading = ref(false)

// here we handle our form submission
const handleSignup = handleSubmit(async (values) => {
  if (loading.value) return

  loading.value = true

  await sleep(1600)

  if (values.email !== 'awesome@cssninja.io') {
    setFieldError('email', 'This email is already taken! Tip: use awesome@cssninja.io')
    scrollTo('#email')
    loading.value = false
    return
  }

  notyf.primary('You have successfully signed up!')
  loading.value = false
})
</script>

<template>
  <form
    method="post"
    novalidate
    @submit.prevent="handleSignup"
  >
    <VField
      id="email"
      v-slot="{ field }"
      label="Your email"
    >
      <VControl icon="lucide:user">
        <VInput
          type="email"
          placeholder="john.doe@gmail.com"
          autocomplete="username"
        />
        <p v-if="field?.errorMessage" class="help is-danger">
          {{ field.errorMessage }}
        </p>
      </VControl>
    </VField>
    <VField
      id="password"
      v-slot="{ field }"
      label="Choose a password"
    >
      <VControl icon="lucide:lock">
        <VInput
          type="password"
          placeholder="Not$3cret"
          autocomplete="new-password"
        />
        <p v-if="field?.errorMessage" class="help is-danger">
          {{ field.errorMessage }}
        </p>
      </VControl>
    </VField>
    <VField
      id="passwordCheck"
      v-slot="{ field }"
      label="Confirm your new password"
    >
      <VControl icon="lucide:check">
        <VInput
          type="password"
          placeholder="Not$3cret"
          autocomplete="new-password"
        />
        <p v-if="field?.errorMessage" class="help is-danger">
          {{ field.errorMessage }}
        </p>
      </VControl>
    </VField>
    <VField
      id="birthdate"
      v-slot="{ field }"
      label="Birthdate"
    >
      <VControl icon="lucide:calendar">
        <ClientOnly>
          <VDatePicker
            :model-value="field?.value"
            color="green"
            trim-weeks
            @update:model-value="field?.handleChange"
          >
            <template #default="{ inputValue, inputEvents }">
              <input
                class="input"
                type="text"
                :value="inputValue"
                placeholder="Select your birthdate"
                v-on="inputEvents"
              >
              <p v-if="field?.errorMessage" class="help is-danger">
                {{ field.errorMessage }}
              </p>
            </template>
          </VDatePicker>
        </ClientOnly>
      </VControl>
    </VField>
    <VField
      id="interests"
      v-slot="{ field }"
      class="pb-4"
      label="Choose 2 or 3 center of interests"
    >
      <VControl>
        <VSelect multiple size="9">
          <VOption value="Food">
            Food
          </VOption>
          <VOption value="Home Appliances">
            Home Appliances
          </VOption>
          <VOption value="Computer & Office">
            Computer & Office
          </VOption>
          <VOption value="Home Improvement">
            Home Improvement
          </VOption>
          <VOption value="Home & Garden">
            Home & Garden
          </VOption>
          <VOption value="Sports & Entertainment">
            Sports & Entertainment
          </VOption>
          <VOption value="Toys & Hobbies">
            Education & Office Supplies
          </VOption>
          <VOption value="Security & Protection">
            Security & Protection
          </VOption>
          <VOption value="Lights & Lighting">
            Lights & Lighting
          </VOption>
        </VSelect>
        <p v-if="field?.errorMessage" class="help is-danger">
          {{ field.errorMessage }}
        </p>
        <p class="help">
          Hold down the <kbd>Ctrl</kbd> (windows) / <kbd>Command</kbd> (Mac) button to
          select multiple options.
        </p>
      </VControl>
    </VField>
    <VField
      id="area"
      v-slot="{ field }"
      class="pb-4"
      label="Choose your timezone"
    >
      <VControl>
        <VSelect>
          <VOption :value="{ timezone: 'europe/paris', label: 'Paris' }">
            europe
          </VOption>
          <VOption :value="{ timezone: 'asia/tokyo', label: 'Tokyo' }">
            asia
          </VOption>
          <VOption :value="{ timezone: 'america/new_york', label: 'New York' }">
            america
          </VOption>
          <VOption :value="{ timezone: 'australia/sydney', label: 'Sydney' }">
            australia
          </VOption>
        </VSelect>
        <p v-if="field?.errorMessage" class="help is-danger">
          {{ field.errorMessage }}
        </p>
      </VControl>
    </VField>
    <VField
      id="allergens"
      v-slot="{ field }"
      label="Pick your allergens"
    >
      <VControl>
        <VCheckbox
          class="pl-0"
          color="primary"
          value="peanuts"
        >
          Peanuts
        </VCheckbox>
        <VCheckbox
          id="allergens-milk"
          color="primary"
          value="milk"
        >
          Milk
        </VCheckbox>
        <VCheckbox
          id="allergens-egg"
          color="primary"
          value="egg"
        >
          Egg
        </VCheckbox>
        <VCheckbox
          id="allergens-fish"
          color="primary"
          value="fish"
        >
          Fish
        </VCheckbox>
        <VCheckbox
          id="allergens-soybeans"
          color="primary"
          value="soybeans"
        >
          Soybeans
        </VCheckbox>
      </VControl>
      <p v-if="field?.errorMessage" class="help is-danger">
        {{ field.errorMessage }}
      </p>
    </VField>
    <div class="py-4">
      <!-- eslint-disable-next-line vue/require-v-for-key -->
      <div v-for="(element, index) in fields" class="my-3">
        <div class="columns">
          <VField
            :id="`feedback[${index}].title`"
            v-slot="{ field }"
            label="Name your experience"
            class="column is-two-fifths"
          >
            <VControl>
              <VInput
                type="email"
                placeholder="john.doe@gmail.com"
                autocomplete="username"
              />
              <p v-if="field?.errorMessage" class="help is-danger">
                {{ field.errorMessage }}
              </p>
            </VControl>
          </VField>
          <VField
            :id="`feedback[${index}].rating`"
            v-slot="{ field }"
            class="ml-4"
            label="Give a rating"
          >
            <VControl>
              <VRangeRating class="mt-5" size="medium" />
              <p v-if="field?.errorMessage" class="help is-danger">
                {{ field.errorMessage }}
              </p>
            </VControl>
          </VField>
          <VIconButton
            class="is-remove"
            :style="{}"
            light
            raised
            circle
            color="danger"
            icon="lucide:trash-2"
            @click="() => remove(index)"
          />
        </div>
      </div>
      <div class="mb-5">
        <VButton @click="() => push({ rating: 3, title: '' })">
          Add feedback
        </VButton>
        <p v-if="errorMessage" class="help is-danger">
          {{ errorMessage }}
        </p>
      </div>
    </div>
    <VField id="agreeTerms" v-slot="{ field }">
      <VControl>
        <VCheckbox paddingless>
          I agree to the <a href="#">terms and conditions</a>
        </VCheckbox>

        <p v-if="field?.errorMessage" class="help is-danger">
          {{ field.errorMessage }}
        </p>
      </VControl>
    </VField>
    <VField id="emailOptin">
      <VControl>
        <VCheckbox color="primary" paddingless>
          I want to receive exclusive news and updates
        </VCheckbox>
      </VControl>
    </VField>
    <VButtons class="pt-4">
      <VButton
        :loading="loading"
        type="submit"
        color="primary"
      >
        Submit
      </VButton>
      <VButton type="reset" @click="handleReset">
        Reset
      </VButton>
    </VButtons>
    <div class="demo-code-wrapper">
      <div class="demo-state">
        <pre>{{ values }}</pre>
      </div>
      <div class="demo-state">
        <pre>{{ errors }}</pre>
      </div>
    </div>
  </form>
</template>

<style lang="scss" scoped>
.is-remove {
  margin-inline-start: 1.5rem;
  margin-top: 2.25rem;
}

.demo-code-wrapper {
  display: flex;
  flex-direction: column-reverse;
  margin-top: 2rem;
  overflow-x: auto;

  .demo-code {
    flex-grow: 1;
  }

  .demo-state {
    // flex-grow: 1;
    position: relative;
    margin-bottom: 1.5rem;
    max-width: 100%;

    &::before {
      position: absolute;
      top: 0.6em;
      inset-inline-end: 1em;
      z-index: 2;
      font-size: 0.8rem;
      color: #888;
      content: 'values';
    }
  }
}

@media only screen and (width <= 767px) {
  .is-remove {
    margin-inline-start: 1rem;
    margin-top: 1em;
    margin-bottom: 2.25rem;
  }
}
</style>
```

<!--/code-->
