roiui
itemalertsource: ok · hit
Alert
A component for displaying important messages to users.
npx shadcn add "https://roiui.com/r/alert.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightclass-variance-authority
Files2 · source cached 10/9/2026, 5:25:29 PM
registry/brook/ui/alert/alert.tsx
import { cva, type VariantProps } from "class-variance-authority";
import type { ReactNode } from "react";
import { cn } from "../../../lib/utils";
import styles from "./alert.module.css";
const alertVariants = cva(styles.alert, {
variants: {
variant: {
default: styles.default,
destructive: styles.destructive,
warning: styles.warning,
success: styles.success,
info: styles.info,
},
},
defaultVariants: {
variant: "default",
},
});
function DefaultAlertIcon() {
return (
<svg
aria-hidden="true"
fill="none"
height="16"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 16V11.5" />
<path d="M12 8.01172V8.00172" />
</svg>
);
}
interface AlertProps extends React.ComponentProps<"div">, VariantProps<typeof alertVariants> {
icon?: ReactNode;
}
function Alert({ className, variant, icon, children, ...props }: AlertProps) {
const filteredChildren = Array.isArray(children)
? children.filter((child) => !(child && typeof child === "object" && "type" in child && child.type === "svg"))
: children;
return (
<div className={cn(alertVariants({ variant }), className)} data-slot="alert" role="alert" {...props}>
<div className={styles.shadowRing} />
<div className={styles.notchBg} />
<div className={styles.notchBorder} />
<div className={styles.iconWrap}>{icon ?? <DefaultAlertIcon />}</div>
{filteredChildren}
</div>
);
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn(styles.title, className)} data-slot="alert-title" {...props} />;
}
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn(styles.description, className)} data-slot="alert-description" {...props} />;
}
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn(styles.action, className)} data-slot="alert-action" {...props} />;
}
export { Alert, AlertAction, AlertDescription, AlertTitle };
registry/brook/ui/alert/alert.module.css
.alert {
position: relative;
margin-top: 1.5rem;
width: 100%;
border-radius: 1rem;
display: grid;
grid-template-columns: 1fr auto;
align-items: start;
row-gap: 0.125rem;
padding: 1rem 1.25rem;
background-color: var(--card);
font-size: 0.75rem;
line-height: 1.2;
border: 1px solid var(--border);
}
.shadowRing {
position: absolute;
inset: -1px;
border-radius: inherit;
pointer-events: none;
box-shadow: var(--shadow-ring);
}
.notchBg,
.notchBorder {
position: absolute;
top: -13px;
left: 15px;
width: 26px;
height: 26px;
border-radius: 50%;
pointer-events: none;
}
.notchBg {
background-color: var(--background);
}
.notchBorder {
box-shadow: 0 0 0 1px var(--border);
clip-path: inset(50% -3px -3px -3px);
}
.iconWrap {
position: absolute;
top: -7px;
left: 20px;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
color: var(--alert-accent);
}
.iconWrap svg {
width: 16px;
height: 16px;
}
.title {
margin: 0;
font-weight: 400;
font-size: 0.875rem;
line-height: 1.25rem;
letter-spacing: -0.02em;
color: var(--secondary-foreground);
grid-column-start: 1;
}
.description {
margin: 0;
font-weight: 300;
font-size: 0.875rem;
line-height: 1.25rem;
color: var(--muted-foreground);
grid-column-start: 1;
}
.description :global(p) {
margin: 0;
}
.action {
grid-column-start: 2;
grid-row: 1 / span 2;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
gap: 0.25rem;
}
.default {
--alert-accent: var(--muted-foreground);
}
.destructive {
--alert-accent: var(--destructive);
}
.warning {
--alert-accent: var(--warning-foreground);
}
.success {
--alert-accent: var(--success);
}
.info {
--alert-accent: var(--info);
}
@media (max-width: 640px) {
.alert {
padding: 1rem 0.75rem;
row-gap: 0.5rem;
}
.title {
font-size: 0.9375rem;
font-weight: 500;
}
.description {
font-size: 0.9375rem;
line-height: 1.5;
}
.action {
grid-column: 1 / -1;
grid-row: auto;
margin-top: 0.5rem;
justify-content: stretch;
}
.action > * {
width: 100%;
}
}