Frappe UIFrappe UI

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

Your trial ends soon!

Upgrade to keep enjoying features.

title
description
theme
icon
primaryAction
secondaryAction
dismissible
<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.

Contacts added successfully
Deal moved to Negotiation
vue
<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.

SLA timer has started
Meeting scheduled
vue
<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().

vue
<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.

Your trial ends soon!

Upgrade to keep enjoying features and future technical support.

vue
<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.

Contact import is in progress

Importing contacts.csv — large imports may take a few minutes.

vue
<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
typescript
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
}
title
string

Main heading text of the alert. Optional when the `#title` slot is used

description
string

Supporting text below the title; its presence switches the alert to the banner layout

theme
= 'gray'
StatusTheme

Color theme of the status icon and the row action label; the container never changes with theme

icon
boolean | string | Component

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

primaryAction
AlertAction

Primary action button (`ButtonProps` plus `onClick({ dismiss })`)

secondaryAction
AlertAction

Second action button; its presence forces the banner layout

dismissible
= false
boolean

Shows the dismiss (×) button, which emits `dismiss`

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 }`

dismiss
[]

Fired when the user dismisses the alert — the × button or an action's `context.dismiss()`. The parent owns hiding.