Skip to content

Alert

Alert is one shallow surface for visible guidance, warnings, operation results, and recoverable failures. Put native headings, paragraphs, lists, links, and buttons inside it, then choose announcement semantics from when the message appears.

Klean does not infer urgency from color or from the component name. Alert renders no role and no live region by default.

Alert.vue

Installation

One command detects Vue, React, or Svelte and writes the matching source into the application's conventional component directory:

Run one command from a Boring Stack application. Klean detects the framework and conventional destination, then adds the framework-native source and its direct dependencies.

Terminal
npx klean-ui add alert

  • No initializer or configuration file
  • No framework, alias, or theme questions
  • No Klean runtime dependency

The result works immediately with neutral monochrome defaults. There is no initializer, configuration file, provider, visual preset, severity map, or shared Klean runtime.

Usage

The application writes the real content and opts into urgent announcement only when a new failure needs immediate attention.

Vue

DeploymentError.vue
<script setup>
import Alert from '@/components/ui/alert/Alert.vue'
</script>

<template>
  <Alert
    as="section"
    role="alert"
    aria-labelledby="deployment-error-title"
    class="bg-red-50 text-red-950 dark:bg-red-950 dark:text-red-100"
  >
    <h2 id="deployment-error-title" class="font-medium">Deployment failed</h2>
    <p class="mt-1 leading-6 text-red-800 dark:text-red-200">
      The server could not be reached. No production files changed.
    </p>
    <button type="button" class="mt-4 cursor-pointer font-medium underline">
      Try again
    </button>
  </Alert>
</template>

React

DeploymentError.jsx
import Alert from '@/components/ui/alert/Alert.jsx'

export default function DeploymentError() {
  return (
    <Alert
      as="section"
      role="alert"
      aria-labelledby="deployment-error-title"
      className="bg-red-50 text-red-950 dark:bg-red-950 dark:text-red-100"
    >
      <h2 id="deployment-error-title" className="font-medium">
        Deployment failed
      </h2>
      <p className="mt-1 leading-6 text-red-800 dark:text-red-200">
        The server could not be reached. No production files changed.
      </p>
      <button
        type="button"
        className="mt-4 cursor-pointer font-medium underline"
      >
        Try again
      </button>
    </Alert>
  )
}

Svelte

DeploymentError.svelte
<script>
  import Alert from '$lib/components/ui/alert/Alert.svelte'
</script>

<Alert
  as="section"
  role="alert"
  aria-labelledby="deployment-error-title"
  class="bg-red-50 text-red-950 dark:bg-red-950 dark:text-red-100"
>
  <h2 id="deployment-error-title" class="font-medium">Deployment failed</h2>
  <p class="mt-1 leading-6 text-red-800 dark:text-red-200">
    The server could not be reached. No production files changed.
  </p>
  <button type="button" class="mt-4 cursor-pointer font-medium underline">
    Try again
  </button>
</Alert>

API

InputDefaultPurpose
asdivNative container such as div, section, or aside.
class / classNameOrdinary Tailwind classes merged after the neutral defaults.
native attributesIDs, role, ARIA relationships, test hooks, and other native attributes.
default contentNative headings, paragraphs, lists, links, buttons, and application UI.

There is no severity, tone, variant, status, color, icon, dismissible, AlertTitle, AlertDescription, or AlertItem API. Those ideas are clearer as application markup and state.

Choose semantics from the lifecycle

Appearance cannot decide how assistive technology should announce a message.

SituationMarkup
Static guidance already present with the pageNo role
Supporting information that is useful but not a live updaterole="note" when the extra semantic is worthwhile
A non-urgent result inserted after an operationrole="status" or an existing polite live region
An urgent failure inserted after the user actsrole="alert"

Do not put role="alert" on every red surface or on every individual field error. A form may announce one error summary while each field keeps its own visible error and aria-describedby relationship.

Compose real checklists

A warnings checklist is still an Alert. The heading, count, items, suggestions, and actions remain native markup so application data and operations stay obvious.

DeploymentChecklist.vue

