DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Modal

An accessible dialog overlay built on Base UI. Compound component pattern with Media, Header, Body, Footer, Title, Description, and Close. Five sizes, five roundings, scrim modes (frosted, dim, shadow), full modality spectrum, full-screen takeover, nested dialogs, LTR and RTL support.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  Modal,
  ModalTrigger,
  ModalClose,
  ModalContent,
  ModalHeader,
  ModalTitle,
  ModalDescription,
  ModalBody,
  ModalFooter,
} from "@tessinaui/ui";
<Modal>
  <ModalTrigger className="...">Open</ModalTrigger>

  <ModalContent>
    <ModalHeader>
      <ModalTitle>Confirm changes</ModalTitle>
      <ModalDescription>This action cannot be undone.</ModalDescription>
    </ModalHeader>

    <ModalBody>
      <p>Additional content goes here.</p>
    </ModalBody>

    <ModalFooter>
      <ModalClose className="...">Cancel</ModalClose>
      <ModalClose className="...">Save</ModalClose>
    </ModalFooter>
  </ModalContent>
</Modal>

Examples

Default

A basic modal with a header, body, and footer driven by trigger and close buttons.

Sizes

Five panel widths — sm, md, lg, xl, and full.

Rounded

Five corner-rounding options applied to the panel and its controls.

Curving your own content

Content you put inside — a media slot pinned to the top of the panel — should curve like the chrome around it, or it reads as pasted on. The modal panel publishes its inner radius as a CSS variable:

<img className="rounded-[var(--modal-media-radius)]" />

It tracks whatever the component is actually doing: the explicit step when rounded is set (16px at md), and 0 at rounded="none" — so your content squares off exactly when the container does.

Confirmation

A destructive confirmation with a leading icon and an error-intent action.

Form

A modal hosting form fields for editing data.

Non-dismissible

A modal that ignores backdrop and Escape dismissal and hides the X button.

Scrim

Three background treatments: blur frosts the page (default), dim is a plain dark overlay, shadow keeps the page visible with the panel separated by elevation.

Media

A full-bleed hero with the close button floating over it — the announcement pattern.

Prompt

A small input prompt with paired half-width actions.

Result

A success state with a stacked footer — the affirmative action on top, fine print below.

Stepper

A multi-step tour: pagination dots in the footer's start slot, Back/Next trailing.

Plans

A wide pricing chooser — segmented billing toggle in the header's action slot, radio cards, divided footer with fine print.

Share

Invite input, per-person role menus (a popup inside the modal), and a Copy-link action opposite the primary.

Fullscreen

The M3 full-screen takeover: edge-to-edge panel, back + verb CTA in the top bar.

Nested

A confirmation stacked over an open modal — focus moves to the top layer, Escape unwinds one at a time.

API Reference

Modal

Root component. Wraps Dialog.Root from Base UI and provides size and dir context to all children.

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "xl" | "full""md"Max-width of the popup panel
width"narrow" | "default" | "wide""default"Additional width constraint — narrow (xs), default (from size), wide (3xl)
rounded"full" | "lg" | "md" | "sm" | "none""full"Corner radius, cascading to the panel, close button, and media slot
fullscreenbooleanfalseEdge-to-edge takeover: square corners, full viewport, safe-area padding
dir"ltr" | "rtl""ltr"Text direction — applied to the popup and all its children
openboolean—Controlled open state
defaultOpenbooleanfalseUncontrolled initial open state
onOpenChange(open: boolean, event: Event) => void—Callback when open state changes
onOpenChangeComplete(open: boolean) => void—Called after the open/close animation settles
dismissiblebooleantrueWhether backdrop click and Escape close the modal — false keeps only explicit closes
modalboolean | "trap-focus"truetrue traps focus, locks scroll, shows the scrim. "trap-focus" traps focus only. false is fully non-modal
scrim"blur" | "dim" | "shadow""blur"Background treatment while open (modal only)

ModalTrigger

Re-export of Dialog.Trigger. Renders a <button> that opens the modal. Apply styles via className or swap the element via the render prop:

<ModalTrigger render={<Button />}>Open modal</ModalTrigger>

ModalContent

Renders the popup panel inside a Portal. Includes the backdrop automatically.

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "xl" | "full"from <Modal>Overrides the size from context
width"narrow" | "default" | "wide"from <Modal>Overrides the width from context
rounded"full" | "lg" | "md" | "sm" | "none"from <Modal>Overrides the corner rounding from context
classNamestring—Extra classes on the popup

All other native <div> attributes (forwarded to Dialog.Popup) are accepted — including Base UI's initialFocus and finalFocus for custom focus targets.

ModalMedia

Full-bleed media hero placed before ModalHeader — image, illustration, or gradient block. Rounds its top corners with the modal's rounded and clips its content.

