Last week, I covered a few of my top takeaways from using Tailwind CSS in some real projects. I discussed a lot of the positive experiences I had, but I also brought up one of my frustrations with the framework. Namely, the lack of percentage-based height utilities.
Fortunately for us, Tailwind recently released a plugin system so we can create packages for any functionality that we need! So let's dive in and see how to create a percentage-based height plugin.
The Set Up
As usual, we'll be starting with a fresh Laravel project with Tailwind installed. For a detailed walkthrough on setting that up, you can check out my previous post. One caveat is that now we should add @tailwind components
to our app.sass
file in between our two other @tailwind
directives. With everything all set up, we can check out our Tailwind config file and start writing our plugin.
Coding Our Plugin
At the bottom of our Tailwind config file, we'll notice that there's a new Array for plugins
that looks like this:
plugins: [
require('tailwindcss/plugins/container')({
// center: true,
// padding: '1rem',
}),
]
Out-of-the-box, the Tailwind team has given us an example plugin to get us started. While they're importing their container plugin from their package, we can begin writing our plugin by adding a function to the Array.
plugins: [
require('tailwindcss/plugins/container')({
// center: true,
// padding: '1rem',
}),
function() {
}
]
Anytime we want to add a new plugin; we can just add a new function. Now, according to the docs, plugin functions can receive a destructured Object containing 5 helper functions.
These helper functions are addUtilities()
, addComponents()
, e()
, prefix()
, and config()
. addUtlities()
lets us register new utility classes. addComponents
is just like addUtilities
, but instead of classes we're registering more complex component classes.
e()
helps us create class names by escaping strings. prefix()
lets us manually apply the user-defined Tailwind prefix to our generated classes. And finally, config()
lets us access the user's Tailwind configuration so we can reference their options.
All of these helpers are super useful, but I bet that we'll only have to worry about using addUtilities()
, addComponents()
, and e()
for the majority of our plugins.
For our percentage height plugin, we'll only be using e()
and addUtilities()
. So, let's add that destructured Object to our function.
plugins: [
require('tailwindcss/plugins/container')({
// center: true,
// padding: '1rem',
}),
function({e, addUtilities}) {
}
]
Great! Before we write our next batch of code, let's take a moment to think about what we're trying to do. We are trying to generate a bunch of height classes that follow the existing Tailwind format (.h-1/2
) and use the CSS-in-JS syntax to assign their percentage height
values. Then, we'll need to group them all into an Array and pass them through to the addUtilites
helper function to register them with Tailwind.
I think the easiest place to start would be an Array of Objects, where the Object's properties hold the class name and the height value. So, let's add that to our function.
plugins: [
require('tailwindcss/plugins/container')({
// center: true,
// padding: '1rem',
}),
function({e, addUtilities}) {
let percents = [
{
key: '1/2',
value: '50%'
},
{
key: '1/3',
value: '33.33333%'
},
{
key: '2/3',
value: '66.66667%'
},
{
key: '1/4',
value: '25%'
},
{
key: '3/4',
value: '75%'
},
{
key: '1/5',
value: '20%'
},
{
key: '2/5',
value: '40%'
},
{
key: '3/5',
value: '60%'
},
{
key: '4/5',
value: '80%'
},
{
key: '1/6',
value: '16.66667%'
},
{
key: '5/6',
value: '83.33333%'
},
{
key: '1/10',
value: '10%'
},
{
key: '3/10',
value: '30%'
},
{
key: '7/10',
value: '70%'
},
{
key: '9/10',
value: '90%'
},
{
key: 'full',
value: '100%'
},
];
}
]
Alright, now we're cooking. With our key
value
data ready, it'll be pretty easy to use JavaScript's native .map()
functionality to iterate over all of these Objects and generate the Array we need.
plugins: [
require('tailwindcss/plugins/container')({
// center: true,
// padding: '1rem',
}),
function({e, addUtilities}) {
let percents = [
{
key: '1/2',
value: '50%'
},
{
key: '1/3',
value: '33.33333%'
},
{
key: '2/3',
value: '66.66667%'
},
{
key: '1/4',
value: '25%'
},
{
key: '3/4',
value: '75%'
},
{
key: '1/5',
value: '20%'
},
{
key: '2/5',
value: '40%'
},
{
key: '3/5',
value: '60%'
},
{
key: '4/5',
value: '80%'
},
{
key: '1/6',
value: '16.66667%'
},
{
key: '5/6',
value: '83.33333%'
},
{
key: '1/10',
value: '10%'
},
{
key: '3/10',
value: '30%'
},
{
key: '7/10',
value: '70%'
},
{
key: '9/10',
value: '90%'
},
{
key: 'full',
value: '100%'
},
];
const heightUtilities = percents.map(item => {
return {
[`.h-${e(item.key)}`]: {
height: item.value
}
}
});
}
]
Rock on! All we're doing here is mapping our percents
variable to a new constant for heightUtilities
. We use an ES6 to pass in the item
, and then we generate the CSS we need. Since our class names use special characters, we can use e()
and some String interpolation to parse out a valid class name.
With our classes ready to go, we can go ahead and register them with addUtilities()
.
plugins: [
require('tailwindcss/plugins/container')({
// center: true,
// padding: '1rem',
}),
function({e, addUtilities}) {
let percents = [
{
key: '1/2',
value: '50%'
},
{
key: '1/3',
value: '33.33333%'
},
{
key: '2/3',
value: '66.66667%'
},
{
key: '1/4',
value: '25%'
},
{
key: '3/4',
value: '75%'
},
{
key: '1/5',
value: '20%'
},
{
key: '2/5',
value: '40%'
},
{
key: '3/5',
value: '60%'
},
{
key: '4/5',
value: '80%'
},
{
key: '1/6',
value: '16.66667%'
},
{
key: '5/6',
value: '83.33333%'
},
{
key: '1/10',
value: '10%'
},
{
key: '3/10',
value: '30%'
},
{
key: '7/10',
value: '70%'
},
{
key: '9/10',
value: '90%'
},
{
key: 'full',
value: '100%'
},
];
const heightUtilities = percents.map(item => {
return {
[`.h-${e(item.key)}`]: {
height: item.value
}
}
});
addUtilities(heightUtilities, {
variants: ['responsive'],
});
}
]
Notice that we've also included the responsive variant configuration for these classes. addUtilites()
can accept some additional options when we use it. This configuration lets Tailwind take care of generating all of the responsive variations for us.
If we run our build process now, we can see that our new height
utilities are available for us to use!
Extracting to an NPM Package
While this is super awesome to have in our project, it's annoying to have to copy this every time we want to add these classes. Instead, we can extract this to an NPM package and just include it in our projects!
To start, we can create a new directory with an index.js
file inside of it. Inside of index.js
, we'll add a module.exports
so our package users can import our functionality.
module.exports = function () {
}
Now, we can simply copy and paste our plugin into our export.
module.exports = function () {
function({e, addUtilities}) {
let percents = [
{
key: '1/2',
value: '50%'
},
{
key: '1/3',
value: '33.33333%'
},
{
key: '2/3',
value: '66.66667%'
},
{
key: '1/4',
value: '25%'
},
{
key: '3/4',
value: '75%'
},
{
key: '1/5',
value: '20%'
},
{
key: '2/5',
value: '40%'
},
{
key: '3/5',
value: '60%'
},
{
key: '4/5',
value: '80%'
},
{
key: '1/6',
value: '16.66667%'
},
{
key: '5/6',
value: '83.33333%'
},
{
key: '1/10',
value: '10%'
},
{
key: '3/10',
value: '30%'
},
{
key: '7/10',
value: '70%'
},
{
key: '9/10',
value: '90%'
},
{
key: 'full',
value: '100%'
},
];
const heightUtilities = percents.map(item => {
return {
[`.h-${e(item.key)}`]: {
height: item.value
}
}
});
addUtilities(heightUtilities, {
variants: ['responsive'],
});
}
}
Who knew writing an NPM package could be so easy? Now to get it up on npmjs.com, we can jump into our CLI.
Inside of our package directory, we can run npm init
to create a package.json
file. It's pretty easy just to follow the prompts. The only one that tripped me up was the tests one. But, I just left it blank, and everything worked fine.
Just like that our package is ready to be published on NPM. To start, we'll need to create a user account, if you don't already have one, by running npm adduser
. Next, we'll need to double check that the name we'd like to use isn't already taken by doing a quick search on npmjs.com.
Once we're all set, we can just run npm publish
and now we can see our package at https://npmjs.com/package/<package>
. Bada bing bada boom, we have ourselves an NPM package.
You don't have to worry about publishing the height plugin as a package, because you can already find it here.
The Wrap-Up
Tailwind has made leaps and bounds since its release, and with the addition of plugins we can finally encapsulate and share all of the wonderful utilities and components we've been working on. I hope you feel confident to go out and add your own plugins to the ecosystem, and be sure to share them with me! As always, feel free to ask me any questions on Twitter. And until next time, happy coding!