Alert

Displays a callout for user attention with contextual feedback messages.

Installation

Install the component Alert in your project using the CLI.

Alert.tsx

pnpm dlx behsseui@latest add Alert

Install the component manually.

Create a ui folder at the root of the project, then a component folder inside it, and finally a Alert.tsx file in that folder.

Copy and paste the following code into your project.

ui/components/Alert.tsx

1import type { HTMLAttributes, ReactNode } from "react"
2import { Slot, type AsChildProps } from "./internals/Slot"
3import { cva, type VariantProps } from "class-variance-authority"
4import { cn } from "@/lib/utils"
5
6const alertVariants = cva(
7 "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 {
9 variants: {
10 variant: {
11 default: "bg-background text-foreground",
12 destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
13 success: "border-green-500/50 text-green-600 dark:text-green-500 [&>svg]:text-green-600 dark:[&>svg]:text-green-500",
14 warning: "border-yellow-500/50 text-yellow-600 dark:text-yellow-500 [&>svg]:text-yellow-600 dark:[&>svg]:text-yellow-500",
15 info: "border-blue-500/50 text-blue-600 dark:text-blue-500 [&>svg]:text-blue-600 dark:[&>svg]:text-blue-500",
16 },
17 },
18 defaultVariants: {
19 variant: "default",
20 },
21 }
22)
23
24const alertTitleVariants = cva("mb-1 font-medium leading-none tracking-tight")
25
26const alertDescriptionVariants = cva("text-sm [&_p]:leading-relaxed")
27
28type AlertProps = AsChildProps<
29 {
30 children: ReactNode
31 className?: string
32 } & VariantProps<typeof alertVariants>,
33 HTMLAttributes<HTMLDivElement>
34>
35
36export function Alert({
37 children,
38 className,
39 variant = "default",
40 asChild,
41 ...props
42}: AlertProps) {
43 const Component = asChild ? Slot : "div"
44 return (
45 <Component
46 role="alert"
47 className={cn(alertVariants({ variant, className }))}
48 {...props}
49 >
50 {children}
51 </Component>
52 )
53}
54
55type AlertTitleProps = AsChildProps<
56 {
57 children: ReactNode
58 className?: string
59 },
60 HTMLAttributes<HTMLHeadingElement>
61>
62
63export function AlertTitle({
64 children,
65 className,
66 asChild,
67 ...props
68}: AlertTitleProps) {
69 const Component = asChild ? Slot : "h5"
70 return (
71 <Component className={cn(alertTitleVariants(), className)} {...props}>
72 {children}
73 </Component>
74 )
75}
76
77type AlertDescriptionProps = AsChildProps<
78 {
79 children: ReactNode
80 className?: string
81 },
82 HTMLAttributes<HTMLParagraphElement>
83>
84
85export function AlertDescription({
86 children,
87 className,
88 asChild,
89 ...props
90}: AlertDescriptionProps) {
91 const Component = asChild ? Slot : "div"
92 return (
93 <Component className={cn(alertDescriptionVariants(), className)} {...props}>
94 {children}
95 </Component>
96 )
97}
98

For Alert.tsx to work, create an internals folder inside ui/component, then create a Slot.tsx file in it with the following code.

ui/components/internals/Slot.tsx

1import {
2 Children,
3 cloneElement,
4 isValidElement,
5 type HTMLAttributes,
6 type PropsWithChildren,
7 type ReactElement,
8 type ReactNode,
9} from "react";
10import { twMerge } from "tailwind-merge";
11
12// Type utilitaire pour simplifier le typage des composant supportant asChild
13export type AsChildProps<BaseProps, SecondaryProps = {}> =
14 | ({ asChild: true; children: ReactNode } & BaseProps)
15 | ({ asChild?: false; children?: ReactNode } & BaseProps & SecondaryProps);
16
17export function Slot(props: PropsWithChildren<HTMLAttributes<HTMLElement>>) {
18 const children = Children.toArray(props.children).filter((c) =>
19 isValidElement(c)
20 );
21
22 if (children.length !== 1) {
23 throw new Error("Slot must have exactly one child element");
24 }
25
26 const child = children[0] as ReactElement<HTMLAttributes<HTMLElement>>;
27
28 return cloneElement(child, {
29 ...props,
30 ...child.props,
31 style:
32 props.style || child.props.style
33 ? {
34 ...props.style,
35 ...child.props.style,
36 }
37 : undefined,
38 className:
39 props.className || child.props.className
40 ? twMerge(props.className, child.props.className)
41 : undefined,
42 });
43}

Usages

Different variants and use cases for the Alert component.

Default

A default alert for general information.

Default.tsx

<Alert>
  <AlertTitle>Heads up!</AlertTitle>
  <AlertDescription>You can add components to your app using the CLI.</AlertDescription>
</Alert>

With Icon

An alert with an icon. Place the icon component as the first child of Alert.

With Icon.tsx

import Info from "@/ui/icons/Info"

<Alert>
  <Info />
  <AlertTitle>Heads up!</AlertTitle>
  <AlertDescription>You can add components to your app using the CLI.</AlertDescription>
</Alert>

Destructive

An alert for error or destructive messages.

Destructive.tsx

import AlertCircle from "@/ui/icons/AlertCircle"

<Alert variant="destructive">
  <AlertCircle />
  <AlertTitle>Error</AlertTitle>
  <AlertDescription>Your session has expired. Please log in again.</AlertDescription>
</Alert>

Success

An alert for success messages.

Success.tsx

import CheckCircle from "@/ui/icons/CheckCircle"

<Alert variant="success">
  <CheckCircle />
  <AlertTitle>Success</AlertTitle>
  <AlertDescription>Your changes have been saved successfully.</AlertDescription>
</Alert>

Warning

An alert for warning messages.

Warning.tsx

import AlertTriangle from "@/ui/icons/AlertTriangle"

<Alert variant="warning">
  <AlertTriangle />
  <AlertTitle>Warning</AlertTitle>
  <AlertDescription>Your account is about to expire.</AlertDescription>
</Alert>

Info

An alert for informational messages.

Info.tsx

import Info from "@/ui/icons/Info"

<Alert variant="info">
  <Info />
  <AlertTitle>Info</AlertTitle>
  <AlertDescription>A new version is available.</AlertDescription>
</Alert>