Modal

Lightweight Teleport-backed dialog for short blocking tasks, with focus restore, Escape/backdrop dismissal, and header/footer slots.

VerifiedSince 0.3.4

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 title header for accessible labeling. If you replace header, keep the title visible and avoid passing a stale title that 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 width values for docs and app surfaces because the content panel also has internal padding.
  • Reach for fullscreen only when the content itself owns the screen (image or diagram preview). It ignores width, 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

PropTypeDefaultDescription
modelValuebooleanrequiredControls whether the dialog is visible. Used by v-model.
titlestring''Default header title. When present, the generated heading is linked with aria-labelledby.
widthstring'480px'Inline width applied to the content panel. Prefer responsive values such as min(92vw, 520px). Ignored when fullscreen is set.
fullscreenbooleanfalseStretch the panel to the full visible viewport instead of centring a sized panel.

Events

EventPayloadDescription
update:modelValue(value: boolean)Emitted when the component requests visibility changes.
close()Emitted after backdrop click, Escape, or close-button dismissal.

Slots

SlotPropsDescription
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 with v-if when closed.
  • The overlay exposes role="dialog", aria-modal="true", and tabindex="-1".
  • With the default header and non-empty title, the dialog links aria-labelledby to 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 by close.
  • 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.
  • TModal forwards props, attrs, update:modelValue, close, and default/header/footer slots to TxModal.

Technologies

  • Accessibility note: aria-labelledby is generated only from the default title heading. If you provide a custom header, keep title empty unless that slot also renders an element with the generated id; otherwise the dialog can point at a missing label.
  • Verified coverage: modal.test.ts checks dialog semantics, title linkage, inline width, body/footer slots, focus restore on close/unmount, backdrop/Escape/close-button dismissal, custom header without title linkage, and TModal title forwarding.
  • Component sources: packages/tuffex/packages/components/src/modal/src/TxModal.vue and TModal.vue.
  • Export alias: packages/tuffex/packages/components/src/modal/index.ts exports installable TxModal and TModal; default export is TxModal.
  • Coverage: packages/tuffex/packages/components/src/modal/__tests__/modal.test.ts verifies dialog semantics, focus behavior, dismissal events, slots, and wrapper forwarding.
查看源码
packages/tuffex/packages/components/src/modal/index.ts