Alert
Displays a callout for user attention with optional icon, action, and close button.
npx shadcn@latest add @velaui/alertpnpm dlx velaui add @velaui/alertyarn dlx shadcn@latest add @velaui/alertbunx --bun shadcn@latest add @velaui/alertveni add alertInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/alertpnpm dlx velaui add @velaui/alertyarn dlx shadcn@latest add @velaui/alertbunx --bun shadcn@latest add @velaui/alertExamples
- Default
- Info
- Success
- Warning action
- Destructive close
- Dismissible
Usage
Props
Alert
| Prop | Type | Default |
|---|---|---|
appearance | "outline" | "solid" | "subtle" | "outline" |
variant | "default" | "info" | "success" | "warning" | "destructive" | "default" |
Takes these and every prop of AlertProps.
AlertAction
Takes every prop of React.ComponentProps<"button">.
AlertClose
Takes every prop of React.ComponentProps<"button">.
AlertContent
Takes every prop of React.ComponentProps<"div">.
AlertDescription
Takes every prop of React.ComponentProps<"div">.
AlertTitle
Takes every prop of React.ComponentProps<"div">.
Dependencies
- npm
class-variance-authority@0.7.1- Installs with it
- 1 icon
Source
components/ui/alert.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { Close02 } from "@/components/icons/close-02";
import { cn } from "@/lib/utils";
const alertVariants = cva(
// The leading icon sits at the top of the box with a 3px optical offset —
// translate rather than margin so it never occupies layout space.
"relative flex w-full items-center gap-3 rounded-md p-3 text-left text-sm leading-[1.4] font-medium [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:translate-y-[3px] [&>svg]:self-start [&>svg]:text-current",
{
variants: {
appearance: {
// Solid and subtle draw no stroke — reserve it as transparent so all
// three appearances measure the same height.
outline: "border border-border-default",
solid: "border border-transparent",
subtle: "border border-transparent",
},
variant: {
default: "",
info: "",
success: "",
warning: "",
destructive: "",
},
},
// Two slots are painted from here rather than from their own cva: the action
// and the description both take a colour the design reads off the CELL, not
// off the appearance or the variant alone. Outline draws the action as an
// outlined pill in the variant colour, solid inverts it to a white pill with
// the surface colour as its label, and subtle fills it with the variant
// colour. Description follows the same per-cell rule — muted on the light
// surfaces, a tint of white on solid, and the variant colour on subtle.
compoundVariants: [
{
appearance: "outline",
variant: "default",
className:
"border-border-default bg-bg-main text-text-strong [&_[data-slot=alert-action]]:border-border-default [&_[data-slot=alert-action]]:text-text-strong",
},
{
appearance: "outline",
variant: "info",
className:
"border-info-middle text-info-middle [&_[data-slot=alert-action]]:border-info-middle [&_[data-slot=alert-action]]:text-info-middle",
},
{
appearance: "outline",
variant: "success",
className:
"border-success-middle text-success-middle [&_[data-slot=alert-action]]:border-success-middle [&_[data-slot=alert-action]]:text-success-middle",
},
{
appearance: "outline",
variant: "warning",
className:
"border-warning-middle text-warning-middle [&_[data-slot=alert-action]]:border-warning-middle [&_[data-slot=alert-action]]:text-warning-middle",
},
{
appearance: "outline",
variant: "destructive",
className:
"border-negative-middle text-negative-middle [&_[data-slot=alert-action]]:border-negative-middle [&_[data-slot=alert-action]]:text-negative-middle",
},
{
appearance: "solid",
variant: "default",
className:
"bg-primary-default text-text-inverse [&_[data-slot=alert-action]]:bg-bg-main [&_[data-slot=alert-action]]:text-text-strong [&_[data-slot=alert-description]]:text-text-inverse-muted",
},
{
appearance: "solid",
variant: "info",
className:
"bg-info-middle text-base-white [&_[data-slot=alert-action]]:bg-base-white [&_[data-slot=alert-action]]:text-info-middle [&_[data-slot=alert-description]]:text-base-white/70",
},
{
appearance: "solid",
variant: "success",
className:
"bg-success-middle text-base-white [&_[data-slot=alert-action]]:bg-base-white [&_[data-slot=alert-action]]:text-success-middle [&_[data-slot=alert-description]]:text-base-white/70",
},
{
appearance: "solid",
variant: "warning",
className:
"bg-warning-strong text-base-white [&_[data-slot=alert-action]]:bg-base-white [&_[data-slot=alert-action]]:text-warning-strong [&_[data-slot=alert-description]]:text-base-white/70",
},
{
appearance: "solid",
variant: "destructive",
className:
"bg-negative-middle text-base-white [&_[data-slot=alert-action]]:bg-base-white [&_[data-slot=alert-action]]:text-negative-middle [&_[data-slot=alert-description]]:text-base-white/70",
},
{
appearance: "subtle",
variant: "default",
className:
"bg-bg-surface-1 text-text-strong [&_[data-slot=alert-action]]:bg-primary-default [&_[data-slot=alert-action]]:text-text-inverse",
},
{
appearance: "subtle",
variant: "info",
className:
"bg-info text-info-middle [&_[data-slot=alert-action]]:bg-info-middle [&_[data-slot=alert-action]]:text-base-white [&_[data-slot=alert-description]]:text-info-middle",
},
{
appearance: "subtle",
variant: "success",
className:
"bg-success text-success-middle [&_[data-slot=alert-action]]:bg-success-middle [&_[data-slot=alert-action]]:text-base-white [&_[data-slot=alert-description]]:text-success-middle",
},
{
appearance: "subtle",
variant: "warning",
className:
"bg-warning text-warning-middle [&_[data-slot=alert-action]]:bg-warning-middle [&_[data-slot=alert-action]]:text-base-white [&_[data-slot=alert-description]]:text-warning-middle",
},
{
appearance: "subtle",
variant: "destructive",
className:
"bg-negative text-negative-middle [&_[data-slot=alert-action]]:bg-negative-middle [&_[data-slot=alert-action]]:text-base-white [&_[data-slot=alert-description]]:text-negative-middle",
},
],
defaultVariants: {
appearance: "outline",
variant: "default",
},
},
);
export type AlertProps = React.ComponentProps<"div"> &
VariantProps<typeof alertVariants>;
function Alert({
className,
appearance = "outline",
variant = "default",
...props
}: AlertProps) {
return (
<div
data-slot="alert"
data-appearance={appearance}
data-variant={variant}
role="alert"
className={cn(alertVariants({ appearance, variant }), className)}
{...props}
/>
);
}
function AlertContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-content"
className={cn(
"flex min-w-0 flex-1 flex-col gap-0.5 overflow-hidden",
className,
)}
{...props}
/>
);
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"w-full text-sm leading-[1.4] font-medium [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-text-strong",
className,
)}
{...props}
/>
);
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"w-full text-xs leading-[1.3] font-normal text-balance text-text-muted md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-text-strong [&_p:not(:last-child)]:mb-4",
className,
)}
{...props}
/>
);
}
function AlertAction({ className, ...props }: React.ComponentProps<"button">) {
return (
<button
type="button"
data-slot="alert-action"
className={cn(
// Geometry only — every colour on this pill is a per-cell decision and
// arrives from the parent's compoundVariants. The transparent border is
// reserved so the solid and subtle pills measure the same as the
// outlined one.
"inline-flex h-6 shrink-0 items-center justify-center gap-1 rounded-md border border-transparent bg-clip-padding px-2 text-xs leading-[1.3] font-semibold whitespace-nowrap transition-opacity outline-none hover:opacity-90 focus-visible:ring-3 focus-visible:ring-current/20 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:size-3 [&_svg]:shrink-0",
className,
)}
{...props}
/>
);
}
function AlertClose({
className,
children,
...props
}: React.ComponentProps<"button">) {
return (
<button
type="button"
data-slot="alert-close"
className={cn(
"shrink-0 rounded-xs text-current opacity-70 transition-opacity outline-none hover:opacity-100 focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow disabled:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className,
)}
{...props}
>
{children ?? <Close02 />}
<span className="sr-only">Close</span>
</button>
);
}
export {
Alert,
AlertAction,
AlertClose,
AlertContent,
AlertDescription,
AlertTitle,
alertVariants,
};