Notification
Create dismissible, toast-style messages — each one rendered as an HLAlert.
Basic Usage
Notifications are created imperatively. The flow has three parts:
- A provider must sit above the component in the tree.
HLContentWrapincludes one, so in most docs examples (and apps that already wrap their tree inHLContentWrap) no extra setup is needed. UseHLNotificationProviderdirectly only when you need custom placement or a custom container target — seeHLNotificationProviderbelow. - Inside a component that descends from the provider, call
useHLNotification()to get thenotificationinstance. - Call
notification.create(), rendering your content withh(HLAlert, …). It returns the created instance, which exposes adestroy()method — capture it (asnotificationInstancein the example) and callnotificationInstance.destroy()from the alert'scloseevent to dismiss that notification.
<script setup lang="ts">
import { h } from 'vue'
import { HLAlert, HLButton, useHLNotification } from '@platform-ui/highrise'
// The component this runs in must descend from a notification provider
// (e.g. be wrapped in HLContentWrap).
const notification = useHLNotification()
function openNotification() {
const notificationInstance = notification.create({
duration: 5000,
content: () =>
h(
HLAlert,
{
id: 'test-alert',
title: 'Notification',
type: 'notification',
closable: true,
actionOne: {
text: 'This is a button',
onActionClick: () => {},
},
actionTwo: {
text: 'This is a button with icon',
disabled: false,
},
// Dismiss this notification when the alert is closed.
onClose: () => notificationInstance.destroy(),
},
{
default: () => 'Notification description',
}
),
})
}
</script>
<template>
<HLButton id="open-notification" @click="openNotification">Create Notification</HLButton>
</template>Transform Legacy Notification Options
Use transformNotificationOpts when you need to map legacy /ghl-ui notification options into the HighRise notification shape.
transformNotificationOpts builds the HLAlert content for you from the legacy fields (title, description, content, meta, type, action, avatar), so you pass a flat options object instead of an h(HLAlert, …) render function. type maps to the alert's colour (success → green, warning → orange, error → red, info → blue), and duration drives both the notification's dismiss timer and the alert's own auto-close countdown.
<script setup lang="ts">
import { h, onBeforeUnmount } from 'vue'
import { HLButton, transformNotificationOpts, useHLNotification } from '@platform-ui/highrise'
const notification = useHLNotification()
let notificationInstance: { destroy: () => void } | null = null
function openNotificationTransformed() {
notificationInstance = notification.create(
transformNotificationOpts({
title: 'Custom object successfully updated',
duration: 2000,
type: 'success',
description: 'This is a description',
content: 'This is a content',
meta: 'This is a meta',
action: () =>
h(HLButton, { id: 'dismiss-button', onClick: () => notificationInstance?.destroy() }, { default: () => 'Dismiss' }),
})
)
}
// Clear any notifications still on screen when this component unmounts.
onBeforeUnmount(() => notification.destroyAll())
</script>
<template>
<HLButton id="open-notification-transformed" @click="openNotificationTransformed">Create Notification</HLButton>
</template>Sharing one notification instance across an app
useHLNotification() must run inside a component that descends from HLNotificationProvider. But in a real app you usually want to fire notifications from many places — stores, composables, deeply-nested components — where you can't (or don't want to) call useHLNotification() again.
The recommended pattern is to call useHLNotification() once in a root component (inside the provider), stash the instance in a small module singleton, and expose typed helpers (createSuccessNotification, createErrorNotification, …) that render an HLAlert for you. The helpers build the HLAlert content — including auto-destroy on close — so callers only pass a title and message.
import { HLAlert, useHLNotification } from '@platform-ui/highrise'
import type { HLAlertColor } from '@platform-ui/highrise'
import { h } from 'vue'
type NotificationApi = ReturnType<typeof useHLNotification>
let notificationInstance: NotificationApi | null = null
/**
* Call once from a root component that descends from `HLNotificationProvider`
* (see `Root.vue` below), passing the instance returned by `useHLNotification()`.
* Every helper below then routes through that single instance.
*/
export const initNotification = (notification: NotificationApi) => {
notificationInstance = notification
}
export const clearNotification = () => {
notificationInstance = null
}
const getNotification = () => {
if (!notificationInstance) {
console.warn('Notification instance is not ready yet. Did you call initNotification() in your root component?')
return null
}
return notificationInstance
}
let uid = 0
export interface NotifyOptions {
title: string
message?: string
/** Auto-dismiss after N ms. Pass `0` to keep it until closed manually. Defaults to 3000. */
duration?: number
}
const notify = (color: HLAlertColor, { title, message, duration = 3000 }: NotifyOptions) => {
const notification = getNotification()
if (!notification) return null
const id = `hr-notification-${uid++}`
const instance = notification.create({
duration,
content: () =>
h(
HLAlert,
{
id,
title,
color,
type: 'notification',
closable: true,
onClose: () => instance?.destroy(),
},
{ default: () => message }
),
})
return instance
}
export const createSuccessNotification = (options: NotifyOptions) => notify('green', options)
export const createErrorNotification = (options: NotifyOptions) => notify('red', options)
export const createWarningNotification = (options: NotifyOptions) => notify('orange', options)
export const createInfoNotification = (options: NotifyOptions) => notify('blue', options)<script setup lang="ts">
// Mounted once, inside HLNotificationProvider.
import { onBeforeUnmount } from 'vue'
import { useHLNotification } from '@platform-ui/highrise'
import { clearNotification, initNotification } from './notificationUtils'
// Call useHLNotification() directly in setup — it injects from the provider,
// so it must not be deferred into onMounted or an event handler.
initNotification(useHLNotification())
onBeforeUnmount(() => {
clearNotification()
})
</script>// Any module, store, or component — no provider context needed.
import { createSuccessNotification } from './notificationUtils'
createSuccessNotification({
title: 'Saved',
message: 'Your changes were saved.',
})Placement
The placement prop accepts top, bottom, top-left, top-right, bottom-left, and bottom-right. It defaults to top-right, so omitting it entirely gives you the same result as the top-right trigger below.
<script setup lang="ts">
const placementOptions = ['top-left', 'top', 'top-right', 'bottom-left', 'bottom', 'bottom-right'] as const
</script>
<template>
<div class="grid p-4" style="grid-template-rows: repeat(2, 1fr); grid-template-columns: repeat(3, 1fr); gap: 10px;">
<HLNotificationProvider v-for="placement in placementOptions" :key="placement" :placement="placement">
<NotificationTrigger
:id="'placement-' + placement"
:title="placement + ' title'"
:description="placement + ' description'"
:duration="1000"
>
{{ placement }}
</NotificationTrigger>
</HLNotificationProvider>
</div>
</template>
<!-- Omitting `placement` altogether is the same as placement="top-right" --><script setup lang="ts">
import { h } from 'vue'
import { HLAlert, HLButton, useHLNotification } from '@platform-ui/highrise'
const props = withDefaults(
defineProps<{
id: string
title?: string
description?: string
duration?: number
}>(),
{
title: 'Notification',
description: 'Notification description',
duration: 5000,
}
)
const notification = useHLNotification()
let notificationInstance: { destroy: () => void } | null = null
const handleCreateNotification = () => {
notificationInstance = notification.create({
content: () =>
h(
HLAlert,
{
title: props.title,
closable: true,
id: props.id,
type: 'notification',
color: 'green',
onClose: () => {
notificationInstance?.destroy()
},
},
{
default: () => props.description,
}
),
duration: props.duration,
})
}
</script>
<template>
<HLButton :id="id" @click="handleCreateNotification">
<slot>Create Notification</slot>
</HLButton>
</template>Max Notifications
The max prop caps the number of notifications shown at once, queuing any beyond the limit.
<template>
<HLNotificationProvider placement="top-right" :max="3">
<NotificationTrigger id="notification-provider-max-trigger" :duration="0">
Fire Notifications
</NotificationTrigger>
</HLNotificationProvider>
</template><script setup lang="ts">
import { h } from 'vue'
import { HLAlert, HLButton, useHLNotification } from '@platform-ui/highrise'
const props = withDefaults(
defineProps<{
id: string
title?: string
description?: string
duration?: number
}>(),
{
title: 'Notification',
description: 'Notification description',
duration: 5000,
}
)
const notification = useHLNotification()
let notificationInstance: { destroy: () => void } | null = null
const handleCreateNotification = () => {
notificationInstance = notification.create({
content: () =>
h(
HLAlert,
{
title: props.title,
closable: true,
id: props.id,
type: 'notification',
color: 'green',
onClose: () => {
notificationInstance?.destroy()
},
},
{
default: () => props.description,
}
),
duration: props.duration,
})
}
</script>
<template>
<HLButton :id="id" @click="handleCreateNotification">
<slot>Create Notification</slot>
</HLButton>
</template>Teleport Target
The to prop teleports notifications into a specific DOM element instead of the document body.
<template>
<HLNotificationProvider to="#notification-teleport-target" :container-style="{ position: 'absolute' }" :max="2">
<div class="space-y-3">
<NotificationTrigger id="notification-provider-teleport-trigger" :duration="50000">
Create Teleported Notification
</NotificationTrigger>
<div
id="notification-teleport-target"
style="position: relative; min-height: 180px; border: 1px dashed #98A2B3; border-radius: 8px; padding: 16px; overflow: hidden;"
>
Custom teleport target
</div>
</div>
</HLNotificationProvider>
</template><script setup lang="ts">
import { h } from 'vue'
import { HLAlert, HLButton, useHLNotification } from '@platform-ui/highrise'
const props = withDefaults(
defineProps<{
id: string
title?: string
description?: string
duration?: number
}>(),
{
title: 'Notification',
description: 'Notification description',
duration: 5000,
}
)
const notification = useHLNotification()
let notificationInstance: { destroy: () => void } | null = null
const handleCreateNotification = () => {
notificationInstance = notification.create({
content: () =>
h(
HLAlert,
{
title: props.title,
closable: true,
id: props.id,
type: 'notification',
color: 'green',
onClose: () => {
notificationInstance?.destroy()
},
},
{
default: () => props.description,
}
),
duration: props.duration,
})
}
</script>
<template>
<HLButton :id="id" @click="handleCreateNotification">
<slot>Create Notification</slot>
</HLButton>
</template>Notification Lifecycle Events
create() accepts two lifecycle callbacks alongside content and duration:
onAfterEnter— the notification has finished its enter transition.onAfterLeave— it has fully left and been removed from the DOM.
Use onAfterLeave for cleanup that must not run while the notification is still animating out, such as releasing a queued item or firing the next step in a sequence.
Event Log:
<script setup lang="ts">
import { h } from 'vue'
import { HLAlert, HLButton, useHLNotification } from '@platform-ui/highrise'
const notification = useHLNotification()
let notificationInstance: { destroy: () => void } | null = null
const handleCreateNotification = () => {
notificationInstance = notification.create({
duration: 3000,
onAfterEnter: () => console.log('onAfterEnter — finished entering'),
onAfterLeave: () => console.log('onAfterLeave — removed from the DOM'),
content: () =>
h(
HLAlert,
{
id: 'lifecycle-alert',
title: 'Lifecycle',
closable: true,
type: 'notification',
color: 'green',
onClose: () => notificationInstance?.destroy(),
},
{ default: () => 'Auto-dismisses after 3000ms, or close it yourself.' }
),
})
}
</script>
<template>
<HLButton id="notification-lifecycle-trigger" @click="handleCreateNotification">Create Notification</HLButton>
</template>Imports
import { HLAlert, HLNotificationProvider, transformNotificationOpts, useHLNotification } from '@platform-ui/highrise'
import type { HLNotificationOptions, HLNotificationProviderProps } from '@platform-ui/highrise'Props
HLNotificationProvider Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-right' | Where notifications appear on screen. |
max | number | undefined | Maximum number of notifications shown at once. Any beyond the limit are queued. Uncapped when omitted. |
to | string | HTMLElement | undefined | Target element (or selector) to teleport the notification container into. Defaults to document.body. |
container-class | string | undefined | Class applied to the notification container. |
container-style | string | CSSProperties | undefined | Inline style applied to the notification container. |
scrollable | boolean | true | Allow the container to scroll when notifications overflow. Ignored for top and bottom placements. |
keep-alive-on-hover | boolean | false | Pause a notification's auto-close timer while the pointer is over it. Only applies to notifications created with a duration; can also be set per notification via create(). |
Slots
HLNotificationProvider Slots
| Name | Parameters | Description |
|---|---|---|
default | () | The default slot. |
Notification API
Call useHLNotification() inside a component that descends from a provider (directly, or via HLContentWrap). It returns an instance with two methods:
| Method | Signature | Description |
|---|---|---|
create | (options: HLNotificationOptions) | Shows a notification and returns a reference to it (see below). |
destroyAll | () => void | Immediately removes every notification created through this instance. |
create options
HLNotificationOptions carries the notification-shell settings. The visible content — title, description, colour, close button, actions — comes from the HLAlert you render inside content, so those live on HLAlert, not here. See the Alert props for that surface.
| Option | Type | Default | Description |
|---|---|---|---|
content | () => VNodeChild | undefined | Render function for the notification body. Return an HLAlert (via h) to get the standard visual. |
duration | number | undefined | Auto-dismiss after this many milliseconds. Omit (or 0) to keep it until dismissed manually. |
keepAliveOnHover | boolean | false | Pause this notification's auto-close timer while the pointer is over it. Has no effect without a duration. |
onAfterEnter | () => void | undefined | Called once the notification has finished its enter transition. |
onAfterLeave | () => void | undefined | Called once the notification has finished its leave transition and is removed from the DOM. |
onMouseenter | (e: MouseEvent) => void | undefined | Called when the pointer enters the notification. |
onMouseleave | (e: MouseEvent) => void | undefined | Called when the pointer leaves the notification. |
INFO
The notification shell's own close button is always suppressed — create() forces closable: false on it — so the visible close control comes from the HLAlert you render in content. Keep closable: true on that alert and wire its close event to destroy().
The create return value
create() returns a reference to the notification it created:
| Member | Signature | Description |
|---|---|---|
destroy | () => void | Dismisses this specific notification. |
key | string | The unique key assigned to this notification. |
A common pattern is to keep the returned reference and call destroy() from the HLAlert's close event so the notification is removed when the user dismisses the alert:
const instance = notification.create({
duration: 5000,
content: () =>
h(HLAlert, { id: 'saved', title: 'Saved', onClose: () => instance.destroy() }, { default: () => 'Your changes were saved.' }),
})Accessibility
The notification's accessibility comes from the HLAlert you render inside it:
HLAlertrenders withrole="alert"by default, which maps toaria-live="assertive"so assistive technology announces it as soon as it appears. Set the alert'sroletostatusfor non-urgent messages, which announces politely (aria-live="polite") without interrupting.- Keep
closable: true(the default) so the alert renders a keyboard-focusable close control; wire itscloseevent to the notification'sdestroy()so dismissing it also removes the notification. - An auto-closing
HLAlertpauses its own timer while it is focused or hovered and resumes on blur/leave, so keyboard users who tab into a notification are not raced by the timeout. - Provide an
ariaLabelon any icon-only action button (actionOne/actionTwo) so its purpose is announced.