Modal
It just pops and shows you something.
Layout tokens
HLModal supports global CSS tokens for sizing defaults:
--hr-modal-widthcontrols the default modal width.--hr-modal-content-max-heightcontrols scrollable body max-height.
Default
<template>
<HLButton @click="showDefaultModal=!showDefaultModal" id="modal-btn-default">Default Modal</HLButton>
<HLModal id="example-modal-default" type="default" v-model:show="showDefaultModal" :mask-closable="false">
<template #header> Modal Header </template>
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton } from '@platform-ui/highrise'
import { ref } from 'vue'
const showDefaultModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script><template>
<HLButton @click="showInfoModal=!showInfoModal" id="modal-btn-info">Info Modal</HLButton>
<HLModal id="example-modal-info" type="info" v-model:show="showInfoModal" :mask-closable="false">
<template #header> Modal Header </template>
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton } from '@platform-ui/highrise'
import { ref } from 'vue'
const showInfoModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script><template>
<HLButton @click="showSuccessModal=!showSuccessModal" id="modal-btn-success">Success Modal</HLButton>
<HLModal id="example-modal-success" type="success" v-model:show="showSuccessModal" :mask-closable="false">
<template #header> Modal Header </template>
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton } from '@platform-ui/highrise'
import { ref } from 'vue'
const showSuccessModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script><template>
<HLButton @click="showWarningModal=!showWarningModal" id="modal-btn-warning">Warning Modal</HLButton>
<HLModal id="example-modal-warning" type="warning" v-model:show="showWarningModal" :mask-closable="false">
<template #header> Modal Header </template>
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton } from '@platform-ui/highrise'
import { ref } from 'vue'
const showWarningModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script><template>
<HLButton @click="showErrorModal=!showErrorModal" id="modal-btn-error">Error Modal</HLButton>
<HLModal id="example-modal-error" type="error" v-model:show="showErrorModal" :mask-closable="false">
<template #header> Modal Header </template>
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton } from '@platform-ui/highrise'
import { ref } from 'vue'
const showErrorModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script>With Custom Header
<HLButton @click="showCustomHeaderModal=!showCustomHeaderModal" id="modal-custom-header-btn-default">Open Modal</HLButton>
<HLModal id="example-custom-header-modal-default" type="default" v-model:show="showCustomHeaderModal" :showHeaderIcon="false">
<template #header>
<HLHeaderLite title="Modal Header" size="lg" subtitle="This is a sample subtitle" :closable="false">
<template #header-title>
<HLText size="2xl" :weight="'semibold'"> Modal Header with 2xl size </HLText>
</template>
<template #header-icons>
<HLIcon size="lg" color="black">
<TrendUp01Icon />
</HLIcon>
</template>
</HLHeaderLite>
</template>
{{modalText}}
</HLModal>Show Back Button
<template>
<HLButton @click="showBackModal=!showBackModal" id="modal-back-btn-default">Open Modal</HLButton>
<HLModal
id="example-back-modal-default"
type="default"
v-model:show="showBackModal"
showBack
@back="showBackModal=false"
:mask-closable="false"
>
<template #header> Modal Header </template>
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton } from '@platform-ui/highrise'
import { ref } from 'vue'
const showBackModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script>Mask Closable
When the mask is closable, the modal will be closed when the mask is clicked.
<template>
<HLButton @click="showMaskClosableModal=!showMaskClosableModal" id="modal-mask-closable-btn-default">Open Modal</HLButton>
<HLModal id="example-mask-closable-modal-default" type="default" v-model:show="showMaskClosableModal" maskClosable>
<template #header> Modal Header </template>
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton } from '@platform-ui/highrise'
import { ref } from 'vue'
const showMaskClosableModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script>With Footer
<template>
<HLButton @click="showModal=!showModal" id="modal-custom-header-footer-btn-default">Open Modal</HLButton>
<HLModal id="example-custom-header-footer-modal-default" type="default" v-model:show="showModal">
<template #header> Modal Header </template>
{{modalText}}
<template #footer>
<div class="p-4">
<HLSectionFooter id="footer" :top-padding="false" :bottom-padding="false" :horizontal-padding="false">
<HLSectionFooterItem>
<HLButton id="cancel">Discard</HLButton>
<HLButton id="save" color="blue" variant="primary">Ok </HLButton>
</HLSectionFooterItem>
</HLSectionFooter>
</div>
</template>
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton, HLSectionFooter, HLSectionFooterItem } from '@platform-ui/highrise'
import { ref } from 'vue'
const showModal = ref(false)
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script>Focus Trap
When trapFocus is enabled (default), pressing Tab cycles through focusable elements inside the modal without escaping to the page behind it.
<template>
<HLButton @click="showModal = true">Open Form Modal</HLButton>
<HLModal id="form-modal" v-model:show="showModal" :trap-focus="true" :auto-focus="true" :show-header-icon="false">
<template #header>Add Team Member</template>
<div style="display: flex; flex-direction: column; gap: 16px; padding: 8px 0;">
<HLFormItem label="Name">
<HLInput v-model="name" placeholder="Enter full name" />
</HLFormItem>
<HLFormItem label="Email">
<HLInput v-model="email" placeholder="Enter email address" />
</HLFormItem>
<HLFormItem label="Role">
<HLSelect v-model:value="role" :options="roleOptions" placeholder="Select a role" />
</HLFormItem>
</div>
<template #footer>
<div style="display: flex; gap: 8px; justify-content: flex-end; padding: 8px;">
<HLButton @click="showModal = false">Cancel</HLButton>
<HLButton variant="primary" color="blue" @click="showModal = false">Save</HLButton>
</div>
</template>
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton, HLFormItem, HLInput, HLSelect } from ‘@platform-ui/highrise’
import { ref } from ‘vue’
const showModal = ref(false)
const name = ref(‘’)
const email = ref(‘’)
const role = ref(null)
const roleOptions = [
{ label: ‘Admin’, value: ‘admin’ },
{ label: ‘Editor’, value: ‘editor’ },
{ label: ‘Viewer’, value: ‘viewer’ },
]
</script>Overlays inside a Modal
When you place an overlay component (e.g. HLSelect, HLPopover, HLDatePicker) inside an HLModal whose content is long enough to scroll, the overlay can detach from its trigger while the modal content scrolls. This happens because the overlay defaults to teleporting into <body>, which is outside the modal's scrolling container.
To keep the overlay anchored to its trigger, pass to="#<modal-id> .hr-dialog__content--text" on the overlay. This teleports the overlay's DOM into the modal's scrollable body, so trigger and overlay move together when content scrolls.
<template>
<HLButton @click="showAnchoringModal = true" id="modal-anchoring-btn">Open scrollable modal with overlays</HLButton>
<HLModal id="example-anchoring-modal" class="example-anchoring-modal--compact" v-model:show="showAnchoringModal" :show-header-icon="false">
<template #header>Anchoring overlays during scroll</template>
<HLFormItem label="Role">
<HLSelect v-model:value="anchoringRole" :options="roleOptions" placeholder="Select a role" to="#example-anchoring-modal .hr-dialog__content--text" />
</HLFormItem>
<HLFormItem label="Start date">
<HLDatePicker v-model:value="anchoringDate" type="date" placeholder="Pick a date" to="#example-anchoring-modal .hr-dialog__content--text" />
</HLFormItem>
<HLFormItem label="Start time">
<HLTimePicker v-model:value="anchoringTime" format="HH:mm" to="#example-anchoring-modal .hr-dialog__content--text" />
</HLFormItem>
{{modalText}}
{{modalText}}
{{modalText}}
</HLModal>
</template>
<script setup lang="ts">
import { HLModal, HLButton, HLFormItem, HLSelect, HLDatePicker, HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const showAnchoringModal = ref(false)
const anchoringRole = ref(null)
const anchoringDate = ref(null)
const anchoringTime = ref(null)
const roleOptions = [
{ label: 'Admin', value: 'admin' },
{ label: 'Editor', value: 'editor' },
{ label: 'Viewer', value: 'viewer' },
]
const modalText = 'Tempore vivo desidero. Bene defungo perferendis calco avarus quas crepusculum accendo.'
</script>
<style>
/* Scope the content max-height via a class on the modal — Modal.vue binds :style internally,
so a static style attribute on <HLModal> can be overridden. */
.example-anchoring-modal--compact .hr-dialog__content--text {
--hr-modal-content-max-height: 320px;
}
</style>Accessibility
role="dialog",aria-modal="true", andaria-labelledby(pointing to the modal title) are set automatically.- Focus is trapped inside the modal by default (
trapFocus). PressEscapeto close. - Point explanatory text to
aria-describedby, especially for confirmations or forms. - Sync the trigger button’s
aria-expanded/aria-controls(or provide another status message) so users know when the modal opens/closes. - Provide a stable
id; the component links the header slot to the dialog automatically via${id}-title.
Imports
import { HLModal } from '@platform-ui/highrise'Props
| Name | Type | Default | Description |
|---|---|---|---|
| id | string | undefined | hr-modal-{n} (auto) | The unique identifier of the modal. When omitted, a stable hr-modal-{n} id is generated and the header title receives {id}-title wired through aria-labelledby. |
| autoFocus | boolean | false | Automatically focus on the modal when opened |
| blockScroll | boolean | true | Prevent background page scroll while modal is open |
| className | string | undefined | undefined | Custom class name for the modal |
| closeOnEsc | boolean | true | Close the modal when the Escape key is pressed |
| footerDivider | boolean | false | Show a divider above the footer area |
| maskClosable | boolean | true | Close the modal when clicking on the mask |
| show | boolean | false | Control the visibility of the modal |
| showBack | boolean | false | Show the back button in the modal |
| showClose | boolean | true | Show the close button in the modal |
| showFooter | boolean | true | Show the footer section in the modal |
| showHeader | boolean | true | Show the header section in the modal |
| showHeaderIcon | boolean | true | Show the icon in the header section |
| to | string | HTMLElement | undefined | undefined | Specify the container to which the modal is appended |
| trapFocus | boolean | true | Traps the focus inside the modal |
| type | 'default' | 'info' | 'success' | 'warning' | 'error' | 'default' | Type of the modal indicating its purpose |
| width | number | 483 (via var(--hr-modal-width)) | Width of the modal in pixels. Default resolves through the --hr-modal-width CSS variable published by HLContentWrap. |
| zIndex | number | undefined | undefined | z-index of the modal |
Note
If you're facing issues with unfocusable input elements when you have multiple instances open, try setting :trapFocus="false" on the underlying instance of the component.
Accessibility
- Focus trapping keeps keyboard users within dialogs/drawers so they can’t tab into the page behind the overlay. Disable it only when you intentionally need the background to stay reachable (for example, nested panels) and provide guidance on how to return to the original surface.
- When you turn trapping off, manage focus manually: move focus to the element that should be active next and offer a clear close button so users can re-enter the dialog flow without relying on a mouse.
Emits
| Name | Default | Trigger |
|---|---|---|
@after-enter | () => void | after the modal has fully entered |
@after-leave | () => void | after the modal has fully left |
@back | () => void | when the back button is clicked |
@before-leave | () => void | before the modal starts to leave |
@close | () => void | when the modal is closed |
@esc | () => void | when the Escape key is pressed |
@left-click | () => void | when the footer left button is clicked |
@mask-click | (payload: PointerEvent) => void | when the mask (overlay) is clicked |
@right-primary-click | () => void | when the footer right primary button is clicked |
@right-secondary-click | () => void | when the when the footer right secondary button is clicked |
@update:show | (payload: boolean) => void | when the visibility of the modal is updated (show/hide toggle) |
Slots
| Name | Parameters | Description |
|---|---|---|
| default | () | The default content slot |
| header | () | The header content slot |
| footer | () | The footer content slot |