All of the components in Tailwind UI are designed for the latest version of Tailwind CSS, which is currently Tailwind CSS v3.4. To make sure that you are on the latest version of Tailwind, update via npm:
npm install tailwindcss@latest
All of the examples in Tailwind UI rely on the default Tailwind CSS configuration, but some rely on additional first-party plugins like @tailwindcss/forms
, @tailwindcss/typography
, and @tailwindcss/aspect-ratio
.
When an example requires any plugins or configuration changes, it will be noted in a comment at the top of the example.
If you're new to Tailwind CSS, you'll want to read the Tailwind CSS documentation as well to get the most out of Tailwind UI.
We've used Inter for all of the Tailwind UI examples because it's a beautiful font for UI design and is completely open-source and free. Using a custom font is nice because it allows us to make the components look the same on all browsers and operating systems.
You can use any font you want in your own project of course, but if you'd like to use Inter, the easiest way is to first add it via the CDN:
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
Then add "InterVariable" to your "sans" font family in your Tailwind config:
// tailwind.config.js
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['InterVariable', ...defaultTheme.fontFamily.sans],
},
},
},
// ...
}
All of the components in Tailwind UI are provided in three formats: React, Vue, and vanilla HTML.
The React and Vue examples are fully functional out-of-the-box, and are powered by Headless UI — a library of unstyled components we designed to integrate perfectly with Tailwind CSS.
The vanilla HTML examples do not include any JavaScript and are designed for people who prefer to write any necessary JavaScript themselves, or who want to integrate with a framework other than React or Vue.
The vast majority of components don't need JavaScript at all and are completely ready to go out of the box, but things that are interactive like dropdowns, dialogs, etc. require you to write some JS to make them work the way you'd expect.
In these situations we've provided some simple comments in the HTML to explain things like what classes you need to use for different states (like a toggle switch being on or off), or what classes we recommend for transitioning elements on to or off of the screen (like a dialog opening).
We've done our best to ensure that all of the markup in Tailwind UI is as accessible as possible, but when you're building interactive components, many accessibility best practices can only be implemented with JavaScript.
For example:
aria-expanded="true"
when a dropdown is open, setting aria-checked
to true when a toggle is on, updating aria-activedescendant
when navigating the options in an autocomplete, etc.)
If you're using Tailwind UI with React or Vue, all of this complexity is handled for you automatically by Headless UI, but if you are providing your own JS, it is up to you to follow accessibility best practices when adding interactive behavior.
To learn more about building accessible UI components, we recommend studying the WAI-ARIA Authoring Practices published by the W3C.
When an element needs different classes applied based on some state (like a toggle being on or off), we list the classes for each state in a comment directly above the element:
<!-- On: "bg-indigo-600", Off: "bg-gray-200" -->
<span aria-checked="false" class="bg-gray-200 relative inline-block shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline" role="checkbox" tabindex="0">
<!-- On: "translate-x-5", Off: "translate-x-0" -->
<span aria-hidden="true" class="translate-x-0 inline-block size-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span>
</span>
The HTML we provide is always pre-configured for one of the defined states, and the classes that you need to change when switching states are always at the very beginning of the class list so they are easy to find.
As an example, to adapt this HTML for Alpine.js, you can conditionally apply the correct classes using the :class
directive based on some state you've declared in x-data
:
<span
x-data="{ isOn: false }"
@click="isOn = !isOn"
:aria-checked="isOn"
:class="{'bg-indigo-600': isOn, 'bg-gray-200': !isOn }"
class="bg-gray-200 relative inline-block shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline"
role="checkbox"
tabindex="0"
>
<span
aria-hidden="true"
:class="{'translate-x-5': isOn, 'translate-x-0': !isOn }"
class="translate-x-0 inline-block size-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"
></span>
</span>
We've included a basic click handler here to demonstrate the general idea, but please reference the WAI-ARIA Authoring Practices when building components like this to ensure you implement all of the necessary keyboard interactions and properly manage any required ARIA attributes.
For elements that should be dynamically shown or hidden (like the panel on a dropdown), we include the recommended transition styles in a comment directly above the dynamic element:
<div class="relative ...">
<button type="button" class="...">
Options
</button>
<!--
Show/hide this element based on the dropdown state
Entering: "transition ease-out duration-100 transform"
From: "opacity-0 scale-95"
To: "opacity-100 scale-100"
Closing: "transition ease-in duration-75 transform"
From: "opacity-100 scale-100"
To: "opacity-0 scale-95"
-->
<div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
<div class="rounded-md bg-white shadow-xs">
<!-- Snipped -->
</div>
</div>
</div>
As an example, to adapt this HTML for Alpine.js you would use the x-transition
directive to apply the right classes at each point in the transition lifecycle:
<div x-data="{ isOpen: false }" class="relative ...">
<button type="button" @click="isOpen = !isOpen" class="...">
Options
</button>
<div
x-show="isOpen"
x-transition:enter="transition ease-out duration-100 transform"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75 transform"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg"
>
<div class="rounded-md bg-white shadow-xs">
<!-- Snipped -->
</div>
</div>
</div>
We've included a basic click handler here to demonstrate the general idea, but please reference the WAI-ARIA Authoring Practices when building components like this to ensure you implement all of the necessary keyboard interactions and properly manage any required ARIA attributes.
Since the vanilla HTML examples included in Tailwind UI can't take advantage of things like loops, there is a lot of repetition that wouldn't actually be there in a real-world project where the HTML was being generated from some dynamic data source. We might give you a list component with 5 list items for example that have all the utilities duplicated on each one, whereas in your project you'll actually be generating those list items by looping over an array.
When adapting our examples for your own projects, we recommend creating reusable template partials or JS components as needed to manage any duplication.
Learn more about this in the "Extracting Components" documentation on the Tailwind CSS website.
Tailwind UI for React depends on Headless UI to power all of the interactive behavior and Heroicons for icons, so you'll need to add these two libraries to your project:
npm install @headlessui/react @heroicons/react
These libraries and Tailwind UI itself all require React >= 16.
All React examples are provided as a simple single component and make no assumptions about how you want to break things down, what prop APIs you want to expose, or where you get any data from.
Some data has been extracted into basic local variables just to clean up duplication and make the code easier to read and understand, but we've tried to do as little as possible to avoid enforcing any unnecessarily rigid opinions.
When you're adapting code from Tailwind UI for your own projects, you should break the examples down into smaller components as necessary to achieve whatever level of reuse you need for your project.
For example, you might start with this stacked list component:
const people = [
{
name: 'Calvin Hawkins',
email: 'calvin.hawkins@example.com',
image:
'https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Kristen Ramos',
email: 'kristen.ramos@example.com',
image:
'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Ted Fox',
email: 'ted.fox@example.com',
image:
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
]
export default function Example() {
return (
<ul className="divide-y divide-gray-200">
{people.map((person) => (
<li key={person.email} className="py-4 flex">
<img className="size-10 rounded-full" src={person.image} alt="" />
<div className="ml-3">
<p className="text-sm font-medium text-gray-900">{person.name}</p>
<p className="text-sm text-gray-500">{person.email}</p>
</div>
</li>
))}
</ul>
)
}
After adapting the content for your own project, breaking it down into separate components, and wiring up your data source, it might look more like this:
function HockeyTeamItem({ team }) {
return (
<li className="py-4 flex">
<img className="size-10 rounded-full" src={team.logo} alt="" />
<div className="ml-3">
<p className="text-sm font-medium text-gray-900">{team.name}</p>
<p className="text-sm text-gray-500">{team.city}</p>
</div>
</li>
)
}
export default function HockeyTeamList({ teams }) {
return (
<ul className="divide-y divide-gray-200">
{teams.map((team) => <HockeyTeamItem key={team.id} team={team} />)}
</ul>
)
}
Tailwind UI is more like a set of blueprints, patterns, and ideas than a rigid UI kit. The code you end up with at the end of the day is yours, and you can factor it however you like.
Tailwind UI for Vue depends on Headless UI to power all of the interactive behavior and Heroicons for icons, so you'll need to add these two libraries to your project:
npm install @headlessui/vue @heroicons/vue
These libraries and Tailwind UI itself all require Vue 3+. We do not currently offer support for Vue 2.
All Vue examples are provided as a simple single component and make no assumptions about how you want to break things down, what prop APIs you want to expose, or where you get any data from.
Some data has been extracted into basic local variables just to clean up duplication and make the code easier to read and understand, but we've tried to do as little as possible to avoid enforcing any unnecessarily rigid opinions.
When you're adapting code from Tailwind UI for your own projects, you should break the examples down into smaller components as necessary to achieve whatever level of reuse you need for your project.
For example, you might start with this stacked list component:
<template>
<ul class="divide-y divide-gray-200">
<li v-for="person in people" :key="person.email" class="py-4 flex">
<img class="size-10 rounded-full" :src="person.image" alt="" />
<div class="ml-3">
<p class="text-sm font-medium text-gray-900">{{ person.name }}</p>
<p class="text-sm text-gray-500">{{ person.email }}</p>
</div>
</li>
</ul>
</template>
<script>
const people = [
{
name: 'Calvin Hawkins',
email: 'calvin.hawkins@example.com',
image:
'https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Kristen Ramos',
email: 'kristen.ramos@example.com',
image:
'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Ted Fox',
email: 'ted.fox@example.com',
image:
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
]
export default {
setup() {
return {
people,
}
},
}
</script>
After adapting the content for your own project, breaking it down into separate components, and wiring up your data source, it might look more like this:
<!-- HockeyTeamList.vue -->
<template>
<ul class="divide-y divide-gray-200">
<HockeyTeamItem v-for="team in teams" :key="team.id" :team="team"/>
</ul>
</template>
<script>
export default {
props: {
teams: Array
},
}
</script>
<!-- HockeyTeamListItem.vue -->
<template>
<li class="py-4 flex">
<img class="size-10 rounded-full" :src="team.logo" alt="" />
<div class="ml-3">
<p class="text-sm font-medium text-gray-900">{{ team.name }}</p>
<p class="text-sm text-gray-500">{{ team.city }}</p>
</div>
</li>
</template>
<script>
export default {
props: {
team: Object
},
}
</script>
Tailwind UI is more like a set of blueprints, patterns, and ideas than a rigid UI kit. The code you end up with at the end of the day is yours, and you can factor it however you like.
All of the icons we use in Tailwind UI come from Heroicons, which is a free MIT-licensed icon set we designed and developed ourselves when we started working on Tailwind UI.
Images in Tailwind UI come almost exclusively from Unsplash. It's a great resource if you need freely-usable photography for your projects.
Some of the examples in Tailwind UI use illustrations from the free Lucid Illustrations pack by Pixsellz. You can grab the full set of illustrations and check out their other design resources on their website.
We've discontinued the Figma assets so we can focus our efforts on building more great examples with Tailwind CSS.
We used to provide Figma assets for Tailwind UI, but they were just an absolutely enormous amount of work to maintain and very few people were using them. We've made the really hard decision to discontinue them so we can spend more time on the actual code which is where we think we can provide the most value.
Customers of Tailwind UI can download the final Figma file we released, but please note the Figma file does not receive updates and does not contain any examples released after July 14, 2021.