The list preserves item count and navigation for screen-reader users. Visible words—not colored dots—carry each result. Buttons keep native keyboard behavior, while the application owns pending state, success, failure, and the actual operation.

Style products, not variants

The same Alert source can serve Hagfish's editorial visual language and Slipway's operational density without naming either treatment in the API.

AlertRecipes.vue

If a treatment repeats, make a small application-owned wrapper or recipe. Do not move product colors, spacing, icons, or severity mapping into Alert.

Accessibility

  • Use a heading level that fits the page outline; Alert never chooses one.
  • Keep icons decorative with aria-hidden="true" when visible text already communicates the meaning.
  • Do not communicate warning, success, or urgency through color alone.
  • Use real buttons for actions and real anchors or Boring Stack Links for navigation.
  • Keep dismiss controls explicitly labeled and application-owned.
  • Ensure urgent messages are inserted after the live region exists; a server-rendered alert already present at page load may not be announced.
  • Prefer one form error summary instead of many simultaneous assertive announcements.

Durable behavior

Alert owns no open, dismissed, loading, or severity state. Static guidance simply renders. Operation results derive from application state. If dismissal must survive navigation or sessions, the application chooses the correct Durable UI home and keeps a recovery path where the information matters.

Buttons inside an Alert own no hidden work. Pending state, cancellation, rollback, and error recovery remain with the operation that produced the message.

When to use

Use Alert for visible information that deserves a distinct surface: a warning, form summary, operation result, recovery instruction, compatibility note, or short checklist of blockers.

When not to use

  • Use Toast for transient notification queues that should not occupy document layout.
  • Use ordinary text when spacing and typography already make the message clear.
  • Use Dialog when the user must make a focused modal decision.
  • Use field errors beside Input and Textarea, with one optional summary for the form.

Complete framework source

Copy, inspect, and change the complete source for your framework.

Vue source

Alert.vue
<script setup>
import { computed, ref, useAttrs } from 'vue'
import { twMerge } from 'tailwind-merge'

defineOptions({ inheritAttrs: false })

defineProps({
  as: { type: [String, Object, Function], default: 'div' }
})

const attrs = useAttrs()
const element = ref()

const forwardedAttrs = computed(() => {
  const { class: _class, 'data-slot': _dataSlot, ...rest } = attrs
  return rest
})

defineExpose({ element })
</script>

<template>
  <component
    :is="as"
    ref="element"
    v-bind="forwardedAttrs"
    data-slot="alert"
    :class="
      twMerge(
        'relative w-full rounded-md bg-gray-100 p-4 text-sm text-gray-950 dark:bg-gray-900 dark:text-white',
        attrs.class
      )
    "
  >
    <slot />
  </component>
</template>

React source

Alert.jsx
import { forwardRef } from 'react'
import { twMerge } from 'tailwind-merge'

const BASE_CLASSES =
  'relative w-full rounded-md bg-gray-100 p-4 text-sm text-gray-950 dark:bg-gray-900 dark:text-white'

const Alert = forwardRef(function Alert(
  { as: Component = 'div', className, 'data-slot': _dataSlot, ...props },
  ref
) {
  return (
    <Component
      {...props}
      ref={ref}
      data-slot="alert"
      className={twMerge(BASE_CLASSES, className)}
    />
  )
})

export default Alert

Svelte source

Alert.svelte
<script>
  import { twMerge } from "tailwind-merge";

  let {
    as = "div",
    children,
    class: className,
    "data-slot": _dataSlot,
    ...props
  } = $props();

  let element;

  export function getElement() {
    return element;
  }
</script>

<svelte:element
  this={as}
  {...props}
  bind:this={element}
  data-slot="alert"
  class={twMerge(
    "relative w-full rounded-md bg-gray-100 p-4 text-sm text-gray-950 dark:bg-gray-900 dark:text-white",
    className,
  )}
>
  {#if children}
    {@render children()}
  {/if}
</svelte:element>

  • Button — supplies truthful actions and navigation inside an Alert.
  • Toast — handles transient queued notifications.
  • Dialog — handles focused modal decisions.
  • Input — keeps field-level errors and relationships close to the field.

All open source projects are released under the MIT License.