Extracting Components

Dealing with duplication and keeping utility-first projects maintainable.


Tailwind encourages a utility-first workflow, where designs are initially implemented using only utility classes to avoid premature abstraction.

Extracting Components - 图1

  1. <!-- A marketing page card built entirely with utility classes -->
  2. <div class="md:flex">
  3. <div class="md:flex-shrink-0">
  4. <img class="rounded-lg md:w-56" src="https://images.unsplash.com/photo-1556740738-b6a63e27c4df?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=448&q=80" width="448" height="299" alt="Woman paying for a purchase">
  5. </div>
  6. <div class="mt-4 md:mt-0 md:ml-6">
  7. <div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">Marketing</div>
  8. <a href="#" class="block mt-1 text-lg leading-tight font-semibold text-gray-900 hover:underline">Finding customers for your new business</a>
  9. <p class="mt-2 text-gray-600">Getting a new business off the ground is a lot of hard work. Here are five ideas you can use to find your first customers.</p>
  10. </div>
  11. </div>

But as a project grows, you’ll inevitably find yourself repeating common utility combinations to recreate the same component in many different places. This is most apparent with small components, like buttons, form elements, badges, etc.

Extracting Components - 图2

  1. <!-- Repeating these classes for every button can be painful -->
  2. <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  3. Button
  4. </button>

Keeping a long list of utility classes in sync across many component instances can quickly become a real maintenance burden, so when you start running into painful duplication like this, it’s a good idea to extract a component.


Extracting HTML components

It’s very rare that all of the information needed to define a UI component can live entirely in CSS — there’s almost always some important corresponding HTML structure you need to use as well.

Extracting Components - 图3Don’t rely on CSS classes to extract complex components

Extracting Components - 图4

  1. <style>
  2. .vacation-card { /* ... */ }
  3. .vacation-card-info { /* ... */ }
  4. .vacation-card-eyebrow { /* ... */ }
  5. .vacation-card-title { /* ... */ }
  6. .vacation-card-price { /* ... */ }
  7. </style>
  8. <!-- Even with custom CSS, you still need to duplicate this HTML structure -->
  9. <div class="vacation-card">
  10. <img class="vacation-card-image" src="..." alt="Beach in Cancun">
  11. <div class="vacation-card-info">
  12. <div>
  13. <div class="vacation-card-eyebrow">Private Villa</div>
  14. <div class="vacation-card-title">
  15. <a href="/vacations/cancun">Relaxing All-Inclusive Resort in Cancun</a>
  16. </div>
  17. <div class="vacation-card-price">$299 USD per night</div>
  18. </div>
  19. </div>
  20. </div>

For this reason, it’s often better to extract reusable pieces of your UI into template partials or JavaScript components instead of writing custom CSS classes.

By creating a single source of truth for a template, you can keep using utility classes without any of the maintenance burden created by duplicating the same classes in multiple places.

Extracting Components - 图5Create a template partial or JavaScript component

  1. <!-- In use -->
  2. <VacationCard
  3. img="/img/cancun.jpg"
  4. imgAlt="Beach in Cancun"
  5. eyebrow="Private Villa"
  6. title="Relaxing All-Inclusive Resort in Cancun"
  7. pricing="$299 USD per night"
  8. url="/vacations/cancun"
  9. />
  10. <!-- ./components/VacationCard.vue -->
  11. <template>
  12. <div>
  13. <img class="rounded" :src="img" :alt="imgAlt">
  14. <div class="mt-2">
  15. <div>
  16. <div class="text-xs text-gray-600 uppercase font-bold">{{ eyebrow }}</div>
  17. <div class="font-bold text-gray-700 leading-snug">
  18. <a :href="url" class="hover:underline">{{ title }}</a>
  19. </div>
  20. <div class="mt-2 text-sm text-gray-600">{{ pricing }}</div>
  21. </div>
  22. </div>
  23. </div>
  24. </template>
  25. <script>
  26. export default {
  27. props: ['img', 'imgAlt', 'eyebrow', 'title', 'pricing', 'url']
  28. }
  29. </script>

The above example uses Vue, but the same approach can be used with React components, ERB partials, Blade components, Twig includes, etc.


Extracting CSS components with @apply

For small components like buttons and form elements, creating a template partial or JavaScript component can often feel too heavy compared to a simple CSS class.

