Skip to main content
Technology & EngineeringCss Styling Services250 lines

Tailwind CSS

"Tailwind CSS: utility-first CSS, responsive design, dark mode, custom theme, plugins, @apply, JIT, content configuration, arbitrary values"

Quick Summary29 lines
Tailwind CSS is a utility-first CSS framework that provides low-level utility classes to build custom designs directly in markup. Instead of writing custom CSS, you compose styles by combining small, single-purpose classes. The JIT (Just-In-Time) engine generates only the CSS you actually use, producing tiny production bundles. Tailwind encourages co-locating style decisions with markup, eliminating the mental overhead of naming CSS classes and the risk of dead CSS accumulating over time.

## Key Points

2. **Keep utility strings readable.** Group related utilities logically (layout, spacing, typography, color, state) and break long class lists across multiple lines.
3. **Leverage design tokens via @theme.** Define colors, spacing scales, and fonts in @theme to maintain consistency and enable theming.
4. **Use responsive prefixes mobile-first.** Start with the smallest screen and layer on `sm:`, `md:`, `lg:` overrides for larger viewports.
5. **Prefer semantic color names.** Use `text-brand` rather than `text-blue-500` so the palette can change in one place.
6. **Use `clsx` or `cva` for conditional classes.** Avoid string concatenation for variant logic; use a utility library for clarity and correctness.
7. **Purge unused CSS in production.** Ensure content paths are correctly configured so the JIT engine can tree-shake unused utilities.
1. **Overusing @apply to recreate BEM.** This defeats the purpose of utility-first CSS and reintroduces the naming and dead-CSS problems Tailwind eliminates.
2. **Inline style objects alongside Tailwind classes.** Mixing `style={{ marginTop: 12 }}` with className utilities creates two competing style systems and makes maintenance harder.
3. **Hardcoding pixel values everywhere.** Using `w-[347px]` instead of the spacing scale or a responsive unit leads to brittle layouts that break at different viewports.
4. **Ignoring dark mode from the start.** Retrofitting dark mode is painful; always add `dark:` variants alongside their light counterparts.
5. **Applying state variants to parent and child redundantly.** Use the `group-hover:` and `peer-*:` utilities to derive child state from a parent rather than duplicating hover listeners.

## Quick Example

```bash
npm install -D tailwindcss @tailwindcss/postcss postcss
```

```css
@import "tailwindcss";
@source "../components/**/*.{ts,tsx}";
@source "../lib/**/*.{ts,tsx}";
```
skilldb get css-styling-services-skills/Tailwind CSSFull skill: 250 lines
Paste into your CLAUDE.md or agent config

Tailwind CSS

Core Philosophy

Tailwind CSS is a utility-first CSS framework that provides low-level utility classes to build custom designs directly in markup. Instead of writing custom CSS, you compose styles by combining small, single-purpose classes. The JIT (Just-In-Time) engine generates only the CSS you actually use, producing tiny production bundles. Tailwind encourages co-locating style decisions with markup, eliminating the mental overhead of naming CSS classes and the risk of dead CSS accumulating over time.

Setup

Installation with Next.js / React + Vite

npm install -D tailwindcss @tailwindcss/postcss postcss

PostCSS Configuration

// postcss.config.js
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

CSS Entry Point

/* app/globals.css */
@import "tailwindcss";

/* Custom theme overrides */
@theme {
  --color-brand: #3b82f6;
  --color-brand-dark: #1d4ed8;
  --font-family-display: "Inter", sans-serif;
}

Content Configuration

Tailwind v4 automatically detects your source files. For explicit control, use the @source directive:

@import "tailwindcss";
@source "../components/**/*.{ts,tsx}";
@source "../lib/**/*.{ts,tsx}";

Key Techniques

Responsive Design with Mobile-First Breakpoints

function ProductCard({ product }: { product: Product }) {
  return (
    <div className="w-full p-4 sm:w-1/2 md:w-1/3 lg:w-1/4">
      <div className="rounded-lg border border-gray-200 bg-white shadow-sm
                      transition-shadow hover:shadow-md">
        <img
          src={product.image}
          alt={product.name}
          className="h-48 w-full rounded-t-lg object-cover sm:h-56 lg:h-64"
        />
        <div className="p-4 sm:p-6">
          <h3 className="text-lg font-semibold text-gray-900 sm:text-xl">
            {product.name}
          </h3>
          <p className="mt-1 text-sm text-gray-500 line-clamp-2 sm:line-clamp-3">
            {product.description}
          </p>
          <span className="mt-3 block text-xl font-bold text-brand">
            ${product.price}
          </span>
        </div>
      </div>
    </div>
  );
}

Dark Mode

function SettingsPanel() {
  return (
    <section className="rounded-xl bg-white p-6 shadow dark:bg-gray-800 dark:shadow-gray-900/30">
      <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100">
        Settings
      </h2>
      <p className="mt-2 text-gray-600 dark:text-gray-400">
        Manage your preferences below.
      </p>
      <button
        className="mt-4 rounded-lg bg-brand px-4 py-2 text-white
                   hover:bg-brand-dark
                   dark:bg-blue-500 dark:hover:bg-blue-400"
      >
        Save Changes
      </button>
    </section>
  );
}

