Modal
A modal is an overlay dialog that focuses the user’s attention on a specific task or content.
Ready to use
A Modal is a focused container that appears above the page to display essential, interruptive, or task-related content without navigating away from the current view. It temporarily disables interaction with the underlying interface and requires the user to complete an action before returning to the previous context.
Anatomy
Section titled “Anatomy”- Container
- Close button
- Optional image
- Content slot
- Action area
- Backdrop
When to use it
Section titled “When to use it”Use the Modal when:
- The user must take an action before continuing (confirm, acknowledge, complete a task).
- You need to display content that requires full focus without distractions.
- The interaction must not be missed (legal notices, blocking states).
Avoid the Modal when:
- The information is optional or lightweight, and could sit inline or in a Popover.
- The interruption breaks the user flow unnecessarily.
- A non-blocking alternative (sheet, inline block, banner) would feel smoother.
Properties
Section titled “Properties”Variant
Three composition shapes: Full screen (large surfaces such as comparison views), With image (a media-led modal where the visual anchors the message), and Without image (text-and-actions, the default for confirmations and forms).
Close button
Mandatory in every variant — a tertiary Button-Main at the LG size with the close icon. Optional dismissals like tapping outside the modal are not defined at component level and must not replace the visible close button.
Image
Optional media slot at the top of the modal, sourced from the Image component for consistent responsive behaviour. Recommended aspect ratio is 16:9 or 9:16.
Content slot
Mandatory and flexible — holds text, images, simple forms, or component instances. The modal supplies the container and behaviour; the slot’s content follows its own component guidelines.
Action area
Holds Primary, Secondary, Tertiary, and Link buttons drawn from the Button Main and Link Button components. Layout reflows from horizontal (desktop and tablet) to vertical (mobile, or whenever long labels need the room).
Backdrop
Mandatory — the modal sits above a semi-transparent Backdrop that blocks interaction with underlying content and isolates focus. Tokens for colour, opacity, and elevation live in the Backdrop component.
Platform considerations
Section titled “Platform considerations”Desktop
The modal appears as a centred dialog with a backdrop. Horizontal action layout is the default, with buttons side-by-side following the standard hierarchy.
Tablet
Same centred dialog with horizontal actions. When labels are long enough to wrap, switch to vertical actions to keep the buttons readable.
Mobile
The modal automatically adopts a bottom-sheet layout, sliding up from the bottom. Actions stack vertically so the touch targets stay in the thumb zone. Structure, slot, close button, and action sets stay identical — only the layout changes.
Best practices
Section titled “Best practices”Treat the modal as a focus tool — the moment it appears, nothing else should pull the user’s attention.
Do
Use a modal when the user must focus on a single task or piece of information, keep the close button visible and accessible, trap focus until the modal closes and return focus to the trigger on dismiss, and keep all content inside the modal reachable in a logical reading order.
Don't
Don’t use a modal for long or complex content (consider a dedicated page when copy grows), don’t stack multiple modals, and don’t rely on the backdrop alone for dismissal — the close button is mandatory.
Content guidelines
Section titled “Content guidelines”The modal’s first line of copy carries the most weight — keep it short and name the decision the user needs to make. Body copy explains the consequence (“Deleting will remove all saved settings”); action buttons name the verb (“Delete”, “Cancel”). Avoid filler (“Are you sure you want to…?”) in favour of direct phrasing, and keep button labels consistent across modals so the choices recognise quickly.
Styles
Section titled “Styles”A modal is an overlay dialog that focuses the user’s attention on a specific task or content.
<div class="tng-modal"> <div class="tng-modal-panel"> <button class="tng-button is-tertiary is-ghost"> <span>Close</span> <i class="tng-icon icon-close" aria-hidden="true"></i> </button> <div class="tng-modal-content"> <div class="tng-slot is-primary"></div> </div> </div></div>Fullscreen
Section titled “Fullscreen”You can use the positioning utilities to create a fullscreen modal.
<div class="tng-modal | p-absolute at-maximum"> <div class="tng-modal-panel"> <button class="tng-button is-tertiary is-ghost"> <span>Close</span> <i class="tng-icon icon-close" aria-hidden="true"></i> </button> <div class="tng-modal-content"> <div class="tng-slot is-primary"></div> </div> </div></div>With Scroll
Section titled “With Scroll”When a modal contains potentially more content than fits in the viewport, you can use the overflow utility to make the content area scrollable.
<div class="tng-modal" style="max-block-size: 300px"> <div class="tng-modal-panel"> <button class="tng-button is-tertiary is-ghost"> Close <i class="tng-icon icon-close" aria-hidden="true"></i> </button> <div class="tng-overflow-scroll is-block"> <div class="tng-modal-content"> <p class="tng-text-body">…</p> <p class="tng-text-body">…</p> </div> </div> </div></div>With Image
Section titled “With Image”<div class="tng-modal"> <div class="tng-frame"> <div class="tng-slot"></div> </div> <div class="tng-modal-panel"> <button class="tng-button is-tertiary is-ghost"> Close <i class="tng-icon icon-close" aria-hidden="true"></i> </button> <div class="tng-modal-content"> <div class="tng-slot is-primary"></div> </div> </div></div>Recipes
Section titled “Recipes”Modal Dialog
Section titled “Modal Dialog”Import the Modal component from @tmedxp/react-components.
Properties
Section titled “Properties”ModalProperties extends <PropsWithChildren<Omit<ComponentProps<'dialog'>, 'open'>>>.
| Prop | Type | Description | Optional |
|---|---|---|---|
instance | ModalInstance | The modal instance used to open / close | ❌ |
position | ModalPosition | Position: 'ABSOLUTE' | 'FIXED' | 'RELATIVE' | ✅ |
placement | ModalPlacement | Placement: centered, top, bottom, etc. | ✅ |
labels | ModalLabels | Collection of labels used by the modal | ❌ |
frameContent | React.ReactElement | Content of tng-frame container if framed | ✅ |
Example without frame
Section titled “Example without frame”import { Modal, ModalPlacement, ModalPosition, useModalInstance, Button,} from '@tmedxp/react-components';
const modalInstance = useModalInstance();const ModalExample = () => { return ( <> <Button buttonType="button" buttonSize="md" buttonStyle="primary" onClick={modalInstance.open} text="Open modal overlay" /> <Modal instance={modalInstance} position={ModalPosition.ABSOLUTE} placement={ModalPlacement.CENTER} labels={{ Close: 'Close overlay' }} > <p class="tng-text-body"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p> </Modal> </> );};
export { ModalExample };Example with frame
Section titled “Example with frame”import { Modal, ModalPlacement, ModalPosition, useModalInstance, Button,} from '@tmedxp/react-components';
const modalInstance = useModalInstance();const frameContent = <div className="tng-slot"></div>;const ModalExample = () => { return ( <> <Button buttonType="button" buttonSize="md" buttonStyle="primary" onClick={modalInstance.open} text="Open framed modal overlay" /> <Modal instance={modalInstance} position={ModalPosition.ABSOLUTE} placement={ModalPlacement.CENTER} labels={{ Close: 'Close overlay' }} frameContent={frameContent} > <p class="tng-text-body"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p> </Modal> </> );};
export { ModalExample };useDialog hook
Section titled “useDialog hook”Pairs with Modal to manage open/close state.
Properties
Section titled “Properties”id— a string referencing the dialog.isModal—trueto show as a modal.onSubmit— invoked when the dialog is closing.
Returned API
Section titled “Returned API”reference— the dialog’s DOM reference.properties— the dialog’s properties.softClose()— close the dialog.openOverlay()— open the dialog.
Example
Section titled “Example”const dialog = useDialog({ id: 'overlay', isModal: true, onSubmit: () => alert('The dialog has been closed'),});
dialog.openOverlay();
dialog.softClose();For designers
Section titled “For designers”- The Modal must trap focus while it is open, keeping keyboard and assistive technology users inside the dialog.
- Focus must return to the trigger once the Modal is closed.
- The first focusable element should be the Close Button.
- The Modal must announce itself to screen readers as a dialog using the correct accessibility attributes (for example
role="dialog"andaria-modal="true"). - Content inside the slot must follow the accessibility rules of each component placed inside it.
For developers
Section titled “For developers”The <dialog> element
Section titled “The <dialog> element”Always render a modal on a <dialog> element and open it with showModal(). This gives you built-in browser behaviour that is difficult to replicate manually:
- Promotes the dialog to the top layer — no
z-indexconflicts. - Renders a
::backdroppseudo-element automatically. - Traps focus inside the dialog while it is open.
- Returns focus to the previously focused element on close.
- Closes on Escape by default.
Roles & attributes
Section titled “Roles & attributes”| Attribute | Purpose |
|---|---|
aria-labelledby | Point to the modal’s visible title so assistive technologies announce it. |
aria-describedby | Optionally point to a description paragraph for additional context. |
aria-controls | Set on the trigger button, referencing the dialog’s id. |
aria-modal="true" | Implied automatically when using showModal(). |
Inert background
Section titled “Inert background”When a modal is open the rest of the page must be unreachable. showModal() handles this for the accessibility tree, but pointer events on the underlying page can still leak through. Set document.body.inert = true while the dialog is open and reset it on close, as shown in the Modal Dialog recipe.
Keyboard interaction
Section titled “Keyboard interaction”| Key | Action |
|---|---|
| Tab / Shift+Tab | Cycles focus through focusable elements inside the dialog (focus trap is built in with showModal()). |
| Escape | Closes the dialog (built-in). Listen for the close event to run cleanup. |
Closing with a form
Section titled “Closing with a form”Wrap the dialog content in a <form method="dialog">. Any <button> inside the form that is not type="button" will close the dialog and its value will be available via dialog.returnValue. This keeps close / confirm actions accessible without custom JavaScript.
Focus Order
Section titled “Focus Order”The Modal follows a consistent focus-trap pattern across all devices:
Desktop & Portable Devices (keyboard navigation)
- Initial focus moves to the first interactive element: the Close button (always present). This ensures a predictable and accessible starting point regardless of the content inserted in the slot.
- Tab / Shift+Tab cycle through all interactive elements inside the Modal in this order:
- Close button
- Interactive elements inside the content slot (if any)
- Action buttons (horizontal or vertical set — optional)
- Link (optional)
- Focus cannot escape the Modal until it is closed.
- When closing, focus returns to the element that triggered the Modal.
Mobile & Tablet (touch + assistive tech)
- Mobile users do not navigate with a physical Tab key, but assistive technologies (VoiceOver, TalkBack, Switch Control) follow the same logical focus sequence defined for desktop.
- The focus-trap still applies:
- Screen readers move through the elements in the same order: Close → Slot content → Action buttons (optional) → Link (optional).
- Users cannot reach content outside the Modal until it is closed.
Universal rule — Closing the Modal always returns accessibility focus to the element that opened it.