Write A Plugin For Tailwind CSS
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:
- Set up a Tailwind CSS project
- Understand what we want the plugin to do
- Hardcode the plugin
- Expand the functionality to work for any values
- Split the code out from the project
- 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
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
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:
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:
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
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:
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 callingprops.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
We can then pass this variable into the addUtilities
function we have access to
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
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
Without extra user configuration, this will return the object:
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
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
So to pass in our parameters:
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
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
Making the whole file look like this
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
This is needed to ensure that the import of tailwindcss/plugin
works
Then in the plugin folder run (may need sudo)
and in the project folder run
And replace the require statement to
and running the project it should still have the same behavior
Now we’re finally ready to publish this package to npm! Just run
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