Arbitrary Values and Custom Properties

function HeroBanner() {
  return (
    <div className="relative h-[calc(100vh-4rem)] bg-[url('/hero.webp')]
                    bg-cover bg-[center_25%]">
      <div className="absolute inset-0 bg-gradient-to-b from-black/60 to-black/20" />
      <div className="relative z-10 flex h-full items-end pb-[clamp(2rem,8vw,6rem)]
                      px-[5vw]">
        <h1 className="text-[clamp(2rem,5vw,4.5rem)] font-extrabold leading-tight
                       text-white drop-shadow-lg">
          Build Something Great
        </h1>
      </div>
    </div>
  );
}

Extracting Components with @apply

/* Use sparingly — prefer component abstraction in React instead */
@layer components {
  .btn-primary {
    @apply inline-flex items-center justify-center rounded-lg bg-brand
           px-5 py-2.5 text-sm font-medium text-white
           transition-colors hover:bg-brand-dark
           focus:outline-none focus:ring-2 focus:ring-brand/50
           disabled:cursor-not-allowed disabled:opacity-50;
  }

  .input-field {
    @apply block w-full rounded-lg border border-gray-300 bg-white
           px-4 py-2.5 text-sm text-gray-900 placeholder-gray-400
           focus:border-brand focus:ring-1 focus:ring-brand
           dark:border-gray-600 dark:bg-gray-700 dark:text-white;
  }
}

Custom Plugin

// tailwind-plugins/typography-utils.js
import plugin from "tailwindcss/plugin";

export const typographyUtils = plugin(({ addUtilities, theme }) => {
  addUtilities({
    ".text-balance": {
      "text-wrap": "balance",
    },
    ".text-pretty": {
      "text-wrap": "pretty",
    },
    ".truncate-2": {
      display: "-webkit-box",
      "-webkit-line-clamp": "2",
      "-webkit-box-orient": "vertical",
      overflow: "hidden",
    },
  });
});

Conditional Classes with clsx

import { clsx } from "clsx";

interface BadgeProps {
  variant: "success" | "warning" | "error" | "info";
  children: React.ReactNode;
}

function Badge({ variant, children }: BadgeProps) {
  return (
    <span
      className={clsx(
        "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
        {
          "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400":
            variant === "success",
          "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400":
            variant === "warning",
          "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400":
            variant === "error",
          "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400":
            variant === "info",
        }
      )}
    >
      {children}
    </span>
  );
}

Animation Utilities

function LoadingSpinner() {
  return (
    <div className="flex items-center gap-3">
      <svg
        className="h-5 w-5 animate-spin text-brand"
        viewBox="0 0 24 24"
        fill="none"
      >
        <circle className="opacity-25" cx="12" cy="12" r="10"
                stroke="currentColor" strokeWidth="4" />
        <path className="opacity-75" fill="currentColor"
              d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
      </svg>
      <span className="animate-pulse text-sm text-gray-500">Loading...</span>
    </div>
  );
}

Best Practices

  1. Use the component pattern over @apply. Extract React components rather than creating @apply classes. The component already encapsulates markup and logic, so it is the natural place for style encapsulation too.
  2. Keep utility strings readable. Group related utilities logically (layout, spacing, typography, color, state) and break long class lists across multiple lines.
  3. Leverage design tokens via @theme. Define colors, spacing scales, and fonts in @theme to maintain consistency and enable theming.
  4. Use responsive prefixes mobile-first. Start with the smallest screen and layer on sm:, md:, lg: overrides for larger viewports.
  5. Prefer semantic color names. Use text-brand rather than text-blue-500 so the palette can change in one place.
  6. Use clsx or cva for conditional classes. Avoid string concatenation for variant logic; use a utility library for clarity and correctness.
  7. Purge unused CSS in production. Ensure content paths are correctly configured so the JIT engine can tree-shake unused utilities.

Anti-Patterns

  1. Overusing @apply to recreate BEM. This defeats the purpose of utility-first CSS and reintroduces the naming and dead-CSS problems Tailwind eliminates.
  2. Inline style objects alongside Tailwind classes. Mixing style={{ marginTop: 12 }} with className utilities creates two competing style systems and makes maintenance harder.
  3. Hardcoding pixel values everywhere. Using w-[347px] instead of the spacing scale or a responsive unit leads to brittle layouts that break at different viewports.
  4. Ignoring dark mode from the start. Retrofitting dark mode is painful; always add dark: variants alongside their light counterparts.
  5. Applying state variants to parent and child redundantly. Use the group-hover: and peer-*: utilities to derive child state from a parent rather than duplicating hover listeners.
  6. Neglecting accessibility. Utility classes make it easy to forget semantic HTML. Always pair visual styling with proper aria-* attributes, focus rings (focus-visible:), and keyboard navigation.

Install this skill directly: skilldb add css-styling-services-skills

Get CLI access →