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
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);
});
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));
// ...
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 }),
};
},
// ...
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",
},
];
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);
useRouter().push(redirect);
} catch (error) {
// Remove status code and path from error message
const message = (error as Error).message.replace(/\d+ (.*) .*/, "$1");
formErrors.value = [message];
}
}
</script>
<template>
<FormKit type="form" :errors="formErrors" submit-label="Login" @submit="handleSubmit">
<FormKitSchema :schema="loginFormSchema" />
</FormKit>
</template>
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).
Conclusion
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)
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 asformkit-invalid:
.formkit.com/guides/create-a-tailwi...
This is absolutely amazing!
I'll update the article series when I have some spare time and I'll definitely include this package.