Docs
Modal

Modal

Modal dialog overlaid on the page. Blocks interaction with the background content until the user confirms/cancels the action. Supports controlled usage (isOpen/onDismiss) or uncontrolled via trigger.

When to use

✅ Use when…🚫 Avoid when…
  • For destructive confirmation (delete, overwrite).
  • For short flows that require full focus (login, "accept terms").
  • To inspect/edit an entity without losing the list's context.
  • For transient messages (an action's success). Use Notification.
  • For short contextual content anchored to an element. Use Popover.
  • For choices from a list. Use Dropdown.

Basic usage

The content is passed via children.

<Modal trigger={<Button text="simple modal" />}>
  <Text text="Lorem ipsum dolor sit amet." />
</Modal>

Variants

The modal has four color variants: primary (default), warning, success, danger. The color affects the header icon and the footer's primary button.

<Modal variant="primary" {...otherProps} />
<Modal variant="warning" {...otherProps} />
<Modal variant="success" {...otherProps} />
<Modal variant="danger" {...otherProps} />

Controlled Modal

When the state needs to be managed outside (e.g. opening in response to a network event), use isOpen/onDismiss.

import { Button } from '@apollion-dsi/core/elements/button';
import { Icon } from '@apollion-dsi/core/elements/icon';
import { Modal } from '@apollion-dsi/core/elements/modal';
import { useToggle } from '@apollion-dsi/core/hooks';
import { cog } from '@apollion-dsi/core/icons';
 
const Example = () => {
  const { active: isOpen, disable, enable } = useToggle();
 
  return (
    <>
      <Button onClick={enable} text="Show Modal" />
      <Modal isOpen={isOpen} onDismiss={disable} icon={<Icon icon={cog} />} title="Modal Title" tagline="Lorem ipsum.">
        <Text text="Content" />
      </Modal>
    </>
  );
};

Uncontrolled Modal

Pass a trigger — it can be an element (receives an automatic onClick) or a render function with { isOpen, open }.

<Modal trigger={<Button text="Show Modal" />} icon={<Icon icon={cog} />} title="Modal Title" tagline="Lorem ipsum.">
  <Text text="Content" />
</Modal>

Sizes

small, base (default), medium, large — control the maximum width.

<Modal size="small" trigger={<Button text="small" />}> ... </Modal>
<Modal size="base" trigger={<Button text="base" />}> ... </Modal>
<Modal size="medium" trigger={<Button text="medium" />}> ... </Modal>
<Modal size="large" trigger={<Button text="large" />}> ... </Modal>

Footer

To display the footer, provide primaryAction. The button layout is controlled by footer: right (default), expanded, full. The callbacks receive { close } to dismiss the modal after the action.

<Modal
  footer="right"
  secondaryText="Cancel"
  primaryText="Confirm"
  primaryAction={({ close }) => {
    save();
    close();
  }}
  {...otherProps}
>
  ...
</Modal>

Accessibility

role="dialog" with aria-modal, labeled by the title. On open, focus moves to the first interactive element; Tab and Shift+Tab cycle inside the dialog; Escape closes; on close, focus returns to the opener.

Two caveats:

  • Without title, the dialog has no accessible name — pass aria-label.
  • With noCloseButton, Escape still closes, but mouse-only users depend on the overlay. Ensure an explicit exit action in the footer.

The close button text comes from closeLabel (default 'Fechar').

Properties

Prop
Type
Default
Description
children
ReactNode
Modal body content.
closeLabel
string
Fechar
Accessible name for the close button. An icon-only button has no name of its own: without this, a screen reader announces just "button".
footer
"expanded" | "right" | "full"
right
Layout of the footer buttons. - `right`: both on the right (default). - `expanded`: secondary on the left, primary on the right. - `full`: primary only, spanning the full width.
icon
ReactNode
Icon displayed to the left of the title in the header.
isOpen
boolean
Controls the open state when NOT using `trigger`.
noCloseButton
boolean
false
Hides the header's close button. Reserved for flows where the modal can only be exited through a confirmed action (e.g. accepting terms).
onDismiss
(() => void)
Called when clicking the overlay or the close button.
onModalOpen
(() => void)
Callback fired when the modal opens — useful for tracking.
primaryAction
((props: { close: () => void; }) => void)
Callback for the footer's primary button. Receiving `{ close }` lets you trigger the action and close the modal in the same function.
primaryButton
ReactNode
Replaces the default primary button. Receives injected `onClick` and `fullWidth`.
primaryText
string
Confirmar
Primary button text (ignored when `primaryButton` is provided).
role
AriaRole
Custom `aria-role` (e.g. `alertdialog`).
secondaryAction
((props: { close: () => void; }) => void)
Callback for the footer's secondary button.
secondaryButton
ReactNode | ((p: { close: () => void; }) => ReactNode)
Replaces the default secondary button (accepts a render prop).
secondaryText
string
Cancelar
Secondary button text (ignored when `secondaryButton` is provided).
size
"small" | "base" | "medium" | "large"
'base'
Semantic width of the modal.
tagline
string
Supporting text displayed below the title.
title
string
Title displayed in the header.
variant
"primary" | "success" | "warning" | "danger"
primary
Theme color (used on the icon and the primary button).

Besides the props above, every component accepts the layout props (spacing, color, flex/grid, sizing, border) — not repeated here.

See also

Theming — surface ladder

Modal sits on the surface ladder: the panel paints surface.overlay (top of the ladder) and the backdrop uses surface.scrim. The roles are mode-aware by construction — paper is lighter than the canvas in both light and dark — and every ink inside resolves against this ground through the ground channel. Override a role per polarity with createTheme({ colors: { ladder: … } }); see Dark Mode Engine.