Write A Plugin For Tailwind CSS

Sam Robbins
author
Sam Robbins
curved piece of hardware floating

Tailwind CSS uses the utility classes, which have a single purpose, such as a class flex, which would make display equal to flex. This approach means you rarely have to write your own CSS as there is a utility class to do what you need to do.

However, utility-first frameworks like Tailwind can’t include every CSS style due to size concerns, and that it would require a lot of work by the maintainers. This is most common for advanced CSS properties such as perspective which are much less likely to be used compared to the classes included in Tailwind. To solve this problem, Tailwind CSS includes a plugin system that allows you to define new classes.

What will we build

In this tutorial, we’ll build a Tailwind CSS plugin to add the CSS property column-count to our project. You can see the repository of the plugin we’ll build here and a Tailwind Play of how it works here.

The plugin is also published on npm; check it out!

This is the process that we will follow:

  1. Set up a Tailwind CSS project
  2. Understand what we want the plugin to do
  3. Hardcode the plugin
  4. Expand the functionality to work for any values
  5. Split the code out from the project
  6. Publish to npm

This is just an illustrative example, and using the knowledge from this you’ll be able to expand this to any other CSS property you want.

Prerequisites

This article assumes a basic knowledge of CSS and JavaScript, along with the knowledge of a framework that works with Tailwind CSS.

Step 0 — Set up a Tailwind CSS project

Tailwind CSS works with a wide variety of frameworks, such as Next.js, Laravel, and more. You can find out how to do this for your favorite framework here. Or if you just want to play around with this concept. Tailwind Play will get you a project set up with Tailwind in your browser. However, note that Steps 4 and 5 won’t work in Tailwind Play as it doesn’t allow you to separate into separate files.

Step 1 — Understand what we want the plugin to do

First, we need to understand what we want our plugin to do, to find out more about the column-count property you can read the MDN article.

Next is in understanding what classes we want, this structure will be familiar to you if you’ve used utility classes before, but if not, we’re wanting one that looks like this

.col-count-2 {
column-count: 2;
}

This makes it very clear how the class name relates to the applied CSS. Now in our project, we can add this to the global stylesheet, so it can be used everywhere.

Then create a div with this class applied, such as

<div class="col-count-2">Lorem impsum...</div>

Adding enough text in this div will split it into two columns, this is the behavior we want from our plugin.

Step 2 — Hardcode the plugin

During your setup of Tailwind CSS in your project, you should have created a file named tailwind.config.js, this is where we will start our work on the plugin. In case you haven’t done this, you can find the structure of the file here. First, we need to import the package which allows us to create plugins, add this at the top of the file:

const plugin = require('tailwindcss/plugin')

Under module.exports you should have a plugins key, if not, you can add it now. Inside here we want to add a call of the plugin function we just imported. So the file should look like this:

const plugin = require('tailwindcss/plugin')
module.exports = {
// other keys
plugins: [plugin()],
}

Inside the call to plugin, we want to pass it a function, this function doesn’t need a name, so can just be passed as

function(){
}

However, we do want this function to take some parameters, as they are provided by the plugin function. The one we’re going to use first is addUtilities, which is a function to add utility classes. So that changes the function to:

function({addUtilities}){
}

The use of the curly brackets inside the function is a method called destructuring, this allows us to specify which of the parameters we want to use and their name. This would be the same as doing function(props) and then calling props.addUtilities whenever we want to use the function.

Now we want to write the CSS classes we want to be implemented, this is done using the CSS-in-JS syntax, you can find a conversion table here, but it generally boils down to converting from kebab case to camel case.

Kebab case uses all lower case and replaces spaces with dashes, such as column-count. Camel case has the first-word lowercase and the remaining words uppercase with no symbol to show a space.

So to get the same CSS as we have previously declared we will create a variable

const myStyles = {
'.col-count-2': {
columnCount: 2,
},
}

We can then pass this variable into the addUtilities function we have access to

addUtilities(myStyles)

Now you can delete the global style, and you should still get the same behavior

Step 3 — Expand the functionality to work for any values

So far we haven’t made anything more convenient, as the global CSS has the same effect as the plugin, however, because the plugin is defined using JavaScript, it allows us to have more control over the output.

First, we want to provide some defaults for our plugin, for this example, we’ll have 2, 3, and 4. To add these, we need to pass a second parameter to the plugin function we have called. The first parameter is the function we have already created with addUtilities passed in as props, so after this function, put a comma, then pass in the object

