Modals

Modal structure, overlay patterns, and keyboard accessibility.

On this page

Modals Are Floating UI

A modal is a layer above the page that temporarily blocks normal interaction. In production, modals are tricky because they must handle layering, scrolling, focus, accessibility, and mobile behavior. Tailwind handles the layout and styling; JavaScript handles open/close and focus management.

Modal building blocks

  • Overlay (backdrop)
  • Centered modal panel
  • Header/body/footer structure
  • Close button
  • Keyboard support (Escape, focus trap)
  • Scroll handling (page scroll lock, modal content scroll)

Basic modal structure

This is a minimal production-friendly structure. The overlay covers the screen, and the modal panel is centered with padding for small screens.

<div class="fixed inset-0 z-50">
  <div class="absolute inset-0 bg-black/50"></div>

  <div class="relative min-h-full flex items-center justify-center p-4">
    <div class="w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200">
      <div class="p-6">...</div>
    </div>
  </div>
</div>

Header / body / footer pattern

Separating sections keeps spacing consistent and makes the modal easier to scan.

<div class="w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200">
  <div class="p-6 flex items-start justify-between gap-4">
    <div>
      <h2 class="text-lg font-semibold text-slate-900">Edit profile</h2>
      <p class="mt-1 text-sm text-slate-600">Update your public information.</p>
    </div>

    <button aria-label="Close"
            class="h-9 w-9 inline-flex items-center justify-center rounded-md
                   hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-blue-500">
      ✕
    </button>
  </div>

  <div class="px-6 pb-6">
    <div class="space-y-4">
      <label class="block">
        <span class="text-sm font-medium text-slate-900">Name</span>
        <input class="mt-2 w-full rounded-md border border-slate-300 px-3 py-2 text-sm
                     focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
      </label>
    </div>
  </div>

  <div class="border-t border-slate-200 px-6 py-4 flex items-center justify-end gap-2">
    <button class="px-4 py-2 rounded-md border border-slate-300 hover:bg-slate-50">Cancel</button>
    <button class="px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700">Save</button>
  </div>
</div>

Modal sizing rules

  • Use max-w-* to control width (max-w-md / max-w-lg / max-w-xl).
  • Use w-full so it fits small screens.
  • Use p-4 on the wrapper to ensure safe margins on mobile.

Scrollable modal content

Long forms or content should scroll inside the modal panel, not behind it.

<div class="w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200
            max-h-[80vh] flex flex-col">
  <div class="p-6 border-b border-slate-200">Header</div>
  <div class="p-6 overflow-y-auto">
    Long content...
  </div>
  <div class="p-4 border-t border-slate-200">Footer</div>
</div>

Overlay click and Escape behavior

In production, users expect:

  • Clicking the overlay closes the modal (optional for destructive flows).
  • Pressing Escape closes the modal.

These behaviors are implemented in JavaScript, but your structure should support it cleanly (overlay layer separated from the panel).

Focus management (accessibility)

A real modal must trap focus while open and restore focus to the trigger element when closed. Tailwind cannot do this; JavaScript must. If you skip focus management, keyboard users can tab behind the modal, which breaks accessibility.

Scroll locking

When a modal is open, the page behind it should not scroll. The common production approach is to set body overflow to hidden while the modal is open. Without scroll lock, users can end up scrolling the page behind the overlay.

Dark mode modal

Ensure panel surface, border, and text roles adapt in dark mode.

<div class="w-full max-w-lg rounded-xl shadow-2xl border border-slate-200 bg-white
            dark:border-slate-700 dark:bg-slate-900">
  <div class="p-6">
    <h2 class="text-lg font-semibold text-slate-900 dark:text-slate-100">Modal</h2>
    <p class="mt-1 text-sm text-slate-600 dark:text-slate-400">Text</p>
  </div>
</div>

Common mistakes

  • No z-index strategy (modal appears behind other elements).
  • Overlay without background opacity (modal feels broken).
  • Scrolling the page behind the modal (no scroll lock).
  • No focus trap and no Escape handling (accessibility failure).
  • Modal too wide or no padding on mobile (content touches edges).

Production checklist

  • Use fixed inset-0 + z-50 for reliable layering.
  • Overlay is separate from panel (supports click-to-close).
  • Panel uses w-full + max-w-* + mobile padding wrapper.
  • Long content scrolls inside the panel (max-h + overflow-y-auto).
  • Implement Escape + focus trap + scroll lock in JavaScript.
  • Verify dark mode surface, border, and text contrast.