Creating a Tailwind CSS theme for FormKit
Tags: tailwindcss, vue, javascript, webdev
Published: 2022-03-10 20:30:16

---
title: Creating a Tailwind CSS theme for FormKit
published: true
description: A step-by-step guide to creating a Tailwind CSS theme for your FormKit forms.
tags: tailwindcss, vue, javascript, webdev
cover_image: https://cdn.formk.it/og/dev.to/creating-a-tailwind-css-theme.png
---

## FormKit ❤️ Tailwind CSS

[FormKit](https://formkit.com) ships with first-class support for [Tailwind CSS](https://tailwindcss.com). 

For small projects – using Tailwind CSS with FormKit can be as easy as using the [inline class props](https://formkit.com/essentials/styling#section-key-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 <code>.vue</code> 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](https://formkit.com/essentials/styling#section-key-class-props) or the [`classes` prop](https://formkit.com/essentials/styling#classes-prop) available on the `<FormKit />` component.

Here's a contact form styled using only the `classes` prop for a FormKit form:

```html
<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>
```

{% embed https://stackblitz.com/edit/formkit-inline-tailwind-example?ctl=1&embed=1&file=src/components/ContactForm.vue&hideExplorer=1&hideNavigation=1&view=preview %}

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.

```bash
npm install @formkit/tailwindcss
```

From there we need to:

- Add the `@formkit/tailwindcss` plugin to our project's `tailwind.config.js` file.
- Import `generateClasses` from `@formkit/tailwindcss` and use it where we define our FormKit config options.

```js
// tailwind.config.js
module.exports {
  ...
  plugins: [
    require('@formkit/tailwindcss').default
  ]
  ...
}
```

```js
// 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:

```js
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');
```

{% embed https://stackblitz.com/edit/formkit-tailwind-global-config-input?ctl=1&embed=1&file=src/main.js&hideExplorer=1&hideNavigation=1&view=preview %}

## 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.

```js
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',
  },
};
```

{% embed https://stackblitz.com/edit/formkit-tailwind-global-config-input-n53j76?ctl=1&embed=1&file=src/theme.js&hideExplorer=1&hideNavigation=1&view=preview %}

## 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.

```js
// 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,
}
```

{% embed https://stackblitz.com/edit/formkit-tailwind-global-config-theme?ctl=1&embed=1&hideExplorer=1&hideNavigation=1&file=src/theme.js&view=preview %}

## 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](/essentials/styling#section-key-class-props) or the [classes](/essentials/styling#classes-prop) prop on a given `FormKit` element.

Of particular importance when performing an override is the [`$reset` modifier](/essentials/styling#resetting-classes). 

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.

```html
<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>
```

{% embed https://stackblitz.com/edit/formkit-tailwind-global-config-overrides?ctl=1&embed=1&file=src/components/ExampleInput.vue&hideExplorer=1&hideNavigation=1&view=preview %}

## 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](/advanced/core) of the framework as well as [the FormKit schema](/advanced/schema) which allows generating forms from JSON with conditionals, expressions, and more!

Now go forth and make beautiful forms!