Skip to content

Modal

A dialog overlay with focus trapping, keyboard dismissal, and multiple size options.

Preview

Confirmation dialog (small)

Delete project?

This action cannot be undone. All data will be permanently removed.

Form dialog (medium)

Create new project

Fill in the details below to set up your project.

Scrollable content (large)

Terms of Service

Please review and accept the terms below.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.

Features

  • Focus trapping -- focus cycles within the modal when open. The first focusable element receives focus on mount.
  • Escape to close -- pressing Escape fires the close handler.
  • Overlay dismiss -- clicking the backdrop closes the modal (can be disabled).
  • Sizes -- four width presets from compact (400 px) to full-screen.
  • Scroll lock -- page scroll is disabled while the modal is open.

Sizes

SizeMax widthUse case
sm400 pxConfirmations, simple alerts
md560 pxForms, detail views
lg720 pxLong content, terms, multi-step
fullcalc(100vw - 48px)Media viewers, complex editors

Variants

VariantDescription
ConfirmationShort message with confirm/cancel actions
FormContains input fields and a submit action
ScrollableFixed header/footer with scrollable body

Props

PropTypeDefaultDescription
openbooleanfalseWhether the modal is visible
onClose() => void--Called on escape press or overlay click
size'sm' | 'md' | 'lg' | 'full''md'Dialog width
titlestring--Modal title
descriptionstring--Supporting text below the title
closeOnOverlaybooleantrueWhether clicking the overlay closes the modal
showClosebooleantrueShow the close (X) button
classNamestring--Additional CSS classes on the dialog
childrenReact.ReactNode--Modal body content
footerReact.ReactNode--Footer content (typically action buttons)

Code example

React

tsx
import { useState } from 'react';
import { Modal, Button } from '@thepace/lexicon/components';

function ConfirmDialog() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Delete project</Button>

      <Modal
        open={open}
        onClose={() => setOpen(false)}
        size="sm"
        title="Delete project?"
        description="This action cannot be undone. All data will be permanently removed."
        footer={
          <>
            <Button variant="secondary" onClick={() => setOpen(false)}>
              Cancel
            </Button>
            <Button variant="danger" onClick={() => setOpen(false)}>
              Delete
            </Button>
          </>
        }
      />
    </>
  );
}

Vanilla HTML

html
<div class="lex-modal-overlay" role="presentation">
  <div class="lex-modal lex-modal--sm" role="dialog" aria-modal="true" aria-labelledby="modal-title">
    <button class="lex-modal__close" aria-label="Close">&times;</button>
    <h2 id="modal-title" class="lex-modal__title">Delete project?</h2>
    <p class="lex-modal__description">This action cannot be undone.</p>
    <div class="lex-modal__body">
      <!-- content -->
    </div>
    <div class="lex-modal__footer">
      <button class="lex-button lex-button--secondary">Cancel</button>
      <button class="lex-button lex-button--danger">Delete</button>
    </div>
  </div>
</div>

CSS class reference

ClassPurpose
.lex-modal-overlayFull-screen backdrop
.lex-modalDialog container
.lex-modal--smSmall size (400 px)
.lex-modal--mdMedium size (560 px)
.lex-modal--lgLarge size (720 px)
.lex-modal--fullFull-screen size
.lex-modal__closeClose button
.lex-modal__titleTitle text
.lex-modal__descriptionDescription text
.lex-modal__bodyBody content area
.lex-modal__footerFooter with actions

Accessibility

  • The dialog uses role="dialog" and aria-modal="true".
  • aria-labelledby points to the title element.
  • If description is provided, aria-describedby points to it.
  • Focus is trapped inside the modal while open.
  • When the modal closes, focus returns to the element that triggered it.
  • The close button has aria-label="Close".
  • The overlay receives role="presentation" so it is not announced by screen readers.
  • Body scroll is locked while the modal is open.

Guidelines

Do

  • Use the smallest size that fits your content.
  • Always provide a clear close mechanism (X button, Cancel, Escape).
  • Use confirmation dialogs for destructive actions.

Don't

  • Don't stack modals on top of modals.
  • Don't use modals for simple, inline interactions — use a popover instead.
  • Don't auto-open modals on page load unless absolutely necessary.

Released under the MIT License. A product by the pace.