组件
警告提示 Alert
用于在页面流中展示需要持久保留的状态通告、错误排查指引与操作建议。
基础用法
警告提示的基础形态,包含 info、success、warning、destructive 语义变体,支持标题与详细描述:
Loading…
安装与引入
通过 CLI 自动添加组件,或手动复制源码至项目中:
pnpm dlx @wui-design/cli@latest add @wui/alert安装依赖库与图标库
pnpm add motion lucide-react class-variance-authority clsx tailwind-merge复制组件源码到
components/ui/alert.tsx"use client"
import * as React from "react"
import {
CircleCheckIcon,
CircleXIcon,
InfoIcon,
TriangleAlertIcon,
XIcon,
} from "lucide-react"
import { cva } from "class-variance-authority"
import { AnimatePresence, motion, useReducedMotion } from "motion/react"
import { cn } from "@/lib/utils"
export type AlertVariant =
| "default"
| "info"
| "success"
| "warning"
| "destructive"
const alertVariants = cva(
"relative flex w-full items-start gap-3 rounded-md border px-4 py-3 text-sm",
{
variants: {
variant: {
default: "border-border bg-background text-foreground",
info: "border-info-border bg-info-subtle text-foreground",
success: "border-success-border bg-success-subtle text-foreground",
warning: "border-warning-border bg-warning-subtle text-foreground",
destructive:
"border-destructive-border bg-destructive-subtle text-foreground",
},
size: {
default: "min-h-12",
compact: "min-h-10 px-3 py-2",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const defaultIcons = {
default: InfoIcon,
info: InfoIcon,
success: CircleCheckIcon,
warning: TriangleAlertIcon,
destructive: CircleXIcon,
} as const
export interface AlertProps extends Omit<React.ComponentProps<"div">, "title"> {
/** Semantic appearance and default icon. @default "default" */
variant?: AlertVariant
/** Density preset. @default "default" */
size?: "default" | "compact"
/** Optional heading displayed above the description. */
title?: React.ReactNode
/** Custom leading icon. Pass `false` to hide the icon. */
icon?: React.ReactNode | false
/** Optional action rendered at the end of the alert. */
action?: React.ReactNode
/** Show a close control. @default false */
closable?: boolean
/** Controlled visibility. */
visible?: boolean
/** Initial visibility in uncontrolled mode. @default true */
defaultVisible?: boolean
/** Called whenever visibility changes. */
onVisibleChange?: (visible: boolean) => void
}
/** A persistent inline status alert with optional action and dismissal. */
function Alert({
className,
variant = "default",
size = "default",
title,
children,
icon,
action,
closable = false,
visible,
defaultVisible = true,
onVisibleChange,
role,
...props
}: AlertProps) {
const [internalVisible, setInternalVisible] = React.useState(defaultVisible)
const isVisible = visible ?? internalVisible
const DefaultIcon = defaultIcons[variant]
const reduceMotion = useReducedMotion()
function setVisible(next: boolean) {
if (visible === undefined) setInternalVisible(next)
onVisibleChange?.(next)
}
// The wrapper fades the alert and collapses its height, so content below
// closes the gap smoothly instead of jumping when the alert is dismissed.
return (
<AnimatePresence initial={false}>
{isVisible ? (
<motion.div
key="alert"
data-slot="alert-presence"
className="w-full"
initial={reduceMotion ? { opacity: 0 } : { height: 0, opacity: 0 }}
animate={reduceMotion ? { opacity: 1 } : { height: "auto", opacity: 1 }}
exit={reduceMotion ? { opacity: 0 } : { height: 0, opacity: 0 }}
transition={
reduceMotion
? { duration: 0 }
: {
height: { duration: 0.28, ease: [0.22, 1, 0.36, 1] },
opacity: { duration: 0.18, ease: "easeOut" },
}
}
style={{ overflow: "hidden" }}
>
<div
data-slot="alert"
data-variant={variant}
data-size={size}
role={
role ??
(variant === "warning" || variant === "destructive"
? "alert"
: "status")
}
className={cn(alertVariants({ variant, size }), className)}
{...props}
>
{icon !== false ? (
<span
data-slot="alert-icon"
className={cn(
"mt-0.5 flex shrink-0 text-muted-foreground [&_svg]:size-4",
variant === "info" && "text-info",
variant === "success" && "text-success",
variant === "warning" && "text-warning",
variant === "destructive" && "text-destructive"
)}
>
{icon ?? <DefaultIcon />}
</span>
) : null}
<span data-slot="alert-content" className="min-w-0 flex-1">
{title ? (
<span data-slot="alert-title" className="block font-medium leading-5">
{title}
</span>
) : null}
{children ? (
<span
data-slot="alert-description"
className={cn(
"block leading-5 text-muted-foreground",
title && "mt-0.5"
)}
>
{children}
</span>
) : null}
</span>
{action ? (
<span data-slot="alert-action" className="ml-2 flex shrink-0 items-center">
{action}
</span>
) : null}
{closable ? (
<button
type="button"
data-slot="alert-close"
aria-label="关闭提示"
className="-mr-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/30 [&_svg]:size-4"
onClick={() => setVisible(false)}
>
<XIcon />
</button>
) : null}
</div>
</motion.div>
) : null}
</AnimatePresence>
)
}
export { Alert, alertVariants }
属性 Props
Alert 支持以下配置属性,并会继承原生 <div> 容器元素的全部 HTML 属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| variant | "default" | "info" | "success" | "warning" | "destructive" | "default" | 提示的语义主题与对应色彩样式,同时决定默认图标及 ARIA 角色。 |
| size | "default" | "compact" | "default" | 内边距与高度密度预设。compact 适合单行简短提示。 |
| title | React.ReactNode | — | 提示的主标题,显示在详细描述上方并加粗渲染。 |
| children | React.ReactNode | — | 提示的详细描述文本或结构化内容。 |
| icon | React.ReactNode | false | — | 自定义前导图标。传入 `false` 可完全隐藏左侧图标。 |
| action | React.ReactNode | — | 位于右侧的自定义操作区(如按钮、链接或操作组)。 |
| closable | boolean | false | 是否在右上角显示关闭叉号按钮。 |
| visible | boolean | — | 受控模式下的显示与隐藏状态。 |
| defaultVisible | boolean | true | 非受控模式下的初始显示状态。 |
| onVisibleChange | (visible: boolean) => void | — | 当提示因点击关闭按钮或受控更新而发生可见性改变时触发的回调。 |
| role | string | — | 覆盖默认的 ARIA 角色属性(默认 warning / destructive 为 alert,其余为 status)。 |
| className | string | — | 应用于外层容器的额外 CSS 类名。 |
事件 Events
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| onVisibleChange | (visible: boolean) => void | — | 用户点击关闭按钮(`closable`)或受控状态变更时触发,参数为最新的可见布尔值。 |
使用场景与设计规范
Alert 是页面流内持续可见的静态反馈组件,通常驻留在页面或表单特定位置,向用户传达与当前上下文强相关的状态或警告。
- Alert vs Message (Toast):
Alert:位于页面文档流内,占据实际版面高度,适合承载长段说明、需长期留存的错误排查指南或包含明确纠错操作的卡片。Message:浮动于屏幕顶层(Portal),超时自动消失,适合对保存、复制等即时操作结果进行极简反馈(1~2 秒即可阅读完毕)。
- 语义色彩规范:
info:中性提示、版本发布公告、功能提示。success:流程完成确认、安全合规检查通过。warning:非阻断性预警(如额度快耗尽、证书即将到期)。destructive:阻断性错误、连接失败、数据被删除风险。
- 操作明确性:尾部
action按钮应使用明确动词(如“重新连接”、“升级配额”),避免使用含糊的“确定”。
场景示例
尾部操作 (Action)
将“重试”、“更新账单”或“撤销”等下一步关键动作放置在提示右侧:
Loading…
可关闭与受控显示 (Dismissible)
开启 closable 后用户可主动关闭已知悉的公告或临时预警,配合 visible 与 onVisibleChange 实现受控管理。关闭时提示条淡出并收起高度,下方内容平滑补位;重新显示时反向展开。需要与相邻元素保持间距时,把 margin 写在 className 上,间距会随高度一起收起:
Loading…
紧凑单行模式 (Compact)
在表单顶部或空间紧凑的弹窗内,使用 size="compact" 展现无标题的单行轻量提示:
Loading…
自定义图标与隐藏图标
支持传入自定义图标或传入 icon={false} 开启极简纯文字模式:
Loading…
业务场景:安全风险警报与证书预警
在控制台监控与安全中心,组合告警等级、高对比度行动按钮与审计说明文案:
Loading…
无障碍与交互 Accessibility
- 动态 ARIA 角色分配:
warning与destructive变体默认挂载role="alert"与aria-live="assertive",屏幕阅读器会在出现时立即优先播报。info、success与default变体默认挂载role="status"与aria-live="polite",屏幕阅读器会在空闲时平稳朗读。
- 关闭按钮可访问性:内置关闭按钮配置了
aria-label="关闭提示",支持 Tab 聚焦并通过 Enter / Space 触发关闭。 - 动效降级:开启系统「减弱动态效果」后,关闭与显示只做淡入淡出,不再过渡高度。
- 多重感知设计:不单单依靠色彩传达错误,每个语义变体均有特定的图形图标,并在视觉上提供了明确的文字内容。