---
title: Modal (React) - USWDS + Tailwind
description: Focus attention on one task or message with a modal. Compare
  default, large, and required-action dialogs.
---

# Modal

Focus attention on one task or message with a modal. Compare default, large, and required-action dialogs.

## Examples

### Default

```tsx
import { ButtonGroup, Modal } from '@uswds-tailwind/react'
import { useState } from 'react'

export default function ModalDefaultDemo() {
  const [open, setOpen] = useState(false)

  return (
    <Modal.Root open={open} onOpenChange={event => setOpen(event.open)}>
      <Modal.Trigger>Open default modal</Modal.Trigger>
      <Modal.Backdrop />
      <Modal.Positioner>
        <Modal.Content>
          <Modal.Body>
            <Modal.Title>
              <h2>Are you sure you want to continue?</h2>
            </Modal.Title>
            <Modal.Description>
              <p>You have unsaved changes that will be lost.</p>
            </Modal.Description>
            <Modal.Footer>
              <ButtonGroup.Root>
                <ButtonGroup.Button onClick={() => setOpen(false)}>Continue without saving</ButtonGroup.Button>
                <ButtonGroup.Button unstyled className="p-3" onClick={() => setOpen(false)}>Go back</ButtonGroup.Button>
              </ButtonGroup.Root>
            </Modal.Footer>
          </Modal.Body>
          <Modal.CloseTrigger aria-label="Close this window" />
        </Modal.Content>
      </Modal.Positioner>
    </Modal.Root>
  )
}
```

### Large

```tsx
import { ButtonGroup, Modal } from '@uswds-tailwind/react'
import { useState } from 'react'

export default function ModalLargeDemo() {
  const [open, setOpen] = useState(false)

  return (
    <Modal.Root size="lg" open={open} onOpenChange={event => setOpen(event.open)}>
      <Modal.Trigger>Open large modal</Modal.Trigger>
      <Modal.Backdrop />
      <Modal.Positioner>
        <Modal.Content>
          <Modal.Body>
            <Modal.Title>
              <h2>Are you sure you want to continue?</h2>
            </Modal.Title>
            <Modal.Description>
              <p>You have unsaved changes that will be lost.</p>
            </Modal.Description>
            <Modal.Footer>
              <ButtonGroup.Root>
                <ButtonGroup.Button onClick={() => setOpen(false)}>Continue without saving</ButtonGroup.Button>
                <ButtonGroup.Button unstyled className="p-3" onClick={() => setOpen(false)}>Go back</ButtonGroup.Button>
              </ButtonGroup.Root>
            </Modal.Footer>
          </Modal.Body>
          <Modal.CloseTrigger aria-label="Close this window" />
        </Modal.Content>
      </Modal.Positioner>
    </Modal.Root>
  )
}
```

### Forced Action

```tsx
import { ButtonGroup, Modal } from '@uswds-tailwind/react'
import { useState } from 'react'

export default function ModalForcedActionDemo() {
  const [open, setOpen] = useState(false)

  return (
    <Modal.Root forceAction open={open} onOpenChange={event => setOpen(event.open)}>
      <Modal.Trigger>Open modal with forced action</Modal.Trigger>
      <Modal.Backdrop />
      <Modal.Positioner>
        <Modal.Content>
          <Modal.Body>
            <Modal.Title>
              <h2>Your session will end soon.</h2>
            </Modal.Title>
            <Modal.Description>
              <p>
                You've been inactive for too long. Please choose to stay signed in or sign out.
                Otherwise, you'll be signed out automatically in 5 minutes.
              </p>
            </Modal.Description>
            <Modal.Footer>
              <ButtonGroup.Root>
                <ButtonGroup.Button onClick={() => setOpen(false)}>Yes, stay signed in</ButtonGroup.Button>
                <ButtonGroup.Button unstyled className="p-3" onClick={() => setOpen(false)}>Sign out</ButtonGroup.Button>
              </ButtonGroup.Root>
            </Modal.Footer>
          </Modal.Body>
        </Modal.Content>
      </Modal.Positioner>
    </Modal.Root>
  )
}
```

## Component API

Root provides context. Keep Backdrop and Positioner beside the trigger inside Root, and place Content inside Positioner. Give the dialog an accessible name through Title or aria-label on Root.

### Root

Root controls whether the dialog is open and sets its size.

| Prop | Type | Default | Description |
| - | - | - | - |
| `size` | `"default" \| "lg"` | `default` | Changes content width and body/title spacing. |
| `open` | `boolean` | `—` | Controlled open state; update it after a change callback. |
| `onOpenChange` | `({ open: boolean }) => void` | `—` | Called when an open-state change is requested. |
| `forceAction` | `boolean` | `false` | Prevents Escape and outside interaction from dismissing the dialog. Supply an explicit action. |
| `aria-label` | `string` | `—` | Names the dialog directly and takes precedence over the Title relationship. |
| `ids` | `{ root?: string; trigger?: string; backdrop?: string; positioner?: string; content?: string; closeTrigger?: string; title?: string; description?: string }` | `—` | Overrides generated part IDs. Use Root IDs when a label or external control needs a stable input or trigger target. |

The dialog traps focus, prevents background scrolling, and restores focus after closing. These behaviors are managed automatically; the dialog role is fixed to dialog.

### Trigger `<button>`

Opens the dialog and uses the default Button styling. Accepts native button props.

| Attribute | Value | Purpose |
| - | - | - |
| `data-scope, data-part` | `modal, trigger` | Select the trigger. |
| `data-state` | `open \| closed` | Style the open and closed states. |

### Backdrop `<div>`

Covers the page behind the dialog. Clicking it closes the dialog when forceAction is false.

| Attribute | Value | Purpose |
| - | - | - |
| `data-scope, data-part` | `modal, backdrop` | Select the backdrop. |
| `data-state` | `open \| closed` | Style the open and closed states. |

### Positioner `<div>`

Centers the dialog content.

| Attribute | Value | Purpose |
| - | - | - |
| `data-state` | `open \| closed` | Style the open and closed states. |

### Content `<div>`

Holds the dialog content. Use Title or aria-label on Root to name the dialog.

| Attribute | Value | Purpose |
| - | - | - |
| `data-scope, data-part` | `modal, content` | Select the dialog content. |
| `data-state` | `open \| closed` | Style the open and closed states. |

### Title `<div>`

Names the dialog. Put a heading inside it at the appropriate page level.

| Attribute | Value | Purpose |
| - | - | - |
| `data-scope, data-part` | `modal, title` | Select the title. |

### Description `<div>`

Provides an optional description for the dialog.

| Attribute | Value | Purpose |
| - | - | - |
| `data-scope, data-part` | `modal, description` | Select the description. |

### Body `<div>`

Spacing responds to Root's size variant.

### Footer `<div>`

Provides spacing for dialog actions.

### CloseTrigger `<button>`

Closes the dialog. Supply aria-label when using the default icon-only button.

| Prop | Type | Description |
| - | - | - |
| `aria-label` | `string` | Overrides the default accessible name. |

| Attribute | Value | Purpose |
| - | - | - |
| `data-scope, data-part` | `modal, close-trigger` | Select the close button. |

### Accessibility

The dialog manages focus, labeling, and closed-state visibility. Provide a useful title or label, meaningful action names, and an explicit way to finish a forced-action dialog.
