콘텐츠로 건너뛰기

알림

양식 오류, 경고 및 성공 확인을 위한 인라인 콜아웃 — 네오브루탈리즘 스타일의 테두리와 강한 그림자가 적용된 상태 색상의 패널.

import { CheckCircle2Icon, InfoIcon } from "lucide-react"

import {

얼럿은 인라인 콜아웃입니다. 아이콘, 제목, 설명, 그리고 선택적인 액션을 CSS 그리드에 배치합니다. 아래에 깔린 프리미티브 없이 의존성 없는 role="alert" div로 동작하므로 두 백엔드에서 완전히 동일합니다. 네오브루탈리즘 공식(두꺼운 테두리, 선명한 그림자, 굵은 서체)에 맞춰 스타일을 입혔습니다. 여기에 status 축으로 오류, 성공, 경고, 정보 색상을 지정합니다.

다음과 같은 경우에 사용합니다.

  • 폼과 요청 오류 — 유효성 검사 실패나 실패한 API 호출을 문제가 생긴 지점 바로 옆에 드러냅니다.
  • 성공 및 상태 확인 — "설정이 저장되었습니다", "체험판이 곧 만료됩니다", "결제가 완료되었습니다" 같은 메시지를 status prop으로 색까지 맞춰 보여 줍니다.
  • 후속 동작이 딸린 콜아웃 — "활성화", "실행 취소", "업그레이드" 같은 AlertAction 버튼을 함께 두는 경고나 업그레이드 안내.

설치

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/alert.json
npx shadcn@latest add https://neobrutalism.com/r/base/alert.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/alert.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/alert.json

다음 코드를 복사해 프로젝트에 붙여넣습니다.

components/ui/alert.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

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

// CSS-grid layout (shadcn-style): drop an icon as a direct child and the alert
// auto-arranges into an icon column + a content column — no manual flex wrapper.
// `has-[>svg]` adds the icon column only when an icon is present. NeoBrutalist
// chrome: hard 2px border, square corners, hard offset shadow.
const alertVariants = cva(
  cn(
    "group/alert relative grid w-full grid-cols-[0_1fr] items-start gap-y-1 rounded border-2 px-4 py-3 text-left text-sm shadow-md",
    "has-[>svg]:grid-cols-[1.25rem_1fr] has-[>svg]:gap-x-3",
    "has-data-[slot=alert-action]:pr-14",
    "[&>svg]:size-5 [&>svg]:translate-y-0.5 [&>svg]:shrink-0 [&>svg]:text-current"
  ),
  {
    variants: {
      // shadcn's variant axis (kept for API compatibility). `solid` is a Neobrutalism
      // addition.
      variant: {
        default:
          "border-border bg-background text-foreground *:data-[slot=alert-description]:text-muted-foreground",
        destructive:
          "border-red-900 bg-red-300 text-red-900 *:data-[slot=alert-description]:text-red-900/80",
        solid:
          "border-border bg-foreground text-background *:data-[slot=alert-description]:text-background/80",
      },
      // Neobrutalism status axis — additive; overrides the variant's colors when set.
      status: {
        error:
          "border-red-900 bg-red-300 text-red-900 *:data-[slot=alert-description]:text-red-900/80",
        success:
          "border-green-900 bg-green-300 text-green-900 *:data-[slot=alert-description]:text-green-900/80",
        warning:
          "border-yellow-900 bg-yellow-300 text-yellow-900 *:data-[slot=alert-description]:text-yellow-900/80",
        info: "border-blue-900 bg-blue-300 text-blue-900 *:data-[slot=alert-description]:text-blue-900/80",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

function Alert({
  className,
  variant,
  status,
  ...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
  return (
    <div
      data-slot="alert"
      role="alert"
      className={cn(alertVariants({ variant, status }), className)}
      {...props}
    />
  )
}

function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="alert-title"
      className={cn(
        "col-start-2 min-h-5 font-head text-base leading-tight tracking-tight [&_a]:underline [&_a]:underline-offset-3",
        className
      )}
      {...props}
    />
  )
}

function AlertDescription({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="alert-description"
      className={cn(
        "col-start-2 grid justify-items-start gap-1 text-sm [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p]:leading-relaxed [&_p:not(:last-child)]:mb-4",
        className
      )}
      {...props}
    />
  )
}

function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="alert-action"
      className={cn("absolute top-2 right-2", className)}
      {...props}
    />
  )
}

export { Alert, AlertTitle, AlertDescription, AlertAction, alertVariants }

프로젝트 설정에 맞게 import 경로를 업데이트합니다.

사용법

import {
  Alert,
  AlertAction,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
<Alert>
  <InfoIcon />
  <AlertTitle>Heads up!</AlertTitle>
  <AlertDescription>
    You can add components and dependencies to your app using the cli.
  </AlertDescription>
  <AlertAction>
    <Button variant="outline">Enable</Button>
  </AlertAction>
</Alert>

구성

Alert를 만들려면 다음 구성을 사용합니다:

Alert
├── Icon
├── AlertTitle
├── AlertDescription
└── AlertAction

예제

기본

아이콘, 제목, 설명을 갖춘 기본적인 알림입니다.

import { CheckCircle2Icon } from "lucide-react"

import {

파괴적

variant="destructive"를 사용해 파괴적인 알림을 만듭니다.

import { AlertCircleIcon } from "lucide-react"

import {

액션

AlertAction을 사용해 버튼이나 다른 액션 요소를 알림에 추가합니다.

import {
  Alert,
  AlertAction,

커스텀 색상

Alert 컴포넌트에 bg-amber-50 dark:bg-amber-950 같은 커스텀 클래스를 추가하여 알림 색상을 커스터마이즈할 수 있습니다.

import { AlertTriangleIcon } from "lucide-react"

import {

RTL

Neobrutalism에서 RTL을 활성화하는 방법은 RTL 설정 가이드를 참조하세요.

"use client"

import * as React from "react"

접근성

얼럿 루트는 role="alert"로 렌더링됩니다. 암묵적인 assertive 라이브 리전이므로, 스크린 리더는 해당 콘텐츠가 DOM에 삽입되는 순간 그 내용을 읽습니다. 이 안내는 동적으로 삽입될 때만 발생합니다. 페이지가 로드될 때부터 이미 존재한 얼럿은 일반 텍스트로 읽히므로, 반드시 들려야 하는 내용이라면 조건부로 렌더링하세요. role="alert"는 포커스를 옮기지 않습니다. 사용자가 계속 진행하기 전에 반드시 응답해야 한다면 얼럿 다이얼로그를 사용하고, AlertAction은 유일한 진행 경로가 아니라 단축 수단으로 다루세요.

API 참조

Alert

Alert 컴포넌트는 사용자의 주의를 끄는 콜아웃을 표시합니다.

PropTypeDefault
variant"default" | "destructive""default"

AlertTitle

AlertTitle 컴포넌트는 알림의 제목을 표시합니다.

PropTypeDefault
classNamestring-

AlertDescription

AlertDescription 컴포넌트는 알림의 설명이나 내용을 표시합니다.

PropTypeDefault
classNamestring-

AlertAction

AlertAction 컴포넌트는 알림의 오른쪽 위 모서리에 절대 배치되는 액션 요소(예: 버튼)를 표시합니다.

PropTypeDefault
classNamestring-