Tailwind CSS Button Styles: A Practical Guide

Build a complete button system with Tailwind CSS covering variants, sizes, disabled and loading states, icon buttons, and button groups with practical code examples.

·5 min read·Tailwind CSS
Tailwind CSS Button Styles: A Practical Guide

Buttons are the most interactive element on any page, and they're usually the first component developers build when starting a project. Getting them right with Tailwind CSS means thinking through variants, sizes, states, and edge cases upfront. (When you're ready to formalize button variants alongside the rest of your tokens, our Tailwind design system guide covers how to slot this into a larger system.)

Here's how to build a complete button system using nothing but Tailwind utility classes.

Base Styles

Every button variant shares a foundation. Define these once and build on top of them:

/* Base button */
/* inline-flex items-center justify-center gap-2 rounded-lg
   text-sm font-medium whitespace-nowrap
   transition-colors duration-150
   focus-visible:outline-none focus-visible:ring-2
   focus-visible:ring-offset-2 focus-visible:ring-blue-500
   disabled:opacity-50 disabled:pointer-events-none */

Key details here:

  • inline-flex items-center justify-center gap-2 handles text and icon alignment.
  • whitespace-nowrap prevents button text from wrapping.
  • transition-colors duration-150 makes hover and active states feel smooth.
  • The focus-visible utilities provide keyboard-accessible focus rings without showing them on mouse clicks.
  • disabled styles use pointer-events-none to prevent click handlers from firing.

Variants

A solid button system needs at least four variants: primary, secondary, outline, and ghost.

/* Primary — high-emphasis actions */
/* bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 */

/* Secondary — medium-emphasis actions */
/* bg-gray-100 text-gray-900 hover:bg-gray-200 active:bg-gray-300
   dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700 */

/* Outline — low-emphasis or paired with primary */
/* border border-gray-300 bg-transparent text-gray-700
   hover:bg-gray-50 active:bg-gray-100
   dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800 */

/* Ghost — minimal emphasis, often for navigation or toolbars */
/* bg-transparent text-gray-700 hover:bg-gray-100 active:bg-gray-200
   dark:text-gray-300 dark:hover:bg-gray-800 */

You might also want a destructive variant for delete confirmations:

/* Destructive */
/* bg-red-600 text-white hover:bg-red-700 active:bg-red-800 */

Sizes

Three sizes cover most use cases. The differences are in padding, font size, and height:

/* Small — compact UIs, tables, toolbars */
/* h-8 px-3 text-xs rounded-md */

/* Medium (default) */
/* h-10 px-4 text-sm rounded-lg */

/* Large — hero CTAs, prominent actions */
/* h-12 px-6 text-base rounded-lg */

Using fixed heights (h-8, h-10, h-12) instead of relying on padding alone ensures buttons align consistently in flex and grid layouts.

Loading State

A loading button should be visually distinct and non-interactive. Replace the label text with a spinner, or overlay one:

/* Loading button */
/* relative text-transparent pointer-events-none */

/* Spinner (pseudo-element or child) */
.button-spinner {
  position: absolute;
  width: 1rem;
  height: 1rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 9999px;
  animation: spin 0.6s linear infinite;
}

The text-transparent trick keeps the button the same width as its non-loading state (the text is still there, just invisible). This prevents layout shifts when toggling between states.

An alternative approach keeps the text visible alongside the spinner:

/* Loading with visible text */
/* opacity-80 pointer-events-none */
/* Prepend: animate-spin h-4 w-4 border-2 border-current
   border-r-transparent rounded-full */

Icon Buttons

Square buttons with only an icon need equal width and height. Use the same height values from your size system:

/* Icon button — small */
/* h-8 w-8 p-0 */

/* Icon button — medium */
/* h-10 w-10 p-0 */

/* Icon button — large */
/* h-12 w-12 p-0 */

Always include an aria-label on icon-only buttons for accessibility. The visual icon is meaningless to screen readers.

Button Groups

When buttons sit side by side in a group, remove individual border radii and collapse shared borders:

/* Button group container */
/* inline-flex rounded-lg overflow-hidden */

/* Group items */
/* rounded-none border-r border-gray-200 last:border-r-0 */

The container handles the outer radius, and each item removes its own rounding. The overflow-hidden on the container clips the corners cleanly.

Advanced: Animated Borders and Hover Effects

If you want buttons that feel more premium, animated borders and gradient hover effects add polish without heavy dependencies. Our roundup of creative CSS hover effects for buttons collects ten patterns you can drop in directly. This is also where I draw inspiration from component libraries like Spell UI, which offers animated border and hover components that go beyond flat color swaps.

A simple gradient hover effect using only Tailwind:

/* Gradient hover */
/* relative overflow-hidden bg-gray-900 text-white */
/* Before pseudo-element: */
.gradient-button::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.1) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  transform: translateX(-100%);
  transition: transform 0.5s ease;
}
.gradient-button:hover::before {
  transform: translateX(100%);
}

This creates a light sweep effect on hover — subtle enough for professional UIs, but noticeable enough to feel intentional. For more advanced motion you can build in your config, see our custom animations with Tailwind CSS guide.

Dark Mode Considerations

Every variant needs dark mode treatment. For the underlying setup, see our Tailwind dark mode guide. The general pattern:

  • Primary — Usually works as-is since the brand color is consistent across themes.
  • Secondary — Swap light grays for dark grays (bg-gray-100 becomes dark:bg-gray-800).
  • Outline — Adjust border and text colors for contrast on dark backgrounds.
  • Ghost — Same swap as secondary for the hover background.

Test each variant against both light and dark backgrounds. Contrast ratios should meet WCAG AA (4.5:1 for text, 3:1 for interactive elements).

Putting It Together

A complete button system with four variants, three sizes, loading state, icon support, and dark mode covers 95% of real-world needs. Build it once, document the combinations, and stop reinventing button styles on every project.

More Articles