Modals, Dropdowns and Tooltips

Advanced
14 min

Modals, Dropdowns and Tooltips

Overlays are the components most often built badly: focus escapes, they cannot animate in, and screen readers never hear them. Modern HTML fixes most of this natively, and Tailwind v4 ships the variants to style it. In this lesson you will build an accessible modal on <dialog>, animate it with starting:, create a dropdown with the Popover API, and add a tooltip that works for mouse and keyboard.

A modal on the native dialog element

<dialog> opened with showModal() traps focus, closes on Escape, renders in the top layer, and exposes a ::backdrop:

html
<dialog id="settings" class="m-auto w-full max-w-lg rounded-xl p-6 shadow-xl backdrop:bg-black/50"> <h2 class="text-lg font-semibold">Settings</h2> <form method="dialog" class="mt-6 flex justify-end"> <button class="rounded-md border px-4 py-2 text-sm">Close</button> </form> </dialog> <button onclick="settings.showModal()">Open</button>

m-auto centres the dialog because the browser positions it with inset: 0. backdrop: targets ::backdrop, so backdrop:bg-black/50 dims the page behind. A <form method="dialog"> closes the dialog on submit without JavaScript, and the button's value becomes dialog.returnValue.

Animating open and close

A closed dialog is display: none, which normally cannot transition. transition-discrete lets display participate and starting: (@starting-style) sets the state to animate from:

html
<dialog class="opacity-0 scale-95 transition-all transition-discrete duration-200 open:opacity-100 open:scale-100 starting:open:opacity-0 starting:open:scale-95">

Read the classes as a timeline: closed (opacity-0 scale-95), open (open:opacity-100), and the frame the open animation starts from (starting:open:opacity-0); the exit animation follows when open is removed. The backdrop fades the same way with backdrop:opacity-0 open:backdrop:opacity-100 starting:open:backdrop:opacity-0. Add motion-reduce:transition-none for reduced motion.

Dropdown menus with the Popover API

The popover attribute gives any element top-layer rendering, light dismiss (click outside or Escape) and a toggle button with no JavaScript; Tailwind's open: variant matches :popover-open:

html
<button popovertarget="user-menu" class="rounded-md border px-3 py-2 text-sm">Account</button> <div id="user-menu" popover class="m-0 w-56 rounded-lg border bg-white p-1 shadow-lg opacity-0 transition-discrete open:opacity-100 starting:open:opacity-0"> <a href="/profile" class="block rounded-md px-3 py-2 text-sm hover:bg-gray-100">Profile</a> <button class="block w-full rounded-md px-3 py-2 text-left text-sm text-red-600 hover:bg-red-50">Sign out</button> </div>

Because a popover lives in the top layer, absolute positioning relative to the trigger does not apply; the browser centres it. Place it beside its button with CSS anchor positioning where supported or a small positioning library, or use the <details> pattern with group-open: for a menu in normal flow.

Tooltips

A tooltip is a hidden label shown on hover and focus, positioned above its trigger and linked with aria-describedby:

html
<span class="group relative inline-block"> <button aria-describedby="tip-copy" class="rounded-md px-3 py-2 hover:bg-gray-100">Copy</button> <span id="tip-copy" role="tooltip" class="pointer-events-none invisible absolute bottom-full left-1/2 mb-2 -translate-x-1/2 whitespace-nowrap rounded bg-gray-900 px-2 py-1 text-xs text-white opacity-0 transition group-hover:visible group-hover:opacity-100 group-focus-within:visible group-focus-within:opacity-100"> Copy to clipboard </span> </span>

bottom-full mb-2 places it above the trigger, left-1/2 -translate-x-1/2 centres it, and pointer-events-none stops flicker when the cursor drifts onto it. Keep tooltips short; anything interactive belongs in a popover.

Common mistakes

  • Building a modal from a fixed div without focus trapping, Escape handling or aria-modal; <dialog> provides all three.
  • Using hidden/block for open and closed states, which cannot animate.
Quick Quiz
Question 1 of 2

Which variant styles the dimmed layer behind an open `<dialog>`?

Key Takeaways

  • Use <dialog> with showModal() for modals; style the overlay with backdrop: and close with <form method="dialog">.
  • Animate dialogs and popovers with transition-discrete, open: and starting:open: classes.
  • The popover attribute gives dropdowns light dismiss and top-layer rendering; open: styles the open state.
  • Tooltips combine group-hover: and group-focus-within: with role="tooltip" and aria-describedby.

Next lesson: Accessibility with Tailwind: Focus, Screen Readers and Motion — make every component usable with a keyboard, a screen reader and reduced-motion settings.

Modals, Dropdowns and Tooltips - Tailwind CSS | CodeYourCraft | CodeYourCraft