PropTypeDefaultDescription
overlayClosebooleanfalseFloat a close button over the media (pair with ModalHeader showClose={false})

ModalHeader

PropTypeDefaultDescription
iconReact.ReactNode—Optional leading icon beside the title
backReact.ReactNode—Leading back affordance — typically <ModalBack aria-label="Back" />
actionReact.ReactNode—Trailing cluster between title and close: a Reset link, a segmented toggle, a header checkbox
showClosebooleantrueWhether to render the built-in X close button. Set to false to use a custom <ModalClose>
dividerbooleanfalseHairline under the header (desktop dialog anatomy)

ModalBack

Icon-only back button for multi-step modals — ghost, xs, radius from the modal's rounded, arrow auto-flips in RTL. aria-label is required. Everything except icon forwards to IconButton.

ModalTitle

Renders Dialog.Title (linked to aria-labelledby). Accepts all <div> attributes plus className.

ModalDescription

Renders Dialog.Description (linked to aria-describedby). Accepts all <p> attributes plus className.

ModalBody

Scrollable content wrapper. Uses flex-1 overflow-y-auto — grows to fill space between header and footer.

ModalFooter

PropTypeDefaultDescription
stackedbooleanfalseStack the actions full-width. Put the AFFIRMATIVE button first in the DOM — it renders on top (M3/HIG rule)
dividerbooleanfalseHairline above the footer
startReact.ReactNode—Start-aligned slot opposite the actions: stepper dots, a link, fine print. In stacked mode it renders below the buttons

Default layout is a flex row aligned to the end (justify-end). Keep to at most three actions (M3 allows two; Apple/Fluent/Carbon cap at three).

ModalClose

Re-export of Dialog.Close. Renders a <button> that closes the modal. Apply styles via className or swap via render prop:

<ModalClose render={<Button variant="secondary" />}>Cancel</ModalClose>

ModalBackdrop

Pre-styled backdrop. Included inside <ModalContent> by default — only use directly for custom layouts.

Sizes

SizeMax-widthUse case
sm384 pxConfirmations, alerts, short forms
md460 pxStandard dialogs (default)
lg512 pxMedium-complexity forms
xl576 pxRich forms, previews
full100% − 2remFull-screen panels

Animations

The modal uses CSS transitions driven by Base UI's data attributes:

AttributeWhen appliedEffect
data-starting-styleFirst frame of openingopacity 0, scale 0.96
(none)Open steady stateopacity 1, scale 1
data-ending-styleDuring close animationopacity 0, scale 0.96

The backdrop fades in/out independently with transition-opacity. All enter/exit motion is disabled under prefers-reduced-motion: reduce.

Controlled vs Uncontrolled

Uncontrolled — use <ModalTrigger> and <ModalClose> for open/close:

<Modal>
  <ModalTrigger className="...">Open</ModalTrigger>
  <ModalContent>
    <ModalFooter>
      <ModalClose className="...">Close</ModalClose>
    </ModalFooter>
  </ModalContent>
</Modal>

Controlled — manage open state yourself:

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

<Modal open={open} onOpenChange={setOpen}>
  <ModalTrigger onClick={() => setOpen(true)} className="...">Open</ModalTrigger>
  <ModalContent>
    <ModalFooter>
      <button onClick={() => setOpen(false)}>Cancel</button>
      <button onClick={() => { doSomething(); setOpen(false); }}>Confirm</button>
    </ModalFooter>
  </ModalContent>
</Modal>

Accessibility

  • Renders as role="dialog" with aria-modal="true"
  • <ModalTitle> is linked via aria-labelledby
  • <ModalDescription> is linked via aria-describedby
  • Focus is trapped inside the dialog when open
  • Scroll is locked on <body> when open
  • Escape key closes the modal (unless dismissible={false})
  • Focus returns to the trigger when the modal closes
  • The backdrop provides a click-outside dismiss target (all scrim modes, including the transparent shadow)
  • modal="trap-focus" keeps focus inside without locking page scroll; modal={false} renders no scrim and leaves the page interactive
  • Every part exposes a stable data-slot attribute (modal-content, modal-header, modal-footer, modal-media, modal-back, …) for testing and agent tooling
  • Nested modals move focus to the top-most panel; Escape unwinds one layer at a time
MeterNavigation Menu

On this page

PlaygroundInstallationUsageExamplesDefaultSizesRoundedCurving your own contentConfirmationFormNon-dismissibleScrimMediaPromptResultStepperPlansShareFullscreenNestedAPI ReferenceModalModalTriggerModalContentModalMediaModalHeaderModalBackModalTitleModalDescriptionModalBodyModalFooterModalCloseModalBackdropSizesAnimationsControlled vs UncontrolledAccessibility