임동욱 임동욱 08-19
merge: origin/develop — @fox 신규 컴포넌트 6종 반영
충돌 2건은 양쪽이 같은 자리에 서로 다른 것을 **더하기만** 한 경우라 합집합으로 풀었다.

- styles/components.scss: hub의 파티셜 목록에 fox-status-indicator·fox-toast를 더했다.
- dev-test/component-registry.tsx: 데모 함수(hub의 ModalDemo·DemoThumb + origin/develop의
  ToastDemo)와 카탈로그 항목(45 공통 + hub 5 + origin/develop 6 = 56)을 모두 남겼다.
  git이 두 삽입의 공통 꼬리(`…}`)를 맞물려 놓아 마커 그대로는 함수가 끊긴다 — 그래서
  마커를 고치지 않고 양쪽 원본에서 함수·항목 단위로 다시 세웠다.

검증: tsc·eslint 무오류, next build 성공(/dev-test/design이 빌드 시 프리렌더되므로 병합된
레지스트리가 실제로 렌더되는 것까지 확인).

Co-Authored-By: Claude Opus 5 
@dbc6bcb81150a32d11bc2c2bae4ace17d94f9125
 
@fox/core/components/fox-alert/fox-alert.tsx (added)
+++ @fox/core/components/fox-alert/fox-alert.tsx
@@ -0,0 +1,132 @@
+import type { ReactNode, Ref } from "react";
+import {
+  FoxChatDotsIcon,
+  FoxCheckCircleIcon,
+  FoxInfoIcon,
+  FoxProhibitIcon,
+  FoxWarningIcon,
+} from "../../icons";
+import { cx } from "../../utils";
+
+export type FoxAlertType =
+  | "default"
+  | "information"
+  | "success"
+  | "warning"
+  | "danger";
+
+export interface FoxAlertProps {
+  /**
+   * 알림의 성격. 아이콘·배경·테두리·제목 색이 여기서 갈린다.
+   *
+   * 계열이 바꾸는 것은 배경 · 테두리 · 제목 글자색과 아이콘이다. 본문 글자색은 계열과
+   * 무관하게 같다.
+   *
+   * 아이콘은 계열이 정하고 굵기는 시안대로 `duotone`이며, 색은 제목 글자와 같은 계열색이다.
+   */
+  type?: FoxAlertType;
+  /** 굵은 제목 줄. 넘기지 않으면 영역을 렌더링하지 않는다. */
+  title?: ReactNode;
+  /** 제목 아래 본문. 넘기지 않으면 영역을 렌더링하지 않는다. */
+  message?: ReactNode;
+  /**
+   * 보조기술이 읽는 방식을 정한다(`FoxToast`와 같은 규칙).
+   *
+   * - `false`(기본): `role="status"` — 하던 말을 끊지 않는다. 화면과 함께 처음부터 놓여
+   *   있는 알림은 이쪽이다.
+   * - `true`: `role="alert"` — 읽던 것을 끊고 바로 읽는다. 사용자의 동작 결과로 **그 자리에
+   *   나타난** 알림에만 쓴다.
+   *
+   * `type="danger"`가 자동으로 이걸 켜지 않는다 — 위험한 내용인 것과 지금 끼어들어야 하는
+   * 것은 다른 문제이고, 페이지와 함께 그려지는 경고까지 낭독을 끊으면 방해만 된다.
+   */
+  urgent?: boolean;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  id?: string;
+  /** 배치 조정용. 시안의 폭(720px)을 바꿔야 할 때도 여기로 준다. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+/** `Record`로 고정해 계열을 추가하면 항목 누락이 타입 에러가 되게 한다. */
+const TYPE_CLASS: Record<FoxAlertType, string> = {
+  default: "fox-alert--default",
+  information: "fox-alert--information",
+  success: "fox-alert--success",
+  warning: "fox-alert--warning",
+  danger: "fox-alert--danger",
+};
+
+/**
+ * 계열이 정하는 아이콘. 호출부가 넘기지 않는다 — 무엇을 그릴지는 계열이 이미 답한다
+ * (`FoxTag`와 같은 규칙).
+ *
+ * 시안 `ico/*`가 Phosphor 원본이라 이름이 그대로 붙는다(`ico/Prohibit` →
+ * `FoxProhibitIcon`). 손으로 SVG를 그려 넣지 않는다 — 아이콘은 생성물이 단일 출처다.
+ *
+ * 굵기는 시안이 `Weight=Duotone`이라 다섯 개 모두 `duotone`이다. 기본값(`regular`)으로 두면
+ * 속이 빈 선 그림이 되어 시안과 달라지므로 반드시 적는다.
+ *
+ * duotone은 연한 면(`opacity` 0.2)과 진한 선을 겹쳐 그리는데 **둘 다 `currentColor`다** —
+ * 색은 스타일이 `.fox-alert__icon`에 준 하나를 쓰고, 연한 쪽만 알아서 흐려진다.
+ */
+const TYPE_ICON: Record<FoxAlertType, ReactNode> = {
+  default: <FoxChatDotsIcon weight="duotone" />,
+  information: <FoxInfoIcon weight="duotone" />,
+  success: <FoxCheckCircleIcon weight="duotone" />,
+  warning: <FoxWarningIcon weight="duotone" />,
+  danger: <FoxProhibitIcon weight="duotone" />,
+};
+
+/**
+ * @fox 알럿. 화면 안에 놓이는 띠 모양 알림이다 — 아이콘 상자 · 제목 · 본문이 한 줄로 선다.
+ *
+ * **모달이 아니다.** 화면을 덮지도, 포커스를 가두지도, 확인 버튼을 갖지도 않는다. 내용 흐름
+ * 안에 그대로 놓이고, 언제 나타나고 사라질지는 호출부가 정한다(`FoxToast`와 같은 방식).
+ *
+ * 상호작용이 없어 `"use client"`가 아니다 — 서버 컴포넌트로 렌더된다.
+ *
+ * ⚠️ 시안 주석: **본문은 두 줄 이내**로 쓴다. 길이를 코드가 막지는 않는다 — 세 줄이 되면
+ * 아이콘 상자만 가운데 남고 띠가 늘어난다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxAlert({
+  type = "default",
+  title,
+  message,
+  urgent = false,
+  hidden = false,
+  id,
+  className,
+  ref,
+}: FoxAlertProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <div
+      ref={ref}
+      id={id}
+      role={urgent ? "alert" : "status"}
+      className={cx("fox-alert", TYPE_CLASS[type], className)}
+    >
+      {/* 아이콘이 정해지기 전에도 상자는 그린다 — 시안이 흰 판을 배치의 일부로 준다. */}
+      <span className="fox-alert__icon" aria-hidden="true">
+        {TYPE_ICON[type]}
+      </span>
+
+      <div className="fox-alert__content">
+        {title === undefined ? null : (
+          <strong className="fox-alert__title">{title}</strong>
+        )}
+        {message === undefined ? null : (
+          <span className="fox-alert__message">{message}</span>
+        )}
+      </div>
+    </div>
+  );
+}
 
@fox/core/components/fox-alert/index.ts (added)
+++ @fox/core/components/fox-alert/index.ts
@@ -0,0 +1,1 @@
+export { FoxAlert, type FoxAlertProps, type FoxAlertType } from "./fox-alert";
 
