diff --git a/playground/app.vue b/playground/app.vue
index ff53a8e0..705ae024 100644
--- a/playground/app.vue
+++ b/playground/app.vue
@@ -11,6 +11,7 @@ const appConfig = useAppConfig()
const components = [
'accordion',
+ 'alert',
'avatar',
'badge',
'button',
diff --git a/playground/pages/alert.vue b/playground/pages/alert.vue
new file mode 100644
index 00000000..b9e7216b
--- /dev/null
+++ b/playground/pages/alert.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/runtime/components/Alert.vue b/src/runtime/components/Alert.vue
new file mode 100644
index 00000000..4b1ca764
--- /dev/null
+++ b/src/runtime/components/Alert.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+ {{ description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/runtime/types/index.d.ts b/src/runtime/types/index.d.ts
index d1aad59e..07c6b65c 100644
--- a/src/runtime/types/index.d.ts
+++ b/src/runtime/types/index.d.ts
@@ -1,5 +1,6 @@
export * from '../components/App.vue'
export * from '../components/Accordion.vue'
+export * from '../components/Alert.vue'
export * from '../components/Avatar.vue'
export * from '../components/Badge.vue'
export * from '../components/Button.vue'
diff --git a/src/theme/alert.ts b/src/theme/alert.ts
new file mode 100644
index 00000000..0ebd7847
--- /dev/null
+++ b/src/theme/alert.ts
@@ -0,0 +1,83 @@
+export default (config: { colors: string[] }) => ({
+ slots: {
+ root: 'relative overflow-hidden w-full rounded-lg p-4 flex gap-2.5',
+ wrapper: 'min-w-0 flex-1 flex flex-col gap-1',
+ title: 'text-sm font-medium',
+ description: 'text-sm',
+ icon: 'shrink-0 size-5',
+ avatar: 'shrink-0',
+ actions: 'flex gap-1.5 shrink-0',
+ close: 'p-0'
+ },
+ variants: {
+ color: {
+ ...Object.fromEntries(config.colors.map((color: string) => [color, ''])),
+ white: '',
+ gray: '',
+ black: ''
+ },
+ variant: {
+ solid: '',
+ outline: '',
+ soft: '',
+ subtle: ''
+ },
+ multiline: {
+ true: {
+ root: 'items-start',
+ actions: 'items-start mt-1'
+ },
+ false: {
+ root: 'items-center',
+ actions: 'items-center'
+ }
+ }
+ },
+ compoundVariants: [...config.colors.map((color: string) => ({
+ color,
+ variant: 'solid',
+ class: {
+ root: `bg-${color}-500 dark:bg-${color}-400 text-white dark:text-gray-900`
+ }
+ })), ...config.colors.map((color: string) => ({
+ color,
+ variant: 'outline',
+ class: {
+ root: `text-${color}-500 dark:text-${color}-400 ring ring-inset ring-${color}-500 dark:ring-${color}-400`
+ }
+ })), ...config.colors.map((color: string) => ({
+ color,
+ variant: 'soft',
+ class: {
+ root: `bg-${color}-50 dark:bg-${color}-400/10 text-${color}-500 dark:text-${color}-400`
+ }
+ })), ...config.colors.map((color: string) => ({
+ color,
+ variant: 'subtle',
+ class: {
+ root: `bg-${color}-50 dark:bg-${color}-400/10 text-${color}-500 dark:text-${color}-400 ring ring-inset ring-${color}-500/25 dark:ring-${color}-400/25`
+ }
+ })), {
+ color: 'white',
+ variant: 'solid',
+ class: {
+ root: 'ring ring-inset ring-gray-300 dark:ring-gray-700 text-gray-900 dark:text-white bg-white dark:bg-gray-900'
+ }
+ }, {
+ color: 'gray',
+ variant: 'solid',
+ class: {
+ root: 'ring ring-inset ring-gray-300 dark:ring-gray-700 text-gray-700 dark:text-gray-200 bg-gray-50 dark:bg-gray-800'
+ }
+ }, {
+ color: 'black',
+ variant: 'solid',
+ class: {
+ root: 'text-white dark:text-gray-900 bg-gray-900 dark:bg-white'
+ }
+ }],
+ defaultVariants: {
+ color: 'white',
+ variant: 'solid'
+ }
+})
diff --git a/src/theme/index.ts b/src/theme/index.ts
index 918be8bf..44bf620a 100644
--- a/src/theme/index.ts
+++ b/src/theme/index.ts
@@ -1,4 +1,5 @@
export { default as accordion } from './accordion'
+export { default as alert } from './alert'
export { default as avatar } from './avatar'
export { default as badge } from './badge'
export { default as button } from './button'
diff --git a/test/components/Alert.spec.ts b/test/components/Alert.spec.ts
new file mode 100644
index 00000000..816c8c4d
--- /dev/null
+++ b/test/components/Alert.spec.ts
@@ -0,0 +1,29 @@
+import { describe, it, expect } from 'vitest'
+import Alert, { type AlertProps } from '../../src/runtime/components/Alert.vue'
+import ComponentRender from '../component-render'
+
+describe('Alert', () => {
+ it.each([
+ ['with title', { props: { title: 'Title' } }],
+ ['with description', { props: { title: 'Title', description: 'Description' } }],
+ ['with icon', { props: { title: 'Title', icon: 'i-heroicons-light-bulb' } }],
+ ['with avatar', { props: { title: 'Title', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } } }],
+ ['with as', { props: { title: 'Title', as: 'article' } }],
+ ['with color green', { props: { title: 'Title', color: 'green' as const } }],
+ ['with color white', { props: { title: 'Title', color: 'white' as const } }],
+ ['with color gray', { props: { title: 'Title', color: 'gray' as const } }],
+ ['with color black', { props: { title: 'Title', color: 'black' as const } }],
+ ['with variant outline', { props: { title: 'Title', variant: 'outline' as const } }],
+ ['with variant soft', { props: { title: 'Title', variant: 'soft' as const } }],
+ ['with variant link', { props: { title: 'Title', variant: 'subtle' as const } }],
+ ['with class', { props: { class: 'w-48' } }],
+ ['with ui', { props: { ui: { title: 'font-bold' } } }],
+ ['with leading slot', { slots: { title: () => 'Leading slot' } }],
+ ['with title slot', { slots: { title: () => 'Title slot' } }],
+ ['with description slot', { slots: { description: () => 'Description slot' } }],
+ ['with close slot', { slots: { close: () => 'Close slot' } }]
+ ])('renders %s correctly', async (nameOrHtml: string, options: { props?: AlertProps, slots?: any }) => {
+ const html = await ComponentRender(nameOrHtml, options, Alert)
+ expect(html).toMatchSnapshot()
+ })
+})
diff --git a/test/components/__snapshots__/Alert.spec.ts.snap b/test/components/__snapshots__/Alert.spec.ts.snap
new file mode 100644
index 00000000..1e6cf4b5
--- /dev/null
+++ b/test/components/__snapshots__/Alert.spec.ts.snap
@@ -0,0 +1,217 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Alert > renders with as correctly 1`] = `
+"
+
+
+
+"
+`;
+
+exports[`Alert > renders with avatar correctly 1`] = `
+"

+
+
+
"
+`;
+
+exports[`Alert > renders with class correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with close slot correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with color black correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with color gray correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with color green correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with color white correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with description correctly 1`] = `
+"
+
+
+
Title
+
Description
+
+
+
+
"
+`;
+
+exports[`Alert > renders with description slot correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with icon correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with leading slot correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with title correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with title slot correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with ui correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with variant link correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with variant outline correctly 1`] = `
+""
+`;
+
+exports[`Alert > renders with variant soft correctly 1`] = `
+""
+`;