{
theme: {
colCount: {
2: "2",
3: "3",
4: "4",
},
},
}

This is the same as the theme key in the main Tailwind config but allows you to include values with your plugin. The benefit of doing this is that users can then use extend in their theme configuration to add different values.

Now to get these values in our main function, we need to take in theme alongside addUtilities and then do

const values = theme('colCount')

Without extra user configuration, this will return the object:

{
2: "2",
3: "3",
4: "4",
}

So now we want to turn these values into an object that the addUtilities function will understand

As this is an object rather than a list, we can’t use a map straight away, instead, we can use Object.entries to get the entries from an object like this

const utilities = Object.entries(values).map(([key, value]) => {})

This will provide the key and value as two props to a function, so inside the curly brackets we can return whatever we want each entry to be

When constructing our classes, we need one more function from Tailwind, e, this escapes whatever string is passed to it. Say for example if instead of listing the number of columns, a user wanted to represent how the page was divided, so 1/2, 1/3, etc, the / causes an issue with class names as it is, so it needs escaping to be 1\/2. So alongside addUtilities and theme also include e in the props.

Before we write it out in code, to be clear what we want to produce, each of the objects in the list should look like

{ '.col-count-2': { columnCount: '2' } }

So to pass in our parameters:

return {
[`.${e(`col-count-${key}`)}`]: {columnCount: `${value}`},
}

On the left we have a template string, first with the dot for a class name, so that it isn’t escaped, then the escape function is called on another template string which includes the key at the end. The right is simpler in that is just an object with the columnCount CSS-in-JS syntax we discussed before relating to a template string of value so that it gets represented as a string, rather than the actual value

Putting that all together, we get this constant

const utilities = Object.entries(values).map(([key, value]) => {
return {
[`.${e(`col-count-${key}`)}`]: {columnCount: `${value}`},
}
})

Step 4 — Split the code out from the project

Now we want to split the code we have written out from our project so that it can be reused between projects. The first step in doing this is to move the code into a separate file, to do this first create a JavaScript file of whatever name you want, for my example I’ll use plugin.js. First, we need to copy over the declaration to require tailwindcss/plugin then we can set a variable, in my case colCount to the whole plugin function. Then at the bottom export it with

module.exports = colCount

Making the whole file look like this

const plugin = require('tailwindcss/plugin')
const colCount = plugin(
function ({addUtilities, theme, e}) {
const values = theme('colCount')
var utilities = Object.entries(values).map(([key, value]) => {
return {
[`.${e(`col-count-${key}`)}`]: {columnCount: `${value}`},
}
})
addUtilities(utilities)
},
{
theme: {
colCount: {
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
},
},
},
)
module.exports = colCount

Now replacing all the information within the plugins key in tailwind.config.js, you can just have require("./plugin") and you will get exactly the same behavior.

Step 5 - Publish to npm

Next, you have the option of publishing this to npm, this is the easiest way to share the code between your projects and ensure updates are installed in all projects, and also allow for others to use your code.

Firstly you will need a npm account, which you can sign up for here. On the command line, you can then run npm login to ensure it has access to your account.

To create the package, create an empty folder and run npm init, for the package name, set it to @username/tailwind-col-count replacing username with your npm username. This creates a scoped package, as package names have to be unique, so for test packages like this, it’s better to have them scoped, so it doesn’t use up a package name for someone else to use. The rest of the fields aren’t crucial for this project, but you will note that the main script file is index.js, so this is the file in which we will have our plugin.

Now we can copy all the code from plugin.js in our project over to a new file index.js in the new project. We can now test this locally by running the following command in our plugin project

npm install -D tailwindcss@latest

This is needed to ensure that the import of tailwindcss/plugin works

Then in the plugin folder run (may need sudo)

npm link

and in the project folder run

npm link @username/tailwind-col-count

And replace the require statement to

require("@username/tailwind-col-count")

and running the project it should still have the same behavior

Now we’re finally ready to publish this package to npm! Just run

npm publish --access public

and it will be published, and you can install it using npm install in any of your projects

Summary

Creating this plugin introduced the following key points in creating a Tailwind CSS Plugin

  • Adding more utility classes is done with the addUtilities function, to which you pass an object describing what utilities to add
  • To define CSS properties in a plugin, the CSS-in-JS syntax must be used
  • Default values for the plugin are provided using the theme key in the plugin, as this allows the user to further expand their configuration
  • User-provided text for class names should be escaped to ensure they work correctly
  • The plugin can be separated from the tailwind.config.js file to copy between projects or publish on npm