Tailwind tips and tricks
Tailwind CSS offers a fast and flexible way to style projects, with plenty of ways to refine your workflow. From making the most of utility classes to structuring components effectively and using tools that enhance the experience, these tips can help you work more efficiently.
Tailwind takes a different approach from traditional CSS frameworks. Instead of pre-designed components, it provides low-level utility classes that let you build custom designs without writing custom styling. This methodology, often called utility-first, wasn’t always widely accepted—many developers initially dismissed it as cluttered or unmanageable. However, as projects grew in complexity, Tailwind’s approach proved to be more scalable, maintainable, and efficient, leading to its widespread adoption today.
With that in mind, here are some tips to help you get even more out of it.
Styling with attributes
Tailwind provides powerful attribute-based styling options that help avoid unnecessary !important
overrides when managing class conflicts. By using data attributes and ARIA states, you can apply styles conditionally based on element attributes, keeping your styles cleaner and more maintainable.
For example, the data-*
attribute allows styles to adjust dynamically without extra logic, whether toggling a boolean value or handling more complex states:
<div data-active class="border border-gray-300 data-active:border-purple-500">
...
</div>
<div data-size="large" class="data-[size=large]:p-8">
...
</div>
Similarly, ARIA attributes can be leveraged for state-based styling, such as handling active navigation links without needing !important
:
<a href="#" class="text-gray-600 aria-[current=page]:text-black">
...
</a>
Use caution with **
Like the *:
variant, the **:
variant in Tailwind allows you to style child elements—but with a key difference: it applies styles to all descendants, not just direct children. This makes it useful for styling content embedded from a CMS or other external sources where you don’t have full control over the markup.
However, relying too heavily on **:
can reduce one of Tailwind’s biggest advantages—keeping styles close to the elements they affect. Overuse can make styles harder to track and maintain, so it’s best reserved for cases where other approaches aren’t practical.
<div class="**:[svg>path]:fill-blue-500">
...
<svg>
<path d="M10 20 l5 0 l5 -10 l5 10 l5 0" fill="black" />
</svg>
...
</div>
Card links
When making a card component clickable, a tempting approach may be to wrap the entire card in an <a>
tag. While this achieves the desired behaviour, it’s not advisable because anchors containing block-level elements should be discouraged.
A better approach is to place the <a>
inside the card heading, and to further nest a <span>
to extend the clickable area to cover the entire card:
<div class="relative">
<h2>
<a href="#">
...
<span class="absolute inset-0"></span>
</a>
</h2>
<div>...</div>
</div>
This ensures the link is both semantic and accessible, while still providing the expected user experience of a fully clickable card.
Dynamic classes
Many developers look to dynamically construct class names to keep their code as clean and concise as possible, but this can cause issues with how Tailwind scans your source files. As it only relies on simple text matching, it has no way to process dynamically generated strings, meaning interpolation like text-${color}-600
won’t work. Instead, you need to ensure all potential class names are explicitly listed in your code, allowing them to be detected and included in the output CSS.
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}">
...
</div>
For a more complex use case, such as a button component with styling based on props, you can map each prop value to a fully formed class string. This approach not only ensures they will be detected, but also makes the code more flexible, scalable, and easier to maintain.
const colorVariants = {
blue: 'bg-blue-600 hover:bg-blue-500 text-white',
red: 'bg-red-500 hover:bg-red-400 text-white',
yellow: 'bg-yellow-300 hover:bg-yellow-400 text-black',
}
<button class="colorVariants[color]">
...
</button>
Order classes
When working with Tailwind, the order of class names doesn’t affect the final styles due to how utility classes work. However, keeping them organised in a consistent manner makes code easier to read and maintain. Manually sorting classes can be tedious, but there are tools that will automate this process.
Prettier
You can use the official Prettier plugin from the Tailwind team, which will sort classes according to their recommended class order.
Headwind
Alternatively, the Headwind extension can provide similar functionality from within the editor.
Component libraries
Building custom UI components from scratch can be time-consuming, especially when ensuring accessibility and expected interactions. Component libraries help by handling these complexities, allowing you to focus on styling and layout. Those that integrate well with utility classes make it even easier to use them alongside Tailwind, letting you customise the look without fighting built-in styles.
Headless UI
As expected, the Tailwind team have a solution for this too. It offers unstyled, accessible components for React and Vue.
Radix
Similarly, Radix provides a robust set of low-level components with a strong focus on accessibility and flexibility. Originally developed for React, it has since been ported to many other frameworks.
Packages
Managing conditional or variant-based class names in Tailwind can quickly become cumbersome without the right approach. Fortunately, there are packages that can streamline this process and improve your workflow.
Class Variance Authority
cva
simplifies the creation of component classes that vary based on props, making it easier to manage multiple states or variations of a component in a structured way. By defining variants and their corresponding styles in a central location, it helps you avoid clutter and repetition in your class names.
Tailwind Merge
On the other hand, tailwind-merge
is perfect for handling conflicting or duplicated class names, especially when dynamically generating class strings. It intelligently merges classes and removes any conflicts, ensuring that your styles are applied as intended without the need for manual overrides.
Tools
A variety of tools are available to make working with Tailwind more efficient, helping to speed up development and improve workflow.
Intellisense
In terms of development there is Tailwind Intellisense, the official plugin for VSCode provides autosuggestions, syntax highlighting, and inline documentation for your Tailwind classes.
Colours
When it comes to design and handling colours specifically, there are a couple great resources you can use. There is tailwindcolors.com which provides an easy way explore the default colour palette, and to copy values into your design software. But if you need to generate your own custom colour palette, tints.dev is the place to go.