- Published on
TailwindCSS: Master Advanced Techniques for Dark Mode, Theming, and More
- Authors
- Name
- James Shopland
- @jollyshopland
I have recently been using a lot of tailwindcss based components such as shadcn/ui as well as creating my own. This has taught me a lot of the best ways to utilize tailwind to suit your needs and I wanted to document that.
TailwindCSS has revolutionized the way we design interfaces, offering unparalleled flexibility and control. In this post, we'll dive into advanced techniques for leveraging TailwindCSS, focusing on custom theming, dark mode, and helper functions. These methods will not only streamline your workflow but also elevate your UI designs.
This guide will assume you have installed tailwind and done the basic configuration. Further, I recommend the Tailwind CSS IntelliSense vscode plugin.
Theming
When it comes to colors in tailwind you may be used to using them like this: bg-red-600
. While this is an okay method it has some downsides. Firstly, you are bound to tailwind's predefined colors. Secondly, if you want to change your sites theme
you will have to find every entry of that color to update it. Instead we could create our own themes and extend tailwind to use these.
1. Add to css
First we need to add the custom colors to your tailwind css file (This is where you would have added the tailwind directives on setup). shadcn/ui has some good defaults of this, but we need to come up with a few sensible variables to meet our needs.
Colors will be defined in the HLS color channels. This allows us to use the opacity modifiers such as bg-background/80
. If you want to go from rgb/hex to hls, you can paste it into google and it will usually show the hls variant as well. Don't include the color space function or opacity modifiers won't work
Lets start with a background color and a foreground color. Background is self explanatory, foreground will be used for things such as text, so it should be a color that works well on the background.
You will want to add all these variables within the root selector, and tailwinds @layer base
.
Now lets add a custom color for elements like buttons. Again here, primary will be the color of a button for example whereas the foreground variant will be text that works well on that color.
Now lets add a few more color defaults:
Another useful helper we can use is radius. This again gives us the option to modify this on all components later on if needed.
For more examples, check out this page by shadcn: https://ui.shadcn.com/docs/theming#list-of-variables
2. Add to tailwind
Now we have our CSS variables we need to extend tailwind to enable us to use these.
Go to your tailwind config and modify it like so, replacing the names with those you defined earlier:
Above you will notice we have extended border radius to allow us to still use the size modifiers, with our custom definition of a base radius.
Further, primary is defined as an object with DEFAULT
and foreground
. This allows us
to use bg-primary
which will use that the --primary
variable and bg-primary-foreground
to use the foreground one.
Now you can get started using these as you would with any other tailwind classes, such as bg-primary text-foreground rounded-sm
Dark mode
Tailwind makes it really easy to utilize dark mode, using the dark:
modifier. However, we can make this easier using the variables we defined above, making it so we don't even need to use the dark modifier.
First, in your tailwind config file, ensure dark mode is configured using class mode.
Next we can go back to our custom variables in our css file to add our dark mode theme.
In the same @layer base
we defined earlier, add .dark
outside of the root definition and for each color we defined earlier, add the color it should be in dark mode:
That is all. Now whenever the tailwind theme is switched from light mode to dark mode, your theme should adjust with it automatically. This works as the color definitions are changed when the html element has the class dark
applied.
If you want to add your own theme switcher, view the tailwind docs
If you want to set the theme only by the users color scheme choice, you could modify the above and add a copy of your colors but dark mode based into here:
Helper Functions
You may have seem a helper function in various projects for tailwind called cn
. This uses a combination of clsx and tailwind-merge. It enables conditional classes and stops styling conflicts.
To get started we need to install those 2 packages:
Now, we need to add a function. You can place this anywhere you like, most place it in lib/utils.ts
Now we have the function what does it do?
tailwind-merge
Firstly, the tailwind-merge aspect of it stops style conflicts by merging classes where possible. For example with the button below, we have set the padding 3 times px-1 py-2
and p-3
.
The cn helper using tailwind-merge will merge this, using the last class passed in as priority so the button styling will become
Why?
You may be wondering what the use-case of this is. The best example is when building custom components that allow some styling overriding. Using the button above as an example, say we exposed a className prop to allow the components styles to be overridden if needed for a one off for example.
We can now override the style on the button if needed without worrying about conflicts.
clsx
The clsx part of the helper allows us to construct our class names conditionally.
For example:
Now in the above component, we can style based on a prop or state being true. So if isWarning
is true, it will be yellow and if isError
is true is will be red.
There are several ways to write these conditions that are documented here. You could use objects for example, the above would become:
Hopefully that explains why the cn helper is so powerful and useful when building out your own custom components.
Variants
Lastly, I want to show off class-variance-authority
. This is a useful helper to manage different variants of components that you may need without needing to duplicate the logic for it. For example, a button with a primary, secondary and destructive design.
To get started install the package:
Further, if you are using the tailwind intellisense plugin you will want to add the following to you .vs settings.json
This will enable the intellisense plugin to work on cva functions as well.
Usage
For the usage of this we will look at a simple button. We need to create a variants function:
The above is a simple variants example. Essentially, the first string of tailwind classes are the defaults, that are applied to every variant.
Next we define our variants in an object, utilizing 2 selectors, variant and size. These are sensibly separated to allow us to change the size of each variant.
Lastly, we tell cva what should be used as default, if nothing is supplied to the function. Aka, the default style.
Now when we use this in our component, we can pass through the variant and size name as strings/props such as "destructive" and "lg". We do this all in the cn helper as well to help with style conflicts as explained above.
You can explore the cva docs here for more examples and complex use cases.
ESLint
Lastly, we can utilize an eslint plugin to enforce best practices and consistency using tailwind. While there is an official prettier plugin from tailwind for ordering classNames the eslint plugin offers this and more.
To install this, I will assume you already have eslint set up.
First install the eslint plugin
Next make the following changes to your eslint file:
Thats all! Now you should get helpful hints about tailwind className orders, that are auto fixable using eslint as well as some other best practice rules. For example if you had px-2 py-2
the plugin would warn you that this could be condensed to p-2
For more of the rules, see here
Wrap Up
Thanks for reading, hopefully this has gone over some of the best ways to use tailwind in a project and shown you some awesome techniques to help with creating custom components. From customizing themes to effortless dark mode integration, these strategies are key to creating dynamic and visually appealing UIs. I encourage you to experiment with these methods in your projects. Share your experiences or any questions in the comments below!