Skip to content
edmi-ui

Alert

Inline message in the page flow. Not dismissed by time; use for state the user should notice before they act.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
Terminal window
npx shadcn@latest add @edmi-ui/alert
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/alert
Terminal window
yarn dlx shadcn@latest add @edmi-ui/alert
Terminal window
bunx shadcn@latest add @edmi-ui/alert

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/alert
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/alert
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/alert
Terminal window
bunx shadcn-vue@latest add @edmi-ui/alert

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/alert.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/alert.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/alert.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/alert.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import {
Alert,
AlertAction,
AlertDescription,
AlertTitle,
} from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
<div className="grid w-full max-w-xl gap-3">
<Alert>
<InfoIcon />
<AlertTitle>Devnet only</AlertTitle>
<AlertDescription>
Prices come from a mock oracle. Nothing here is real money.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<TriangleAlertIcon />
<AlertTitle>Transaction failed</AlertTitle>
<AlertDescription>
Slippage was above 1%. Try a smaller order.
</AlertDescription>
</Alert>
<Alert variant="success">
<CircleCheckIcon />
<AlertTitle>Index launched</AlertTitle>
<AlertDescription>MAG4 is live and accepting joins.</AlertDescription>
<AlertAction>
<Button size="xs" variant="outline">
View
</Button>
</AlertAction>
</Alert>
{/* … */}
</div>
<script setup lang="ts">
import {
Alert,
AlertAction,
AlertDescription,
AlertTitle,
} from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
</script>
<template>
<div class="grid w-full max-w-xl gap-3">
<Alert>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path /></svg>
<AlertTitle>Devnet only</AlertTitle>
<AlertDescription>Prices come from a mock oracle. Nothing here is real money.</AlertDescription>
</Alert>
<Alert variant="destructive">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path /></svg>
<AlertTitle>Transaction failed</AlertTitle>
<AlertDescription>Slippage was above 1%. Try a smaller order.</AlertDescription>
</Alert>
<Alert variant="success">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path /></svg>
<AlertTitle>Index launched</AlertTitle>
<AlertDescription>MAG4 is live and accepting joins.</AlertDescription>
<AlertAction>
<Button size="xs" variant="outline">View</Button>
</AlertAction>
</Alert>
<Alert variant="brand">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path /></svg>
<AlertTitle>Autopilot is on</AlertTitle>
<AlertDescription>Rebalances run automatically with the theme accent.</AlertDescription>
</Alert>
<!-- … -->
</div>
</template>
<script lang="ts">
import * as Alert from "$lib/components/ui/alert";
import { Button } from "$lib/components/ui/button";
</script>
<div class="grid w-full max-w-xl gap-3">
<Alert.Root>
<Alert.Title>Devnet only</Alert.Title>
<Alert.Description>Prices come from a mock oracle. Nothing here is real money.</Alert.Description>
</Alert.Root>
<Alert.Root variant="destructive">
<Alert.Title>Transaction failed</Alert.Title>
<Alert.Description>Slippage was above 1%. Try a smaller order.</Alert.Description>
</Alert.Root>
<Alert.Root variant="success">
<Alert.Title>Index launched</Alert.Title>
<Alert.Description>MAG4 is live and accepting joins.</Alert.Description>
<Alert.Action>
<Button size="xs" variant="outline">View</Button>
</Alert.Action>
</Alert.Root>
<Alert.Root variant="brand">
<Alert.Title>Autopilot is on</Alert.Title>
<Alert.Description>Rebalances run automatically with the theme accent.</Alert.Description>
</Alert.Root>
<Alert.Root variant="info">
<Alert.Title>Oracle updated</Alert.Title>
<Alert.Description>Prices refresh every 15 seconds.</Alert.Description>
</Alert.Root>
<!-- … -->
</div>
registry/ui/alert.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "cn";
import type * as React from "react";
// Soft fill + tinted 30-40% border for coloured variants, never solid (DESIGN §4.12).
const alertVariants = cva(
"group/alert relative grid w-full grid-cols-[1fr_auto] items-start gap-x-3 gap-y-0.5 rounded-xl border px-4 py-3.5 text-left text-sm has-[>svg]:grid-cols-[20px_1fr_auto] *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "border-border bg-card text-card-foreground",
destructive:
"border-[color-mix(in_srgb,var(--destructive)_40%,var(--popover))] bg-destructive-soft text-destructive-text",
brand:
"border-[color-mix(in_srgb,var(--brand)_40%,var(--popover))] bg-brand-soft text-brand-text", // ✦
success:
"border-[color-mix(in_srgb,var(--success)_40%,var(--popover))] bg-success-soft text-success-text", // ✦
warning:
"border-[color-mix(in_srgb,var(--warning)_40%,var(--popover))] bg-warning-soft text-warning-text", // ✦
info: "border-[color-mix(in_srgb,var(--info)_40%,var(--popover))] bg-info-soft text-info-text", // ✦
},
},
defaultVariants: {
variant: "default",
},
},
);
function Alert({
className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
data-variant={variant ?? "default"}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
);
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"col-start-1 font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3",
className,
)}
{...props}
/>
);
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"col-start-1 text-[13px] text-balance text-muted-foreground group-has-[>svg]/alert:col-start-2 md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4",
"group-data-[variant=destructive]/alert:text-destructive-text/80",
className,
)}
{...props}
/>
);
}
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-center group-has-[>svg]/alert:col-start-3",
className,
)}
{...props}
/>
);
}
export { Alert, AlertAction, AlertDescription, AlertTitle };
registry/ui/alert/Alert.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import type { AlertVariants } from '.'
import { cn } from '@/registry/edmi/lib/utils'
import { alertVariants } from '.'
const props = defineProps<{
class?: HTMLAttributes['class']
variant?: AlertVariants['variant']
}>()
</script>
<template>
<div
data-slot="alert"
:data-variant="variant ?? 'default'"
role="alert"
:class="cn(alertVariants({ variant }), props.class)"
>
<slot />
</div>
</template>
registry/ui/alert/AlertAction.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-slot="alert-action"
:class="cn('col-start-2 row-span-2 row-start-1 self-center group-has-[>svg]/alert:col-start-3', props.class)"
>
<slot />
</div>
</template>
registry/ui/alert/AlertDescription.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-slot="alert-description"
:class="cn('col-start-1 text-[13px] text-balance text-muted-foreground group-has-[>svg]/alert:col-start-2 md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4 group-data-[variant=destructive]/alert:text-destructive-text/80', props.class)"
>
<slot />
</div>
</template>
registry/ui/alert/AlertTitle.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-slot="alert-title"
:class="cn('col-start-1 font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3', props.class)"
>
<slot />
</div>
</template>
registry/ui/alert/index.ts
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
export { default as Alert } from "./Alert.vue";
export { default as AlertAction } from "./AlertAction.vue";
export { default as AlertDescription } from "./AlertDescription.vue";
export { default as AlertTitle } from "./AlertTitle.vue";
// Soft fill + tinted 30-40% border for coloured variants, never solid (DESIGN 4.12).
export const alertVariants = cva(
"group/alert relative grid w-full grid-cols-[1fr_auto] items-start gap-x-3 gap-y-0.5 rounded-xl border px-4 py-3.5 text-left text-sm has-[>svg]:grid-cols-[20px_1fr_auto] *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "border-border bg-card text-card-foreground",
destructive:
"border-[color-mix(in_srgb,var(--destructive)_40%,var(--popover))] bg-destructive-soft text-destructive-text",
brand:
"border-[color-mix(in_srgb,var(--brand)_40%,var(--popover))] bg-brand-soft text-brand-text", // ✦
success:
"border-[color-mix(in_srgb,var(--success)_40%,var(--popover))] bg-success-soft text-success-text", // ✦
warning:
"border-[color-mix(in_srgb,var(--warning)_40%,var(--popover))] bg-warning-soft text-warning-text", // ✦
info: "border-[color-mix(in_srgb,var(--info)_40%,var(--popover))] bg-info-soft text-info-text", // ✦
},
},
defaultVariants: {
variant: "default",
},
},
);
export type AlertVariants = VariantProps<typeof alertVariants>;
src/lib/registry/ui/alert/alert-action.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-action"
class={cn("col-start-2 row-span-2 row-start-1 self-center group-has-[>svg]/alert:col-start-3", className)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ui/alert/alert-description.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-description"
class={cn("col-start-1 text-[13px] text-balance text-muted-foreground group-has-[>svg]/alert:col-start-2 md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4 group-data-[variant=destructive]/alert:text-destructive-text/80", className)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ui/alert/alert-title.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-title"
class={cn("col-start-1 font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3", className)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ui/alert/alert.svelte
<script lang="ts" module>
import { type VariantProps, tv } from "tailwind-variants";
// Soft fill + tinted 30-40% border for coloured variants, never solid (DESIGN 4.12).
export const alertVariants = tv({
base: "group/alert relative grid w-full grid-cols-[1fr_auto] items-start gap-x-3 gap-y-0.5 rounded-xl border px-4 py-3.5 text-left text-sm has-[>svg]:grid-cols-[20px_1fr_auto] *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
variants: {
variant: {
default: "border-border bg-card text-card-foreground",
destructive: "border-[color-mix(in_srgb,var(--destructive)_40%,var(--popover))] bg-destructive-soft text-destructive-text",
brand: "border-[color-mix(in_srgb,var(--brand)_40%,var(--popover))] bg-brand-soft text-brand-text", // ✦
success: "border-[color-mix(in_srgb,var(--success)_40%,var(--popover))] bg-success-soft text-success-text", // ✦
warning: "border-[color-mix(in_srgb,var(--warning)_40%,var(--popover))] bg-warning-soft text-warning-text", // ✦
info: "border-[color-mix(in_srgb,var(--info)_40%,var(--popover))] bg-info-soft text-info-text", // ✦
},
},
defaultVariants: {
variant: "default",
},
});
export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
</script>
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
variant = "default",
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
variant?: AlertVariant;
} = $props();
</script>
<div
bind:this={ref}
data-slot="alert"
data-variant={variant}
role="alert"
class={cn(alertVariants({ variant }), className)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ui/alert/index.ts
import Root from "./alert.svelte";
import Action from "./alert-action.svelte";
import Description from "./alert-description.svelte";
import Title from "./alert-title.svelte";
export { type AlertVariant, alertVariants } from "./alert.svelte";
export {
Action,
Action as AlertAction,
Description,
Description as AlertDescription,
Root,
//
Root as Alert,
Title,
Title as AlertTitle,
};
Prop Type Default
Alert variant default | destructive | brand | success | warning | info default
AlertAction slot, placed in the last column none

Parts: Alert, AlertTitle, AlertDescription, AlertAction. A direct svg child becomes the leading icon.

  • brand, success, warning, info variants: soft fill with a tinted 40% border (DESIGN §4.12), never solid. success is always green (--success-soft/--success-text); brand follows the theme accent.