Tessina UI
ConfiguratorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleCheckboxChipCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFile Upload 2FlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Modal

An accessible dialog overlay built on Base UI. Compound component pattern with Header, Body, Footer, Title, Description, and Close. Five sizes. Five corner-rounding options. 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.

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.

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)
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) => void—Callback when open state changes
dismissiblebooleantrueWhether clicking the backdrop or pressing Escape closes the modal
modalbooleantrueWhether the dialog uses modal behavior (focus trap, scroll lock)

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""full"Corner rounding of the panel
classNamestring—Extra classes on the popup

All other native <div> attributes (forwarded to Dialog.Popup) are accepted.

ModalHeader

PropTypeDefaultDescription
iconReact.ReactNode—Optional leading icon beside the title
showClosebooleantrueWhether to render the built-in X close button. Set to false to use a custom <ModalClose>

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

Flex row aligned to the end (justify-end). Accepts className to override layout if needed.

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.

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

Meter

Gauge displaying a known value within a fixed range — battery, disk usage, score, capacity. Linear or circular, four sizes, full intent palette with optional threshold-based auto-coloring, locale-aware formatting, and a multi-segment MeterGroup variant for stacked usage.

Navigation Menu

A full top-bar navigation pattern — logo, centre menu with mega-menu popups, and right-aligned action slots for language selector and auth CTAs. Ships two desktop trigger treatments (`pill`, `underline`), a `mode="mobile"` variant with a right-side drawer + submenu drill-in, richer mega-menu rows (tag + badge), and a theme-aware logo that swaps between light and dark sources. Built on @base-ui/react with 5 sizes, 5 rounded options, and LTR/RTL support.

On this page

PlaygroundInstallationUsageExamplesDefaultSizesRoundedConfirmationFormNon-dismissibleAPI ReferenceModalModalTriggerModalContentModalHeaderModalTitleModalDescriptionModalBodyModalFooterModalCloseModalBackdropSizesAnimationsControlled vs UncontrolledAccessibility