Customizing Tailwind CSS

Just like in the golden days of Bootstrap, popular CSS frameworks like Tailwind involve the danger of many websites looking very similar. Especially with the UI components released as Tailwind UI it is super easy to get going with a beautiful default for almost every part of a website or application. Don't get me wrong, those are beautiful components throughout and using them to get something up quickly makes a lot of sense. The downside however is that your sites and applications will look very generic and close to others on the web, which means you're missing the chance to leave a lasting impression with you design and gain some brand awareness that a unique design can give.

The irony is, Tailwind is really well suited as a base UI system which is also fairly easy to customize and make your own. A lot of times people are underestimating the benefits of unique designs and put the customization somewhere in their backlog of things to do later. Let's change that! Let's take a look at how we can make Tailwind look different while keeping all the underlying goodies.

If you've never worked with Tailwind CSS or utility classes before, there's an introduction article I wrote over here. If you're already familiar with Tailwind, great! Let's dive right in!

Getting started

To get started, let's set up a very basic project that consist of nothing but bare HTML / CSS and JavaScript. Setting up Tailwind with Frameworks like Vue, Laravel and others might vary, because there are better ways of integrating it into the existing build steps. It's worth checking out the official documentation, to get instructions on how to do that – it's pretty good.

We'll keep it simple and start by creating the bare minimum we need from the terminal.

# Create our main HTML and CSS files as well as a bare PostCSS config file
touch index.html main.css postcss.config.js # Initialize project with npm
npm init -y # Install tailwind with PostCSS, autoprefixer and PostCSS CLI
npm install -D tailwindcss@latest postcss@latest postcss-cli autoprefixer@latest # Create a minimal Tailwind config file
npx tailwindcss init

Next, we need to add the following base config to the PostCSS configuration file we just created, so it knows what to do when compiling our CSS. In general Tailwind is a PostCSS plugin and you can absolutely use it with other preprocessors too.

// postcss.config.js
module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }

Finally we want to create an npm script that allows us to compile everything together by typing a single command. This is our build step for this tutorial and as mentioned above, when integrating Tailwind in different frameworks this might look different for you.