@fox/core/components/fox-helper-text/fox-helper-text.tsx (added)
+++ @fox/core/components/fox-helper-text/fox-helper-text.tsx
@@ -0,0 +1,105 @@
+import type { ReactNode, Ref } from "react";
+import {
+  FoxChatDotsIcon,
+  FoxCheckCircleIcon,
+  FoxInfoIcon,
+  FoxProhibitIcon,
+  FoxWarningIcon,
+} from "../../icons";
+import { cx } from "../../utils";
+
+/** `FoxAlert`와 같은 다섯 계열이다 — 시안이 두 컴포넌트에 같은 축을 쓴다. */
+export type FoxHelperTextType =
+  | "default"
+  | "information"
+  | "success"
+  | "warning"
+  | "danger";
+
+export interface FoxHelperTextProps {
+  /** 글자색과 아이콘이 여기서 갈린다. */
+  type?: FoxHelperTextType;
+  /** 보여 줄 문구. */
+  message: ReactNode;
+  /**
+   * 아이콘을 그릴지. 시안은 다섯 계열 모두 아이콘을 달지만, 글자만 필요한 자리가 있어
+   * 끌 수 있게 둔다 — 폼 컴포넌트의 헬퍼가 지금 아이콘 없이 쓰이는 자리가 많다
+   * (`FoxInput.messageIcon`이 선택값인 것과 같은 사정).
+   */
+  showIcon?: boolean;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  /**
+   * 입력의 `aria-describedby`가 가리킬 값. 폼 옆에 놓을 때 넘겨서 이어 준다 —
+   * 이어 주지 않으면 화면에만 보이고 소리로는 전달되지 않는다.
+   */
+  id?: string;
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 `type`을 쓴다. */
+  className?: string;
+  ref?: Ref<HTMLParagraphElement>;
+}
+
+/** `Record`로 고정해 계열을 추가하면 항목 누락이 타입 에러가 되게 한다. */
+const TYPE_CLASS: Record<FoxHelperTextType, string> = {
+  default: "fox-helper-text--default",
+  information: "fox-helper-text--information",
+  success: "fox-helper-text--success",
+  warning: "fox-helper-text--warning",
+  danger: "fox-helper-text--danger",
+};
+
+/**
+ * 계열이 정하는 아이콘. 글리프는 `FoxAlert`와 같은 것을 쓰고 **굵기만 다르다** —
+ * 알럿은 `duotone`, 여기는 `regular`다(시안 확인).
+ */
+const TYPE_ICON: Record<FoxHelperTextType, ReactNode> = {
+  default: <FoxChatDotsIcon />,
+  information: <FoxInfoIcon />,
+  success: <FoxCheckCircleIcon />,
+  warning: <FoxWarningIcon />,
+  danger: <FoxProhibitIcon />,
+};
+
+/**
+ * @fox 헬퍼 텍스트. 입력 아래 한 줄로 붙는 안내·오류 문구다.
+ *
+ * 상태를 갖지 않는다 — 어떤 계열로 보일지는 호출부가 정한다.
+ *
+ * TODO(폼 통합, 적용 후 이 문단 삭제): 폼 컴포넌트 여섯 곳(`FoxInput`·`FoxTextArea`·
+ * `FoxEmail`·`FoxPhoneNumber`·`FoxAddress`의 `__message`, `FoxSelect`의 `__hint`)이
+ * 아직 각자 그린다. 이걸로 모으는 절차와 주의점은 `@fox/styles/_fox-helper-text.scss`
+ * 상단에 적어 두었다 — `FoxSelect`만 간격과 오류 아이콘 색이 달라지므로 확인이 필요하다.
+ *
+ * 상호작용이 없어 `"use client"`가 아니다 — 서버 컴포넌트로 렌더된다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxHelperText({
+  type = "default",
+  message,
+  showIcon = true,
+  hidden = false,
+  id,
+  className,
+  ref,
+}: FoxHelperTextProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <p
+      ref={ref}
+      id={id}
+      className={cx("fox-helper-text", TYPE_CLASS[type], className)}
+    >
+      {showIcon ? (
+        <span className="fox-helper-text__icon" aria-hidden="true">
+          {TYPE_ICON[type]}
+        </span>
+      ) : null}
+      <span className="fox-helper-text__message">{message}</span>
+    </p>
+  );
+}
 
@fox/core/components/fox-helper-text/index.ts (added)
+++ @fox/core/components/fox-helper-text/index.ts
@@ -0,0 +1,5 @@
+export {
+  FoxHelperText,
+  type FoxHelperTextProps,
+  type FoxHelperTextType,
+} from "./fox-helper-text";
 
@fox/core/components/fox-snackbar-list/fox-snackbar-list.tsx (added)
+++ @fox/core/components/fox-snackbar-list/fox-snackbar-list.tsx
@@ -0,0 +1,56 @@
+import type { ReactNode, Ref } from "react";
+import { cx } from "../../utils";
+
+export interface FoxSnackbarListProps {
+  /** 묶음을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */
+  label?: string;
+  /** 묶음 이름 역할을 하는 요소의 id. `label`보다 우선한다 — 화면의 글자와 어긋나지 않는다. */
+  labelledBy?: string;
+  /** `FoxSnackbar`들. */
+  children?: ReactNode;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  /** 배치 조정용. 띄우는 자리(fixed·bottom 등)와 시안의 폭(640px)을 여기로 덮는다. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+/**
+ * @fox 스낵바 묶음. 스낵바가 여러 개일 때 세로로 쌓는다. 배치만 책임지고 상태를 갖지 않는다 —
+ * 무엇이 몇 개 떠 있는지, 어떤 순서로 쌓을지는 호출부가 소유한다.
+ *
+ * 자식에게 덮어쓰는 값이 없다(`FoxTagList`·`FoxChipArea`와 다른 점이다) — 스낵바에는 계열도
+ * 크기도 없어서 묶음이 통일할 것이 폭과 간격뿐이다.
+ *
+ * 방향 수식어가 없다 — 스낵바는 세로로 쌓이는 것이 유일한 배치다.
+ *
+ * `role="group"`이다. 각 스낵바가 스스로 `role="status"`로 읽히므로 묶음은 자리만 만든다 —
+ * 묶음까지 live region으로 만들면 하나가 뜰 때마다 목록 전체가 다시 읽힌다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxSnackbarList({
+  label,
+  labelledBy,
+  children,
+  hidden = false,
+  className,
+  ref,
+}: FoxSnackbarListProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <div
+      ref={ref}
+      role="group"
+      aria-label={labelledBy ? undefined : label}
+      aria-labelledby={labelledBy}
+      className={cx("fox-snackbar-list", className)}
+    >
+      {children}
+    </div>
+  );
+}
 
@fox/core/components/fox-snackbar-list/index.ts (added)
+++ @fox/core/components/fox-snackbar-list/index.ts
@@ -0,0 +1,1 @@
+export { FoxSnackbarList, type FoxSnackbarListProps } from "./fox-snackbar-list";
 
@fox/core/components/fox-snackbar/fox-snackbar.tsx (added)
+++ @fox/core/components/fox-snackbar/fox-snackbar.tsx
@@ -0,0 +1,131 @@
+"use client";
+
+import type { ReactNode, Ref } from "react";
+import { FoxXIcon } from "../../icons";
+import { cx } from "../../utils";
+import { FoxTextButton } from "../fox-text-button";
+
+export interface FoxSnackbarProps {
+  /**
+   * 글자 앞에 붙는 아이콘. 넘기지 않으면 자리를 만들지 않는다.
+   *
+   * 계열이 없는 조각이라 무엇을 그릴지는 호출부가 정한다(`FoxAlert`처럼 계열이 아이콘을
+   * 고르지 않는다). `@fox/core/icons`에서 가져다 넘긴다 — 시안은 `fill` 굵기다:
+   * `<FoxCheckCircleIcon weight="fill" />`.
+   *
+   * 색은 스타일이 정한다(`icon-theme-primary`) — 글리프가 `fill: currentColor`라 넘기는
+   * 쪽에서 색을 줄 필요가 없다.
+   */
+  icon?: ReactNode;
+  /** 굵은 첫 줄. 넘기지 않으면 영역을 렌더링하지 않는다 — 본문만 있는 스낵바가 된다. */
+  title?: ReactNode;
+  /** 제목 아래 본문. 넘기지 않으면 영역을 렌더링하지 않는다. */
+  message?: ReactNode;
+  /**
+   * 오른쪽 글자 버튼의 문구. 없으면 버튼을 그리지 않는다.
+   *
+   * 버튼은 `FoxTextButton`의 `primary`·`md`로 고정이다(시안 지정) — 계열·크기를 열지 않는
+   * 것은 스낵바마다 버튼 모양이 달라지면 같은 조각으로 읽히지 않기 때문이다.
+   */
+  actionLabel?: string;
+  /** 글자 버튼을 눌렀을 때. `actionLabel`이 없으면 의미 없다. */
+  onAction?: () => void;
+  /**
+   * 닫기(X)를 눌렀을 때. **필수다** — 시안이 닫기를 항상 그리므로, 눌러도 아무 일이 없는
+   * 버튼이 남지 않게 호출부가 반드시 처리를 준다.
+   */
+  onClose: () => void;
+  /** 닫기 버튼을 읽어 줄 이름. X 아이콘은 장식이라 이 글자가 유일한 이름이다. */
+  closeLabel?: string;
+  /**
+   * 보조기술이 읽는 방식을 정한다(`FoxToast`·`FoxAlert`와 같은 규칙).
+   *
+   * - `false`(기본): `role="status"` — 하던 말을 끊지 않는다.
+   * - `true`: `role="alert"` — 읽던 것을 끊고 바로 읽는다. 지금 손을 멈춰야 하는 알림에만.
+   */
+  urgent?: boolean;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  id?: string;
+  /** 배치 조정용. 띄우는 자리(fixed·bottom 등)와 시안의 폭(640px)을 여기로 덮는다. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+/**
+ * @fox 스낵바. 흰 판 위에 굵은 한 줄과 글자 버튼이 서고, 오른쪽 끝에 닫기가 붙는다.
+ * 크기 수식어가 없다 — 시안에 하나뿐이다.
+ *
+ * `FoxToast`와 형제지만 **누를 것이 있다.** 토스트는 읽고 지나가는 조각이라 스스로 사라지고,
+ * 스낵바는 사용자가 되돌리거나(실행 취소) 이어서 할 일이 있어 손이 닿을 때까지 남는다.
+ *
+ * **상태를 갖지 않는다.** 언제 뜨고 언제 사라질지, 화면 어디에 놓일지는 전부 호출부가 정한다
+ * — `onClose`는 "닫아 달라"는 알림이지 이 조각이 스스로 사라지는 것이 아니다.
+ *
+ * ⚠️ 보조기술은 **이미 화면에 있던 live region의 내용이 바뀔 때** 읽는다. 이 요소를 통째로
+ * 새로 붙이면 조합에 따라 읽히지 않을 수 있다(`FoxToast`와 같은 사정).
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxSnackbar({
+  icon,
+  title,
+  message,
+  actionLabel,
+  onAction,
+  onClose,
+  closeLabel = "닫기",
+  urgent = false,
+  hidden = false,
+  id,
+  className,
+  ref,
+}: FoxSnackbarProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <div
+      ref={ref}
+      id={id}
+      role={urgent ? "alert" : "status"}
+      className={cx("fox-snackbar", className)}
+    >
+      {icon ? (
+        <span className="fox-snackbar__icon" aria-hidden="true">
+          {icon}
+        </span>
+      ) : null}
+
+      <div className="fox-snackbar__content">
+        {title === undefined ? null : (
+          <strong className="fox-snackbar__title">{title}</strong>
+        )}
+        {message === undefined ? null : (
+          <span className="fox-snackbar__message">{message}</span>
+        )}
+      </div>
+
+      {actionLabel ? (
+        <FoxTextButton
+          type="primary"
+          size="md"
+          label={actionLabel}
+          onAction={onAction}
+          className="fox-snackbar__action"
+        />
+      ) : null}
+
+      <button
+        type="button"
+        className="fox-snackbar__close"
+        aria-label={closeLabel}
+        onClick={onClose}
+      >
+        <FoxXIcon />
+      </button>
+    </div>
+  );
+}
 
@fox/core/components/fox-snackbar/index.ts (added)
+++ @fox/core/components/fox-snackbar/index.ts
@@ -0,0 +1,1 @@
+export { FoxSnackbar, type FoxSnackbarProps } from "./fox-snackbar";
 
@fox/core/components/fox-status-indicator/fox-status-indicator.tsx (added)
+++ @fox/core/components/fox-status-indicator/fox-status-indicator.tsx
@@ -0,0 +1,71 @@
+import type { ReactNode, Ref } from "react";
+import { cx } from "../../utils";
+
+/** `FoxAlert`·`FoxHelperText`와 같은 다섯 계열이다 — 시안이 같은 축을 쓴다. */
+export type FoxStatusIndicatorType =
+  | "default"
+  | "information"
+  | "success"
+  | "warning"
+  | "danger";
+
+export interface FoxStatusIndicatorProps {
+  /** 점과 글자의 색이 여기서 갈린다. */
+  type?: FoxStatusIndicatorType;
+  /** 상태를 나타내는 글자. */
+  label: ReactNode;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  id?: string;
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 `type`을 쓴다. */
+  className?: string;
+  ref?: Ref<HTMLSpanElement>;
+}
+
+/** `Record`로 고정해 계열을 추가하면 항목 누락이 타입 에러가 되게 한다. */
+const TYPE_CLASS: Record<FoxStatusIndicatorType, string> = {
+  default: "fox-status-indicator--default",
+  information: "fox-status-indicator--information",
+  success: "fox-status-indicator--success",
+  warning: "fox-status-indicator--warning",
+  danger: "fox-status-indicator--danger",
+};
+
+/**
+ * @fox 상태 표시. 작은 점 하나와 글자로 상태를 알린다. 크기 수식어가 없다 — 시안에 하나뿐이다.
+ *
+ * **점은 장식이다**(`aria-hidden`). 상태의 의미는 글자가 전부 담는다 — 색만으로 뜻을 전하면
+ * 색을 구분하지 못하는 사용자에게는 아무것도 전달되지 않기 때문이고, 그래서 `label`이
+ * 선택값이 아니라 필수값이다.
+ *
+ * 값이 바뀌는 것을 소리로 알려야 하는 자리(예: 처리 중 → 완료)라면 감싼 쪽이 live region을
+ * 만든다 — 이 조각은 상태를 갖지 않으므로 스스로 알리지 않는다.
+ *
+ * 상호작용이 없어 `"use client"`가 아니다 — 서버 컴포넌트로 렌더된다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxStatusIndicator({
+  type = "default",
+  label,
+  hidden = false,
+  id,
+  className,
+  ref,
+}: FoxStatusIndicatorProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <span
+      ref={ref}
+      id={id}
+      className={cx("fox-status-indicator", TYPE_CLASS[type], className)}
+    >
+      <span className="fox-status-indicator__dot" aria-hidden="true" />
+      <span className="fox-status-indicator__label">{label}</span>
+    </span>
+  );
+}
 
@fox/core/components/fox-status-indicator/index.ts (added)
+++ @fox/core/components/fox-status-indicator/index.ts
@@ -0,0 +1,5 @@
+export {
+  FoxStatusIndicator,
+  type FoxStatusIndicatorProps,
+  type FoxStatusIndicatorType,
+} from "./fox-status-indicator";
 
@fox/core/components/fox-toast/fox-toast.tsx (added)
+++ @fox/core/components/fox-toast/fox-toast.tsx
@@ -0,0 +1,67 @@
+import type { ReactNode, Ref } from "react";
+import { cx } from "../../utils";
+
+export interface FoxToastProps {
+  /**
+   * 알림 문구.
+   *
+   * 이름이 `label`이 아니다 — 다른 컴포넌트의 `label`은 컨트롤에 붙는 이름이지만, 이쪽은
+   * 조각 안에 담기는 **내용**이라 성격이 다르다.
+   */
+  message: ReactNode;
+  /**
+   * 보조기술이 읽는 방식을 정한다.
+   *
+   * - `false`(기본): `role="status"` — 하던 말을 끊지 않고 차례가 오면 읽는다. 저장 완료처럼
+   *   놓쳐도 되는 알림.
+   * - `true`: `role="alert"` — 읽던 것을 끊고 바로 읽는다. 지금 손을 멈춰야 하는 알림에만
+   *   쓴다. 남발하면 화면 낭독이 계속 끊긴다.
+   */
+  urgent?: boolean;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  id?: string;
+  /** 배치 조정용. 띄우는 위치(fixed·bottom 등)를 여기로 준다. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+/**
+ * @fox 토스트. 어두운 판 위에 한 줄짜리 알림을 얹는다. 크기 수식어가 없다 — 시안에 하나뿐이다.
+ *
+ * **상태를 갖지 않는다.** 언제 뜨고 언제 사라질지, 화면 어디에 놓일지는 전부 호출부가 정한다
+ * (사용자 결정) — 이 조각은 조건부로 렌더하고 `className`으로 자리를 잡아 주면 된다. 타이머와
+ * 위치를 안에 두면 SCSS만 쓰는 소비자가 그 절반을 못 쓰고, 뒤이어 만들 스낵바와 규칙이 갈린다.
+ *
+ * 상호작용이 없어 `"use client"`가 아니다 — 서버 컴포넌트로 렌더된다.
+ *
+ * ⚠️ 보조기술은 **이미 화면에 있던 live region의 내용이 바뀔 때** 읽는다. 이 요소를 통째로 새로
+ * 붙이면 브라우저·리더 조합에 따라 읽히지 않을 수 있다. 확실히 읽혀야 하는 자리라면 호출부가
+ * 빈 `<div role="status">`를 미리 두고 그 안에서 토스트를 갈아 끼운다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxToast({
+  message,
+  urgent = false,
+  hidden = false,
+  id,
+  className,
+  ref,
+}: FoxToastProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <div
+      ref={ref}
+      id={id}
+      role={urgent ? "alert" : "status"}
+      className={cx("fox-toast", className)}
+    >
+      {message}
+    </div>
+  );
+}
 
@fox/core/components/fox-toast/index.ts (added)
+++ @fox/core/components/fox-toast/index.ts
@@ -0,0 +1,1 @@
+export { FoxToast, type FoxToastProps } from "./fox-toast";
@fox/core/components/index.ts
--- @fox/core/components/index.ts
+++ @fox/core/components/index.ts
@@ -1,5 +1,6 @@
 export * from "./fox-accordion";
 export * from "./fox-address";
+export * from "./fox-alert";
 export * from "./fox-badge";
 export * from "./fox-badge-dot";
 export * from "./fox-badge-group";
@@ -25,6 +26,7 @@
 export * from "./fox-email";
 export * from "./fox-file-upload";
 export * from "./fox-form-label";
+export * from "./fox-helper-text";
 export * from "./fox-icon-button";
 export * from "./fox-input";
 export * from "./fox-link-button";
@@ -41,7 +43,10 @@
 export * from "./fox-select-option";
 export * from "./fox-select-option-item";
 export * from "./fox-select-text";
+export * from "./fox-snackbar";
+export * from "./fox-snackbar-list";
 export * from "./fox-spinner";
+export * from "./fox-status-indicator";
 export * from "./fox-tab";
 export * from "./fox-tab-item";
 export * from "./fox-table";
@@ -51,6 +56,7 @@
 export * from "./fox-tag-list";
 export * from "./fox-text-area";
 export * from "./fox-text-button";
+export * from "./fox-toast";
 export * from "./fox-toggle-quantity";
 export * from "./fox-toggle-switch";
 export * from "./fox-list-search";
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -1,8 +1,9 @@
 "use client";
 
 import { FoxInput, FoxInputState, FoxSelect, FoxSelectItem, FoxSelectOption, FoxSelectOptionItem } from "@/@fox/core/components";
-import { useState, type CSSProperties, type ReactNode } from "react";
+import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
 import { FoxAddress, type FoxAddressSearchResult, type FoxAddressValue } from "../core/components/fox-address";
+import { FoxAlert } from "../core/components/fox-alert";
 import { FoxBreadcrumb } from "../core/components/fox-breadcrumb";
 import { FoxButton } from "../core/components/fox-button";
 import { FoxButtonGroup } from "../core/components/fox-button-group";
@@ -18,6 +19,7 @@
 import { FoxEmail } from "../core/components/fox-email";
 import { FoxFileUpload, type FoxFileItem } from "../core/components/fox-file-upload";
 import { FoxFormLabel } from "../core/components/fox-form-label";
+import { FoxHelperText } from "../core/components/fox-helper-text";
 import { FoxIconButton } from "../core/components/fox-icon-button";
 import { FoxLinkButton } from "../core/components/fox-link-button";
 import { FoxPagination } from "../core/components/fox-pagination";
@@ -27,6 +29,7 @@
 import { FoxRadioIcon } from "../core/components/fox-radio-icon";
 import { FoxTag } from "../core/components/fox-tag";
 import { FoxTagList } from "../core/components/fox-tag-list";
+import { FoxToast } from "../core/components/fox-toast";
 import { FoxTextArea, type FoxTextAreaState } from "../core/components/fox-text-area";
 import { FoxTextButton } from "../core/components/fox-text-button";
 import { FoxToggleSwitch } from "../core/components/fox-toggle-switch";
@@ -50,6 +53,10 @@
 import { FoxTab } from "../core/components/fox-tab";
 import { FoxTabItem } from "../core/components/fox-tab-item";
 import { FoxSpinner } from "../core/components/fox-spinner";
+import { FoxCheckCircleIcon } from "../core/icons";
+import { FoxSnackbar } from "../core/components/fox-snackbar";
+import { FoxSnackbarList } from "../core/components/fox-snackbar-list";
+import { FoxStatusIndicator } from "../core/components/fox-status-indicator";
 import { FoxChip, type FoxChipItem } from "../core/components/fox-chip";
 import { FoxChipArea } from "../core/components/fox-chip-area";
 import { FoxSelectText } from "../core/components/fox-select-text";
@@ -601,7 +608,6 @@
   );
 }
 
-/** 모달은 열어 봐야 보이므로 여는 버튼과 함께 낸다. */
 function ModalDemo({
   size,
   title,
@@ -654,11 +660,6 @@
   );
 }
 
