Explore Tailwind CSS—one of the most popular CSS frameworks—and how to use it in the real world.
Consistency in coding practices is a cornerstone of practical software development. Coding style guides offer a standardized framework for writing clear, maintainable and scalable code. These guides are particularly valuable for frontend engineers working on design systems, where uniformity in naming conventions and structure directly impacts collaboration and efficiency.
For a deeper dive into the importance of coding style guides and some popular methodologies, check out Coding Style Guides—A Standardized Approach to Writing Code.
To learn more about the role of frontend engineers in design systems, check out The Role of Frontend Engineers in Design Systems, Part 1 and Part 2.
With the above context, Tailwind CSS takes a unique, consistent approach to styling by focusing on utility-first principles. In this article, we’ll explore Tailwind CSS, its core concepts, features and how it’s applied in real-world projects.
Tailwind CSS is a modern CSS framework designed to streamline the development process. Created by Adam Wathan, Steve Schoger, Jonathan Reinink and David Hemphill, Tailwind was first released in 2017 as a utility-first framework to provide developers with flexibility and speed in styling their applications. Since its inception, it has become one of the most popular CSS frameworks in the web development community.
Unlike traditional CSS frameworks like Bootstrap, Tailwind focuses on granular control over styles, allowing developers to build custom designs quickly. With this, Tailwind prioritizes speed and customization by eliminating the need for writing custom CSS for most tasks. Whether prototyping a user interface or developing a production-grade application, Tailwind provides the tools to accelerate development while maintaining consistency.
At its core, Tailwind CSS introduces the concept of utility-first classes. These single-purpose classes like text-center, bg-blue-500 or px-4 describe the styles they apply. This approach eliminates the abstraction layer of custom class names, making styles directly visible in our HTML. Some of the key benefits of this approach are:
To understand Tailwind CSS’s utility-first approach, let’s compare a simple example of styling a card component using 1) traditional CSS, 2) a component-based framework like Bootstrap and 3) Tailwind CSS.
In traditional CSS, developers create custom component class names and define the styles in a separate CSS file. Here’s an example:
<div class="card">
<h2 class="card-title">Card Title</h2>
<p class="card-content">This is some content inside the card.</p>
</div>
.card {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
max-width: 300px;
margin: auto;
}
.card-title {
font-size: 1.25rem;
margin-bottom: 8px;
}
.card-content {
color: #6c757d;
}
In the above example, we’ve defined a .card
class with properties like background color, border, padding and width. The .card-title
and .card-content
classes define additional styling for the title and content, respectively. This approach requires maintaining a separate CSS file and managing class naming conventions.
A CSS framework like Bootstrap simplifies styling by providing predefined classes for standard UI components. Developers can apply these classes directly to HTML elements without writing custom CSS.
<div class="card">
<div class="card-body">
<h5 class="card-title">Card Title</h5>
<p class="card-text">This is some content inside the card.</p>
</div>
</div>
In this example, Bootstrap’s card, card-body, card-title and card-text classes style the card component. While this approach reduces the need for custom CSS, it’s limited to the framework’s predefined classes, which might not always align with a project’s specific design requirements.
Tailwind takes a utility-first approach, where each class represents a specific style. Instead of relying on pre-styled components or custom CSS, styles are applied directly in the HTML.
<div class="bg-gray-100 border border-gray-300 rounded-lg p-4 max-w-sm">
<h2 class="text-lg font-semibold mb-2">Card Title</h2>
<p class="text-gray-600">This is some content inside the card.</p>
</div>
Here’s a breakdown of the Tailwind utility classes used in the example above:
This unique approach to styling eliminates the need for a separate CSS file or reliance on predefined component classes. The styles are explicit, readable and easily modified in our HTML!
While Tailwind CSS offers numerous benefits, it’s essential to understand its potential challenges and quirks. One common concern is that Tailwind’s utility-first approach can make HTML files appear verbose and cluttered, especially when components require many utility classes. This can impact readability, particularly in larger codebases, unless there is a clear structure or documentation.
Imagine a more complex card component (than we’ve seen above) that includes additional elements like an image, tags and a footer. Here’s how it might look styled entirely with Tailwind utility classes:
<div
class="bg-white border border-gray-300 rounded-lg shadow-md max-w-md mx-auto p-6 space-y-4"
>
<img
src="https://via.placeholder.com/300x200"
alt="Card image"
class="rounded-md w-full h-48 object-cover"
/>
<div class="space-y-2">
<h2 class="text-xl font-bold text-gray-800">Card Title</h2>
<p class="text-gray-600 leading-relaxed">
This is a detailed description of the card.
</p>
</div>
<div class="flex items-center space-x-2">
<span
class="bg-blue-100 text-blue-800 text-xs font-medium py-1 px-2 rounded-full"
>Tag 1</span
>
<span
class="bg-green-100 text-green-800 text-xs font-medium py-1 px-2 rounded-full"
>Tag 2</span
>
<span
class="bg-yellow-100 text-yellow-800 text-xs font-medium py-1 px-2 rounded-full"
>Tag 3</span
>
</div>
<div class="flex justify-between items-center pt-4 border-t border-gray-200">
<button
class="bg-blue-500 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-600 transition duration-300"
>
Learn More
</button>
<span class="text-sm text-gray-500">Last updated: 2 days ago</span>
</div>
</div>
While this example effectively uses Tailwind’s utility classes to achieve a detailed design, it demonstrates the potential verbosity of the utility-first approach. The HTML is readable but cluttered, making it challenging to identify the component’s structure quickly.
Tailwind offers developers the capability to use an @apply directive to consolidate utility classes into reusable CSS classes. Using the @apply
directive on the verbose card example above, we can simplify the HTML by moving the repeated utility classes into reusable CSS classes. This improves readability and makes the component more maintainable and scalable in larger projects.
.card {
@apply bg-white border border-gray-300 rounded-lg shadow-md max-w-md mx-auto p-6 space-y-4;
}
.card-image {
@apply rounded-md w-full h-48 object-cover;
}
.card-title {
@apply text-xl font-bold text-gray-800;
}
.card-description {
@apply text-gray-600 leading-relaxed;
}
.card-tags {
@apply flex items-center space-x-2;
}
.card-tag {
@apply bg-blue-100 text-blue-800 text-xs font-medium py-1 px-2 rounded-full;
}
.card-footer {
@apply flex justify-between items-center pt-4 border-t border-gray-200;
}
.card-button {
@apply bg-blue-500 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-600 transition duration-300;
}
.card-updated {
@apply text-sm text-gray-500;
}
The HTML becomes significantly cleaner and easier to read with the utility classes abstracted into reusable styles.
<div class="card">
<img
src="https://via.placeholder.com/300x200"
alt="Card image"
class="card-image"
/>
<div class="space-y-2">
<h2 class="card-title">Card Title</h2>
<p class="card-description">This is a detailed description of the card.</p>
</div>
<div class="card-tags">
<span class="card-tag">Tag 1</span>
<span class="card-tag">Tag 2</span>
<span class="card-tag">Tag 3</span>
</div>
<div class="card-footer">
<button class="card-button">Learn More</button>
<span class="card-updated">Last updated: 2 days ago</span>
</div>
</div>
In addition to dealing with verbose class definitions, new developers may face a learning curve when working with Tailwind for the first time. The framework’s extensive set of utility classes and naming conventions can take time to memorize and fully understand. Although Tailwind’s documentation is comprehensive and helpful, onboarding might initially slow down developers unfamiliar with utility-first frameworks.
Despite that, Tailwind CSS’s benefits often outweigh its challenges, especially as developers become accustomed to its workflow. The initial learning curve gives way to increased productivity, design consistency and flexibility, making it a favorite for many teams working on scalable, modern applications.
Tailwind offers granular control over styling through the direct application of utility classes, while a component framework like Progress KendoReact is designed to accelerate development with prebuilt, feature-rich components.
Combining these two tools can provide significant flexibility, but it isn’t always practical because the overlap in functionality can lead to redundant effort. Tailwind’s utility classes provide highly customized styles, which can conflict with KendoReact’s prebuilt designs or require additional effort to maintain consistency between the two approaches.
Introduced through the UnstyledContext
provider, the KendoReact Unstyled mode allows developers to strip away default styles and apply custom styles using their own CSS classes. This feature is handy when integrating with frameworks like Tailwind. Using Unstyled mode, we can leverage Tailwind’s utility classes to style KendoReact components without worrying about conflicts with predefined styles or themes.
For example, in a form component wrapped with UnstyledContext
, we can replace default KendoReact classes with Tailwind’s utility classes:
import { UnstyledContext } from "@progress/kendo-react-common";
const formPreset = {
uForm: {
form: { main: "bg-gray-100 p-6 rounded-lg" },
field: { main: "mb-4" },
},
};
<UnstyledContext.Provider value={formPreset}>
<Form>{/* Form contents here */}</Form>
</UnstyledContext.Provider>;
This topic introduces an interesting discussion on when to use utility-first frameworks versus UI component libraries—or how to combine the two effectively. In an upcoming article, we’ll explore this further to help you decide when such integrations make sense and how to approach them for maximum efficiency. Stay tuned!
Tailwind CSS’s utility-first approach simplifies styling by providing a consistent, flexible and scalable solution for modern web development. While its verbosity and learning curve can present challenges, the benefits of increased productivity, design consistency and customizability often outweigh these concerns.
If you’ve never tried Tailwind before, check out their installation guide on steps on how to get started.
Hassan is a senior frontend engineer and has helped build large production applications at-scale at organizations like Doordash, Instacart and Shopify. Hassan is also a published author and course instructor where he’s helped thousands of students learn in-depth frontend engineering skills like React, Vue, TypeScript, and GraphQL.