Persona

Modal

Overlay · 13 props · Updated Sep 22, 2026

A modal is a card on a dimmed overlay that stops the page behind it: the background is inert to the pointer, to the keyboard and to assistive technology until the modal closes. That is the whole reason to reach for one, and the whole reason not to.

  • Overlay the dimmed layer over the rest of the page. It is what makes the page behind inert, and it only closes the modal when isDismissable is on.
  • Container the elevated card the modal is built from. The size prop caps its width, not its height.
  • Header the top bar, ruled off from the body. It holds the title and the close button, and the tab bar when one is supplied.
  • Title (optional) names the task in a few words. Omit it and the header keeps only the way out.
  • Close button (optional) the icon button at the top right, on by default and the only exit a modal has out of the box besides Esc. contentProps.showCloseButton removes it.
  • Body the content area, and the only part that scrolls. Content long enough to need that scroll probably belongs on a page.
  • Footer (optional) the row of actions at the bottom, arranged for you. One primary, the rest secondary.

Use a modal when the reader has to act or answer before the flow behind them can continue — confirming something destructive, or supplying the one thing a process is missing. Stopping the page is a claim that nothing else on it matters yet, and if that claim is false the modal is an obstacle.

One at a time. Nesting modals, or opening a second from inside the first, stops a page that is already stopped, and the reader loses track of which Esc closes what.

For a surface that leaves the page usable, see Popover, which sets out the boundary between the three surfaces that open over a page. For content that belongs beside the flow rather than over it, use an inline Card.

A modal has three exits with three different defaults

Section titled “A modal has three exits with three different defaults”

Each has its own switch, and they are not symmetrical:

  • The close button is on by default, and off through contentProps={{ showCloseButton: false }}.
  • Esc closes by default, and stops through isKeyboardDismissDisabled.
  • Clicking the overlay does nothing by default. isDismissable is what turns it on.

So a modal out of the box closes on Esc and on the X, and ignores the overlay — which is worth knowing, because the overlay looks like a dismissal target whether or not it is one. Turn isDismissable on for anything the reader can abandon safely, and leave it off where a stray click would discard work.

Set all three against you and the modal has no exit at all. Nothing in the component prevents that combination, so it is yours to avoid: keep the close button unless the footer supplies a way out, and then keep the footer.

Opening from anything but the trigger prop loses focus on close

Section titled “Opening from anything but the trigger prop loses focus on close”

trigger takes the element that opens the modal and merges the open and close behavior into it. It is also how focus finds its way home — React Aria returns focus to the trigger it was given, so a modal opened from a button that is not the trigger prop leaves focus wherever the page put it after the modal unmounts.

The alternative is the controlled trio, open, defaultOpen and onOpenChange, for the modal that opens in response to something other than a click.

size is md, lg, xl, full or auto, and every one of them caps width only. Height comes from the content, and the body is the part that scrolls.

A modal the reader has to scroll has stopped the page for something the page could have held. The same goes for a flow with more than one step: the modal gives you no back, no address and no way to leave and return, so a second step belongs on a route.

Tabs are the one way a modal holds two views

Section titled “Tabs are the one way a modal holds two views”

tabs renders a Tab Bar in the header and overrides children — pass both and the children are discarded silently. It is the one way a modal holds more than one view, and it is a narrow one: tabs work when the views are alternatives the reader chooses between, and not when they are steps in an order.

noBodyPadding removes the body’s padding for content that supplies its own, such as a full-bleed table.

Three things happen on open, and all three are React Aria’s rather than yours. Focus is contained inside the card, so Tab cycles within it and cannot reach the page behind. Everything outside the modal is hidden from assistive technology with aria-hidden, so a screen reader cannot read past the dialog. The document stops scrolling.

That is useModalOverlay calling useOverlayFocusContain, ariaHideOutside and usePreventScroll, and it is why a modal needs no focus management code around it. A hand-written trap on top competes with the one already running.

React Aria takes the accessible name from the title

Section titled “React Aria takes the accessible name from the title”

title renders the header’s heading as a React Aria Heading with slot="title", which is exactly the hook React Aria’s Dialog looks for when it wires up aria-labelledby. So a modal with a title is named, and the association is not something to set by hand.

A modal without one is unnamed, and React Aria says so: it logs a console warning telling you it needs an aria-label or aria-labelledby. Pass one through contentProps when the modal has no visible heading.

Use alertdialog for the modal the reader did not open

Section titled “Use alertdialog for the modal the reader did not open”

role reaches the dialog through contentProps, and it takes dialog or alertdialog. alertdialog is for the interruption, a session expiring or a save failing, and it asks screen readers to announce the modal’s contents immediately rather than its name alone. dialog, the default, is for the modal the reader asked for.

The title names the task, not the kind of dialog

Section titled “The title names the task, not the kind of dialog”

“Delete account” is a title. “Confirm” is a category, and the reader has to read the body to find out which confirmation they are looking at — which is also what a screen reader announces first, since the title is the modal’s accessible name.

Put the consequence in the body’s first sentence rather than at the end of it. A modal is read in the order it is laid out, and the reader’s hand is already on the footer.

footer takes Buttons directly and arranges them in a row. One of them is the primary and the rest are secondary, the same emphasis rule as on Button, and they are labeled with the answer rather than with agreement: “Delete file” and “Keep file” tell the reader what each does without reading the question again. “Yes” and “No” do not.

Capitalization, punctuation and the rules that apply to every label are on Writing.

NameType
title

string

Title of the Card

trigger

ReactElement<DOMAttributes<FocusableElement>, string>

Component to use as trigger. React Aria trigger props will be merged with this element.

contentProps

ModalCardProps

Props to apply to Modal.Card within

size

auto, md, lg, xl, full

Max width of the modal

footer

ReactNode

Footer section intended for actions. You can pass in Buttons directly and they will render in a HStack.

tabs

TabView[]

Renders tabs within the modal. Adds a tab bar and will render the contents of the selected tab in a tabpanel. Overrides children.

children

ModalRenderable

Children content

noBodyPadding

boolean

When true, removes horizontal and vertical padding from the body. Defaults to false. Can also be set via contentProps.noBodyPadding.

open

boolean

Controls whether the modal is open (controlled)

defaultOpen

boolean

Default open state (uncontrolled)

onOpenChange

((isOpen: boolean) => void)

Handler called when open state changes

isDismissable

boolean

Whether the modal can be dismissed by clicking outside or pressing escape

isKeyboardDismissDisabled

boolean

Whether pressing the escape key closes the modal

Was this page helpful?