Skip to main content
TailwindCSS Basics
CHAPTER 12 Beginner

Tailwind CSS Buttons and Components

Updated: May 12, 2026
25 min read

# Chapter 12: Tailwind CSS Buttons and Components

One of the most common critiques of Tailwind CSS is "HTML clutter." If every button requires 15 utility classes to look good, won't your codebase become an unreadable mess of repeated classes?

If you are writing plain HTML files, yes. But in modern web development, we use component-based frameworks (like React, Vue, Svelte, or Laravel Blade) to encapsulate these classes. In this chapter, we will learn how to abstract Tailwind classes into reusable button components.

---

1. Introduction

A component is a reusable piece of UI. Instead of copying and pasting <button class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">...</button> fifty times across your website, you create one <Button /> component.

By applying the Tailwind classes once inside the component definition, you maintain a single source of truth. If you want to change your brand's button from blue to purple, you change it in one file, and it updates everywhere.

2. Learning Objectives

By the end of this chapter, you will be able to:

  • Identify patterns in utility classes that should be extracted.
  • Understand the difference between extracting to CSS (@apply) vs extracting to Components.
  • Build a reusable Button component with variants (Primary, Secondary, Danger).
  • Understand how props dictate Tailwind class names in JS frameworks.

3. Beginner-Friendly Explanations

The DRY Principle

DRY stands for "Don't Repeat Yourself." If you find yourself highlighting a massive string of Tailwind classes and hitting CTRL+C and CTRL+V into another file, stop. That is a signal that you need to create a component.

Components vs @apply

  • @apply (Traditional way): You write .btn { @apply bg-blue-500 p-2; } in CSS, and use <button class="btn"> in HTML. This is discouraged by Tailwind's creators because you lose the benefits of seeing exactly what an element looks like in the markup.
  • Components (Modern way): You use a framework to create a <Button>Click</Button> tag. The component file contains the Tailwind classes. This is the recommended approach.

4. Syntax Explanation

Because Tailwind is a CSS framework, it doesn't dictate *how* you write your components (that depends on React vs Vue vs Blade). However, the standard pattern involves mapping a "prop" (like variant="primary") to a specific string of Tailwind classes.

5. Real-World Examples

In a React application, a developer will use a button like this: <Button variant="danger" size="lg">Delete Account</Button> Behind the scenes, the Button.jsx file takes those props and translates them to: class="bg-red-500 hover:bg-red-600 px-6 py-3 rounded-lg text-white font-bold"

6. Multiple Code Examples

Let's look at how to build and structure these components.

Example 1: The Bad Way (Repetition)

If you have 3 primary buttons on a page, doing this is an absolute nightmare to maintain:

html
1234
<!-- TailwindCSS Example: Anti-pattern -->
<button class="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button>
<button class="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Update</button>
<button class="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Publish</button>

Example 2: The @apply Way (Okay for plain HTML)

If you are NOT using React/Vue/Blade, and just writing plain HTML files, you can use @apply in your input.css:

css
123456
/* Custom CSS */
@layer components {
  .btn-primary {
    @apply bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2;
  }
}

Usage:

html
1
<button class="btn-primary">Save</button>

Example 3: Component Pattern (React/JSX Concept)

The best way. Define base classes, and variant classes.

