# Tailwind CSS Tips: Patterns You Should Be Using

> Level up your Tailwind CSS workflow with utility composition patterns, custom themes, responsive techniques, and productivity tips for modern UIs.

- URL: https://www.browserutils.dev/blog/tailwind-css-tips
- Published: 2026-08-03
- Updated: 2026-07-02

---

Tailwind CSS has fundamentally changed how many developers write CSS. But there is a significant difference between using Tailwind and using Tailwind well. Many developers stop at basic utility classes and miss the patterns that make Tailwind truly productive. This guide covers the techniques that experienced Tailwind users rely on daily.

## Utility Composition Patterns

### The Component Pattern with @apply

When a set of utilities repeats across many elements, extract it with `@apply`:

```css
/* In your CSS file */
@layer components {
  .btn {
    @apply px-4 py-2 rounded-lg font-medium transition-colors duration-200;
  }

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

  .btn-secondary {
    @apply btn bg-gray-200 text-gray-800 hover:bg-gray-300;
  }
}
```

**When to use @apply:** Only extract utilities when a group of classes represents a reusable component (buttons, cards, badges). Do not extract every combination — that defeats the purpose of utility-first CSS.

**When not to use @apply:** One-off layouts, page-specific styling, or anything that only appears once. Just use the utilities inline.

### Component Libraries with Props

In React, Vue, or Svelte, build component variants with conditional classes:

```jsx
function Button({ variant = 'primary', size = 'md', children, ...props }) {
  const baseClasses = 'rounded-lg font-medium transition-colors duration-200';

  const variants = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
    danger: 'bg-red-600 text-white hover:bg-red-700',
  };

  const sizes = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg',
  };

  return (
    <button className={`${baseClasses} ${variants[variant]} ${sizes[size]}`} {...props}>
      {children}
    </button>
  );
}
```

For more complex variant logic, use the `class-variance-authority` (CVA) library:

```javascript
import { cva } from 'class-variance-authority';

const button = cva('rounded-lg font-medium transition-colors duration-200', {
  variants: {
    intent: {
      primary: 'bg-blue-600 text-white hover:bg-blue-700',
      secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
    },
    size: {
      sm: 'px-3 py-1.5 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg',
    },
  },
  defaultVariants: {
    intent: 'primary',
    size: 'md',
  },
});
```

## Custom Themes

### Extending the Default Theme

```javascript
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#eff6ff',
          100: '#dbeafe',
          200: '#bfdbfe',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
          900: '#1e3a5f',
        },
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
        mono: ['JetBrains Mono', 'monospace'],
      },
      spacing: {
        '18': '4.5rem',
        '88': '22rem',
      },
      borderRadius: {
        '4xl': '2rem',
      },
    },
  },
};
```

Use `extend` to add values without removing Tailwind's defaults. Only override the full key (without `extend`) when you want to replace the default scale entirely.

### CSS Custom Properties for Dynamic Theming

```javascript
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        surface: 'hsl(var(--color-surface) / <alpha-value>)',
        'on-surface': 'hsl(var(--color-on-surface) / <alpha-value>)',
        primary: 'hsl(var(--color-primary) / <alpha-value>)',
      },
    },
  },
};
```

```css
/* Light theme */
:root {
  --color-surface: 0 0% 100%;
  --color-on-surface: 222 47% 11%;
  --color-primary: 221 83% 53%;
}

/* Dark theme */
.dark {
  --color-surface: 222 47% 11%;
  --color-on-surface: 210 40% 98%;
  --color-primary: 217 91% 60%;
}
```

Now `bg-surface`, `text-on-surface`, and `text-primary` automatically adapt to the current theme. This is more flexible than Tailwind's built-in `dark:` variant for complex theming.

## Responsive Design Patterns

### Mobile-First Defaults

Tailwind is mobile-first. Unprefixed utilities apply to all screens, and breakpoint prefixes apply at that breakpoint and above:

```html
<!-- Mobile: stacked, Tablet+: side by side, Desktop: 3 columns -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  <div>Card 1</div>
  <div>Card 2</div>
  <div>Card 3</div>
</div>
```

### Container Queries

Tailwind v3.4+ supports container queries for component-based responsiveness:

