Callout

MDX-style admonition with info / success / warning / error / neutral variants and optional icon override. Ships --info / --success / --warning theme tokens.

Example

Installation

npx shadcn@latest add https://hirael.com/r/callout.json

API

<Callout />

+ native element props
PropTypeDefault
variant"success" | "warning" | "info" | "error" | "neutral" | null"info"
titleReact.ReactNode
iconfalse | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | React.ElementType<any, keyof React.JSX.IntrinsicElements>

Component source

import * as React from "react";
import {
  AlertCircle,
  AlertTriangle,
  Ban,
  CheckCircle2,
  Info,
} from "lucide-react";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const calloutVariants = cva(
  "my-4 flex flex-col gap-2 overflow-hidden rounded-md border-s-4 p-4 text-sm",
  {
    // Status colors come from the --info / --success / --warning theme
    // tokens (shipped with this component's cssVars) so callouts follow
    // the consumer's theme in both modes — no hard-coded palette.
    variants: {
      variant: {
        info: "border-info bg-info/10 text-info",
        success: "border-success bg-success/10 text-success",
        warning: "border-warning bg-warning/10 text-warning",
        error: "border-destructive bg-destructive/10 text-destructive",
        neutral: "border-muted-foreground/50 bg-muted/50 text-foreground",
      },
    },
    defaultVariants: {
      variant: "info",
    },
  },
);

type CalloutVariant = NonNullable<
  VariantProps<typeof calloutVariants>["variant"]
>;

const variantIcons: Record<CalloutVariant, React.ElementType> = {
  info: Info,
  success: CheckCircle2,
  warning: AlertTriangle,
  error: Ban,
  neutral: AlertCircle,
};

export type CalloutProps = Omit<React.ComponentProps<"div">, "title"> &
  VariantProps<typeof calloutVariants> & {
    title?: React.ReactNode;
    icon?: React.ElementType | React.ReactElement | false;
  };

function Callout({
  title,
  icon,
  className,
  variant = "info",
  children,
  ...props
}: CalloutProps) {
  const variantKey = (variant ?? "info") as CalloutVariant;

  const renderIcon = () => {
    if (icon === false) return null;
    if (React.isValidElement(icon)) return icon;
    const IconComponent =
      (icon as React.ElementType | undefined) ?? variantIcons[variantKey];
    return <IconComponent className="size-5 shrink-0" aria-hidden />;
  };

  return (
    <div
      data-slot="callout"
      data-variant={variantKey}
      className={cn(calloutVariants({ variant }), className)}
      {...props}
    >
      {(title || icon !== false) && (
        <div className="flex items-start gap-2">
          {renderIcon()}
          {title && <span className="font-semibold leading-5">{title}</span>}
        </div>
      )}
      {children && (
        <div className="text-sm leading-relaxed [&_a]:underline [&_a]:underline-offset-2">
          {children}
        </div>
      )}
    </div>
  );
}

export { Callout, calloutVariants };

Dependencies

npm

lucide-reactclass-variance-authority