Alert
An inline message that reports status and offers a next step. The layout is content-driven: a title alone renders a single row, a description or a second action switches to a banner.
Playground
Upgrade to keep enjoying features.
<Alert
title="Your trial ends soon!"
description="Upgrade to keep enjoying features."
:primary-action="{ label: 'Update now', onClick: ({ dismiss }) => dismiss() }"
/>Dismissible rows
Plain confirmations with only a × button. The parent hides the alert on @dismiss.
<script setup>
import { ref } from 'vue'
import { Alert } from 'frappe-ui'
// Plain confirmations with only a × button — `:icon="false"` hides the
// gray theme's default info icon, matching the design's neutral rows.
// The parent owns hiding — dismiss just flips a flag.
const messages = ref([
{ id: 1, title: 'Contacts added successfully' },
{ id: 2, title: 'Deal moved to Negotiation' },
])
function remove(id) {
messages.value = messages.value.filter((m) => m.id !== id)
}
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-2">
<Alert
v-for="message in messages"
:key="message.id"
:title="message.title"
:icon="false"
dismissible
@dismiss="remove(message.id)"
/>
<button
v-if="!messages.length"
class="self-start text-sm text-ink-gray-5 underline"
@click="
messages = [
{ id: 1, title: 'Contacts added successfully' },
{ id: 2, title: 'Deal moved to Negotiation' },
]
"
>
Bring the messages back
</button>
</div>
</template>Themed rows
One-line status rows with a single action. The theme colors the icon and the action label.
<script setup>
import { ref } from 'vue'
import { Alert } from 'frappe-ui'
// One-line status rows. The theme colors the icon and the action label;
// the container stays neutral. Actions report in the status line below.
const status = ref('')
function open(target) {
status.value = `${target} opened`
}
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-2">
<Alert
title="SLA timer has started"
theme="blue"
:primary-action="{ label: 'View SLA', onClick: () => open('SLA details') }"
/>
<Alert
title="Failed to create lead"
theme="red"
:primary-action="{ label: 'Try again', onClick: () => open('Lead form') }"
/>
<Alert
title="Meeting scheduled"
theme="green"
:primary-action="{ label: 'Open calendar', onClick: () => open('Calendar') }"
/>
<Alert
title="Storage is almost full"
theme="amber"
:primary-action="{ label: 'Manage', onClick: () => open('Storage settings') }"
/>
<p v-if="status" class="text-sm text-ink-gray-5">{{ status }}</p>
</div>
</template>Banners
A description or a second action switches the alert to the banner layout. A "Dismiss" action calls context.dismiss().
<script setup>
import { ref } from 'vue'
import { Alert } from 'frappe-ui'
// Banners stack a description and two actions. A "Dismiss" secondary action
// calls context.dismiss(); the parent hides the alert with v-if. Primary
// actions report what they did in the status line below the stack.
const showSla = ref(true)
const showSync = ref(true)
const showDuplicate = ref(true)
const status = ref('')
function reset() {
showSla.value = true
showSync.value = true
showDuplicate.value = true
status.value = ''
}
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-3">
<Alert
v-if="showSla"
title="SLA due soon for #58281"
description="The SLA for #58281 will breach in 1 hour"
theme="amber"
:primary-action="{
label: 'Respond now',
onClick: () => (status = 'Ticket #58281 opened'),
}"
:secondary-action="{ label: 'Dismiss', onClick: ({ dismiss }) => dismiss() }"
@dismiss="showSla = false"
/>
<Alert
v-if="showSync"
title="Sync completed with issues"
description="96 of 100 contacts were synced."
theme="amber"
:primary-action="{
label: 'Review',
onClick: () => (status = 'Sync report opened'),
}"
:secondary-action="{ label: 'Dismiss', onClick: ({ dismiss }) => dismiss() }"
@dismiss="showSync = false"
/>
<Alert
v-if="showDuplicate"
title="Duplicate lead detected"
description="A similar lead already exists."
theme="amber"
:primary-action="{
label: 'Review duplicate',
onClick: () => (status = 'Duplicate lead opened'),
}"
:secondary-action="{
label: 'Continue anyway',
onClick: ({ dismiss }) => ((status = 'Lead created'), dismiss()),
}"
@dismiss="showDuplicate = false"
/>
<p v-if="status" class="text-sm text-ink-gray-5">{{ status }}</p>
<button
v-if="!showSla || !showSync || !showDuplicate"
class="self-start text-sm text-ink-gray-5 underline"
@click="reset()"
>
Bring the banners back
</button>
</div>
</template>Dismissible banner
An info banner with one action and a × button in the corner.
<script setup>
import { ref } from 'vue'
import { Alert } from 'frappe-ui'
// An info banner with one action and a × button in the corner.
const showTrialBanner = ref(true)
const status = ref('')
function openBilling() {
status.value = 'Billing page opened'
}
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-2">
<Alert
v-if="showTrialBanner"
title="Your trial ends soon!"
description="Upgrade to keep enjoying features and future technical support."
theme="blue"
dismissible
:primary-action="{ label: 'Update now', onClick: openBilling }"
@dismiss="showTrialBanner = false"
/>
<button
v-else
class="self-start text-sm text-ink-gray-5 underline"
@click="showTrialBanner = true"
>
Bring the banner back
</button>
<p v-if="status" class="text-sm text-ink-gray-5">{{ status }}</p>
</div>
</template>Slot overrides
#prefix replaces the status icon and #description carries rich content.
<script setup>
import { ref } from 'vue'
import { Alert, Spinner } from 'frappe-ui'
// Slot overrides: #prefix swaps the status icon for a Spinner, and
// #description carries rich text.
const showImportBanner = ref(true)
const status = ref('')
function viewProgress() {
status.value = 'Import log opened'
}
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-2">
<Alert
v-if="showImportBanner"
title="Contact import is in progress"
theme="blue"
:primary-action="{ label: 'View progress', onClick: viewProgress }"
:secondary-action="{ label: 'Dismiss', onClick: ({ dismiss }) => dismiss() }"
@dismiss="showImportBanner = false"
>
<template #prefix>
<Spinner size="md" class="text-ink-blue-5" />
</template>
<template #description>
Importing <span class="font-medium text-ink-gray-7">contacts.csv</span> —
large imports may take a few minutes.
</template>
</Alert>
<button
v-else
class="self-start text-sm text-ink-gray-5 underline"
@click="showImportBanner = true"
>
Bring the banner back
</button>
<p v-if="status" class="text-sm text-ink-gray-5">{{ status }}</p>
</div>
</template>API Reference
Show types
import { type Component, type ExtractPublicPropTypes, type PropType } from 'vue'
import type { Action } from '../shared/action'
import type { StatusTheme } from '../shared/statusIcon'
/** Context passed to an alert action's `onClick` handler. Also used by `SidebarCard`. */
export type AlertActionContext = {
/** Emits the component's `dismiss` event. The parent owns hiding. */
dismiss: () => void
}
/**
* Button config for `primaryAction` / `secondaryAction`: `ButtonProps` plus
* an `onClick` that receives `{ dismiss }`. Also used by `SidebarCard`.
*/
export type AlertAction = Action<AlertActionContext>
/**
* Runtime prop definitions — the single source of truth for the alert's props.
* `Alert.vue` passes these to `defineProps`, and the public `AlertProps` type
* is derived from them, so the runtime and the type can never drift apart.
*
* There is no layout prop. The computed layout is stamped on the root as
* `data-layout` — `"row"` when there is no description and no secondary
* action, `"banner"` otherwise.
*/
export const alertProps = {
/** Main heading text of the alert. Optional when the `#title` slot is used */
title: { type: String, default: undefined },
/** Supporting text below the title; its presence switches the alert to the banner layout */
description: { type: String, default: undefined },
/** Color theme of the status icon and the row action label; the container never changes with theme */
theme: { type: String as PropType<StatusTheme>, default: 'gray' },
/** Status icon: unset shows the theme's auto icon (gray shows the info glyph in black ink), `false` hides it, a `lucide-*` string or Component renders a custom theme-colored glyph */
icon: {
type: [Boolean, String, Object, Function] as PropType<
boolean | string | Component
>,
default: undefined,
},
/** Primary action button (`ButtonProps` plus `onClick({ dismiss })`) */
primaryAction: {
type: Object as PropType<AlertAction>,
default: undefined,
},
/** Second action button; its presence forces the banner layout */
secondaryAction: {
type: Object as PropType<AlertAction>,
default: undefined,
},
/** Shows the dismiss (×) button, which emits `dismiss` */
dismissible: { type: Boolean, default: false },
}
/** Public prop types for `<Alert>`. Derived from {@link alertProps}. */
export type AlertProps = ExtractPublicPropTypes<typeof alertProps>
export interface AlertEmits {
/** Fired when the user dismisses the alert — the × button or an action's `context.dismiss()`. The parent owns hiding. */
dismiss: []
}
/** Scoped payload for the `#actions` slot. */
export interface AlertActionsSlotProps {
/** Emits the alert's `dismiss` event. */
dismiss: () => void
}
export interface AlertSlots {
/** Overrides the status icon area */
prefix?: () => any
/** Rich title content (overrides the `title` prop) */
title?: () => any
/** Rich description content; its presence forces the banner layout */
description?: () => any
/** Replaces the auto-rendered action buttons; receives `{ dismiss }` */
actions?: (props: AlertActionsSlotProps) => any
}Main heading text of the alert. Optional when the `#title` slot is used
Supporting text below the title; its presence switches the alert to the banner layout
Color theme of the status icon and the row action label; the container never changes with theme
Status icon: unset shows the theme's auto icon (gray shows the info glyph in black ink), `false` hides it, a `lucide-*` string or Component renders a custom theme-colored glyph
Primary action button (`ButtonProps` plus `onClick({ dismiss })`)
Second action button; its presence forces the banner layout
Shows the dismiss (×) button, which emits `dismiss`
| Slot | Payload |
|---|---|
prefix | — Overrides the status icon area |
title | — Rich title content (overrides the `title` prop) |
description | — Rich description content; its presence forces the banner layout |
actions | AlertActionsSlotProps Replaces the auto-rendered action buttons; receives `{ dismiss }` |
Overrides the status icon area
Rich title content (overrides the `title` prop)
Rich description content; its presence forces the banner layout
Replaces the auto-rendered action buttons; receives `{ dismiss }`
| Event | Payload |
|---|---|
dismiss | [] Fired when the user dismisses the alert — the × button or an action's `context.dismiss()`. The parent owns hiding. |
Fired when the user dismisses the alert — the × button or an action's `context.dismiss()`. The parent owns hiding.