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.
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.
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
<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
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
<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
| Input | Default | Purpose |
|---|---|---|
as | div | Native container such as div, section, or aside. |
class / className | — | Ordinary Tailwind classes merged after the neutral defaults. |
| native attributes | — | IDs, role, ARIA relationships, test hooks, and other native attributes. |
| default content | — | Native 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.
| Situation | Markup |
|---|---|
| Static guidance already present with the page | No role |
| Supporting information that is useful but not a live update | role="note" when the extra semantic is worthwhile |
| A non-urgent result inserted after an operation | role="status" or an existing polite live region |
| An urgent failure inserted after the user acts | role="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.
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.
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
<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
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
<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>