Alert 警告提示
警告提示组件用于向用户展示重要的反馈信息,帮助用户理解当前操作的结果或系统状态。支持成功、错误、警告、信息四种状态类型,并可配置关闭按钮。
何时使用
- 当操作需要用户关注或确认时
- 需要向用户展示操作结果反馈(成功、失败、警告)
- 展示系统级别的通知信息
- 页面中需要显著位置的静态提示信息
- 需要持久化显示的提示内容(区别于短暂的 Toast)
快速开始
安装
pnpm
pnpm add @anker-in/headless-ui基础用法
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function App() {
return (
<Alert status="success">
<AlertTitle>成功</AlertTitle>
<AlertDescription>您的操作已成功完成</AlertDescription>
</Alert>
)
}演示
组件结构
Alert 是一个灵活的复合组件,由以下部分构成:
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
<Alert status="info">
<AlertTitle>提示标题</AlertTitle>
<AlertDescription>详细的提示内容描述</AlertDescription>
</Alert>组件说明
| 组件 | 用途 | HTML 元素 |
|---|---|---|
Alert | 警告提示容器 | <div role="alert"> |
AlertTitle | 提示标题 | <h5> |
AlertDescription | 提示内容 | <div> |
组件组合灵活性:Alert 可以只包含 AlertDescription(无标题),也可以同时包含 AlertTitle 和 AlertDescription。
状态类型(Status)
Alert 提供四种状态类型,每种状态都有对应的图标和语义:
Info - 信息提示
用于一般性信息提示,配有蓝色图标。
<Alert status="info">
<AlertTitle>温馨提示</AlertTitle>
<AlertDescription>
您有 3 个待处理的任务,请及时完成。
</AlertDescription>
</Alert>视觉特征:
- 图标:蓝色圆形背景 (#00BEFA) 的信息图标(i)
- 适用场景:中性的提示信息、帮助文本、补充说明
Success - 成功提示
用于操作成功的反馈,配有绿色图标。
<Alert status="success">
<AlertTitle>操作成功</AlertTitle>
<AlertDescription>
您的数据已成功保存,可以继续下一步操作。
</AlertDescription>
</Alert>视觉特征:
- 图标:绿色圆形背景 (#30D158) 的对勾图标
- 适用场景:表单提交成功、数据保存成功、操作完成确认
Warning - 警告提示
用于需要用户注意但不阻塞操作的警告信息,配有黄色图标。
<Alert status="warning">
<AlertTitle>注意事项</AlertTitle>
<AlertDescription>
您的账户余额不足,可能影响部分功能的使用。
</AlertDescription>
</Alert>视觉特征:
- 图标:黄色圆形背景 (#FFC24D) 的警告图标(!)
- 适用场景:需要注意的非阻塞性问题、潜在风险提示、操作建议
Error - 错误提示
用于操作失败或错误的反馈,配有红色图标。
<Alert status="error">
<AlertTitle>操作失败</AlertTitle>
<AlertDescription>
网络连接失败,请检查网络设置后重试。
</AlertDescription>
</Alert>视觉特征:
- 图标:红色圆形背景 (#FF4D4D) 的错误图标(!)
- 适用场景:操作失败、表单验证错误、系统错误提示
变体(Variants)
Default - 默认样式
使用系统的背景色和前景色,适合大多数场景。
<Alert status="info" variant="default">
<AlertTitle>默认样式</AlertTitle>
<AlertDescription>这是默认的样式变体</AlertDescription>
</Alert>Destructive - 破坏性操作
使用红色边框和文字,强调警告或危险操作。
<Alert status="error" variant="destructive">
<AlertTitle>危险操作</AlertTitle>
<AlertDescription>
此操作将永久删除数据,且无法恢复!
</AlertDescription>
</Alert>使用建议:destructive 变体通常与 status="error" 配合使用,用于强调需要用户谨慎对待的操作或错误。
可关闭功能
通过 closable 属性启用关闭按钮,允许用户手动关闭提示:
import { useState } from 'react'
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function ClosableAlert() {
const [visible, setVisible] = useState(true)
if (!visible) return null
return (
<Alert status="info" closable>
<div onClick={() => setVisible(false)}>
<AlertTitle>可关闭提示</AlertTitle>
<AlertDescription>
点击右侧关闭按钮可以隐藏此提示
</AlertDescription>
</div>
</Alert>
)
}实现说明:目前 Alert 组件的 closable 属性仅显示关闭按钮 UI,实际的关闭逻辑需要在父组件中通过状态管理实现。可以通过包裹一个可点击区域或监听关闭按钮的点击事件来控制 Alert 的显示/隐藏。
Props API
Alert Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| status | 'success' | 'error' | 'warning' | 'info' | - | ❌ | 警告提示的状态类型,决定图标和语义 |
| variant | 'default' | 'destructive' | 'default' | ❌ | 警告提示的样式变体 |
| closable | boolean | false | ❌ | 是否显示关闭按钮 |
| children | ReactNode | - | ✅ | 警告提示的内容(通常包含 AlertTitle 和/或 AlertDescription) |
| className | string | - | ❌ | 自定义 CSS 类名 |
| role | string | 'alert' | ❌ | ARIA 角色(自动设置为 alert) |
AlertTitle Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 标题内容 |
| className | string | - | ❌ | 自定义 CSS 类名 |
AlertDescription Props
| 属性 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| children | ReactNode | - | ✅ | 描述内容 |
| className | string | - | ❌ | 自定义 CSS 类名 |
继承的 Props
Alert 组件继承 HTMLDivElement 的所有属性。
类型定义
import { VariantProps } from 'class-variance-authority'
import * as React from 'react'
// Alert 组件 Props
interface AlertProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof alertVariants> {
/**
* 警告提示的状态类型
* - success: 成功(绿色对勾图标)
* - error: 错误(红色警告图标)
* - warning: 警告(黄色警告图标)
* - info: 信息(蓝色信息图标)
*/
status?: 'success' | 'error' | 'warning' | 'info'
/**
* 是否显示关闭按钮
* 注意:需要配合父组件状态管理实现实际的关闭功能
*/
closable?: boolean
}
// AlertTitle 组件 Props
interface AlertTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}
// AlertDescription 组件 Props
interface AlertDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}
// 组件导出
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(...)
const AlertTitle = React.forwardRef<HTMLParagraphElement, AlertTitleProps>(...)
const AlertDescription = React.forwardRef<HTMLParagraphElement, AlertDescriptionProps>(...)
Alert.displayName = 'Alert'
AlertTitle.displayName = 'AlertTitle'
AlertDescription.displayName = 'AlertDescription'
export { Alert, AlertTitle, AlertDescription }
export type { AlertProps, AlertTitleProps, AlertDescriptionProps }使用示例
基础示例
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function BasicExample() {
return (
<div className="space-y-4">
<Alert status="info">
<AlertTitle>提示</AlertTitle>
<AlertDescription>这是一条信息提示</AlertDescription>
</Alert>
<Alert status="success">
<AlertTitle>成功</AlertTitle>
<AlertDescription>操作已成功完成</AlertDescription>
</Alert>
<Alert status="warning">
<AlertTitle>警告</AlertTitle>
<AlertDescription>请注意这个警告信息</AlertDescription>
</Alert>
<Alert status="error">
<AlertTitle>错误</AlertTitle>
<AlertDescription>发生了一个错误</AlertDescription>
</Alert>
</div>
)
}仅包含描述
对于简短的提示信息,可以只使用 AlertDescription:
import { Alert, AlertDescription } from '@anker-in/headless-ui'
export default function DescriptionOnly() {
return (
<Alert status="info">
<AlertDescription>
您有新的系统消息,请及时查看。
</AlertDescription>
</Alert>
)
}可关闭的警告提示
import { useState } from 'react'
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function ClosableExample() {
const [alerts, setAlerts] = useState({
welcome: true,
update: true,
warning: true,
})
const closeAlert = (key: keyof typeof alerts) => {
setAlerts((prev) => ({ ...prev, [key]: false }))
}
return (
<div className="space-y-4">
{alerts.welcome && (
<Alert status="success" closable>
<div onClick={() => closeAlert('welcome')}>
<AlertTitle>欢迎</AlertTitle>
<AlertDescription>
感谢您使用我们的服务!
</AlertDescription>
</div>
</Alert>
)}
{alerts.update && (
<Alert status="info" closable>
<div onClick={() => closeAlert('update')}>
<AlertTitle>系统更新</AlertTitle>
<AlertDescription>
新版本已发布,包含多项功能改进。
</AlertDescription>
</div>
</Alert>
)}
{alerts.warning && (
<Alert status="warning" closable>
<div onClick={() => closeAlert('warning')}>
<AlertTitle>注意</AlertTitle>
<AlertDescription>
您的会话即将过期,请保存工作内容。
</AlertDescription>
</div>
</Alert>
)}
</div>
)
}表单验证反馈
import { useState } from 'react'
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function FormValidation() {
const [errors, setErrors] = useState<string[]>([])
const [success, setSuccess] = useState(false)
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const email = formData.get('email') as string
const password = formData.get('password') as string
// 验证逻辑
const newErrors: string[] = []
if (!email) newErrors.push('邮箱不能为空')
if (!email.includes('@')) newErrors.push('邮箱格式不正确')
if (!password) newErrors.push('密码不能为空')
if (password.length < 6) newErrors.push('密码至少需要6个字符')
if (newErrors.length > 0) {
setErrors(newErrors)
setSuccess(false)
} else {
setErrors([])
setSuccess(true)
}
}
return (
<form onSubmit={handleSubmit} className="max-w-md space-y-4">
{errors.length > 0 && (
<Alert status="error" variant="destructive">
<AlertTitle>表单验证失败</AlertTitle>
<AlertDescription>
<ul className="mt-2 list-inside list-disc">
{errors.map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</AlertDescription>
</Alert>
)}
{success && (
<Alert status="success">
<AlertTitle>注册成功</AlertTitle>
<AlertDescription>
您的账户已创建,欢迎加入我们!
</AlertDescription>
</Alert>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium">
邮箱
</label>
<input
id="email"
name="email"
type="email"
className="mt-1 block w-full rounded-md border px-3 py-2"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium">
密码
</label>
<input
id="password"
name="password"
type="password"
className="mt-1 block w-full rounded-md border px-3 py-2"
/>
</div>
<button
type="submit"
className="rounded-md bg-primary px-4 py-2 text-white"
>
注册
</button>
</form>
)
}API 请求状态
import { useState } from 'react'
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function ApiStatus() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
const [message, setMessage] = useState('')
const fetchData = async () => {
setStatus('loading')
try {
// 模拟 API 请求
await new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('success') : reject('error')
}, 1000)
})
setStatus('success')
setMessage('数据加载成功')
} catch (error) {
setStatus('error')
setMessage('数据加载失败,请重试')
}
}
return (
<div className="space-y-4">
<button
onClick={fetchData}
disabled={status === 'loading'}
className="rounded-md bg-primary px-4 py-2 text-white disabled:opacity-50"
>
{status === 'loading' ? '加载中...' : '获取数据'}
</button>
{status === 'loading' && (
<Alert status="info">
<AlertTitle>加载中</AlertTitle>
<AlertDescription>
正在从服务器获取数据,请稍候...
</AlertDescription>
</Alert>
)}
{status === 'success' && (
<Alert status="success">
<AlertTitle>成功</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{status === 'error' && (
<Alert status="error" variant="destructive">
<AlertTitle>错误</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
</div>
)
}系统公告
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function SystemAnnouncement() {
return (
<div className="space-y-4">
<Alert status="warning">
<AlertTitle>系统维护通知</AlertTitle>
<AlertDescription>
系统将于 2025 年 1 月 20 日 02:00 - 04:00 进行维护,
期间部分功能可能无法使用,给您带来不便敬请谅解。
</AlertDescription>
</Alert>
<Alert status="info">
<AlertTitle>新功能上线</AlertTitle>
<AlertDescription>
我们很高兴地宣布,全新的数据分析功能已经上线!
<a href="#" className="ml-1 font-medium underline">
了解详情 →
</a>
</AlertDescription>
</Alert>
</div>
)
}自定义样式
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
export default function CustomStyle() {
return (
<div className="space-y-4">
{/* 自定义背景色 */}
<Alert
status="success"
className="border-green-500 bg-green-50 dark:bg-green-950"
>
<AlertTitle className="text-green-800 dark:text-green-200">
订单已完成
</AlertTitle>
<AlertDescription className="text-green-700 dark:text-green-300">
您的订单 #12345 已成功处理并发货
</AlertDescription>
</Alert>
{/* 自定义边框和阴影 */}
<Alert
status="warning"
className="border-2 border-yellow-500 shadow-lg"
>
<AlertTitle>重要提醒</AlertTitle>
<AlertDescription>
请在截止日期前完成认证,否则将影响账户使用
</AlertDescription>
</Alert>
{/* 自定义圆角和内边距 */}
<Alert
status="info"
className="rounded-2xl px-6 py-4"
>
<AlertTitle>温馨提示</AlertTitle>
<AlertDescription>
定期更新密码可以提高账户安全性
</AlertDescription>
</Alert>
</div>
)
}带操作按钮
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
import { Button } from '@anker-in/headless-ui'
export default function AlertWithActions() {
const handleConfirm = () => {
console.log('用户确认了操作')
}
const handleCancel = () => {
console.log('用户取消了操作')
}
return (
<Alert status="warning">
<AlertTitle>确认删除</AlertTitle>
<AlertDescription>
<p className="mb-3">
此操作将永久删除该项目及其所有相关数据,无法恢复。
您确定要继续吗?
</p>
<div className="flex gap-2">
<Button
size="sm"
variant="destructive"
onClick={handleConfirm}
>
确认删除
</Button>
<Button
size="sm"
variant="outline"
onClick={handleCancel}
>
取消
</Button>
</div>
</AlertDescription>
</Alert>
)
}可访问性
ARIA 属性
- Alert 组件自动设置
role="alert",向辅助技术声明这是一个警告提示区域 - 当 Alert 出现时,屏幕阅读器会立即通知用户
- AlertTitle 使用语义化的
<h5>标签,提供清晰的信息层级
最佳实践
提供清晰的标题和描述
使用 AlertTitle 提供简洁的标题,AlertDescription 提供详细的说明文本,帮助所有用户(包括使用屏幕阅读器的用户)快速理解提示内容。
选择合适的状态类型
根据消息的重要性和性质选择正确的 status:
- 成功操作使用
success - 错误信息使用
error - 需要注意的事项使用
warning - 一般信息使用
info
确保足够的颜色对比度
虽然 Alert 使用图标来区分状态,但仍要确保文本与背景的对比度符合 WCAG AA 标准(至少 4.5:1)。
避免仅依赖颜色传达信息
Alert 组件已经为每种状态配备了不同的图标,确保即使是色盲用户也能区分不同类型的提示。
关闭功能的可访问性
如果使用 closable 属性,确保关闭按钮可以通过键盘访问(Tab 键聚焦 + Enter/Space 触发)。当前实现需要在父组件中添加键盘事件处理:
<Alert status="info" closable>
<div
onClick={() => setVisible(false)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setVisible(false)
}
}}
role="button"
tabIndex={0}
>
<AlertTitle>可关闭提示</AlertTitle>
<AlertDescription>按 Enter 或 Space 关闭</AlertDescription>
</div>
</Alert>重要信息持久化
Alert 适合显示需要用户确认或阅读的重要信息。对于临时性的通知,考虑使用 Toast 组件。
最佳实践
✅ 推荐做法
- 为每个 Alert 提供清晰的标题和描述
- 根据消息的严重程度选择合适的 status
- 使用 destructive 变体强调破坏性操作或严重错误
- 在表单顶部显示验证错误的 Alert
- 为可关闭的 Alert 实现状态管理
// ✅ 正确示例:完整的信息结构
<Alert status="error" variant="destructive">
<AlertTitle>操作失败</AlertTitle>
<AlertDescription>
由于网络错误,您的数据未能保存。
请检查网络连接后重试。
</AlertDescription>
</Alert>
// ✅ 正确示例:使用状态管理实现关闭功能
function Example() {
const [visible, setVisible] = useState(true)
if (!visible) return null
return (
<Alert status="info" closable>
<div onClick={() => setVisible(false)}>
<AlertTitle>提示</AlertTitle>
<AlertDescription>这是一条可关闭的提示</AlertDescription>
</div>
</Alert>
)
}❌ 避免做法
- 不要在一个页面同时显示过多 Alert(建议不超过 3 个)
- 不要使用 Alert 显示不重要的信息
- 不要省略 AlertTitle(除非内容非常简短)
- 不要将 Alert 用于临时通知(应使用 Toast)
- 不要在 Alert 中放置过长的文本(超过 3-4 行)
// ❌ 错误示例:信息过于简单,不需要 Alert
<Alert status="info">
<AlertDescription>已加载</AlertDescription>
</Alert>
// ✅ 正确示例:使用 Toast 或简单的文本提示
<p className="text-sm text-muted-foreground">已加载</p>
// ❌ 错误示例:文本过长,影响阅读
<Alert status="info">
<AlertDescription>
这是一段非常非常长的文本,包含了大量的详细信息...
(超过 200 字的内容)
</AlertDescription>
</Alert>
// ✅ 正确示例:长文本使用折叠或链接
<Alert status="info">
<AlertTitle>系统更新</AlertTitle>
<AlertDescription>
系统已更新至最新版本。
<a href="#" className="ml-1 underline">查看详情</a>
</AlertDescription>
</Alert>常见问题
如何实现自动关闭功能?
配合 setTimeout 和状态管理实现:
function AutoCloseAlert() {
const [visible, setVisible] = useState(true)
useEffect(() => {
const timer = setTimeout(() => {
setVisible(false)
}, 5000) // 5 秒后自动关闭
return () => clearTimeout(timer)
}, [])
if (!visible) return null
return (
<Alert status="success" closable>
<div onClick={() => setVisible(false)}>
<AlertTitle>操作成功</AlertTitle>
<AlertDescription>
此提示将在 5 秒后自动关闭
</AlertDescription>
</div>
</Alert>
)
}Alert 和 Toast 有什么区别?
| 特性 | Alert | Toast |
|---|---|---|
| 显示位置 | 文档流内(静态位置) | 屏幕角落(固定位置) |
| 持久性 | 持久显示,需手动关闭 | 临时显示,自动消失 |
| 适用场景 | 重要信息、需确认的内容 | 操作反馈、临时通知 |
| 布局影响 | 占用页面布局空间 | 不影响页面布局 |
如何自定义 Alert 的图标?
目前 Alert 的图标是内置的 SVG,如果需要自定义图标,可以不使用 status 属性,手动传入图标:
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
import { CustomIcon } from './icons'
<Alert className="pl-7">
<CustomIcon className="absolute left-4 top-2 size-6" />
<AlertTitle>自定义图标</AlertTitle>
<AlertDescription>使用自定义的 SVG 图标</AlertDescription>
</Alert>Alert 是否支持多行文本?
支持。AlertDescription 可以包含任意长度的文本,但建议控制在 3-4 行以内以保持良好的可读性:
<Alert status="info">
<AlertTitle>注意事项</AlertTitle>
<AlertDescription>
<p className="mb-2">第一段提示信息。</p>
<p>第二段补充说明。</p>
<ul className="mt-2 list-inside list-disc">
<li>要点一</li>
<li>要点二</li>
</ul>
</AlertDescription>
</Alert>如何在 Alert 中添加链接或按钮?
可以在 AlertDescription 中直接添加交互元素:
<Alert status="warning">
<AlertTitle>版本更新</AlertTitle>
<AlertDescription>
<p className="mb-3">
检测到新版本,建议立即更新以获得最佳体验。
</p>
<div className="flex gap-2">
<button className="rounded bg-primary px-3 py-1 text-sm text-white">
立即更新
</button>
<button className="rounded border px-3 py-1 text-sm">
稍后提醒
</button>
</div>
</AlertDescription>
</Alert>如何让 Alert 只显示一次(使用 localStorage)?
function OneTimeAlert() {
const [visible, setVisible] = useState(() => {
return localStorage.getItem('alert-shown') !== 'true'
})
const handleClose = () => {
localStorage.setItem('alert-shown', 'true')
setVisible(false)
}
if (!visible) return null
return (
<Alert status="info" closable>
<div onClick={handleClose}>
<AlertTitle>欢迎</AlertTitle>
<AlertDescription>
这是您第一次访问,此提示只会显示一次。
</AlertDescription>
</div>
</Alert>
)
}如何实现 Alert 的进入/退出动画?
配合 CSS 过渡或动画库(如 Framer Motion)实现:
import { useState } from 'react'
import { Alert, AlertTitle, AlertDescription } from '@anker-in/headless-ui'
function AnimatedAlert() {
const [visible, setVisible] = useState(true)
return (
<div
className={`transition-all duration-300 ${
visible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4'
}`}
>
{visible && (
<Alert status="success" closable>
<div onClick={() => setVisible(false)}>
<AlertTitle>成功</AlertTitle>
<AlertDescription>操作已完成</AlertDescription>
</div>
</Alert>
)}
</div>
)
}相关资源
更新日志
v2.0.0 (2025-01-17)
- 🎉 完善组件文档
- ✨ 新增四种状态类型(success, error, warning, info)
- ✨ 新增可关闭功能(closable 属性)
- ✨ 新增 destructive 变体
- ♿ 改进可访问性(ARIA 属性、语义化标签)
- 📝 完善使用示例和最佳实践
本文档有帮助吗?
在 GitHub 上反馈