Como Validar Formulários com Vee-Validate no Nuxt 3 (sem usar Yup ou Zod)

Neste tutorial, vou te mostrar como integrar o vee-validate no Nuxt 3 para validar formulários de forma simples, sem depender do Yup ou Zod. O foco será em uma abordagem prática, usando regras de validação declarativas ("required", "numeric", etc.).

Além disso, usamos componentes do PrimeVue como InputText, Select, Button, Message e Toast para criar a interface do formulário com um visual profissional e moderno.

🔧 Pré-requisitos

Antes de começar, certifique-se de que você já tem um projeto Nuxt 3 com:

  • vee-validate
  • @vee-validate/rules
  • @vee-validate/i18n
  • primevue e primeicons
  • tailwindcss (opcional, mas usado nos estilos deste exemplo)

📦 1. Instalação

yarn add vee-validate @vee-validate/rules @vee-validate/i18n

⚙️ 2. Criando o Plugin veeValidate.ts

// plugins/veeValidate.ts
import { defineRule, configure } from 'vee-validate'
import { required, email, numeric, image } from '@vee-validate/rules'
import { localize, setLocale } from '@vee-validate/i18n'
import ptBR from '@vee-validate/i18n/dist/locale/pt_BR.json'

export default defineNuxtPlugin(() => {
  defineRule('required', required)
  defineRule('email', email)
  defineRule('numeric', numeric)
  defineRule('image', image)

  configure({
    generateMessage: localize({ pt_BR: ptBR }),
    validateOnInput: false,
    validateOnBlur: false,
    validateOnModelUpdate: false,
  })

  setLocale('pt_BR')
})

🧩 3. Configurando o Formulário com useForm + defineField

const { defineField, validate, errors } = useForm({
  validationSchema: {
    label: 'required',
    url: 'required',
    tipo: 'required',
    ordem: 'required|numeric',
  },
})

const form = ref({
  label: defineField('label', { label: 'Label' })[0],
  url: defineField('url', { label: 'Url' })[0],
  tipo: defineField('tipo', { label: 'Tipo' })[0],
  ordem: defineField('ordem', { label: 'Ordem' })[0],
})

🧪 4. Validando e Submetendo o Formulário

const submitForm = async () => {
  const validateForm = await validate()
  if (!validateForm.valid) return
  // enviar dados para API ou salvar
}

💬 5. Exibindo Mensagens de Erro

No template, você pode exibir erros assim:

<Message v-if="errors.label" severity="error">{{ errors.label }}</Message>

<template>
  <div class="p-6 max-w-3xl mx-auto">
    <Toast />
    <LoadingComponent v-if="loading" />
    <h2 class="text-2xl font-bold mb-4">Novo Registro</h2>
    <form class="space-y-4" @submit.prevent="submitForm">
      <div>
        <label class="block font-medium">Label</label>
        <InputText
          v-model="form.label"
          placeholder="Nome do Registro"
          class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary"
        />
        <Message
          v-if="errors.label"
          size="small"
          severity="error"
          variant="simple"
          >{{ errors.label }}</Message
        >
      </div>

      <div>
        <label class="block font-medium">URL</label>
        <InputText
          v-model="form.url"
          placeholder="/url-de-acesso"
          class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary"
        />
        <Message
          v-if="errors.url"
          size="small"
          severity="error"
          variant="simple"
          >{{ errors.url }}</Message
        >
      </div>

      <div>
        <label class="block font-medium">Tipo</label>
        <Select
          v-model="form.tipo"
          :options="tipo"
          option-label="name"
          option-value="value"
          placeholder="Selecione um Tipo"
          class="w-full px-4 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary"
        />
        <Message
          v-if="errors.tipo"
          size="small"
          severity="error"
          variant="simple"
          >{{ errors.tipo }}</Message
        >
      </div>

      <div>
        <label class="block font-medium">Ordem</label>
        <InputText
          v-model="form.ordem"
          placeholder="De um numero de ordenação exp: 1"
          type="number"
          class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary"
        />
        <Message
          v-if="errors.ordem"
          size="small"
          severity="error"
          variant="simple"
          >{{ errors.ordem }}</Message
        >
      </div>

      <Button
        class="bg-primary hover:opacity-90 transition border-none"
        label="Salvar Registro"
        type="submit"
      />
    </form>
  </div>
</template>

🖱️ 6. Quando tentar submeter o formulário será validado e mostrará as mensagens de acordo com as regras definidas

Resultado:

Você vai ter um formulário com:

  • Regras de validação simples e legíveis
  • Mensagens localizadas em português
  • Sem dependências extras
  • Total controle sobre o comportamento de validação

nuxt3 vee-validate validação primevue formulário vuejs javascript frontend