In these situations, you can use Tailwind’s @apply directive to easily extract common utility patterns to CSS component classes.

Here’s what a .btn-blue class might look like using @apply to compose it from existing utilities:

Extracting Components - 图6

  1. <button class="btn-blue">
  2. Button
  3. </button>
  4. <style>
  5. .btn-blue {
  6. @apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
  7. }
  8. .btn-blue:hover {
  9. @apply bg-blue-700;
  10. }
  11. </style>

Note that variants like hover:, focus:, and {screen}: can’t be applied directly, so instead apply the plain version of the utility to the appropriate pseudo-selector or media query.

To avoid unintended specificity issues, we recommend wrapping your custom component styles with the @layer components { ... } directive to tell Tailwind what layer those styles belong to:

  1. @tailwind base;
  2. @tailwind components;
  3. @tailwind utilities;
  4. @layer components {
  5. .btn-blue {
  6. @apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
  7. }
  8. .btn-blue:hover {
  9. @apply bg-blue-700;
  10. }
  11. }

Tailwind will automatically move those styles to the same place as @tailwind components, so you don’t have to worry about getting the order right in your source files.

Using the @layer directive will also instruct Tailwind to consider those styles for purging when purging the components layer. Read our Controlling File Size documentation for more details.

Keeping things composable

Say you have these two buttons:

Extracting Components - 图7

  1. <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  2. Button
  3. </button>
  4. <button class="bg-gray-400 hover:bg-gray-500 text-gray-800 font-bold py-2 px-4 rounded">
  5. Button
  6. </button>

It might be tempting to implement component classes for these buttons like this:

  1. @layer components {
  2. .btn-blue {
  3. @apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
  4. }
  5. .btn-blue:hover {
  6. @apply bg-blue-700;
  7. }
  8. .btn-gray {
  9. @apply bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded;
  10. }
  11. .btn-gray:hover {
  12. @apply bg-gray-500;
  13. }
  14. }

The issue with this approach is that you still have potentially painful duplication.

If you wanted to change the padding, font weight, or border radius of all the buttons on your site, you’d need to update every button class.

A better approach is to extract the parts that are the same into a separate class:

  1. @layer components {
  2. .btn {
  3. @apply font-bold py-2 px-4 rounded;
  4. }
  5. .btn-blue {
  6. @apply bg-blue-500 text-white;
  7. }
  8. .btn-blue:hover {
  9. @apply bg-blue-700;
  10. }
  11. .btn-gray {
  12. @apply bg-gray-400 text-gray-800;
  13. }
  14. .btn-gray:hover {
  15. @apply bg-gray-500;
  16. }
  17. }

Now you’d apply two classes any time you needed to style a button:

Extracting Components - 图8

  1. <button class="btn btn-blue">
  2. Button
  3. </button>
  4. <button class="btn btn-gray">
  5. Button
  6. </button>

This makes it easy to change the shared styles in one place by just editing the .btn class.

It also allows you to add new one-off button styles without being forced to create a new component class or duplicate the shared styles:

Extracting Components - 图9

  1. <button class="btn bg-green-500 hover:bg-green-400 text-white">
  2. Button
  3. </button>

Writing a component plugin

In addition to writing component classes directly in your CSS files, you can also add component classes to Tailwind by writing your own plugin:

  1. // tailwind.config.js
  2. const plugin = require('tailwindcss/plugin')
  3. module.exports = {
  4. plugins: [
  5. plugin(function({ addComponents }) {
  6. const buttons = {
  7. '.btn': {
  8. padding: '.5rem 1rem',
  9. borderRadius: '.25rem',
  10. fontWeight: '600',
  11. },
  12. '.btn-blue': {
  13. backgroundColor: '#3490dc',
  14. color: '#fff',
  15. '&:hover': {
  16. backgroundColor: '#2779bd'
  17. },
  18. },
  19. '.btn-red': {
  20. backgroundColor: '#e3342f',
  21. color: '#fff',
  22. '&:hover': {
  23. backgroundColor: '#cc1f1a'
  24. },
  25. },
  26. }
  27. addComponents(buttons)
  28. })
  29. ]
  30. }

This can be a good choice if you want to publish your Tailwind components as a library or make it easier to share components across multiple projects.

Learn more in the component plugin documentation.


← Adding Base Styles   Adding New Utilities →