FormKit ❤️ Tailwind CSS
FormKit ships with first-class support for Tailwind CSS.
For small projects – using Tailwind CSS with FormKit can be as easy as using the inline class props available on the <FormKit />
component. For more advanced use cases FormKit ships with a package (@formkit/tailwindcss
) that makes creating a robust Tailwind CSS theme a breeze.
The following guide will walk you through both processes and show you how they can work together to provide incredible flexibility when styling your FormKit inputs.
This guide assumes you are using a standard Vue 3 build tool like Vite, Nuxt 3, or Vue CLI that will allow you to import
.vue
single file components.
Inline usage for simple use cases
If FormKit represents a small portion of your project — say a single contact form on a brochure website — then you'll likely be able to apply all the styling you need using the ${sectionKey}-class
props or the classes
prop available on the <FormKit />
component.
Here's a contact form styled using only the classes
prop for a FormKit form:
<template>
<FormKit
type="form"
:actions="false"
:config="{
// config override applies to all nested FormKit components
classes: {
outer: 'mb-5',
label: 'block mb-1 font-bold text-sm',
input: 'text-gray-800 mb-1 w-full',
help: 'text-xs text-gray-500',
message: 'text-red-500 text-xs',
},
}"
>
<FormKit
type="text"
label="Name"
help="First and last name"
validation="required"
:classes="{
input: 'border border-gray-400 py-1 px-2 rounded-md',
}"
/>
<FormKit
type="email"
label="Email"
validation="required|email"
:classes="{
input: 'border border-gray-400 py-1 px-2 rounded-md',
}"
/>
<FormKit
type="textarea"
label="Message"
validation="required|length:10"
:classes="{
input: 'border border-gray-400 py-1 px-2 rounded-md',
}"
/>
<FormKit
type="submit"
label="Submit"
:classes="{
outer: 'mb-0',
input: 'bg-blue-500 text-white font-bold py-2 px-3 rounded-md w-auto',
}"
/>
</FormKit>
</template>
This is a low-barrier way to apply Tailwind CSS styles to your FormKit forms. But what if you have multiple forms? Copy-pasting class lists between components is not ideal and can lead to inadvertent variations in styling across your project over time.
Let's explore how we can apply Tailwind CSS classes globally to all FormKit inputs within our project.
Using @formkit/tailwindcss
FormKit ships with a first-party package called @formkit/tailwindcss
that makes it simple to create a Tailwind CSS theme for FormKit.
This package allows you to author your theme as a JavaScript object grouped by input type
and sectionKey
. Additionally, it exposes a number of Tailwind CSS variants based on FormKit state such as formkit-invalid:
and formkit-disabled:
which allow you to dynamically change your input styling.
To get started we first need to add the package to our project.
npm install @formkit/tailwindcss
From there we need to:
- Add the
@formkit/tailwindcss
plugin to our project'stailwind.config.js
file. - Import
generateClasses
from@formkit/tailwindcss
and use it where we define our FormKit config options.
// tailwind.config.js
module.exports {
...
plugins: [
require('@formkit/tailwindcss').default
]
...
}
// app.js
import { createApp } from 'vue'
import App from './App.vue'
import { plugin, defaultConfig } from '@formkit/vue'
import { generateClasses } from '@formkit/tailwindcss'
import '../dist/index.css' // wherever your Tailwind styles exist
createApp(App)
.use(
plugin,
defaultConfig({
config: {
classes: generateClasses({
// our theme will go here.
// ...
// text: {
// label: 'font-bold text-gray-300',
// ...
// }
// ...
}),
},
})
)
.mount('#app')
Once this setup is complete we are ready to begin writing our Tailwind CSS theme!
Our first Tailwind CSS input
To start, let's apply some classes to a text
style input. This will cover a large surface area because we'll be able to easily re-use these styles on other text-like inputs such as email
, password
, date
, etc.
To specifically target text
inputs we'll create a text
key in our theme object and then apply classes to each sectionKey
as needed.
Here is a text
input with Tailwind CSS classes applied using our default FormKit config values:
import { createApp } from 'vue';
import App from './App.vue';
import { plugin, defaultConfig } from '@formkit/vue';
import { generateClasses } from '@formkit/tailwindcss';
createApp(App)
.use(
plugin,
defaultConfig({
config: {
classes: generateClasses({
text: {
outer: 'mb-5',
label: 'block mb-1 font-bold text-sm',
inner: 'bg-white max-w-md border border-gray-400 rounded-lg mb-1 overflow-hidden focus-within:border-blue-500',
input: 'w-full h-10 px-3 bg-transparent border-none focus:outline-none text-base text-gray-700 placeholder-gray-400 focus:outline-none',
help: 'text-xs text-gray-500',
messages: 'list-none p-0 mt-1 mb-0',
message: 'text-red-500 mb-1 text-xs',
},
}),
},
})
)
.mount('#app');
Using variants
That's looking good! But it's fairly static at the moment. It would be nice if we could react with different styles based on the state of our inputs.
The @formkit/tailwindcss
package provides a number of variants you can use in your class lists to dynamically respond to input and form state.
The currently shipped variants are:
formkit-disabled:
formkit-invalid:
formkit-errors:
formkit-complete:
formkit-loading:
formkit-submitted:
formkit-multiple:
formkit-action:
formkit-message-validation:
formkit-message-error:
You can use these variant the same way you would use built-in Tailwind CSS variants such as dark:
and hover:
.
Let's add some variants for formkit-invalid
and formkit-disabled
to our text input styles.
export default {
text: {
outer: 'mb-5 formkit-disabled:opacity-40',
label: 'block mb-1 font-bold text-sm formkit-invalid:text-red-500',
inner: `
max-w-md
border border-gray-400
rounded-lg
mb-1
overflow-hidden
focus-within:border-blue-500
formkit-invalid:border-red-500
`,
input: 'w-full h-10 px-3 border-none text-base text-gray-700 placeholder-gray-400 focus:outline-none',
help: 'text-xs text-gray-500',
messages: 'list-none p-0 mt-1 mb-0',
message: 'text-red-500 mb-1 text-xs',
},
};
Creating a full theme
Now we're cooking! To create a comprehensive theme we need to define class lists for the sectionKeys
of all of the other input types we'll use in our project.
Before we go too far though, there are some improvements we can make.
The generateClasses
function in @formkit/tailwindcss
allows for a special input type key called global
that will apply to all inputs. This is helpful for targeting sectionKeys
such as help
and messages
that are often styled identically across all input types within a project.
Let's create class list definitions for all input types included in FormKit. We'll group common types of inputs into "classifications" to avoid being too repetitive.
// We'll create some re-useable definitions
// because many input types are identical
// in how we want to style them.
const textClassification = {
label: 'block mb-1 font-bold text-sm formkit-invalid:text-red-500',
inner: 'max-w-md border border-gray-400 formkit-invalid:border-red-500 rounded-lg mb-1 overflow-hidden focus-within:border-blue-500',
input: 'w-full h-10 px-3 border-none text-base text-gray-700 placeholder-gray-400',
}
const boxClassification = {
fieldset: 'max-w-md border border-gray-400 rounded-md px-2 pb-1',
legend: 'font-bold text-sm',
wrapper: 'flex items-center mb-1 cursor-pointer',
help: 'mb-2',
input: 'form-check-input appearance-none h-5 w-5 mr-2 border border-gray-500 rounded-sm bg-white checked:bg-blue-500 focus:outline-none focus:ring-0 transition duration-200',
label: 'text-sm text-gray-700 mt-1'
}
const buttonClassification = {
wrapper: 'mb-1',
input: 'bg-blue-500 hover:bg-blue-700 text-white text-sm font-normal py-3 px-5 rounded'
}
// We'll export our definitions using our above
// classification templates and declare
// one-offs and overrides as needed.
export default {
// the global key will apply to _all_ inputs
global: {
outer: 'mb-5 formkit-disabled:opacity-50',
help: 'text-xs text-gray-500',
messages: 'list-none p-0 mt-1 mb-0',
message: 'text-red-500 mb-1 text-xs'
},
button: buttonClassification,
color: {
label: 'block mb-1 font-bold text-sm',
input: 'w-16 h-8 appearance-none cursor-pointer border border-gray-300 rounded-md mb-2 p-1'
},
date: textClassification,
'datetime-local': textClassification,
checkbox: boxClassification,
email: textClassification,
file: {
label: 'block mb-1 font-bold text-sm',
inner: 'max-w-md cursor-pointer',
input: 'text-gray-600 text-sm mb-1 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:bg-blue-500 file:text-white hover:file:bg-blue-600',
noFiles: 'block text-gray-800 text-sm mb-1',
fileItem: 'block flex text-gray-800 text-sm mb-1',
removeFiles: 'ml-auto text-blue-500 text-sm'
},
month: textClassification,
number: textClassification,
password: textClassification,
radio: {
// if we want to override a given sectionKey
// from a classification we can do a spread
// of the default value along with a new
// definition for our target sectionKey.
...boxClassification,
input: boxClassification.input.replace('rounded-sm', 'rounded-full'),
},
range: {
inner: 'max-w-md',
input: 'form-range appearance-none w-full h-2 p-0 bg-gray-200 rounded-full focus:outline-none focus:ring-0 focus:shadow-none'
},
search: textClassification,
select: textClassification,
submit: buttonClassification,
tel: textClassification,
text: textClassification,
textarea: {
...textClassification,
input: 'block w-full h-32 px-3 border-none text-base text-gray-700 placeholder-gray-400 focus:shadow-outline',
},
time: textClassification,
url: textClassification,
week: textClassification,
}
Selective overrides
And there we have it! All FormKit inputs are now styled with Tailwind CSS classes across our entire project.
If we ever need to override any specific one-offs within our project, we can do so using the section-key class props or the classes prop on a given FormKit
element.
Of particular importance when performing an override is the $reset
modifier.
When the FormKit class system encounters a class named $reset
it will discard the current class list for the given sectionKey and only collect class names that occur after the $reset
class. This is helpful for systems like Tailwind CSS where it can be cumbersome to override a large number of classes when you need to deviate from your base theme.
<template>
<FormKit
type="text"
label="I use the global theme we defined"
help="I play by the rules"
/>
<FormKit
type="text"
label="I'm special and have a $reset and custom styles"
help="I'm a rebel"
label-class="$reset italic text-lg text-red-500"
help-class="$reset font-bold text-md text-purple-800"
/>
</template>
Next steps
This guide has walked through creating a Tailwind CSS theme for all input types included in FormKit, but there is still more that could be done!
Here are some ways to take the above guide even further:
- Add dark-mode support using the built-in Tailwind CSS
dark:
modifier. - Combine multiple variants such as
formkit-invalid:formkit-submitted:
to add extra emphasis to invalid fields when a user tries to submit an incomplete form. - Publish your theme as an
npm
package for easy importing and sharing between projects.
If you want to dive in deeper into FormKit there's plenty to learn about the core internals of the framework as well as the FormKit schema which allows generating forms from JSON with conditionals, expressions, and more!
Now go forth and make beautiful forms!
Top comments (0)