// package.json — to compile, run:
// $ npm run compile
"scripts": { "compile": "postcss main.css -o compiled.css"

Great! Now we just need to make sure we include the compiled.css in our HTML file and that's all we need in terms of boilerplate. Time to start the actual customization.

When it comes to styling the base theme and changing defaults like colors, margins, border radius, fonts and such there is a single file we can use to customize all of that, and that's the tailwind.config.js that was just created automatically in our setup step above. By default, it looks like this:

module.exports = { purge: [], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [],

And if you're wondering why it's empty and where the actual default theme is coming from, or just want to dig a little deeper into the defaults, here is a link to the defaults that get applied if we don't customize anything. However, that's exactly what we're here for – customizing. So let's start doing that by adding our own brand color:

module.exports = { purge: [], theme: { extend: { colors: { primary: { light: '#FFF538', DEFAULT: '#FFC233', dark: '#C19A32', }, }, }, }, variants: {}, plugins: [],

In order to be able to use this new color class in our HTML, we'll have to run our build step once, by calling our PostCSS compiler from the terminal, like we defined above:

$ npm run compile

Sweet, we now have a new class at our fingertips called primary which we can use with all the prefixes and modifiers we defined above (light / dark). Let's try it out and see it in action, by applying the new class to a button element and making the button darker on hover:

<!-- In our index.html -->
<button class="btn bg-primary hover:bg-primary-dark transition p-4 my-4"> I'm a button

Looking at spacing and breakpoints

Another important aspect of every layout that we might need to modify or extend, depending on what we want to create are spacings and breakpoints. Luckily for us, creating them is not a whole lot harder than adding our own custom colors, like we just did above. There is a gotcha when it comes to breakpoints however, as they need to be declared in the right order. Let's put it all together and jump into the details:

// Let's see why we need this in a second!
const defaultTheme = require("tailwindcss/defaultTheme"); module.exports = { purge: [], theme: { extend: { colors: { primary: { light: "#FFF538", DEFAULT: "#FFC233", dark: "#C19A32", }, }, // Here we're adding a new breakpoint class screens: { xs: "475px", ...defaultTheme.screens, laaarge: "1600px", }, // Here we're declaring our new spacing classes spacing: { "small-bear": "2rem", "mama-bear": "15rem", "papa-bear": "30rem", }, }, }, variants: {}, plugins: [],

So just to recap what we just added: there's a new require statement at the top and two new blocks for the spacing and breakpoints (screens). Let's look at the spacings first, because they're rather similar to the color classes we defined earlier. In fact, they work just like our colors, except they don't take any modifiers like light – because we don't need that for our spacings. So with those newly defined spacings, we can now add big margins, paddings etc. to our layout. By using the class m-mama-bear for example, we'd add a margin of 15rem to all sides.

When it comes to breakpoints there's a small caveat when creating our own ones, because just like in regular CSS (when writing media queries) the order matters. That same principle applies to Tailwind breakpoints: If we want to declare a breakpoint that is smaller than the default ones, which start at sm 640px, we have to make sure to declare it before the default breakpoints. That's why in the example above, we're using the require statement at the top to grab all the default breakpoints and then when setting up our breakpoints, we're inserting them after our new xs. If we don't do that, our breakpoint will get overwritten just like media queries would be in a regular CSS file. Order matters!

Applying and extending base styles

So besides creating our own utility classes, we want to take a look at extending the Tailwind base styles. Because Tailwind does a full reset, you can quickly see that a regular h1 for example doesn't look much like a headline anymore, unless you start applying classes. That's by design and actually gives us a lot of flexibility. When we design complex components however, we might want to have base elements styled a certain way, even without applying a whole lot of classes. A good way of doing that is using the @apply rule. It allows us to apply a set of rules to a given CSS selector like this:

/* Require the basic Tailwind CSS */
@tailwind base;
@tailwind components;
@tailwind utilities; h1 { @apply text-2xl; /* Applying our text-2xl rule on the h1 */ @apply font-bold; @apply text-primary; /* We can even apply our custom classes! */ font-family: monospace; /* And we can mix with regular CSS */
} h2 { @apply text-xl;

Now without applying any classes directly in our HTML, we have added the predefined properties to our elements right in the CSS. That allows us to give every element a sane default without leaving our predefined system or repeating ourselves. This is great for more complex elements that require custom CSS or edge-cases where we don't want to make a whole new utility class, but just overwrite parts of our system.

Using and creating presets

Now that we've got a good first look at how to technically customize Tailwind, one more concept that is really worth knowing about is how to use presets. In fact the default styles Tailwind ships with are just a preset as well. If we want to keep a single source of truth for things like color, font and such and work on different projects for the same client, presets are super helpful. They allow us to use a single tailwind.config.js as the base and extend it, just like we just extended the default preset. So in fact, every config file is a preset itself. We can use other presets as the base for our project by stating it in our config file as follows:

// tailwind.config.js
module.exports = { presets: [ require('./masters/tailwind-base.js') ], // ...

Because our presets variable is an array, you can already guess that we can also use multiple presets as our foundation and build on them. The important thing to keep in mind with the whole preset approach is that if there are overlapping rules defined, they will get overwritten in order you're requiring them — just like in regular CSS.

What's next?

There's still a lot we haven't covered yet, even though all of the above already sets us up with everything we need to really create fully custom layouts with Tailwind. If you want to dig deeper, I'd recommend looking into plugins and how to optimize Tailwind for use in production.

Here's a great article on creating a useful plugin

and the official documentation is a good starting point to learn more about optimizing for production.

Lastly, you can also grab the full source code of everything we just created above over on GitHub: