On this page

Responsive Design with Tailwind

12 min read TextCh. 2 — Layout and responsive

Responsive Design with Tailwind

Tailwind CSS is built around a mobile-first responsive design system. Every utility class applies at all screen sizes by default. To override a style at a specific screen width and above, you prefix the utility with a breakpoint modifier.

The mobile-first mental model

The key insight is that a utility without a breakpoint prefix applies to all screen sizes. A breakpoint prefix means "apply this from X width and above":

<!-- This text is gray on all screens -->
<p class="text-gray-600">Always gray</p>

<!-- This text is base size on mobile, large on sm+, extra-large on lg+ -->
<p class="text-base sm:text-lg lg:text-xl">Grows with the screen</p>

<!-- Mobile: centered, lg: left-aligned -->
<h1 class="text-center lg:text-left">Responsive alignment</h1>

Think of it as: base styles → small screen overrides → medium screen overrides → large screen overrides.

Default breakpoints

Tailwind v4 ships with these default breakpoints:

Prefix Minimum width Target
sm: 640px Large phones, small tablets
md: 768px Tablets
lg: 1024px Laptops and desktops
xl: 1280px Large desktops
2xl: 1536px Very wide screens

These breakpoints are defined as CSS custom properties and can be overridden in @theme.

Responsive layout patterns

Stack to side-by-side

The most common responsive pattern: a single column on mobile that becomes multi-column on wider screens.

<!-- Single column on mobile, two columns on md+ -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
  <div class="bg-white p-6 rounded-xl shadow">Column 1</div>
  <div class="bg-white p-6 rounded-xl shadow">Column 2</div>
</div>

<!-- Flex column on mobile, row on sm+ -->
<div class="flex flex-col sm:flex-row gap-4">
  <button class="bg-indigo-600 text-white px-6 py-3 rounded-lg">Primary action</button>
  <button class="border border-gray-300 px-6 py-3 rounded-lg">Secondary action</button>
</div>

Show and hide at breakpoints

<!-- Hidden on mobile, visible on md+ -->
<nav class="hidden md:flex items-center gap-6">
  <a href="/features" class="text-gray-600 hover:text-indigo-600">Features</a>
  <a href="/pricing" class="text-gray-600 hover:text-indigo-600">Pricing</a>
</nav>

<!-- Mobile hamburger menu: visible on mobile, hidden on md+ -->
<button class="md:hidden p-2 rounded-lg hover:bg-gray-100" type="button" aria-label="Open menu">
  <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
  </svg>
</button>

Responsive typography scale

<h1 class="text-2xl font-bold sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl">
  Scales with the screen
</h1>

<p class="text-sm sm:text-base lg:text-lg leading-relaxed">
  Body text that gets slightly larger on desktop for readability.
</p>

Responsive spacing

<section class="px-4 py-12 sm:px-6 sm:py-16 lg:px-8 lg:py-24">
  <div class="max-w-7xl mx-auto">
    <!-- Content -->
  </div>
</section>

Custom breakpoints with @theme

Tailwind v4 makes it trivial to define custom breakpoints inside your CSS:

@import "tailwindcss";

@theme {
  /* Add new breakpoints */
  --breakpoint-xs: 480px;    /* Extra small phones in landscape */
  --breakpoint-3xl: 1920px;  /* Ultra-wide monitors */

  /* Override existing breakpoints */
  --breakpoint-sm: 576px;    /* Align with Bootstrap if migrating */
}

After this, xs:, 3xl: prefixes work exactly like built-in breakpoints.

Max-width variants

Sometimes you need to apply a style only below a breakpoint. Use max-* prefixes:

<!-- Applies only below md (< 768px) -->
<div class="max-md:hidden">Hidden on small screens</div>

<!-- Stack on mobile ONLY, row on md+ (explicit mobile targeting) -->
<div class="max-md:flex-col flex gap-4">
  <div>Item 1</div>
  <div>Item 2</div>
</div>

Use max-* sparingly. It signals that you may be thinking in a desktop-first way. Prefer the mobile-first approach whenever possible.

Range variants

Target a specific range between two breakpoints:

<!-- Only applies between md and lg (768px → 1023px) -->
<div class="md:max-lg:text-indigo-600">Indigo only on tablets</div>

The container utility

The container class sets max-width to the current breakpoint's value and can optionally center content:

<!-- Container: max-width adapts to current breakpoint -->
<div class="container mx-auto px-4">
  <!-- Centered content with adaptive max-width -->
</div>

To configure the container in v4, override the breakpoint custom properties. By default the container is not centered — add mx-auto explicitly.

For most apps, a simpler pattern works better:

<!-- Manual max-width container — more explicit and flexible -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
  <!-- Content bounded at 80rem, with responsive padding -->
</div>

Responsive images and media

<!-- Image fills container on mobile, capped at 50% on lg -->
<img
  src="/hero-image.jpg"
  alt="Hero illustration"
  class="w-full lg:w-1/2 rounded-2xl object-cover"
/>

<!-- Aspect ratio: responsive video embed -->
<div class="aspect-video w-full rounded-xl overflow-hidden">
  <iframe
    src="https://www.youtube.com/embed/dQw4w9WgXcQ"
    class="w-full h-full"
    title="Video player"
    allowfullscreen
  ></iframe>
</div>

Complete responsive component: feature section

<section class="py-16 sm:py-24 bg-white">
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">

    <!-- Section header: centered on all screens -->
    <div class="text-center max-w-2xl mx-auto mb-12 sm:mb-16">
      <h2 class="text-3xl sm:text-4xl font-extrabold text-gray-900">Everything you need</h2>
      <p class="mt-4 text-lg text-gray-600">Build faster with a complete toolkit.</p>
    </div>

    <!-- Features grid: 1 col → 2 col → 3 col -->
    <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">

      <div class="flex gap-4">
        <div class="shrink-0 w-10 h-10 rounded-lg bg-indigo-100 flex items-center justify-center">
          <svg class="w-5 h-5 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
            <path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd" />
          </svg>
        </div>
        <div>
          <h3 class="font-semibold text-gray-900">Blazing Fast</h3>
          <p class="mt-1 text-sm text-gray-600">Oxide engine builds in under 100ms on typical projects.</p>
        </div>
      </div>

      <!-- More feature items... -->

    </div>
  </div>
</section>

Practice

  1. Build a responsive navigation bar: logo + desktop nav links + mobile hamburger icon. Use hidden md:flex and md:hidden for the respective elements.
  2. Create a two-column feature section that stacks vertically on mobile. Change the image order on mobile vs. desktop using order-1 and lg:order-2.
  3. Add a custom xs breakpoint at 480px and use it to adjust font sizes for extra-small phones.

Mobile-first means: unbreakpointed classes apply to all sizes
text-gray-600 applies on every screen size. sm:text-lg OVERRIDES it at sm (640px) and above. The correct mental model is: start with the smallest screen, then layer on overrides for larger screens. Never think of it as 'hide on mobile'.
Custom breakpoints with @theme in v4
Define any breakpoint in @theme using --breakpoint-NAME: VALUE. For example, --breakpoint-xs: 480px creates the xs: prefix. --breakpoint-4xl: 2560px creates 4xl: for ultra-wide screens.
Do not use max-width variants unless necessary
Tailwind supports max-sm:, max-md: etc. for styles that only apply BELOW a breakpoint. Use these sparingly. Overusing max-* variants makes it hard to reason about styles. Prefer the mobile-first approach and override upward.
html
<!-- Mobile-first hero: stacks vertically on mobile, side-by-side on lg+ -->
<section class="
  px-4 py-16
  sm:px-6 sm:py-20
  lg:px-8 lg:py-32
  bg-gradient-to-br from-indigo-50 to-purple-50
">
  <div class="max-w-7xl mx-auto">

    <!-- Content: single column on mobile, two columns on lg -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">

      <!-- Text content -->
      <div class="text-center lg:text-left">
        <span class="
          inline-block text-xs font-semibold uppercase tracking-widest
          text-indigo-600 bg-indigo-50 px-3 py-1 rounded-full mb-4
        ">
          New in v4.2
        </span>

        <h1 class="
          text-4xl font-extrabold text-gray-900 leading-tight
          sm:text-5xl
          lg:text-6xl
        ">
          Build fast.<br />
          <span class="text-indigo-600">Ship confidently.</span>
        </h1>

        <p class="
          mt-6 text-base text-gray-600 leading-relaxed max-w-lg mx-auto
          sm:text-lg
          lg:mx-0
        ">
          Tailwind CSS v4 delivers the Oxide engine — 10× faster builds,
          CSS-first configuration, and full container query support.
        </p>

        <!-- CTA buttons: stack on mobile, row on sm+ -->
        <div class="mt-8 flex flex-col sm:flex-row items-center gap-3 justify-center lg:justify-start">
          <a href="/docs" class="w-full sm:w-auto bg-indigo-600 hover:bg-indigo-700 text-white font-semibold px-6 py-3 rounded-xl transition-colors text-center">
            Read the docs
          </a>
          <a href="/play" class="w-full sm:w-auto border border-gray-300 hover:border-indigo-400 text-gray-700 font-semibold px-6 py-3 rounded-xl transition-colors text-center">
            Try the playground
          </a>
        </div>
      </div>

      <!-- Image: hidden on mobile, visible on lg -->
      <div class="hidden lg:block relative">
        <div class="bg-white rounded-2xl shadow-2xl p-6 border border-gray-100">
          <p class="text-xs font-mono text-gray-400 mb-2">tailwind.css</p>
          <pre class="text-sm font-mono text-gray-700 overflow-auto"><code>@import "tailwindcss";
@theme {
  --color-brand: #6366f1;
  --font-sans: "Inter", sans-serif;
}</code></pre>
        </div>
      </div>

    </div>
  </div>
</section>