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.

@component('_partials.code-sample', ['class' => 'p-8'])

Woman paying for a purchase
Marketing
Finding customers for your new business

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.

@endcomponent

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.

@component('_partials.code-sample', ['class' => 'text-center'])

@slot('code')

@endslot @endcomponent

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.

@component('_partials.tip-bad') Don't rely on CSS classes to extract complex components @endcomponent

@component('_partials.code-sample', ['class' => 'p-8'])

Beach
Private Villa
$299 USD per night

@slot('code')

Beach in Cancun
Private Villa
$299 USD per night
@endslot @endcomponent

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.

@component('_partials.tip-good') Create a template partial or JavaScript component @endcomponent

@php
$code = <<<EOF
<!-- In use -->
<VacationCard
  img="/img/cancun.jpg"
  imgAlt="Beach in Cancun"
  eyebrow="Private Villa"
  title="Relaxing All-Inclusive Resort in Cancun"
  pricing="$299 USD per night"
  url="/vacations/cancun"
/>

<!-- ./components/VacationCard.vue -->
<template>
  <div>
    <img className="rounded" :src="img" :alt="imgAlt"/>
    <div className="mt-2">
      <div>
        <div className="text-xs text-gray-600 uppercase font-bold">{{ eyebrow }}</div>
        <div className="font-bold text-gray-700 leading-snug">
          <a :href="url" className="hover:underline">{{ title }}</a>
        </div>
        <div className="mt-2 text-sm text-gray-600">{{ pricing }}</div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    props: ['img', 'imgAlt', 'eyebrow', 'title', 'pricing', 'url']
  }
</script>
EOF;
@endphp

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:

@component('_partials.code-sample', ['lang' => 'html', 'class' => 'text-center'])
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  Button
</button>

@slot('code')
<button className="btn-blue">
  Button
</button>

<style>
.btn-blue {
  @@apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
}
.btn-blue:hover {
  @@apply bg-blue-700;
}
</style>
@endslot
@endcomponent

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.

Classes like this should be added directly after the @@tailwind components directive to avoid specificity issues.

@component('_partials.tip-bad') Don't add custom component classes to the end of your CSS @endcomponent

@@tailwind base;

@@tailwind components;

@@tailwind utilities;

.btn-blue {
  @@apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
}
.btn-blue:hover {
  @@apply bg-blue-700;
}

@component('_partials.tip-good') Add custom component classes before your utilities @endcomponent

@@tailwind base;

@@tailwind components;

.btn-blue {
  @@apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
}
.btn-blue:hover {
  @@apply bg-blue-700;
}

@@tailwind utilities;

Keeping things composable

Say you have these two buttons:

@component('_partials.code-sample', ['lang' => 'html', 'class' => 'text-center'])

@slot('code')

@endslot @endcomponent

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

.btn-blue {
  @@apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
}
.btn-blue:hover {
  @@apply bg-blue-700;
}

.btn-gray {
  @@apply bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded;
}
.btn-gray:hover {
  @@apply bg-gray-500;
}

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:

.btn {
  @@apply font-bold py-2 px-4 rounded;
}

.btn-blue {
  @@apply bg-blue-500 text-white;
}
.btn-blue:hover {
  @@apply bg-blue-700;
}

.btn-gray {
  @@apply bg-gray-400 text-gray-800;
}
.btn-gray:hover {
  @@apply bg-gray-500;
}

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

@component('_partials.code-sample', ['lang' => 'html', 'class' => 'text-center'])

@slot('code')

@endslot @endcomponent

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:

@component('_partials.code-sample', ['lang' => 'html', 'class' => 'text-center'])

@slot('code')

@endslot @endcomponent

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:

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

module.exports = {
  plugins: [
    plugin(function({ addComponents }) {
      const buttons = {
        '.btn': {
          padding: '.5rem 1rem',
          borderRadius: '.25rem',
          fontWeight: '600',
        },
        '.btn-blue': {
          backgroundColor: '#3490dc',
          color: '#fff',
          '&:hover': {
            backgroundColor: '#2779bd'
          },
        },
        '.btn-red': {
          backgroundColor: '#e3342f',
          color: '#fff',
          '&:hover': {
            backgroundColor: '#cc1f1a'
          },
        },
      }

      addComponents(buttons)
    })
  ]
}

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.

Tailwind UI is now in early access!