-/**
- * 그림 자리 확인용 도형. 실제 사진 대신 쓴다 — `@fox`는 앱의 이미지 경로를 참조하지 않고
- * (다른 프로젝트로 통째로 복사되어야 한다), 카탈로그가 외부 파일에 기대지도 않게 한다.
- * 토큰 색을 그대로 쓰므로 라이트·다크에서 함께 바뀐다.
- */
 function DemoThumb() {
   return (
     <svg viewBox="0 0 320 180" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
@@ -670,6 +671,41 @@
         opacity="0.45"
       />
     </svg>
+  );
+}
+
+function ToastDemo() {
+  const [open, setOpen] = useState(false);
+
+  useEffect(() => {
+    if (!open) return;
+    const timer = window.setTimeout(() => setOpen(false), 3000);
+    return () => window.clearTimeout(timer);
+  }, [open]);
+
+  return (
+    <>
+      <FoxButton
+        size="md"
+        type="primary"
+        label="토스트 띄우기"
+        onAction={() => setOpen(true)}
+        disabled={open}
+      />
+      {open ? (
+        <div
+          style={{
+            position: "fixed",
+            insetInlineStart: "50%",
+            insetBlockEnd: "4rem",
+            transform: "translateX(-50%)",
+            zIndex: 100,
+          }}
+        >
+          <FoxToast message="저장되었습니다." />
+        </div>
+      ) : null}
+    </>
   );
 }
 
