Modal
Lightweight Teleport-backed dialog for short blocking tasks, with focus restore, Escape/backdrop dismissal, and header/footer slots.
Usage
Open the dialog from a button, keep the body concise, and close it by updating the bound model.
Loading demo...
Best Practices
- Keep modal work narrow: confirmation, one-step input, or a short decision. Use a drawer/page when content needs navigation, filtering, or long forms.
- Prefer the default
titleheader for accessible labeling. If you replaceheader, keep the title visible and avoid passing a staletitlethat no longer matches the custom header markup. - Put destructive or final actions in the
footer; keep secondary actions visually quieter than the primary action. - Use responsive
widthvalues for docs and app surfaces because the content panel also has internal padding. - Reach for
fullscreenonly when the content itself owns the screen (image or diagram preview). It ignoreswidth, drops the panel radius and shadow, and the body becomes the scrolling region — a short confirmation does not belong there. - Do not store long-running async state only inside modal content. If closing cancels work, model that cancellation in the parent.
Fullscreen Panel
<TxModal v-model="previewOpen" fullscreen :title="current?.name">
<img :src="current.url" alt="">
<template #footer>
<TxButton variant="ghost" @click="previewOpen = false">Close</TxButton>
</template>
</TxModal>
fullscreen stretches the panel to the visible viewport (a dvh height where supported, so mobile browser chrome does not cover the footer). The panel becomes a header/body/footer column: the body takes the remaining space and scrolls, the bars stay put, and the footer's bottom padding carries safe-area-inset-bottom.
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | required | Controls whether the dialog is visible. Used by v-model. |
title | string | '' | Default header title. When present, the generated heading is linked with aria-labelledby. |
width | string | '480px' | Inline width applied to the content panel. Prefer responsive values such as min(92vw, 520px). Ignored when fullscreen is set. |
fullscreen | boolean | false | Stretch the panel to the full visible viewport instead of centring a sized panel. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | (value: boolean) | Emitted when the component requests visibility changes. |
close | () | Emitted after backdrop click, Escape, or close-button dismissal. |
Slots
| Slot | Props | Description |
|---|---|---|
default | - | Main dialog body. |
header | - | Replaces the generated title area while keeping the built-in close button. |
footer | - | Footer action row. Hidden when omitted. |
Overview
- The overlay is mounted under
body, receives a fresh z-index from the shared z-index manager on open, and is removed withv-ifwhen closed. - The overlay exposes
role="dialog",aria-modal="true", andtabindex="-1". - With the default header and non-empty
title, the dialog linksaria-labelledbyto the generated heading. - Opening focuses the overlay root. Closing or unmounting restores the element focused before opening.
- Tab and Shift+Tab cycle inside the topmost visible modal. Keyboard ownership follows live dialog semantics and z-index, so disabling the focused submit button does not let the drawer underneath consume Escape; the next Tab returns focus to the modal even if the browser moved it to
body. - Backdrop click, Escape, and the close button all emit
update:modelValue(false)followed byclose. - With
fullscreen, the overlay keeps its dialog semantics, focus trap, Escape handling and focus restore; only the layout changes — the panel stretches to the visible viewport and its header/body/footer become a fixed-bar column with the body scrolling. - The fullscreen panel covers the whole overlay, so there is no backdrop region left to click: Escape and the close button are the dismissals. Keep a visible close affordance in the header or footer.
TModalforwards props, attrs,update:modelValue,close, anddefault/header/footerslots toTxModal.
Technologies
- Accessibility note:
aria-labelledbyis generated only from the defaulttitleheading. If you provide a customheader, keeptitleempty unless that slot also renders an element with the generated id; otherwise the dialog can point at a missing label. - Verified coverage:
modal.test.tschecks dialog semantics, title linkage, inline width, body/footer slots, focus restore on close/unmount, backdrop/Escape/close-button dismissal, custom header without title linkage, andTModaltitle forwarding. - Component sources:
packages/tuffex/packages/components/src/modal/src/TxModal.vueandTModal.vue. - Export alias:
packages/tuffex/packages/components/src/modal/index.tsexports installableTxModalandTModal; default export isTxModal. - Coverage:
packages/tuffex/packages/components/src/modal/__tests__/modal.test.tsverifies dialog semantics, focus behavior, dismissal events, slots, and wrapper forwarding.
查看源码
packages/tuffex/packages/components/src/modal/index.ts