```html
<div class="@container">
  <div class="flex flex-col @md:flex-row gap-4">
    <img class="w-full @md:w-48" src="..." alt="...">
    <div>
      <h3 class="text-lg @lg:text-xl">Title</h3>
      <p>Description</p>
    </div>
  </div>
</div>
```

Container queries respond to the parent's width, not the viewport. This makes components truly self-contained.

### Responsive Typography

```html
<h1 class="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold">
  Responsive Heading
</h1>
```

Or use `clamp()` for fluid typography:

```css
@layer utilities {
  .text-fluid-lg {
    font-size: clamp(1.25rem, 2vw + 0.5rem, 2rem);
  }
}
```

## Dark Mode

### Class-Based Dark Mode

```javascript
// tailwind.config.js
module.exports = {
  darkMode: 'class',
};
```

```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <h1 class="text-gray-800 dark:text-gray-200">Title</h1>
  <p class="text-gray-600 dark:text-gray-400">Content</p>
</div>
```

Toggle the `dark` class on the `<html>` element with JavaScript:

```javascript
document.documentElement.classList.toggle('dark');
```

### Respecting System Preferences

```javascript
// Check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

// Apply on load
if (prefersDark && !localStorage.theme) {
  document.documentElement.classList.add('dark');
}
```

## Advanced Patterns

### Group and Peer Modifiers

Style children based on parent state:

```html
<a href="#" class="group block p-4 rounded-lg hover:bg-blue-50">
  <h3 class="font-medium group-hover:text-blue-600">Card Title</h3>
  <p class="text-gray-500 group-hover:text-gray-700">Description</p>
</a>
```

Style siblings based on another element's state:

```html
<label>
  <input type="checkbox" class="peer sr-only">
  <span class="peer-checked:text-blue-600 peer-checked:font-bold">
    Toggle option
  </span>
</label>
```

### Arbitrary Values

When design tokens do not cover your exact need:

```html
<div class="w-[calc(100%-2rem)] top-[117px] bg-[#1a1a2e] grid-cols-[1fr_auto_1fr]">
```

Use sparingly. If you find yourself using arbitrary values frequently, add them to your theme configuration instead.

### Prose for Long-Form Content

The `@tailwindcss/typography` plugin styles rendered Markdown and HTML content:

```html
<article class="prose prose-lg dark:prose-invert max-w-none">
  <!-- Rendered Markdown content gets beautiful default styling -->
</article>
```

## Performance Tips

### Purging Unused Styles

Tailwind v3+ uses JIT mode by default, generating only the classes you use. Ensure your `content` configuration covers all template files:

```javascript
module.exports = {
  content: [
    './src/**/*.{html,js,jsx,ts,tsx,vue,svelte,astro}',
    './public/index.html',
  ],
};
```

Missing paths means missing styles in production. Extra paths means slightly slower builds but no impact on output.

### Avoiding Class Duplication

Use the `tailwind-merge` library to handle class conflicts:

```javascript
import { twMerge } from 'tailwind-merge';

// Later class wins
twMerge('px-4 py-2', 'px-6');  // 'py-2 px-6'
```

This is essential when components accept className props that may conflict with base styles.

## Useful Tools

When building custom themes, picking the right colors is critical. Our [Tailwind Color Picker](/tools/tailwind-color-picker) helps you find and fine-tune Tailwind color values, showing the full shade palette and providing the exact class names and hex values. For rapid prototyping, the [Tailwind Playground](/tools/tailwind-playground) lets you experiment with Tailwind classes and see results instantly without setting up a project.

## Key Takeaways

1. Extract components with `@apply` only when you have true reuse — three or more identical instances
2. Build variant systems with CVA or simple conditional classes
3. Use CSS custom properties with Tailwind for dynamic theming beyond light/dark
4. Design mobile-first and add complexity at larger breakpoints
5. Leverage `group` and `peer` modifiers instead of custom JavaScript for interactive styling
6. Keep arbitrary values to a minimum — extend the theme instead
7. Always verify your `content` paths include all template files

Tailwind is at its best when you embrace the utility-first philosophy fully. Resist the urge to recreate traditional CSS patterns. Instead, build components from utilities, compose variants declaratively, and let the framework handle the CSS optimization.