Alert
A standing message about the page.
import { Div } from "@implementjs/core";
import { CircleAlertIcon, TerminalIcon } from "@implementjs/lucide";
import { Alert, AlertDescription, AlertTitle } from "@/lib/components/ui/alert";
export default function AlertDemo() {
return Div(
{ class: "flex w-full max-w-lg flex-col gap-4" },
Alert(
TerminalIcon({ "aria-hidden": true }),
AlertTitle("Heads up"),
AlertDescription("You can add components with the jsrepo CLI."),
),
Alert(
{ variant: "destructive" },
CircleAlertIcon({ "aria-hidden": true }),
AlertTitle("Payment failed"),
AlertDescription("Your card was declined. Try another payment method."),
),
Alert(AlertTitle("No icon, no description — just the line.")),
);
}Installation
npx jsrepo add @implementjs/ui/alert
It installs tailwind-variants at the same time.
Copy the file below to src/lib/components/ui/alert.ts. It imports cn, so copy utils.ts to src/lib/utils.ts too. Then, on top of @implementjs/core and @implementjs/primitives:
npm install tailwind-variants
import { Div, type Child, type ElementProps } from "@implementjs/core";
import { createComponent } from "@implementjs/primitives";
import { tv, type VariantProps } from "tailwind-variants";
import { cn } from "@/lib/utils";
export const alertVariants = tv({
base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90",
},
},
defaultVariants: { variant: "default" },
});
export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
export type AlertProps = ElementProps<"div"> & VariantProps<typeof alertVariants>;
export type AlertTitleProps = ElementProps<"div">;
export type AlertDescriptionProps = ElementProps<"div">;
/**
* A standing message about the page — not a [toast](/ui/toast), which passes,
* and not a [dialog](/ui/dialog), which interrupts.
*
* The grid gains its icon column only when an icon is actually present
* (`has-[>svg]`), so a text-only alert has no empty gutter to explain.
*/
export const Alert = createComponent(function Alert(
{ class: className, variant = "default", ...props }: AlertProps,
...children: Child[]
) {
return Div(
{
role: "alert",
...props,
"data-slot": "alert",
"data-variant": variant,
class: cn(alertVariants({ variant }), className),
},
...children,
);
});
export const AlertTitle = createComponent(function AlertTitle(
{ class: className, ...props }: AlertTitleProps,
...children: Child[]
) {
return Div(
{
...props,
"data-slot": "alert-title",
class: cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className),
},
...children,
);
});
export const AlertDescription = createComponent(function AlertDescription(
{ class: className, ...props }: AlertDescriptionProps,
...children: Child[]
) {
return Div(
{
...props,
"data-slot": "alert-description",
class: cn(
"col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
className,
),
},
...children,
);
});Usage
An alert stays until the situation changes. For something that passes, use a toast; for something that must be answered, an alert dialog.
import { Alert, AlertDescription, AlertTitle } from "@/lib/components/ui/alert";
Alert(
TerminalIcon({ "aria-hidden": true }),
AlertTitle("Heads up"),
AlertDescription("You can add components with the jsrepo CLI."),
);
The icon column
The root is a grid whose first column only appears when an icon is actually there (has-[>svg]), so a text-only alert has no empty gutter to explain. Put the icon first, before the title.
Destructive
Alert({ variant: "destructive" }, CircleAlertIcon(), AlertTitle("Payment failed"));
The variant recolors the title and the description together — the description is reached through *:data-[slot=alert-description], so it follows without being told.
API Reference
Alert
Sets role="alert". A grid that grows an icon column only when an icon is present, so a text-only alert has no empty gutter. Renders a Div; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "destructive" | "default" | destructive turns the title and description red for a failure. |
AlertTitle
The headline. Clamped to one line. Renders a Div; extra props are forwarded onto it.
AlertDescription
The body, in muted text under the title. Renders a Div; extra props are forwarded onto it.