DEV Community

Pascal Martineau
Pascal Martineau

Posted on

Form generation and validation with FormKit

Since our project is already on the bleeding edge, we might just as well use a very exciting form library that just came out of private beta: FormKit.

Setting up FormKit in Nuxt3

At the time of writing this, the official @formkit/nuxt module offers little more than passing the options from an external file to the library and configuring transpilation. Also, since the FormKitSchema component isn't imported by default, we'll setup the library ourselves in a plugin.

yarn add -D @formkit/vue
Enter fullscreen mode Exit fullscreen mode

The library needs to be transpiled to work with ES modules, so in nuxt.config.ts we add @formkit/vue to the build.transpile array.

We can now create our plugin in plugins/formkit.ts:

import { plugin, defaultConfig, FormKitSchema } from "@formkit/vue";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.component("FormKitSchema", FormKitSchema);
  nuxtApp.vueApp.use(plugin, defaultConfig);
Enter fullscreen mode Exit fullscreen mode

Configuring form classes with Tailwind CSS

Let's add some styles to our form sections by customizing the default plugin options:

// ...
import type { DefaultConfigOptions } from "@formkit/vue";

const options: DefaultConfigOptions = {
  config: {
    classes: {
      outer: "mb-3",
      label: "font-bold",
      help: "text-sm text-slate-400",
      messages: "mb-3 text-sm text-red-800",

// ...
  nuxtApp.vueApp.use(plugin, defaultConfig(options));
// ...
Enter fullscreen mode Exit fullscreen mode

Adding the btn class to the submit input is a little more tricky, as it requires hooking into rootClasses:

// ...
    rootClasses(sectionKey, node) {
      return {
        [`formkit-${sectionKey}`]: true,
        ...(sectionKey === "input" && node.props.type === "submit" && { btn: true }),
// ...
Enter fullscreen mode Exit fullscreen mode

Refactoring the login form with FormKit

Our login form's structure (fields types, names, labels and validation) can be abstracted away inside useAuth by adding a FormKit schema property which we'll name loginFormSchema:

// FormKit schema for the login form
const loginFormSchema = [
    $formkit: "text",
    name: "email",
    label: "Email",
    validation: "required|email",
    $formkit: "password",
    name: "password",
    label: "Password",
    validation: "required",
Enter fullscreen mode Exit fullscreen mode

Our FormLogin component can now be rewritten like so:

<script setup lang="ts">
const { login, loginFormSchema } = useAuth();
const formErrors = ref<string[]>([]);

async function handleSubmit(credentials: any) {
  const redirect = (useRoute().query as { redirect: string }).redirect || "/";
  try {
    await login(credentials as LoginFormData);
  } catch (error) {
    // Remove status code and path from error message
    const message = (error as Error).message.replace(/\d+ (.*) .*/, "$1");
    formErrors.value = [message];

  <FormKit type="form" :errors="formErrors" submit-label="Login" @submit="handleSubmit">
    <FormKitSchema :schema="loginFormSchema" />
Enter fullscreen mode Exit fullscreen mode

In the code above, we bind formErrors in order to handle server errors in addition to validation warnings. To make the error human-readable, we extract the meaningful part with a regex before adding it to our form.

Also, we have to cast credentials as LoginFormData because the typings are not available at build time (i.e. FormKit being very dynamic, the structure could be altered at runtime).


This concludes the first public version of this series, which I hope you have found both enjoyable and helpful. I plan on keeping it up to date as the various libraries used evolve.

Top comments (2)

andrewboyd profile image
Andrew Boyd

Hey @lewebsimple — co-maintainer of FormKit here — Wanted to let you know there's a new @formkit/tailwindcss package that makes creating a Tailwind theme a breeze and adds support for Tailwind CSS variants that reflect FormKit input state such as formkit-invalid:.

lewebsimple profile image
Pascal Martineau

This is absolutely amazing!

I'll update the article series when I have some spare time and I'll definitely include this package.