javascript
12345678910111213141516
/* React Component Concept */
const Button = ({ variant = &#039;primary&#039;, children }) => {
  // Base classes that apply to ALL buttons
  const baseClasses = "font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none";
  
  // Classes specific to the variant
  const variants = {
    primary: "bg-indigo-600 hover:bg-indigo-700 text-white focus:ring-indigo-500",
    secondary: "bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-indigo-500",
    danger: "bg-red-600 hover:bg-red-700 text-white focus:ring-red-500"
  };

  const className = `${baseClasses} ${variants[variant]}`;

  return <button className={className}>{children}</button>;
}

Example 4: A Complete Component Library Preview

Using the concept above, you can build a massive library.

html
1234567891011121314151617
<!-- TailwindCSS Example: Rendered output of our Component -->
<div class="flex gap-4 p-8 bg-gray-50">
  <!-- <Button variant="primary"> -->
  <button class="font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none bg-indigo-600 hover:bg-indigo-700 text-white focus:ring-indigo-500">
    Primary Action
  </button>

  <!-- <Button variant="secondary"> -->
  <button class="font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-indigo-500">
    Secondary Action
  </button>

  <!-- <Button variant="danger"> -->
  <button class="font-semibold py-2 px-4 rounded-md shadow-sm transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none bg-red-600 hover:bg-red-700 text-white focus:ring-red-500">
    Delete Account
  </button>
</div>

Example 5: Icon Buttons

html
12345
<!-- TailwindCSS Example -->
<button class="flex items-center gap-2 bg-slate-900 text-white px-4 py-2 rounded-lg hover:bg-slate-800 transition">
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
  <span>Download</span>
</button>

Example 6: Ghost / Text Buttons

html
1234
<!-- TailwindCSS Example -->
<button class="text-indigo-600 font-semibold hover:bg-indigo-50 px-4 py-2 rounded-md transition">
  View Details →
</button>

7. Output Explanations

In Example 3, notice how we separated baseClasses from variants. All buttons share the same padding (py-2 px-4), rounding (rounded-md), and structural focus setup. But only the primary variant gets the blue background. This architecture ensures all your buttons are the exact same size and shape, creating perfect design consistency across your app.

8. Common Mistakes

  1. 1. Creating too many @apply classes: If you create .btn-primary, .btn-secondary, .card-wrapper, .nav-link, etc., you have just reinvented Bootstrap, and you are no longer using Tailwind's utility-first benefits.
  1. 2. Dynamic Class Generation Error: In Javascript, doing this: <div class="bg-${color}-500"> will NOT WORK. Tailwind scans files for *complete* class names (like bg-red-500). It cannot evaluate JS variables. Always write out the full class name in your component logic maps!

9. Best Practices

  • Use a utility like clsx or tailwind-merge: In JS frameworks, dynamically combining strings of classes can lead to conflicts (e.g., passing bg-red-500 to a button that already has bg-blue-500 in its base classes). Libraries like tailwind-merge intelligently resolve these conflicts by ensuring the last class wins.
  • Keep it Simple: A button component usually needs variant (color), size (padding/font size), and disabled props. Don't overcomplicate it.

10. Exercises

  1. 1. Design the HTML/Tailwind string for a "Success" button variant (emerald green).
  1. 2. Design the HTML/Tailwind string for a "Large" size button (larger text, thicker padding).
  1. 3. Combine them in HTML to create a large success button.

11. Mini Project: Button Component Library

Let's build out the HTML representations of a complete Button Design System.

html
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
<!-- TailwindCSS Example: Button Design System -->
<div class="p-12 bg-white max-w-4xl mx-auto space-y-12">
  
  <!-- Sizes -->
  <div>
    <h3 class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-4">1. Sizes</h3>
    <div class="flex items-end gap-4">
      <!-- Size: sm -->
      <button class="bg-indigo-600 text-white font-medium rounded-md px-3 py-1.5 text-sm hover:bg-indigo-700 transition">Small</button>
      
      <!-- Size: md (Base) -->
      <button class="bg-indigo-600 text-white font-medium rounded-md px-4 py-2 text-base hover:bg-indigo-700 transition">Medium (Base)</button>
      
      <!-- Size: lg -->
      <button class="bg-indigo-600 text-white font-medium rounded-lg px-6 py-3 text-lg hover:bg-indigo-700 transition">Large Action</button>
    </div>
  </div>

  <!-- Variants -->
  <div>
    <h3 class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-4">2. Variants (Base Size)</h3>
    <div class="flex flex-wrap gap-4">
      <!-- Primary -->
      <button class="bg-indigo-600 text-white font-medium rounded-md px-4 py-2 hover:bg-indigo-700 shadow-sm transition">Primary</button>
      
      <!-- Secondary / Outline -->
      <button class="bg-white text-gray-700 border border-gray-300 font-medium rounded-md px-4 py-2 hover:bg-gray-50 shadow-sm transition">Secondary</button>
      
      <!-- Soft / Tinted -->
      <button class="bg-indigo-50 text-indigo-700 font-medium rounded-md px-4 py-2 hover:bg-indigo-100 transition">Soft Tint</button>
      
      <!-- Ghost -->
      <button class="bg-transparent text-gray-600 font-medium rounded-md px-4 py-2 hover:bg-gray-100 transition">Ghost Style</button>
    </div>
  </div>

  <!-- States -->
  <div>
    <h3 class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-4">3. States</h3>
    <div class="flex gap-4">
      <!-- Disabled -->
      <button disabled class="bg-indigo-600 text-white font-medium rounded-md px-4 py-2 opacity-50 cursor-not-allowed">Disabled</button>
      
      <!-- Loading State (SVG Spinner + text) -->
      <button disabled class="bg-indigo-600 text-white font-medium rounded-md px-4 py-2 flex items-center gap-2 opacity-75 cursor-wait">
        <svg class="animate-spin h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
          <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
          <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        Processing...
      </button>
    </div>
  </div>

</div>

Output Explanation: This represents how a UI/UX designer thinks about buttons. By standardizing the padding (e.g., px-4 py-2) and font sizes across variants, you guarantee that if a developer places a Primary button next to a Secondary button, they align perfectly. The Loading state uses Tailwind's built in animate-spin utility on the SVG!

12. Coding Challenges

Challenge: Create a Floating Action Button (FAB). It should be a large perfect circle, fixed to the bottom right of the screen, with a heavy shadow, containing a large plus icon.

13. MCQs with Answers

Question 1

Why is using @apply heavily to create traditional CSS classes (like .btn) discouraged?

Question 2

When building a component in Javascript, which of the following string concatenations will fail to be compiled by Tailwind?

14. Interview Questions

Q: If you shouldn't use @apply, how do you prevent repeating 15 Tailwind classes on every single button in a pure HTML project (no JS framework)? *Answer:* If you are strictly using plain HTML and a backend language without a templating engine, you *can* use @apply` for highly repeated elements like buttons. However, the modern standard is to use the templating features of your backend framework (like Laravel Blade components, or Django/Twig includes) to abstract the markup into a reusable file, keeping the raw Tailwind classes in the template.

15. FAQs

Q: Are there pre-built Tailwind component libraries? A: Yes! Headless UI, Radix UI, and shadcn/ui provide excellent, accessible component logic that you style with Tailwind. Tailwind UI is the official premium component library.

16. Summary

Tailwind is designed to be used in component-driven architectures. By defining your complex utility strings inside reusable React, Vue, or Blade components, you keep your templates DRY (Don't Repeat Yourself) while retaining all the power and flexibility of utility-first CSS.

17. Next Chapter Recommendation

Now that we know how to build buttons, we need forms for them to submit! In Chapter 13: Tailwind CSS Forms Styling, we will learn how to tame the notoriously difficult default browser input styles and create beautiful, accessible forms.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·