컴포넌트

Alert

화면 안에 안내 메시지를 표시합니다.

설치

pnpm dlx ply-ui add alert

공통 설치 설정을 먼저 적용합니다.

사용법

AlertTitle과 AlertDescription으로 제목과 설명을 구분합니다.

화면에 유지되는 안내에 사용합니다. 일시적인 작업 결과는 Toast를 사용합니다.

종류

tone으로 안내의 종류를 고릅니다. 종류는 테두리와 옅은 바탕색으로 구분하고, 글자색은 대비를 지키기 위해 바꾸지 않습니다. 색을 구분하기 어려운 사용자도 알 수 있도록 제목에 상황을 적습니다.

tone쓰는 곳기본 role
neutral (기본값)일반 안내status
success끝난 작업의 결과status
warning곧 문제가 될 수 있는 상태status
error작업을 막는 오류alert

role="alert"는 보조기기가 하던 말을 멈추고 바로 읽으므로 사용자가 바로 알아야 하는 오류에만 씁니다. 처음부터 화면에 있는 안내라면 role을 직접 지정해 바꿀 수 있습니다.

제목 수준

AlertTitle은 기본 h3입니다. 페이지 제목 바로 아래에 두면 as="h2", 제목 계층에 넣지 않을 때는 as="p"를 씁니다.

구성

구성 요소역할
Alert알림 내용을 묶습니다. 기본 role은 tone="error"일 때 alert, 나머지는 status입니다.
AlertTitle알림 제목입니다. as로 제목 수준을 고릅니다.
AlertDescription알림의 상세 내용입니다.

구현 코드

components/ui/alert.tsx
alert.tsx
import type { ComponentProps } from "react";
import { cx } from "../../lib/cx";
import "./alert.css";

export type AlertTone = "neutral" | "success" | "warning" | "error";
export type AlertProps = ComponentProps<"div"> & {
  /** 안내의 종류입니다. 색만으로 뜻을 전하지 않도록 제목에도 상황을 적습니다. @defaultValue "neutral" */
  tone?: AlertTone;
};

/** 오류는 바로 읽히도록 `role="alert"`, 나머지는 `role="status"`가 기본입니다. `role`로 바꿀 수 있습니다. */
export function Alert({ tone = "neutral", className, ...props }: AlertProps) {
  return (
    <div
      role={tone === "error" ? "alert" : "status"}
      {...props}
      data-tone={tone}
      className={cx("rbx-alert", className)}
    />
  );
}

export type AlertTitleProps = ComponentProps<"h3"> & {
  /** 페이지의 제목 계층에 맞게 선택합니다. 제목이 필요 없으면 "p"를 씁니다. @defaultValue "h3" */
  as?: "h2" | "h3" | "h4" | "h5" | "h6" | "p";
};
export function AlertTitle({
  as: Tag = "h3",
  className,
  ...props
}: AlertTitleProps) {
  return <Tag {...props} className={cx("rbx-alert-title", className)} />;
}

export function AlertDescription({ className, ...props }: ComponentProps<"p">) {
  return <p {...props} className={cx("rbx-alert-description", className)} />;
}
components/ui/alert.css
alert.css
@layer theme, base, components, utilities;
@layer components.ply-base, components.ply;

@layer components.ply {
  /* alert — semantic HTML composition. */
  .rbx-alert {
    --rbx-alert-accent: var(--rbx-color-stroke-emphasis);
    display: grid;
    gap: 4px;
    border: 1px solid var(--rbx-alert-accent);
    border-radius: 8px;
    padding: 16px;
    background: var(--rbx-color-surface-100);
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
  }
  /* 종류는 테두리와 옅은 바탕으로 구분합니다. 글자색은 대비를 위해 바꾸지 않습니다. */
  .rbx-alert:not([data-tone="neutral"]) {
    background: color-mix(
      in srgb,
      var(--rbx-alert-accent) 8%,
      var(--rbx-color-surface-100)
    );
  }
  .rbx-alert[data-tone="success"] {
    --rbx-alert-accent: var(--rbx-color-system-success);
  }
  .rbx-alert[data-tone="warning"] {
    --rbx-alert-accent: var(--rbx-color-system-warning);
  }
  .rbx-alert[data-tone="error"] {
    --rbx-alert-accent: var(--rbx-color-system-alert);
  }
  .rbx-alert-title {
    margin: 0;
    color: var(--rbx-color-content-emphasis);
    font: var(--rbx-typography-title-large-font);
  }
  .rbx-alert-description {
    margin: 0;
    color: var(--rbx-color-content-default);
    font: var(--rbx-typography-body-medium-font);
  }
}

목차