Vela UI Docsvenizia.ai

Alert

Displays a callout for user attention with optional icon, action, and close button.

npx shadcn@latest add @velaui/alert
npx shadcn@latest add @velaui/alert

Examples

  • Default
  • Info
  • Success
  • Warning action
  • Destructive close
  • Dismissible

View code

Alert · Default

Installation

pnpm dlx velaui add @velaui/alert
pnpm dlx velaui add @velaui/alert

Code

alert-default.tsx
import {
  Alert,
  AlertContent,
  AlertTitle,
  AlertDescription,
} from "@/components/ui/alert";

export default function Example() {
  return (
    <Alert>
      <AlertContent>
        <AlertTitle>Heads up</AlertTitle>
        <AlertDescription>
          You can add components to your app using the CLI.
        </AlertDescription>
      </AlertContent>
    </Alert>
  );
}

View code

Alert · Info

Installation

pnpm dlx velaui add @velaui/alert
pnpm dlx velaui add @velaui/alert

Code

alert-info.tsx
import { InformationCircleContained } from "@/components/icons/information-circle-contained";
import {
  Alert,
  AlertContent,
  AlertTitle,
  AlertDescription,
} from "@/components/ui/alert";

export default function Example() {
  return (
    <Alert appearance="subtle" variant="info">
      <InformationCircleContained />
      <AlertContent>
        <AlertTitle>New feature available</AlertTitle>
        <AlertDescription>
          Try out the new dashboard layout in your settings.
        </AlertDescription>
      </AlertContent>
    </Alert>
  );
}

View code

Alert · Success

Installation

pnpm dlx velaui add @velaui/alert
pnpm dlx velaui add @velaui/alert

Code

alert-success.tsx
import { CheckContained } from "@/components/icons/check-contained";
import { Alert, AlertContent, AlertTitle } from "@/components/ui/alert";

export default function Example() {
  return (
    <Alert appearance="solid" variant="success">
      <CheckContained />
      <AlertContent>
        <AlertTitle>Payment successful</AlertTitle>
      </AlertContent>
    </Alert>
  );
}

View code

Alert · Warning action

Installation

pnpm dlx velaui add @velaui/alert
pnpm dlx velaui add @velaui/alert

Code

alert-warning-action.tsx
import { AlertTriangle } from "@/components/icons/alert-triangle";
import {
  Alert,
  AlertContent,
  AlertTitle,
  AlertDescription,
  AlertAction,
} from "@/components/ui/alert";

export default function Example() {
  return (
    <Alert appearance="outline" variant="warning">
      <AlertTriangle />
      <AlertContent>
        <AlertTitle>Storage almost full</AlertTitle>
        <AlertDescription>
          You're using 90% of your available storage.
        </AlertDescription>
      </AlertContent>
      <AlertAction>Review</AlertAction>
    </Alert>
  );
}

View code

Alert · Destructive close

Installation

pnpm dlx velaui add @velaui/alert
pnpm dlx velaui add @velaui/alert

Code

alert-destructive-close.tsx
import { CloseCircleContained } from "@/components/icons/close-circle-contained";
import {
  Alert,
  AlertContent,
  AlertTitle,
  AlertDescription,
  AlertClose,
} from "@/components/ui/alert";

export default function Example() {
  return (
    <Alert appearance="solid" variant="destructive">
      <CloseCircleContained />
      <AlertContent>
        <AlertTitle>Unable to save changes</AlertTitle>
        <AlertDescription>
          Check your connection and try again.
        </AlertDescription>
      </AlertContent>
      <AlertClose />
    </Alert>
  );
}

View code

Alert · Dismissible

Installation

pnpm dlx velaui add @velaui/alert
pnpm dlx velaui add @velaui/alert

Code

alert-dismissible.tsx
import { AlertTriangle } from "@/components/icons/alert-triangle";
import {
  Alert,
  AlertContent,
  AlertTitle,
  AlertDescription,
  AlertAction,
  AlertClose,
} from "@/components/ui/alert";

export default function Example() {
  return (
    <Alert appearance="subtle" variant="warning">
      <AlertTriangle />
      <AlertContent>
        <AlertTitle>Subscription expiring soon</AlertTitle>
        <AlertDescription>
          Renew now to avoid losing access to your workspace.
        </AlertDescription>
      </AlertContent>
      <AlertAction>Update</AlertAction>
      <AlertClose />
    </Alert>
  );
}

Usage

import {
  Alert,
  AlertContent,
  AlertTitle,
  AlertDescription,
} from "@/components/ui/alert";

export default function Example() {
  return (
    <Alert>
      <AlertContent>
        <AlertTitle>Heads up</AlertTitle>
        <AlertDescription>
          You can add components to your app using the CLI.
        </AlertDescription>
      </AlertContent>
    </Alert>
  );
}

Props

Alert

PropTypeDefault
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,
};