@@ -3555,4 +3591,246 @@
       },
     ],
   },
+  {
+    id: "fox-toast",
+    name: "FoxToast",
+    description:
+      "Figma 시안(toast 441:5471) 토스트메세지 — 상태를 갖지 않습니다: 언제 뜨고 언제 사라질지, 화면 어디에 놓일지는 전부 호출부가 정합니다. 타이머와 위치를 안에 두면 SCSS만 쓰는 소비자가 그 절반을 못 쓰고, 뒤이어 만들 스낵바와 규칙이 갈립니다. 배경·글자색이 *-inverse 토큰이라 다크 모드에서는 둘이 함께 뒤집혀 밝은 판 + 어두운 글자가 됩니다.",
+    variants: [
+      {
+        label: "기본",
+        node: <FoxToast message="저장되었습니다." />,
+      },
+      {
+        label: "문구가 길면 폭이 내용만큼 늘어납니다 (시안에 상한이 없어 넣지 않았습니다)",
+        node: <FoxToast message="입력하신 내용을 저장했습니다. 목록에서 확인할 수 있습니다." />,
+      },
+      {
+        label: "urgent — 모양은 같고 role만 status에서 alert로 바뀝니다 (읽던 것을 끊고 바로 읽음)",
+        node: <FoxToast message="네트워크 연결이 끊겼습니다." urgent />,
+      },
+      {
+        label: "띄우고 지우는 것은 호출부 몫입니다 — 눌러 보세요 (3초 뒤 사라짐, 화면 하단 고정)",
+        node: <ToastDemo />,
+      },
+    ],
+  },
+  {
+    id: "fox-alert",
+    name: "FoxAlert",
+    description:
+      "Figma 시안(alert 258:433) 폭 720px은 시안 값 그대로지만 좁은 자리에서 부모를 뚫지 않게 max-inline-size: 100%를 함께 걸었습니다. 계열은 배경·테두리·제목 글자색을 바꿉니다 — 테두리만 -inverse 토큰이라 연한 배경(5) 위에 한 단계 진한 선(10)이 얹힙니다. 본문 글자색과 아이콘 상자의 흰 배경은 계열과 무관합니다.",
+    variants: [
+      {
+        label: "default",
+        node: (
+          <FoxAlert
+            title="알림 제목이 들어갑니다"
+            message="콘텐츠 내 알럿은 최대 2줄 이내로 작성합니다  콘텐츠 내 알럿은 최대 2줄 이내로 작성합니다  콘텐츠 내 알럿은 최대 2줄 이내로 작성합니다"
+          />
+        ),
+      },
+      {
+        label: "제목만 / 본문만 — 넘기지 않은 쪽은 렌더하지 않습니다",
+        node: (
+          <>
+            <FoxAlert title="제목만 있는 알림" />
+            <FoxAlert message="본문만 있는 알림입니다." />
+          </>
+        ),
+      },
+      {
+        label: "type 5종",
+        node: (
+          <>
+            <FoxAlert type="default" title="default" message="기본" />
+            <FoxAlert type="information" title="information" message="안내" />
+            <FoxAlert type="success" title="success" message="완료" />
+            <FoxAlert type="warning" title="warning" message="주의" />
+            <FoxAlert type="danger" title="danger" message="위험" />
+          </>
+        ),
+      },
+      {
+        label: "좁은 자리(360px)에 넣으면 폭이 부모에 맞춰 줄어듭니다",
+        node: (
+          <div style={{ inlineSize: "36rem", outline: "1px dashed currentColor" }}>
+            <FoxAlert
+              title="좁은 단 안의 알림"
+              message="720px이 아니라 부모 폭까지만 늘어납니다."
+            />
+          </div>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-helper-text",
+    name: "FoxHelperText",
+    description:
+      "Figma 시안(helper-txt) 입력 아래 붙는 안내·오류 한 줄입니다. 계열은 FoxAlert와 같은 다섯 개이고 글리프도 같은 것을 쓰되 굵기만 regular입니다(알럿은 duotone). 계열이 붙으면 글자는 font-system-*, 아이콘은 한 단계 진한 icon-system-*-strong입니다 — default만 글자·아이콘이 같은 회색입니다. 모양 값을 새로 적지 않았습니다 — 시안 값이 _form-field.scss의 message 믹스인과 한 자도 다르지 않아(gap 4px · 아이콘 padding-top 2px · body/sm · 150% · -0.025em) 그대로 부릅니다. 차이는 display 하나뿐이라(시안 inline-flex, 폼 안 flex) 믹스인에 인자로 넘깁니다. ⚠️ 폼 컴포넌트 안의 헬퍼는 아직 이 클래스를 쓰지 않습니다 — FoxInput·FoxTextArea·FoxEmail·FoxPhoneNumber·FoxAddress는 각자 __message를, FoxSelect는 __hint를 그리고, 그쪽 색은 상자의 포커스·오류를 :has()로 따라갑니다(auto-states 믹스인). 모으는 것은 별도 작업입니다.",
+    variants: [
+      {
+        label: "계열 5종 — 아이콘이 글자보다 한 단계 진합니다 (icon-system-*-strong)",
+        node: (
+          <>
+            <FoxHelperText message="기본 안내 문구입니다." />
+            <FoxHelperText type="information" message="입력 형식을 확인해 주세요." />
+            <FoxHelperText type="success" message="사용할 수 있는 아이디입니다." />
+            <FoxHelperText type="warning" message="곧 만료되는 항목입니다." />
+            <FoxHelperText type="danger" message="필수 항목입니다." />
+          </>
+        ),
+      },
+      {
+        label: "showIcon={false} — 글자만 (폼 헬퍼가 아이콘 없이 쓰이는 자리를 위해)",
+        node: (
+          <>
+            <FoxHelperText showIcon={false} message="2자 이상 입력해 주세요." />
+            <FoxHelperText type="danger" showIcon={false} message="필수 항목입니다." />
+          </>
+        ),
+      },
+      {
+        label: "inline-flex라 폭이 글자만큼입니다 (점선은 자리 확인용)",
+        node: (
+          <div style={{ outline: "1px dashed currentColor" }}>
+            <FoxHelperText type="information" message="내용만큼만 차지합니다." />
+          </div>
+        ),
+      },
+      {
+        label: "여러 줄 — 아이콘이 첫 줄에 맞춰 고정됩니다",
+        node: (
+          <div style={{ inlineSize: "24rem" }}>
+            <FoxHelperText
+              type="warning"
+              message="문구가 길어져 두 줄이 되어도 아이콘은 위로 붙어 첫 글줄과 나란히 남습니다."
+            />
+          </div>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-status-indicator",
+    name: "FoxStatusIndicator",
+    description:
+      "Figma 시안(status-indicator 404:6328) 글자로 상태를 알립니다. 점은 장식이라 aria-hidden이고 뜻은 글자가 전부 담습니다 — 그래서 label이 선택값이 아니라 필수값입니다. 색만으로 상태를 전하면 색을 구분하지 못하는 사용자에게는 아무것도 전달되지 않습니다.",
+    variants: [
+      {
+        label: "계열 5종",
+        node: (
+          <>
+            <FoxStatusIndicator label="시작전, 중립적" />
+            <FoxStatusIndicator type="danger" label="중단, 실패, 위험" />
+            <FoxStatusIndicator type="information" label="진행 중" />
+            <FoxStatusIndicator type="warning" label="잠재적 위험, 주의" />
+            <FoxStatusIndicator type="success" label="진행중, 처리중간단계" />
+          </>
+        ),
+      },
+      {
+        label: "inline-flex라 글줄 안에 그대로 놓입니다",
+        node: (
+          <p style={{ margin: 0 }}>
+            현재 상태는 <FoxStatusIndicator type="success" label="정상" /> 입니다.
+          </p>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-snackbar",
+    name: "FoxSnackbar",
+    description:
+      "Figma 시안(snackbar 441:5880) 흰 판 위에 굵은 한 줄과 글자 버튼이 서고 오른쪽 끝에 닫기가 붙습니다. FoxToast와 형제지만 누를 것이 있어서, 읽고 지나가는 토스트와 달리 손이 닿을 때까지 남습니다. 상태를 갖지 않습니다 — onClose는 '닫아 달라'는 알림이지 스스로 사라지는 것이 아니고, 위치도 호출부가 정합니다. 글자 버튼은 FoxTextButton의 primary·md, 닫기는 오른쪽에 붙습니다 — 판의 오른쪽 여백 64px은 글자가 그 아래로 들어가지 않게 비워 두는 자리입니다.",
+    variants: [
+      {
+        label: "기본 — 제목 + 본문 + 액션 + 닫기",
+        node: (
+          <FoxSnackbar
+            title="메세지 타이틀"
+            message="스낵바는 화면 하단에 제공되는 안내 메시지입니다."
+            actionLabel="텍스트 버튼"
+            onAction={() => {}}
+            onClose={() => {}}
+          />
+        ),
+      },
+      {
+        label: "아이콘 — 호출부가 @fox/core/icons에서 골라 넘깁니다 (ico/CheckCircle, weight=fill / 색은 icon-theme-primary)",
+        node: (
+          <FoxSnackbar
+            icon={<FoxCheckCircleIcon weight="fill" />}
+            title="변경 사항을 저장했습니다"
+            message="3분 안에는 되돌릴 수 있습니다."
+            actionLabel="실행 취소"
+            onAction={() => {}}
+            onClose={() => {}}
+          />
+        ),
+      },
+      {
+        label: "제목 없이 / 액션 없이 — 넘기지 않은 쪽은 그리지 않습니다 (닫기는 항상 있습니다)",
+        node: (
+          <>
+            <FoxSnackbar title="제목만 있는 스낵바" actionLabel="보기" onAction={() => {}} onClose={() => {}} />
+            <FoxSnackbar message="제목 없이 본문만 있는 스낵바입니다." actionLabel="보기" onAction={() => {}} onClose={() => {}} />
+            <FoxSnackbar title="액션이 없는 스낵바" message="닫기만 있습니다." onClose={() => {}} />
+          </>
+        ),
+      },
+      {
+        label: "제목이 길면 줄바꿈되고 버튼은 오른쪽에 남습니다",
+        node: (
+          <FoxSnackbar
+            title="제목이 길어져 두 줄이 되어도"
+            message="글자 버튼과 닫기는 자리를 지킵니다. 판은 640px에서 시작하고 오른쪽 64px은 닫기 자리로 비워 둡니다."
+            actionLabel="자세히"
+            onAction={() => {}}
+            onClose={() => {}}
+          />
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-snackbar-list",
+    name: "FoxSnackbarList",
+    description:
+      "Figma 시안(snackbar-list 467:5958) 스낵바가 여러 개일 때 세로로 쌓는 묶음입니다. — 무엇이 몇 개 떠 있는지, 어떤 순서로 쌓을지는 호출부가 소유합니다. 자식에게 덮어쓰는 값이 없다는 점이 FoxTagList·FoxChipArea와 다릅니다.",
+    variants: [
+      {
+        label: "세 개가 쌓인 모습 (간격 8px)",
+        node: (
+          <FoxSnackbarList label="알림">
+            <FoxSnackbar
+              icon={<FoxCheckCircleIcon weight="fill" />}
+              title="변경 사항을 저장했습니다"
+              message="3분 안에는 되돌릴 수 있습니다."
+              actionLabel="실행 취소"
+              onAction={() => {}}
+              onClose={() => {}}
+            />
+            <FoxSnackbar
+              title="파일 3개를 올렸습니다"
+              actionLabel="보기"
+              onAction={() => {}}
+              onClose={() => {}}
+            />
+            <FoxSnackbar message="제목 없이 본문만 있는 스낵바입니다." onClose={() => {}} />
+          </FoxSnackbarList>
+        ),
+      },
+      {
+        label: "하나만 있어도 그대로 씁니다",
+        node: (
+          <FoxSnackbarList label="알림">
+            <FoxSnackbar title="하나만 떠 있는 경우" onClose={() => {}} />
+          </FoxSnackbarList>
+        ),
+      },
+    ],
+  },
 ];
@fox/styles/_form-field.scss
--- @fox/styles/_form-field.scss
+++ @fox/styles/_form-field.scss
@@ -27,8 +27,14 @@
 }
 
 /// 시안 helper-message. 오류·포커스 색은 상태 규칙이 덮는다.
-@mixin message {
-  display: flex;
+///
+/// `$display`는 폼 안에서 `flex`이고, 단독으로 쓰는 `FoxHelperText`에서만
+/// `inline-flex`다 — 그쪽 시안이 내용만큼만 차지하게 준다.
+///
+/// TODO(폼 통합, 적용 후 이 문단 삭제): 이 믹스인을 부르는 폼 다섯 곳은 앞으로
+/// `FoxHelperText`(`.fox-helper-text`)로 옮긴다 — 절차는 `_fox-helper-text.scss` 상단 참고.
+@mixin message($display: flex) {
+  display: $display;
   gap: fox.number(3);
   align-items: flex-start;
   margin: 0;
 
@fox/styles/_fox-alert.scss (added)
+++ @fox/styles/_fox-alert.scss
@@ -0,0 +1,166 @@
+// FoxAlert — 시안: 통합관리자페이지 디자인시스템 Figma alert
+//
+// 화면 안에 놓이는 띠 모양 알림이다. 아이콘 상자 · 제목 · 본문이 한 줄로 선다.
+// **모달이 아니다** — 화면을 덮지 않고 내용 흐름 안에 그대로 놓인다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-alert fox-alert--default" role="status">
+//     <span class="fox-alert__icon">…아이콘…</span>
+//     <div class="fox-alert__content">
+//       <strong class="fox-alert__title">제목</strong>
+//       <span class="fox-alert__message">본문</span>
+//     </div>
+//   </div>
+//
+// 제목·본문은 각각 없을 수 있다. 아이콘 상자는 아이콘이 없어도 그린다 — 시안이 흰 판을
+// 배치의 일부로 주고, 있고 없고에 따라 글자 시작점이 흔들리면 안 된다.
+//
+// 제목이 `heading/xsm`(title 패밀리·bold)이고 본문이 `body/md`(body 패밀리·regular)다.
+// 글자 크기는 둘 다 15px이라 굵기와 패밀리로만 갈린다 — 시안이 그렇게 준다.
+//
+// 토큰이 아닌 값:
+//   - ⚠️ `inline-size: 72rem`: **시안의 폭(720px)을 일단 그대로 적용해 둔 것이다.** Figma가
+//     변수로 export하지 않았고 토큰 스케일에도 720이 없다(modal 계열의 가장 큰 값이 760px).
+//     토큰이 생기면 `fox.*()`로 바꾸고, 폭을 고정하지 않는 쪽으로 정해지면 여기서 뺀다.
+//     그때까지 폭을 바꿔야 하는 자리는 호출부가 `className`으로 덮는다
+//     (`fox-toggle-quantity`와 같은 처리).
+//   - `max-inline-size: 100%`: 시안에 없는 구조 규칙이다. 720px보다 좁은 자리에 놓였을 때
+//     띠가 부모를 뚫고 나가지 않게 막는다 — 고정 폭만 두면 표 안이나 좁은 단에서 넘친다.
+//   - line-height / letter-spacing: 시안 텍스트 스타일 heading/xsm·body/md의 값이나 Figma가
+//     변수로 export하지 않는다. 시안의 -0.375px은 15px 기준이라 `-0.025em`으로 적는다
+//     (다른 컴포넌트와 같은 근거).
+//   - box-sizing / display / flex / aspect-ratio: 구조 규칙.
+//
+// 아이콘은 컴포넌트의 `TYPE_ICON`이 계열마다 하나씩 쥐고 있다(Prohibit · Warning ·
+// CheckCircle · Info · ChatDots, 전부 `weight="duotone"`). 색은 **제목 글자와 같은 계열색**을
+// 쓴다(default는 `font-neutral-strong`) — 시안 확인 완료.
+//
+// duotone은 연한 면과 진한 선을 겹쳐 그리고 **둘 다 `currentColor`를 쓴다** — 연한 쪽은
+// 글리프 자체가 `opacity: 0.2`를 물고 있어 색을 하나만 줘도 두 톤이 나온다.
+//
+// duotone은 연한 면과 진한 선을 겹쳐 그리고 **둘 다 `currentColor`를 쓴다** — 연한 쪽은
+// 글리프 자체가 `opacity: 0.2`를 물고 있어 색을 하나만 줘도 두 톤이 나온다.
+//
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-alert {
+  // 안쪽 여백이 바깥 폭을 늘리지 않게 한다 — 720px이 720px로 그려진다.
+  box-sizing: border-box;
+  display: flex;
+  align-items: center;
+  gap: fox.gap(4);
+  inline-size: 72rem;
+  max-inline-size: 100%;
+  padding: fox.padding(5) fox.padding(6);
+  border: fox.border(1) solid fox.color(border-neutral-subtler);
+  border-radius: fox.radius(4);
+  background: fox.color(surface-neutral-gray);
+
+  // 정사각 흰 판. 글자가 길어져 띠가 늘어나도 크기가 그대로여야 해서 줄어들지 않게 막는다.
+  &__icon {
+    display: flex;
+    flex-shrink: 0;
+    justify-content: center;
+    align-items: center;
+    inline-size: fox.number(13);
+    block-size: fox.number(13);
+    aspect-ratio: 1 / 1;
+    border-radius: fox.radius(4);
+    background: fox.color(element-neutral-inverse);
+    // 글리프가 `fill: currentColor`라 이 색을 그대로 입는다. 계열이 있으면 아래에서 덮는다.
+    color: fox.color(font-neutral-strong);
+  }
+
+  // 글리프는 흰 판(36px)보다 작은 24px이다 — 판이 글리프를 감싸는 여백을 갖는다.
+  // 상자와 마찬가지로 줄어들지 않게 막고 정사각을 못박는다(시안이 둘 다 명시한다).
+  &__icon > * {
+    flex-shrink: 0;
+    inline-size: fox.icon(5);
+    block-size: fox.icon(5);
+    aspect-ratio: 1 / 1;
+  }
+
+  // 남는 폭을 전부 가져간다. `min-inline-size: 0`이 없으면 긴 글이 줄바꿈되지 않고 띠를
+  // 밀어 넓힌다(flex 자식의 기본 최소 크기가 내용 크기라서다).
+  &__content {
+    display: flex;
+    flex: 1 0 0;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: fox.gap(1);
+    min-inline-size: 0;
+  }
+
+  &__title {
+    align-self: stretch;
+    color: fox.color(font-neutral-strong);
+    font-family: fox.font-family(title);
+    font-size: fox.font-size(heading-xsm);
+    font-weight: fox.font-weight(bold);
+    line-height: 1.4;
+    letter-spacing: -0.025em;
+  }
+
+  &__message {
+    align-self: stretch;
+    color: fox.color(font-neutral-strong);
+    font-family: fox.font-family(body);
+    font-size: fox.font-size(body-md);
+    font-weight: fox.font-weight(regular);
+    line-height: 1.5;
+    letter-spacing: -0.025em;
+  }
+
+  // ── 계열 ────────────────────────────────────────────────────────────────────
+  // 계열이 바꾸는 것은 **배경 · 테두리 · 제목 글자색** 셋뿐이다. 본문 글자색과 아이콘 상자의
+  // 흰 배경은 계열과 무관하게 블록 규칙을 그대로 쓴다(시안 확인).
+  //
+  // 테두리만 `-inverse`다 — 같은 계열의 5(연한 배경)와 10(그보다 한 단계 진한 선)을 짝지어
+  // 쓰라고 토큰이 그렇게 갈라져 있다. 다크 모드에서는 세 값이 함께 뒤집힌다(95 / 90 / 20).
+  //
+  // `default`는 블록 규칙이 곧 그 계열의 모양이라 따로 덮을 것이 없다.
+  &--default {
+    // 시안 그대로 — 블록에 있는 값이 전부다.
+  }
+
+  &--information {
+    border-color: fox.color(border-system-information-inverse);
+    background: fox.color(surface-system-information);
+  }
+
+  &--information &__title,
+  &--information &__icon {
+    color: fox.color(font-system-information);
+  }
+
+  &--success {
+    border-color: fox.color(border-system-success-inverse);
+    background: fox.color(surface-system-success);
+  }
+
+  &--success &__title,
+  &--success &__icon {
+    color: fox.color(font-system-success);
+  }
+
+  &--warning {
+    border-color: fox.color(border-system-warning-inverse);
+    background: fox.color(surface-system-warning);
+  }
+
+  &--warning &__title,
+  &--warning &__icon {
+    color: fox.color(font-system-warning);
+  }
+
+  &--danger {
+    border-color: fox.color(border-system-danger-inverse);
+    background: fox.color(surface-system-danger);
+  }
+
+  &--danger &__title,
+  &--danger &__icon {
+    color: fox.color(font-system-danger);
+  }
+}
 
@fox/styles/_fox-helper-text.scss (added)
+++ @fox/styles/_fox-helper-text.scss
@@ -0,0 +1,105 @@
+// FoxHelperText — 시안: 통합관리자페이지 디자인시스템 Figma helper-txt
+//
+// 입력 아래 한 줄로 붙는 안내·오류 문구다. 아이콘 하나와 글자 한 덩어리로 끝난다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <p class="fox-helper-text fox-helper-text--danger">
+//     <span class="fox-helper-text__icon">…아이콘 svg…</span>
+//     <span class="fox-helper-text__message">필수 항목입니다.</span>
+//   </p>
+//
+// 아이콘은 없어도 된다(글자만 있는 자리가 많다). 계열은 `FoxAlert`와 같은 다섯 개이고,
+// 글리프도 같은 것을 쓰되 **굵기만 regular**다(알럿은 duotone).
+//
+// **모양 값을 여기서 새로 적지 않는다.** 시안 값이 `_form-field.scss`의 `message`·
+// `message-icon` 믹스인과 한 자도 다르지 않아(gap 4px · 아이콘 padding-top 2px · body/sm ·
+// line-height 150% · letter-spacing -0.025em) 그대로 부른다. 폼 컴포넌트의 `__message`가
+// 같은 믹스인을 쓰므로, 두 경로가 갈라질 수 없다.
+//
+// 유일한 차이가 `display`다 — 시안이 `inline-flex`를 준다(폼 안에서는 `flex`). 문구가
+// 글줄 안에 놓일 수 있어서 폭을 내용만큼만 갖는 쪽이 맞다.
+//
+// 계열은 **글자와 아이콘의 색만** 바꾼다. `default`는 믹스인이 정한 회색
+// (`font-neutral-subtle`)이고, 나머지 넷은 알럿 제목과 같은 `font-system-*`이다. 시안 확인 완료.
+//
+// 아이콘 크기도 믹스인이 정한 16px(`icon-3`)이 시안 값이다 — 폼 안 헬퍼와 같다(시안 확인 완료).
+//
+// TODO(폼 통합, 적용 후 이 주석 삭제): 폼 컴포넌트 여섯 곳의 헬퍼를 이 클래스로 모은다.
+// 지금은 각자 그린다 — FoxInput · FoxTextArea · FoxEmail · FoxPhoneNumber · FoxAddress가
+// `__message`, FoxSelect가 `__hint`다. 모양은 `_form-field.scss`의 `message` 믹스인 한
+// 곳에서 나와 어긋나지는 않지만 마크업이 여섯 벌이다.
+//
+// 옮길 때 할 일:
+//   1) `FoxHelperText`에 `icon` 오버라이드 prop을 더한다 — 폼은 호출부가 준 아이콘을 쓰고
+//      계열 글리프를 쓰지 않는다.
+//   2) tsx의 `<p class="fox-X__message">`를 `<FoxHelperText className="fox-X__message" …>`로
+//      바꾼다. **클래스를 남기는 것이 요점이다** — 그래야 상태 색 규칙(`auto-states`의
+//      `:focus-within`·`:has([aria-invalid])`)이 선택자를 고치지 않고 그대로 먹는다.
+//   3) 각 컴포넌트 scss에서 `&__message { @include field.message; }`와
+//      `&__message-icon { … }` 블록을 지운다 — `.fox-helper-text`가 대신 준다.
+//   4) 아이콘은 `showIcon={Boolean(messageIcon)}`으로 넘겨 기존 동작(아이콘을 안 주면 안
+//      그린다)을 유지한다.
+//
+// ⚠️ 옮기면 **FoxSelect만 모양이 달라진다.** 진행 전에 확인받을 것:
+//   - 힌트 간격이 `gap 8px` · `align-items: center`에서 `4px` · `flex-start`로 바뀐다
+//     (helper-txt 시안 값이 이쪽이다).
+//   - 오류일 때 아이콘 색이 `icon-system-danger`(danger-50)에서, 글자색을 상속한
+//     `font-system-danger`(danger-60)로 한 단계 진해진다.
+//
+// 나머지 다섯은 값이 같아 화면이 바뀌지 않는다. 앱·다른 컴포넌트가 `__message`·`__hint`
+// 클래스를 직접 참조하는 곳은 없다(확인 완료).
+
+@use "@fox/styles/abstracts" as fox;
+@use "@fox/styles/form-field" as field;
+
+.fox-helper-text {
+  // 시안이 inline-flex다(폼 안에서는 flex) — 믹스인에 인자로 넘겨 한 번만 적는다.
+  @include field.message($display: inline-flex);
+
+  &__icon {
+    @include field.message-icon;
+  }
+
+  // ── 계열 ────────────────────────────────────────────────────────────────────
+  // 글자와 아이콘의 색이 **다르다** — 글자는 `font-system-*`, 아이콘은 한 단계 진한
+  // `icon-system-*-strong`이다(시안 확인). 아이콘 글리프가 `fill: currentColor`라 슬롯에
+  // 색을 주면 그대로 입는다.
+  //
+  // `default`만 예외다 — 글자도 아이콘도 믹스인이 정한 회색(`font-neutral-subtle`)을 함께
+  // 쓰므로 따로 덮을 것이 없다.
+  &--default {
+    // 시안 그대로 — 믹스인에 있는 값이 전부다.
+  }
+
+  &--information {
+    color: fox.color(font-system-information);
+  }
+
+  &--information &__icon {
+    color: fox.color(icon-system-information-strong);
+  }
+
+  &--success {
+    color: fox.color(font-system-success);
+  }
+
+  &--success &__icon {
+    color: fox.color(icon-system-success-strong);
+  }
+
+  &--warning {
+    color: fox.color(font-system-warning);
+  }
+
+  &--warning &__icon {
+    color: fox.color(icon-system-warning-strong);
+  }
+
+  &--danger {
+    color: fox.color(font-system-danger);
+  }
+
+  &--danger &__icon {
+    color: fox.color(icon-system-danger-strong);
+  }
+}
 
@fox/styles/_fox-snackbar-list.scss (added)
+++ @fox/styles/_fox-snackbar-list.scss
@@ -0,0 +1,38 @@
+// FoxSnackbarList — 시안: 통합관리자페이지 디자인시스템 Figma snackbar-list
+//
+// 스낵바가 여러 개일 때 세로로 쌓는 묶음이다. 배치만 갖는다 — 무엇이 몇 개 떠 있는지는
+// 호출부가 소유한다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-snackbar-list" role="group">
+//     …fox-snackbar…
+//     …fox-snackbar…
+//   </div>
+//
+// 자식 폭과 묶음 폭이 같은 640px이다 — 묶음이 자식을 늘이거나 줄이지 않고, `align-items`가
+// `flex-start`라 자식이 자기 폭을 그대로 갖는다(시안 지정).
+//
+// 토큰이 아닌 값:
+//   - ⚠️ `inline-size: 64rem`: **시안의 폭(640px)을 그대로 적용해 둔 것이다.** 자식
+//     `fox-snackbar`와 같은 값이고 근거도 같다 — 토큰 스케일에 640이 없다. 폭을 바꿔야 하는
+//     자리는 호출부가 `className`으로 덮는다.
+//   - display / flex-direction / align-items: 구조 규칙.
+//
+// ⚠️ 모바일에서는 자식과 같은 이유로 고정 폭을 푼다 — 640px을 그대로 두면 화면 밖으로 넘쳐
+// 잘린다(`fox-snackbar` 상단 주석 참고).
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-snackbar-list {
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: fox.gap(3);
+  inline-size: 64rem;
+
+  // 모바일에서는 부모 폭을 채운다. 자식(`fox-snackbar`)도 같은 규칙을 스스로 갖는다.
+  @include fox.mobile {
+    inline-size: 100%;
+  }
+}
 
@fox/styles/_fox-snackbar.scss (added)
+++ @fox/styles/_fox-snackbar.scss
@@ -0,0 +1,181 @@
+// FoxSnackbar — 시안: 통합관리자페이지 디자인시스템 Figma snackbar
+//
+// 흰 판 위에 글자(제목·본문)와 글자 버튼이 서고, 오른쪽 위에 닫기가 붙는다. 제목과 본문은
+// 각각 없을 수 있다 — 한쪽만 있으면 그 줄만 그린다. `FoxToast`와 형제지만
+// **누를 것이 있다** — 그래서 스스로 사라지지 않고 손이 닿을 때까지 남는다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-snackbar" role="status">
+//     <span class="fox-snackbar__icon">…아이콘 svg…</span>              <!-- 선택 -->
+//     <div class="fox-snackbar__content">
+//       <strong class="fox-snackbar__title">저장했습니다</strong>            <!-- 선택 -->
+//       <span class="fox-snackbar__message">3분 안에 되돌릴 수 있습니다.</span>  <!-- 선택 -->
+//     </div>
+//     <button class="fox-text-button fox-text-button--primary fox-text-button--md
+//                    fox-snackbar__action">실행 취소</button>          <!-- 선택 -->
+//     <button class="fox-snackbar__close" aria-label="닫기">…X svg…</button>  <!-- 선택 -->
+//   </div>
+//
+// 글자 버튼은 `FoxTextButton`의 primary·md를 그대로 쓴다 — 스낵바가 버튼 모양을 다시 정하지
+// 않고 `fox-snackbar__action`은 배치만 맡는다.
+//
+// 토큰이 아닌 값:
+//   - ⚠️ `inline-size` / `min-inline-size: 64rem`: **시안의 폭(640px)을 그대로 적용해 둔
+//     것이다.** Figma가 변수로 export하지 않았고 토큰 스케일에도 640이 없다. 폭을 바꿔야 하는
+//     자리는 호출부가 `className`으로 덮는다(`fox-toggle-quantity`·`fox-alert`와 같은 처리).
+//   - ⚠️ `padding-inline-end`가 64px이라 토큰이 없다. 닫기가 `right: 0`으로 오른쪽 끝에
+//     붙으므로(시안 지정) 이 여백은 **글자·버튼이 닫기 밑으로 들어가지 않게 비워 두는
+//     자리**다. 20px + 아이콘 24px + 20px = 64px로 떨어져서 그 조합으로 적는다
+//     (`padding(7)` + `icon(5)` + `padding(7)`).
+//   - line-height / letter-spacing: 시안 텍스트 스타일 heading/xsm의 값이나 Figma가 변수로
+//     export하지 않는다. -0.375px은 15px 기준이라 `-0.025em`으로 적는다(다른 컴포넌트와 같은
+//     근거).
+//   - box-sizing / position / display / flex: 구조 규칙.
+//
+// ⚠️ 그림자 색: 시안이 두 겹을 각각 `dark/alpha/black10`·`alpha/black10`으로 물려 놓았는데
+// 값이 같고(rgba(0,0,0,0.10)) 앞쪽 이름의 토큰은 export에 없다. 두 겹 모두 `alpha-black10`을
+// 쓴다 — `fox-toast`·`fox-select-option`과 같은 선택이다.
+//
+// ⚠️ 폭 세 값(640 / 640 / `grid(wrap-sm)`)은 **PC 기준이다.** 모바일에서 `grid(wrap-sm)`은
+// 320px인데 최소 폭 640px이 그것을 이겨 화면 밖으로 넘친다 — 실제로 잘렸다. 그래서 모바일에서는
+// 최소 폭을 풀고 판이 부모 폭을 채우게 둔다(아래 `fox.mobile` 블록).
+//
+// 시안이 모바일 스낵바의 폭을 따로 주면 그 값으로 바꾼다. 지금은 "화면을 넘지 않는다"만
+// 보장한다.
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-snackbar {
+  // 안쪽 여백이 바깥 폭을 늘리지 않게 한다 — 640px이 640px로 그려진다.
+  box-sizing: border-box;
+  // 닫기 버튼의 위치 기준이 된다.
+  position: relative;
+  display: flex;
+  align-items: center;
+  gap: fox.gap(5);
+  inline-size: 64rem;
+  min-inline-size: 64rem;
+  max-inline-size: fox.grid(wrap-sm);
+  padding-block: fox.padding(6);
+  padding-inline-start: fox.padding(7);
+  // 닫기 버튼이 앉을 자리(위 주석 참고).
+  padding-inline-end: calc(#{fox.padding(7)} + #{fox.icon(5)} + #{fox.padding(7)});
+  border: fox.border(1) solid fox.color(border-neutral-subtle);
+  border-radius: fox.radius(5);
+  background: fox.color(surface-neutral-default);
+
+  // 시안의 neutral/lv2 — 가까운 그림자와 먼 그림자 두 겹이다.
+  box-shadow:
+    fox.shadow(location1) fox.shadow(location1) fox.shadow(blur2) 0
+      fox.primitive(alpha-black10),
+    fox.shadow(location3) fox.shadow(location3) fox.shadow(blur3) 0
+      fox.primitive(alpha-black10);
+
+  // 모바일에서는 640px 고정을 풀고 부모 폭을 채운다(위 주석 참고). `min-inline-size`를 함께
+  // 풀지 않으면 640px이 그대로 남아 화면 밖으로 밀려 잘린다.
+  @include fox.mobile {
+    inline-size: 100%;
+    min-inline-size: 0;
+    max-inline-size: 100%;
+  }
+
+  // 글자 앞 아이콘. 글자가 길어 판이 높아져도 크기가 그대로여야 해서 줄어들지 않게 막는다.
+  //
+  // 색은 `icon-theme-primary`다(시안 확인). 글리프가 `fill: currentColor`라 슬롯에 색만
+  // 주면 그대로 입는다 — 굵기는 호출부가 넘기는 글리프가 정하고 시안은 `fill`이다.
+  //
+  // 크기는 시안 값 그대로 24px(`icon(5)`)이고 닫기 아이콘과 같다. 세로 정렬은 시안에 없어
+  // 판 규칙(가운데)을 그대로 따른다.
+  &__icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+    inline-size: fox.icon(5);
+    block-size: fox.icon(5);
+    aspect-ratio: 1 / 1;
+    color: fox.color(icon-theme-primary);
+  }
+
+  // 넘긴 SVG가 슬롯을 꽉 채운다 — 크기는 슬롯이 정하고 SVG는 따라온다.
+  &__icon > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  // 남는 폭을 전부 가져가 글자 버튼을 오른쪽 끝으로 민다. `min-inline-size: 0`이 없으면
+  // 긴 글이 줄바꿈되지 않고 판을 밀어 넓힌다(flex 자식의 기본 최소 크기가 내용 크기라서다).
+  //
+  // `flex: 1 0 0`으로 남는 폭을 전부 가져가 글자 버튼을 오른쪽 끝으로 민다. 제목과 본문
+  // 사이는 `gap(1)`(2px)이고 둘 다 왼쪽에 붙는다 — 시안 확인 완료(`FoxAlert`의 같은 자리와
+  // 같은 값이다).
+  &__content {
+    display: flex;
+    flex: 1 0 0;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: fox.gap(1);
+    min-inline-size: 0;
+  }
+
+  &__title {
+    align-self: stretch;
+    color: fox.color(font-neutral-strong);
+    font-family: fox.font-family(title);
+    font-size: fox.font-size(heading-xsm);
+    font-weight: fox.font-weight(bold);
+    line-height: 1.4;
+    letter-spacing: -0.025em;
+  }
+
+  &__message {
+    align-self: stretch;
+    color: fox.color(font-neutral-strong);
+    font-family: fox.font-family(body);
+    font-size: fox.font-size(body-md);
+    font-weight: fox.font-weight(regular);
+    line-height: 1.5;
+    letter-spacing: -0.025em;
+  }
+
+  // 글자 버튼은 자기 모양을 그대로 갖는다 — 여기서는 줄어들지만 않게 막는다.
+  &__action {
+    flex-shrink: 0;
+  }
+
+  // 오른쪽 끝에 붙는다(시안 지정 `right: 0`). 흐름에서 빠져 있어 글자·버튼 배치에 끼어들지
+  // 않고, 판의 오른쪽 여백(64px)이 그 아래로 글자가 들어가지 않게 막아 준다.
+  //
+  // 누르는 자리는 48px 정사각이고 아이콘은 24px이다 — 손가락이 닿는 넓이를 아이콘보다 크게
+  // 잡은 것이라, 여백을 줄이면 모바일에서 누르기 어려워진다.
+  //
+  // ⚠️ 시안이 이 칸에 `align-items: flex-end`를 준다. 가로로는 시작(왼쪽), 세로로는 끝(아래)에
+  // 붙어서 **아이콘이 48px 칸의 왼쪽 아래로 치우친다** — 오른쪽 끝에서 16px, 아래에서 8px이다.
+  // 시안 값을 그대로 옮긴 결과이니, 화면에서 가운데로 보여야 하면 `center`로 바꾼다.
+  //
+  // 세로는 판 위쪽에 붙는다(시안 지정 `top: 0`). 가운데 정렬이 아니라서, 본문이 늘어 판이
+  // 높아져도 닫기는 첫 줄 옆에 그대로 남는다.
+  &__close {
+    position: absolute;
+    inset-inline-end: 0;
+    inset-block-start: 0;
+    display: flex;
+    align-items: flex-end;
+    inline-size: fox.number(16);
+    block-size: fox.number(16);
+    aspect-ratio: 1 / 1;
+    padding: fox.padding(3);
+    border: none;
+    background: none;
+    color: fox.color(icon-neutral-default);
+    cursor: pointer;
+  }
+
+  // 아이콘은 24px 정사각으로 못박는다 — 칸이 48px이라 늘어나게 두면 안 된다.
+  &__close > * {
+    flex-shrink: 0;
+    inline-size: fox.icon(5);
+    block-size: fox.icon(5);
+    aspect-ratio: 1 / 1;
+  }
+}
 
@fox/styles/_fox-status-indicator.scss (added)
+++ @fox/styles/_fox-status-indicator.scss
@@ -0,0 +1,88 @@
+// FoxStatusIndicator — 시안: 통합관리자페이지 디자인시스템 Figma status-indicator
+//
+// 작은 점 하나와 글자로 상태를 알린다. 크기 수식어가 없다 — 시안에 하나뿐이다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <span class="fox-status-indicator fox-status-indicator--success">
+//     <span class="fox-status-indicator__dot"></span>
+//     <span class="fox-status-indicator__label">완료</span>
+//   </span>
+//
+// 점은 비어 있는 요소다 — 배경색만으로 그린다. 뜻은 글자가 담고 점은 장식이라
+// 마크업에서 `aria-hidden`이 붙는다.
+//
+// 계열은 **점의 배경과 글자색을 각각 다른 토큰으로** 바꾼다. 점은 `element-*`(칠하는 면),
+// 글자는 `font-*`(읽는 글자)다 — 같은 계열이라도 명도가 달라 시안이 둘을 나눠 준다.
+// 기본은 점이 `element-neutral-default`(회색 면), 글자가 `font-neutral-default`다.
+//
+// 토큰이 아닌 값:
+//   - line-height / letter-spacing: 시안 텍스트 스타일 label/md의 값이나 Figma가 변수로
+//     export하지 않는다. 시안의 -0.375px은 15px 기준이라 `-0.025em`으로 적는다
+//     (다른 컴포넌트와 같은 근거). line-height는 시안이 100%라 `1`이다 — 점과 글자가 한 줄에
+//     서는 조각이라 줄 간격을 두지 않는다.
+//   - display / align-items / aspect-ratio / flex-shrink: 구조 규칙.
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-status-indicator {
+  display: inline-flex;
+  align-items: center;
+  gap: fox.gap(2);
+
+  color: fox.color(font-neutral-default);
+  font-family: fox.font-family(body);
+  font-size: fox.font-size(label-md);
+  font-weight: fox.font-weight(regular);
+  line-height: 1;
+  letter-spacing: -0.025em;
+
+  // 6px 정원. 글자가 길어 줄이 좁아져도 찌그러지지 않게 크기를 못박는다.
+  &__dot {
+    flex-shrink: 0;
+    inline-size: fox.number(4);
+    block-size: fox.number(4);
+    aspect-ratio: 1 / 1;
+    border-radius: fox.radius(max);
+    background: fox.color(element-neutral-default);
+  }
+
+  // ── 계열 ────────────────────────────────────────────────────────────────────
+  // 글자색은 블록에서 상속되므로 루트에 주고, 점은 자기 배경을 따로 받는다.
+  //
+  // `default`는 블록 규칙이 곧 그 계열의 모양이라 따로 덮을 것이 없다.
+  &--default {
+    // 시안 그대로 — 블록에 있는 값이 전부다.
+  }
+
+  &--information {
+    color: fox.color(font-system-information);
+  }
+
+  &--information &__dot {
+    background: fox.color(element-system-information-strong);
+  }
+
+  &--success {
+    color: fox.color(font-system-success);
+  }
+
+  &--success &__dot {
+    background: fox.color(element-system-success-strong);
+  }
+
+  &--warning {
+    color: fox.color(font-system-warning);
+  }
+
+  &--warning &__dot {
+    background: fox.color(element-system-warning-strong);
+  }
+
+  &--danger {
+    color: fox.color(font-system-danger);
+  }
+
+  &--danger &__dot {
+    background: fox.color(element-system-danger-strong);
+  }
+}
 
@fox/styles/_fox-toast.scss (added)
+++ @fox/styles/_fox-toast.scss
@@ -0,0 +1,53 @@
+// FoxToast — 시안: 통합관리자페이지 디자인시스템 Figma toast
+//
+// 어두운 판 위에 한 줄짜리 알림을 얹는다. 크기 수식어가 없다 — 시안에 하나뿐이다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-toast" role="status">저장되었습니다.</div>
+//
+// 글자를 감싸는 엘리먼트를 두지 않는다 — 안에 들어가는 것이 글자 하나뿐이라 따로 규칙을
+// 걸 대상이 없다. 아이콘이나 버튼이 붙는 날이 오면 그때 `__label`을 만든다.
+//
+// **띄우는 위치는 여기서 정하지 않는다.** 시안이 조각의 모양만 주고, 화면 어디에 어떻게
+// 나타나는지는 쓰는 화면이 정한다(사용자 결정). 호출부가 `className`으로 `position: fixed`
+// 같은 배치를 준다.
+//
+// 배경·글자색이 `*-inverse` 토큰이다 — 라이트에서 어두운 판 + 흰 글자이고, 다크 모드에서는
+// 둘이 함께 뒤집혀 밝은 판 + 어두운 글자가 된다. 두 값을 한 쌍으로 골라야 대비가 유지된다.
+//
+// 토큰이 아닌 값(FoxButton·FoxTag와 동일한 근거로 승인된 항목):
+//   - line-height / letter-spacing: 시안 텍스트 스타일 body/md의 값이나 Figma가 변수로
+//     export하지 않는다. 시안의 -0.375px은 15px 기준이라 `-0.025em`으로 적는다 — px으로
+//     박으면 글자 크기가 모바일에서 13px으로 줄 때 비율이 어긋난다.
+//   - box-sizing / display / align-items: 구조 규칙.
+//
+// ⚠️ 그림자 색: 시안이 두 겹을 각각 `dark/alpha/black10`·`alpha/black10`으로 물려 놓았는데
+// 값이 같고(rgba(0,0,0,0.10)) 앞쪽 이름의 토큰은 export에 없다. 그래서 두 겹 모두
+// `alpha-black10`을 쓴다 — `fox-select-option`과 같은 선택이다(다크 모드에서도 검은 그림자).
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-toast {
+  // 안쪽 여백이 바깥 크기를 늘리지 않게 한다.
+  box-sizing: border-box;
+  display: inline-flex;
+  // 글자 하나뿐이라 지금은 티가 나지 않지만, 시안이 세로 가운데 정렬을 명시한다.
+  align-items: center;
+  padding: fox.padding(5) fox.padding(7);
+  border-radius: fox.radius(4);
+  background: fox.color(surface-neutral-default-inverse);
+
+  // 시안의 neutral/lv3 — 가까운 그림자와 먼 그림자 두 겹이다.
+  box-shadow:
+    fox.shadow(location2) fox.shadow(location2) fox.shadow(blur3) 0
+      fox.primitive(alpha-black10),
+    fox.shadow(location4) fox.shadow(location4) fox.shadow(blur4) 0
+      fox.primitive(alpha-black10);
+
+  color: fox.color(font-neutral-strong-inverse);
+  font-family: fox.font-family(body);
+  font-size: fox.font-size(body-md);
+  font-weight: fox.font-weight(regular);
+  line-height: 1.5;
+  letter-spacing: -0.025em;
+}
@fox/styles/components.scss
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
@@ -8,6 +8,7 @@
 @use "fox-button-group";
 @use "fox-button-panel";
 @use "fox-address";
+@use "fox-alert";
 @use "fox-certified";
 @use "fox-check-icon";
 @use "fox-checkbox";
@@ -19,6 +20,7 @@
 @use "fox-email";
 @use "fox-file-upload";
 @use "fox-form-label";
+@use "fox-helper-text";
 @use "fox-input";
 @use "fox-phone-number";
 @use "fox-radio";
@@ -49,7 +51,11 @@
 @use "fox-table-cell";
 @use "fox-tab";
 @use "fox-tab-item";
+@use "fox-snackbar";
+@use "fox-snackbar-list";
 @use "fox-spinner";
+@use "fox-status-indicator";
+@use "fox-toast";
 @use "fox-toggle-quantity";
 @use "fox-page-header";
 @use "fox-modal";
Add a comment
List