Getting set up

Requirements

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.

Optional: Add the Inter font family

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 "Inter var" to your "sans" font family in your Tailwind config:

// tailwind.config.js
const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['Inter var', ...defaultTheme.fontFamily.sans],
      },
    },
  },
  // ...
}

Using HTML and your own JS

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).

Accessibility considerations

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:

  • Making sure components are properly keyboard accessible (dropdowns should be navigated with up/down arrow keys, dialogs should close when you press escape, tabs should be selected using the left/right arrow keys, etc.)
  • Correctly handling focus (you shouldn't be able to tab to an element behind a dialog, the first item in a dropdown should be auto-focused when the dropdown opens, etc.)
  • Synchronizing ARIA attributes with component state (adding 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.)
  • ...and many other concerns.

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.

Dynamic classes

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 flex-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 h-5 w-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 flex-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 h-5 w-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.

Transitions

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.

Creating partials/components

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.


Using React

Installing dependencies

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.

Creating components

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="h-10 w-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="h-10 w-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.


Using Vue

Installing dependencies

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.

Creating components

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="h-10 w-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="h-10 w-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.


Resources & assets

Icons

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

Images in Tailwind UI come almost exclusively from Unsplash. It's a great resource if you need freely-usable photography for your projects.

Illustrations

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.

Figma assets

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.