Skip to content
Guides

Tailwind CSS from Zero to Production - A Complete Guide

Published on:
·7 min read·Author: MDS Software Solutions Group

Tailwind CSS from

poradniki

Tailwind CSS from Zero to Production

Tailwind CSS is a utility-first CSS framework that has revolutionized how developers build user interfaces. Instead of writing custom CSS classes and fighting cascade specificity, Tailwind lets you compose designs directly in your markup using small, single-purpose utility classes. In this guide, we will walk you through the entire journey - from your first installation to an optimized production deployment.

What Is the Utility-First Approach?#

Traditional CSS frameworks like Bootstrap offer pre-built components - buttons, cards, navbars. Tailwind takes a fundamentally different approach. Instead of ready-made components, it provides utility classes that you use to build your own:

<!-- Traditional approach with custom CSS classes -->
<div class="card">
  <h2 class="card-title">Title</h2>
  <p class="card-description">Card description</p>
  <button class="card-button">Action</button>
</div>

<!-- Utility-first approach with Tailwind CSS -->
<div class="rounded-lg border border-gray-200 bg-white p-6 shadow-md">
  <h2 class="mb-2 text-xl font-bold text-gray-900">Title</h2>
  <p class="mb-4 text-gray-600">Card description</p>
  <button class="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700">
    Action
  </button>
</div>

At first glance, the HTML looks cluttered with classes. But in practice, you gain tremendous flexibility. You do not need to invent class names, you do not fight CSS specificity, and you do not jump between HTML and CSS files. Every visual change is immediately visible in the component code.

Why Utility-First Wins#

  • No dead CSS - you only use the classes you need
  • Design consistency - predefined scales for colors, spacing, typography
  • Rapid prototyping - build UI without jumping between files
  • Easy changes - modify classes directly in the component
  • No side effects - changing one component does not break others

Installation and Configuration#

Tailwind CSS v3 - Classic Installation#

Installing Tailwind CSS in a Node.js-based project is straightforward:

# Initialize project (if it does not exist)
npm init -y

# Install Tailwind CSS and dependencies
npm install -D tailwindcss postcss autoprefixer

# Generate configuration files
npx tailwindcss init -p

The init -p command creates two files: tailwind.config.js and postcss.config.js. Now configure the paths to your template files:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

Add Tailwind directives to your main CSS file:

/* src/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Tailwind CSS v4 - A New Era#

Tailwind CSS v4 (released in 2025) introduces fundamental architectural changes. The most significant change is the move from tailwind.config.js to CSS-native configuration:

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

@theme {
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;
  --font-display: "Inter", sans-serif;
  --breakpoint-3xl: 1920px;
}

Installation in v4 is even simpler:

npm install tailwindcss @tailwindcss/vite

# Or for PostCSS
npm install tailwindcss @tailwindcss/postcss

For Vite projects, just add the plugin:

// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [tailwindcss()],
});

In Tailwind v4, you no longer need to define a content array - the framework automatically detects source files. JIT is the default and only compilation mode.

Responsive Design#

Tailwind uses a mobile-first approach. Classes without a prefix target the smallest screens, while breakpoint prefixes (sm:, md:, lg:, xl:, 2xl:) add styles for larger screens:

<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
  <div class="rounded-lg bg-white p-4 shadow">
    <h3 class="text-lg font-semibold md:text-xl lg:text-2xl">
      Responsive heading
    </h3>
    <p class="mt-2 text-sm text-gray-600 md:text-base">
      Text adapts to screen size.
    </p>
  </div>
</div>

Default breakpoints in Tailwind:

| Prefix | Min width | Typical device | |--------|-----------|----------------| | sm | 640px | Large phones | | md | 768px | Tablets | | lg | 1024px | Laptops | | xl | 1280px | Desktops | | 2xl | 1536px | Large monitors |

You can easily customize breakpoints in the configuration:

// tailwind.config.js (v3)
module.exports = {
  theme: {
    screens: {
      tablet: "640px",
      laptop: "1024px",
      desktop: "1280px",
    },
  },
};
/* globals.css (v4) */
@theme {
  --breakpoint-tablet: 640px;
  --breakpoint-laptop: 1024px;
  --breakpoint-desktop: 1280px;
}

Dark Mode#

Tailwind offers built-in support for dark mode. Simply use the dark: prefix:

<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-white">
  <h1 class="text-2xl font-bold text-blue-600 dark:text-blue-400">
    Theme-aware heading
  </h1>
  <p class="text-gray-600 dark:text-gray-300">
    This text automatically changes color in dark mode.
  </p>
  <button class="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">
    Button
  </button>
</div>

Tailwind supports two dark mode strategies:

// tailwind.config.js
module.exports = {
  // 'media' strategy - uses system preferences
  darkMode: "media",

  // 'class' strategy - manual control via class on <html>
  darkMode: "class",

  // 'selector' strategy (v4) - any selector
  darkMode: ["selector", '[data-theme="dark"]'],
};

Example dark mode toggle in React:

function ThemeToggle() {
  const [dark, setDark] = useState(false);

  useEffect(() => {
    document.documentElement.classList.toggle("dark", dark);
  }, [dark]);

  return (
    <button
      onClick={() => setDark(!dark)}
      className="rounded-full bg-gray-200 p-2 dark:bg-gray-700"
    >
      {dark ? "Light mode" : "Dark mode"}
    </button>
  );
}

Custom Theme Configuration#

Tailwind allows extensive theme customization. You can extend the default theme or override it entirely:

// tailwind.config.js (v3)
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#eff6ff",
          100: "#dbeafe",
          200: "#bfdbfe",
          500: "#3b82f6",
          600: "#2563eb",
          700: "#1d4ed8",
          900: "#1e3a5f",
        },
      },
      fontFamily: {
        heading: ["Inter", "sans-serif"],
        body: ["Open Sans", "sans-serif"],
      },
      spacing: {
        18: "4.5rem",
        88: "22rem",
        128: "32rem",
      },
      borderRadius: {
        "4xl": "2rem",
      },
      animation: {
        "fade-in": "fadeIn 0.5s ease-in-out",
        "slide-up": "slideUp 0.3s ease-out",
      },
      keyframes: {
        fadeIn: {
          "0%": { opacity: "0" },
          "100%": { opacity: "1" },
        },
        slideUp: {
          "0%": { transform: "translateY(10px)", opacity: "0" },
          "100%": { transform: "translateY(0)", opacity: "1" },
        },
      },
    },
  },
};

In Tailwind v4, you achieve the same result in CSS:

@theme {
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-700: #1d4ed8;
  --font-heading: "Inter", sans-serif;
  --font-body: "Open Sans", sans-serif;
  --animate-fade-in: fade-in 0.5s ease-in-out;
  --animate-slide-up: slide-up 0.3s ease-out;
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

Tailwind CSS Plugins#

The Tailwind plugin ecosystem significantly extends the framework's capabilities.

Official Plugins#

npm install -D @tailwindcss/typography @tailwindcss/forms @tailwindcss/container-queries
// tailwind.config.js (v3)
module.exports = {
  plugins: [
    require("@tailwindcss/typography"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/container-queries"),
  ],
};

@tailwindcss/typography - automatic styling for CMS or Markdown content:

<article class="prose prose-lg dark:prose-invert mx-auto max-w-3xl">
  <!-- Content from Markdown/CMS will be beautifully styled -->
  <h1>Article title</h1>
  <p>A paragraph with a <a href="#">link</a> and <strong>bold</strong> text.</p>
  <pre><code>console.log('Code is styled too!');</code></pre>
</article>

@tailwindcss/forms - resets form styles for easier customization:

<form class="mx-auto max-w-md space-y-4">
  <input
    type="email"
    placeholder="Your email"
    class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
  />
  <select class="w-full rounded-md border-gray-300 shadow-sm">
    <option>Select an option</option>
  </select>
  <textarea
    class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
    rows="4"
  ></textarea>
</form>

@tailwindcss/container-queries - container queries support:

<div class="@container">
  <div class="flex flex-col @md:flex-row @lg:gap-8">
    <div class="@md:w-1/2">Left column</div>
    <div class="@md:w-1/2">Right column</div>
  </div>
</div>

Writing Custom Plugins#

const plugin = require("tailwindcss/plugin");

module.exports = {
  plugins: [
    plugin(function ({ addUtilities, addComponents, theme }) {
      addUtilities({
        ".text-balance": {
          "text-wrap": "balance",
        },
        ".scrollbar-hidden": {
          "-ms-overflow-style": "none",
          "scrollbar-width": "none",
          "&::-webkit-scrollbar": {
            display: "none",
          },
        },
      });

      addComponents({
        ".btn-primary": {
          backgroundColor: theme("colors.blue.600"),
          color: "#fff",
          padding: `${theme("spacing.2")} ${theme("spacing.4")}`,
          borderRadius: theme("borderRadius.md"),
          fontWeight: theme("fontWeight.semibold"),
          "&:hover": {
            backgroundColor: theme("colors.blue.700"),
          },
        },
      });
    }),
  ],
};

Component Patterns and the @apply Directive#

The @apply directive lets you extract repeated sets of utility classes into custom CSS classes:

/* components.css */
@layer components {
  .btn {
    @apply inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2;
  }

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

  .btn-secondary {
    @apply btn border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus:ring-gray-500;
  }

  .btn-danger {
    @apply btn bg-red-600 text-white hover:bg-red-700 focus:ring-red-500;
  }

  .input-field {
    @apply w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500;
  }

  .card {
    @apply rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-gray-700 dark:bg-gray-800;
  }
}

However, the Tailwind team recommends using @apply sparingly. In component-based projects (React, Vue, Svelte), creating UI components is a better pattern:

// components/Button.tsx
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "primary" | "secondary" | "danger";
  size?: "sm" | "md" | "lg";
}

const variants = {
  primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
  secondary: "border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus:ring-gray-500",
  danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
};

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

export function Button({ variant = "primary", size = "md", className, ...props }: ButtonProps) {
  return (
    <button
      className={`inline-flex items-center justify-center rounded-md font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 ${variants[variant]} ${sizes[size]} ${className ?? ""}`}
      {...props}
    />
  );
}

JIT Mode (Just-in-Time)#

JIT mode, which became the default in Tailwind v3 (and is the only mode in v4), generates styles on demand instead of generating the entire set of classes upfront. This provides several key benefits:

  • Lightning-fast compilation - only used classes are generated
  • Arbitrary values - you can use values outside the design scale
  • Unlimited variants - stacking variants like dark:hover:lg:text-xl
<!-- Arbitrary values -->
<div class="top-[117px] w-[calc(100%-2rem)] bg-[#1da1f2] text-[clamp(1rem,2vw,1.5rem)]">
  Arbitrary CSS values
</div>

<!-- Arbitrary properties -->
<div class="[mask-type:luminance] [--scroll-offset:56px]">
  Arbitrary CSS properties
</div>

<!-- Stacking variants -->
<button class="dark:md:hover:bg-blue-800">
  Variants can be freely combined
</button>

Performance and Purging#

One of the most important aspects of Tailwind in production is CSS file size optimization. Tailwind automatically removes unused classes based on the content array in the configuration.

Tailwind v3#

// tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./public/index.html",
    // Include files from component libraries too
    "./node_modules/@your-org/ui/**/*.{js,ts,jsx,tsx}",
  ],
};

Tailwind v4#

In v4, content detection is automatic. Tailwind scans files in the project directory, skipping node_modules and .git. You can add additional sources:

@import "tailwindcss";
@source "../node_modules/@your-org/ui";

Rules for Safe Purging#

For purging to work correctly, never generate class names dynamically:

// Bad - Tailwind will not detect these classes
const color = "red";
className = `text-${color}-500`;

// Good - full class name
const colorClasses = {
  red: "text-red-500",
  blue: "text-blue-500",
  green: "text-green-500",
};
className = colorClasses[color];

Optimization Results#

A full Tailwind CSS build without purging is over 3 MB. After purging, a typical project uses 5-15 KB (gzip compressed). This is a massive difference that directly impacts Core Web Vitals.

Tailwind UI and Headless UI#

Tailwind UI#

Tailwind UI is the official, paid component library built with Tailwind CSS. It contains hundreds of professionally designed components for marketing, application, and e-commerce use cases:

<!-- Example component from Tailwind UI -->
<nav class="flex items-center justify-between bg-white px-6 py-4 shadow-sm">
  <div class="flex items-center gap-8">
    <a href="/" class="text-xl font-bold text-gray-900">Logo</a>
    <div class="hidden gap-6 md:flex">
      <a href="#" class="text-sm font-medium text-gray-600 hover:text-gray-900">Product</a>
      <a href="#" class="text-sm font-medium text-gray-600 hover:text-gray-900">Pricing</a>
      <a href="#" class="text-sm font-medium text-gray-600 hover:text-gray-900">Contact</a>
    </div>
  </div>
  <button class="rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700">
    Get started
  </button>
</nav>

Headless UI#

Headless UI is a free, official library of unstyled, fully accessible (a11y) components. It provides the logic and interactions while you add styles with Tailwind:

import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";

function UserMenu() {
  return (
    <Menu as="div" className="relative">
      <MenuButton className="flex items-center gap-2 rounded-md px-3 py-2 hover:bg-gray-100">
        <img src="/avatar.jpg" alt="" className="h-8 w-8 rounded-full" />
        <span className="text-sm font-medium">John Doe</span>
      </MenuButton>
      <MenuItems className="absolute right-0 mt-2 w-48 rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5">
        <MenuItem>
          <a href="/profile" className="block px-4 py-2 text-sm text-gray-700 data-[focus]:bg-gray-100">
            Profile
          </a>
        </MenuItem>
        <MenuItem>
          <a href="/settings" className="block px-4 py-2 text-sm text-gray-700 data-[focus]:bg-gray-100">
            Settings
          </a>
        </MenuItem>
        <MenuItem>
          <button className="block w-full px-4 py-2 text-left text-sm text-gray-700 data-[focus]:bg-gray-100">
            Sign out
          </button>
        </MenuItem>
      </MenuItems>
    </Menu>
  );
}

Tailwind CSS vs Bootstrap vs SASS#

Tailwind CSS vs Bootstrap#

| Aspect | Tailwind CSS | Bootstrap | |--------|-------------|-----------| | Approach | Utility-first | Component-based | | Size (production) | 5-15 KB | 25-50 KB | | Customization | Full control | Limited by theme | | Learning curve | Steep at first | Easy to start | | Design | Unique per project | Recognizable "Bootstrap look" | | JavaScript | None (Headless UI optional) | Built-in (jQuery/vanilla) | | Updates | Easy (classes are stable) | Often break compatibility |

Tailwind CSS vs SASS/SCSS#

SASS and Tailwind are not direct competitors - you can use them together. SASS adds variables, mixins, and nesting to CSS, while Tailwind provides a utility class system. However, in practice, Tailwind often eliminates the need for SASS:

// SASS - traditional approach
.hero-section {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: linear-gradient(135deg, $primary, $secondary);

  &__title {
    font-size: 3rem;
    font-weight: 700;
    color: white;

    @media (max-width: 768px) {
      font-size: 2rem;
    }
  }
}
<!-- Tailwind - same result without SASS -->
<section class="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-600 to-purple-600">
  <h1 class="text-3xl font-bold text-white md:text-5xl">Title</h1>
</section>

When to Choose Tailwind#

  • Projects with custom design (not relying on pre-built components)
  • Component-based SPA/SSR applications (React, Vue, Svelte, Next.js)
  • Teams that value consistency and rapid prototyping
  • Projects where CSS performance matters

When Tailwind May Not Be the Best Choice#

  • Quick prototypes without a designer (Bootstrap may be faster)
  • Legacy projects without a component system
  • Very small projects where configuration overhead is not justified

Changes in Tailwind CSS v4#

Tailwind v4 is the biggest update in the framework's history. Here are the most significant changes:

1. CSS-Native Configuration Instead of JavaScript#

@import "tailwindcss";

@theme {
  --color-*: initial; /* Reset default colors */
  --color-primary: #3b82f6;
  --color-gray-50: #f9fafb;
  --color-gray-900: #111827;
}

2. Automatic Content Detection#

You no longer need to define a content array. Tailwind v4 automatically scans project files.

3. Native CSS Nesting#

<div class="bg-blue-500 **:text-white **:font-semibold">
  <!-- All children have white text and semibold weight -->
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</div>

4. New Utilities#

  • size-* - sets both width and height simultaneously
  • inset-shadow-* and inset-ring-* - new shadow variants
  • text-wrap: balance and text-wrap: pretty - native classes
  • not-*, in-*, nth-* variants - advanced selectors

5. Significantly Faster Compilation#

The new engine written in Rust (oxide) provides up to 10x faster compilation than v3.

Best Practices#

1. Organize Classes Consistently#

Use the Prettier plugin (prettier-plugin-tailwindcss) for automatic class sorting:

npm install -D prettier prettier-plugin-tailwindcss
{
  "plugins": ["prettier-plugin-tailwindcss"]
}

2. Use Design Tokens#

Never use arbitrary values when an equivalent exists in the scale:

<!-- Bad -->
<div class="mb-[17px] text-[15px]">...</div>

<!-- Good -->
<div class="mb-4 text-sm">...</div>

3. Use clsx or tailwind-merge for Conditional Classes#

import { twMerge } from "tailwind-merge";
import clsx from "clsx";

function cn(...inputs: (string | undefined | false)[]) {
  return twMerge(clsx(inputs));
}

// Usage
<button className={cn(
  "rounded-md px-4 py-2 font-semibold",
  isPrimary && "bg-blue-600 text-white",
  isDisabled && "cursor-not-allowed opacity-50"
)}>
  Button
</button>

4. Build a Component System#

Instead of copying the same classes in multiple places, create reusable components with well-defined variants. Libraries like cva (class-variance-authority) help with this:

import { cva } from "class-variance-authority";

const badge = cva("inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold", {
  variants: {
    variant: {
      default: "bg-gray-100 text-gray-800",
      success: "bg-green-100 text-green-800",
      warning: "bg-yellow-100 text-yellow-800",
      error: "bg-red-100 text-red-800",
    },
  },
  defaultVariants: {
    variant: "default",
  },
});

// <span className={badge({ variant: "success" })}>Active</span>

5. Prioritize Accessibility (a11y)#

Tailwind offers classes that support accessibility:

<button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2">
  Accessible button
</button>

<!-- Elements visible only to screen readers -->
<span class="sr-only">Close menu</span>

Summary#

Tailwind CSS is a powerful tool that fundamentally changes the approach to styling web applications. The utility-first approach may take some getting used to, but once you are comfortable with it, going back to traditional CSS feels cumbersome. Key takeaways:

  • Utility-first eliminates cascade issues and dead CSS
  • Responsiveness and dark mode are built-in and simple to use
  • JIT provides lightning-fast compilation and full flexibility
  • Purging automatically optimizes CSS size down to a few kilobytes
  • Tailwind v4 brings a revolutionary architectural change with CSS-native configuration
  • The ecosystem (Tailwind UI, Headless UI, plugins) significantly accelerates development

Whether you are building a small landing page or a complex SaaS application, Tailwind CSS provides the tools that let you focus on the product instead of fighting CSS.

Need a Professional Frontend?#

At MDS Software Solutions Group, we build modern web applications using Tailwind CSS, Next.js, and frontend best practices. We offer:

  • Design and implementation of responsive interfaces
  • Migration from Bootstrap/SASS to Tailwind CSS
  • Design system and component library development
  • Frontend performance optimization (Core Web Vitals)
  • Tailwind CSS consulting and training

Contact us to discuss your project and learn how we can help you build a faster, more beautiful, and more maintainable frontend!

Author
MDS Software Solutions Group

Team of programming experts specializing in modern web technologies.

Tailwind CSS from Zero to Production - A Complete Guide | MDS Software Solutions Group | MDS Software Solutions Group