임동욱 임동욱 08-12
feat: FoxTextButton·FoxLinkButton·FoxIconButton 추가
Figma btn-txt(106:2468) · btn-lnk(107:207) · btn-ico(400:6312) ·
btn-ico-toggle(404:5247)을 토큰만으로 옮긴다. 컴파일된 CSS에 원시
hex/px/rem 리터럴은 0건이다.

btn-ico와 btn-ico-toggle은 계열·크기·모양이 같고 켜짐 상태만 달라
FoxIconButton 하나로 합쳤다. 켜짐은 `aria-pressed`로 표시해 스타일 훅과
접근성 표시를 겸한다. 시안에 켜짐과 hover가 겹치는 조합이 없어(상태가 단일
축이다) 켜진 동안에는 켜짐 색이 유지된다.

FoxLinkButton은 href가 있으면 , 없으면 
@27bbef71be2dd252b9f7349eccbe3ecb358343a3
@fox/README.md
--- @fox/README.md
+++ @fox/README.md
@@ -8,7 +8,7 @@
   styles/                SCSS — 여기가 디자인의 전부입니다
     index.scss           토큰 진입점 (앱이 한 번 @use)
     components.scss      공용 컴포넌트 스타일 묶음 진입점
-    _fox-button.scss     컴포넌트별 스타일 (개별 @use 가능)
+    _fox-*.scss          컴포넌트별 스타일 (개별 @use 가능)
     abstracts.scss       저작 진입점 — 함수·믹스인 (CSS 출력 0)
     tokens/              토큰 map = 단일 진실 공급원 (자동 생성)
     _root.scss           map → --fox-* 커스텀 프로퍼티
@@ -69,7 +69,7 @@
 클래스명이 `fox-` 접두사 + BEM이라 전역에 풀어도 충돌하지 않습니다. Vue·Svelte·서버
 템플릿·순수 HTML 어디서든 위 2~4번만 하고 마크업 계약을 지키면 같은 결과가 나옵니다.
 
-각 컴포넌트의 **마크업 계약은 해당 스타일 파일 상단 주석**에 있습니다. 버튼은 이렇습니다.
+각 컴포넌트의 **마크업 계약은 해당 스타일 파일 상단 주석**에 있습니다. 버튼 네 종류는 이렇습니다.
 
 ```html
 <button class="fox-button fox-button--primary fox-button--md">
@@ -77,10 +77,25 @@
   <span class="fox-button__label">버튼</span>
   <span class="fox-button__icon"><!-- svg (선택) --></span>
 </button>
+
+<button class="fox-text-button fox-text-button--primary fox-text-button--md">
+  <span class="fox-text-button__label">텍스트 버튼</span>
+</button>
+
+<a href="…" class="fox-link-button fox-link-button--primary fox-link-button--md">
+  <span class="fox-link-button__label">링크</span>
+  <span class="fox-link-button__icon"><!-- svg (선택, 항상 라벨 뒤) --></span>
+</a>
+
+<button class="fox-icon-button fox-icon-button--ghost fox-icon-button--md" aria-label="공유">
+  <span class="fox-icon-button__icon"><!-- svg --></span>
+</button>
 ```
 
-- 비활성은 네이티브 `disabled` 속성으로 표현합니다(별도 클래스 없음).
+- 비활성은 네이티브 `disabled` 속성으로 표현합니다(별도 클래스 없음). 앵커에는 `disabled`가
+  없으므로 `aria-disabled="true"`를 쓰고 `href`를 뺍니다.
 - 로딩은 앞 아이콘 자리에 `fox-button__spinner`를 두고 `disabled`를 함께 겁니다.
+- 토글은 `fox-icon-button`에 `aria-pressed="true|false"`를 더합니다.
 - 아이콘 SVG는 `currentColor`로 그려야 계열별 색이 적용됩니다. 크기는 슬롯이 정합니다.
 
 `core/`의 React 컴포넌트는 이 클래스를 조립해주는 **얇은 래퍼**일 뿐입니다 — 쓰지 않아도
 
@fox/core/components/fox-icon-button/fox-icon-button.tsx (added)
+++ @fox/core/components/fox-icon-button/fox-icon-button.tsx
@@ -0,0 +1,91 @@
+"use client";
+
+import type { ReactNode, Ref } from "react";
+import { cx } from "../../utils";
+
+export type FoxIconButtonType = "default" | "ghost" | "overlay";
+export type FoxIconButtonSize = "lg" | "md" | "sm" | "xsm";
+
+export interface FoxIconButtonProps {
+  type?: FoxIconButtonType;
+  size: FoxIconButtonSize;
+  /** 네이티브 `type` — prop의 `type`이 시각 계열을 쓰므로 분리했다. */
+  htmlType?: "button" | "submit" | "reset";
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 `type`을 추가한다. */
+  className?: string;
+  /** `currentColor`로 그린 SVG여야 계열별 색이 적용된다. 크기는 슬롯이 정한다. */
+  icon: ReactNode;
+  /** 글자가 보이지 않으므로 `aria-label`이 된다. 무엇을 하는 버튼인지 적는다. */
+  label: string;
+  /**
+   * 주면 토글 버튼이 된다(`aria-pressed`). 생략하면 일반 아이콘 버튼이다.
+   * 호출부가 소유한다 — 버튼이 스스로 뒤집지 않는다.
+   */
+  active?: boolean;
+  disabled?: boolean;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  onAction?: () => void;
+  /** 포인터가 올라오면 `true`, 벗어나면 `false`. */
+  onHoverChange?: (hovered: boolean) => void;
+  ref?: Ref<HTMLButtonElement>;
+}
+
+const TYPE_CLASS: Record<FoxIconButtonType, string> = {
+  default: "fox-icon-button--default",
+  ghost: "fox-icon-button--ghost",
+  overlay: "fox-icon-button--overlay",
+};
+
+const SIZE_CLASS: Record<FoxIconButtonSize, string> = {
+  lg: "fox-icon-button--lg",
+  md: "fox-icon-button--md",
+  sm: "fox-icon-button--sm",
+  xsm: "fox-icon-button--xsm",
+};
+
+/**
+ * @fox 아이콘 버튼. 상태를 갖지 않는다.
+ *
+ * `active`를 주면 토글로 동작한다 — 시안의 btn-ico와 btn-ico-toggle이 계열·크기·모양이
+ * 같고 켜짐 상태만 다르므로 한 컴포넌트로 둔다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxIconButton({
+  type = "default",
+  size,
+  htmlType = "button",
+  className,
+  icon,
+  label,
+  active,
+  disabled = false,
+  hidden = false,
+  onAction,
+  onHoverChange,
+  ref,
+}: FoxIconButtonProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <button
+      ref={ref}
+      type={htmlType}
+      className={cx("fox-icon-button", TYPE_CLASS[type], SIZE_CLASS[size], className)}
+      aria-label={label}
+      aria-pressed={active}
+      disabled={disabled}
+      onClick={disabled ? undefined : onAction}
+      onMouseEnter={onHoverChange ? () => onHoverChange(true) : undefined}
+      onMouseLeave={onHoverChange ? () => onHoverChange(false) : undefined}
+    >
+      <span className="fox-icon-button__icon" aria-hidden="true">
+        {icon}
+      </span>
+    </button>
+  );
+}
 
@fox/core/components/fox-icon-button/index.ts (added)
+++ @fox/core/components/fox-icon-button/index.ts
@@ -0,0 +1,6 @@
+export {
+  FoxIconButton,
+  type FoxIconButtonProps,
+  type FoxIconButtonType,
+  type FoxIconButtonSize,
+} from "./fox-icon-button";
 
@fox/core/components/fox-text-button/fox-text-button.tsx (added)
+++ @fox/core/components/fox-text-button/fox-text-button.tsx
@@ -0,0 +1,90 @@
+"use client";
+
+import type { ReactNode, Ref } from "react";
+import { cx } from "../../utils";
+
+export type FoxTextButtonType = "primary" | "secondary";
+export type FoxTextButtonSize = "lg" | "md" | "sm" | "xsm";
+
+export interface FoxTextButtonProps {
+  type?: FoxTextButtonType;
+  size: FoxTextButtonSize;
+  /** 네이티브 `type` — prop의 `type`이 시각 계열을 쓰므로 분리했다. */
+  htmlType?: "button" | "submit" | "reset";
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 `type`을 추가한다. */
+  className?: string;
+  /** `currentColor`로 그린 SVG여야 계열별 색이 적용된다. 크기는 슬롯이 정한다. */
+  leadingIcon?: ReactNode;
+  trailingIcon?: ReactNode;
+  label: string;
+  disabled?: boolean;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  onAction?: () => void;
+  /** 포인터가 올라오면 `true`, 벗어나면 `false`. */
+  onHoverChange?: (hovered: boolean) => void;
+  ref?: Ref<HTMLButtonElement>;
+}
+
+const TYPE_CLASS: Record<FoxTextButtonType, string> = {
+  primary: "fox-text-button--primary",
+  secondary: "fox-text-button--secondary",
+};
+
+const SIZE_CLASS: Record<FoxTextButtonSize, string> = {
+  lg: "fox-text-button--lg",
+  md: "fox-text-button--md",
+  sm: "fox-text-button--sm",
+  xsm: "fox-text-button--xsm",
+};
+
+/**
+ * @fox 텍스트 버튼. 배경 없이 글자만으로 동작을 나타낸다. 상태를 갖지 않는다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxTextButton({
+  type = "primary",
+  size,
+  htmlType = "button",
+  className,
+  leadingIcon,
+  trailingIcon,
+  label,
+  disabled = false,
+  hidden = false,
+  onAction,
+  onHoverChange,
+  ref,
+}: FoxTextButtonProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <button
+      ref={ref}
+      type={htmlType}
+      className={cx("fox-text-button", TYPE_CLASS[type], SIZE_CLASS[size], className)}
+      disabled={disabled}
+      onClick={disabled ? undefined : onAction}
+      onMouseEnter={onHoverChange ? () => onHoverChange(true) : undefined}
+      onMouseLeave={onHoverChange ? () => onHoverChange(false) : undefined}
+    >
+      {leadingIcon && (
+        <span className="fox-text-button__icon" aria-hidden="true">
+          {leadingIcon}
+        </span>
+      )}
+
+      <span className="fox-text-button__label">{label}</span>
+
+      {trailingIcon && (
+        <span className="fox-text-button__icon" aria-hidden="true">
+          {trailingIcon}
+        </span>
+      )}
+    </button>
+  );
+}
 
@fox/core/components/fox-text-button/index.ts (added)
+++ @fox/core/components/fox-text-button/index.ts
@@ -0,0 +1,6 @@
+export {
+  FoxTextButton,
+  type FoxTextButtonProps,
+  type FoxTextButtonType,
+  type FoxTextButtonSize,
+} from "./fox-text-button";
@fox/core/components/index.ts
--- @fox/core/components/index.ts
+++ @fox/core/components/index.ts
@@ -1,1 +1,4 @@
 export * from "./fox-button";
+export * from "./fox-text-button";
+export * from "./fox-link-button";
+export * from "./fox-icon-button";
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -1,5 +1,10 @@
-import type { ReactNode } from "react";
+"use client";
+
+import { useState, type ReactNode } from "react";
 import { FoxButton } from "../core/components/fox-button";
+import { FoxTextButton } from "../core/components/fox-text-button";
+import { FoxLinkButton } from "../core/components/fox-link-button";
+import { FoxIconButton } from "../core/components/fox-icon-button";
 
 export interface ComponentExample {
   /** 해시 링크에 쓰이는 kebab-case 식별자. */
@@ -16,6 +21,67 @@
     <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
       <path d="M6 3l5 5-5 5" />
     </svg>
+  );
+}
+
+function DemoStar() {
+  return (
+    <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
+      <path d="M8 1.6l1.9 4 4.4.6-3.2 3 .8 4.3L8 11.5l-3.9 2 .8-4.3-3.2-3 4.4-.6z" />
+    </svg>
+  );
+}
+
+// overlay 계열은 어두운 반투명 배경이라 밝은 바탕 위에서는 대비를 볼 수 없다.
+function OnPhoto({ children }: { children: ReactNode }) {
+  return (
+    <span
+      style={{
+        display: "inline-flex",
+        gap: "0.8rem",
+        padding: "0.8rem",
+        borderRadius: "0.8rem",
+        backgroundImage: "url(https://picsum.photos/seed/picsum/200/300)",
+        backgroundSize: "cover",
+      }}
+    >
+      {children}
+    </span>
+  );
+}
+
+// active는 호출부가 소유하므로 켜고 끄는 걸 보려면 예제가 상태를 들고 있어야 한다.
+function IconToggleDemo() {
+  const [on, setOn] = useState(false);
+  return (
+    <>
+      <FoxIconButton
+        size="md"
+        type="default"
+        label={on ? "즐겨찾기 해제" : "즐겨찾기"}
+        icon={<DemoStar />}
+        active={on}
+        onAction={() => setOn((prev) => !prev)}
+      />
+      <FoxIconButton
+        size="md"
+        type="ghost"
+        label={on ? "즐겨찾기 해제" : "즐겨찾기"}
+        icon={<DemoStar />}
+        active={on}
+        onAction={() => setOn((prev) => !prev)}
+      />
+      <OnPhoto>
+        <FoxIconButton
+          size="md"
+          type="overlay"
+          label={on ? "즐겨찾기 해제" : "즐겨찾기"}
+          icon={<DemoStar />}
+          active={on}
+          onAction={() => setOn((prev) => !prev)}
+        />
+      </OnPhoto>
+    </>
   );
 }
 
@@ -90,4 +156,199 @@
       },
     ],
   },
+  {
+    id: "fox-text-button",
+    name: "FoxTextButton",
+    description:
+      "Figma 시안(btn-txt, 컴포넌트 세트 106:2468)을 토큰만으로 옮겼습니다. 높이를 고정하지 않고 패딩과 line-height로만 크기를 잡습니다. lg만 상하 패딩과 간격이 한 단계 넓습니다.",
+    variants: [
+      {
+        label: "계열 (type) — primary는 굵기가 한 단계 더 굵다",
+        node: (
+          <>
+            <FoxTextButton size="md" type="primary" label="primary" />
+            <FoxTextButton size="md" type="secondary" label="secondary" />
+          </>
+        ),
+      },
+      {
+        label: "크기 (size)",
+        node: (
+          <>
+            <FoxTextButton size="lg" label="lg" />
+            <FoxTextButton size="md" label="md" />
+            <FoxTextButton size="sm" label="sm" />
+            <FoxTextButton size="xsm" label="xsm" />
+          </>
+        ),
+      },
+      {
+        label: "아이콘",
+        node: (
+          <>
+            <FoxTextButton size="md" label="leading" leadingIcon={<DemoIcon />} />
+            <FoxTextButton size="md" label="trailing" trailingIcon={<DemoIcon />} />
+            <FoxTextButton
+              size="md"
+              label="both"
+              leadingIcon={<DemoIcon />}
+              trailingIcon={<DemoIcon />}
+            />
+          </>
+        ),
+      },
+      {
+        label: "상태 — disabled",
+        node: (
+          <>
+            <FoxTextButton size="md" label="normal" onAction={() => undefined} />
+            <FoxTextButton size="md" label="disabled" disabled />
+            <FoxTextButton size="md" type="secondary" label="disabled (secondary)" disabled />
+          </>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-link-button",
+    name: "FoxLinkButton",
+    description:
+      "Figma 시안(btn-lnk, 컴포넌트 세트 107:207)을 토큰만으로 옮겼습니다. href를 주면 <a>, 없으면 <button>으로 렌더합니다. 방문 색은 브라우저가 실제 방문 기록으로만 켜므로 아래에서 링크를 한 번 눌러 돌아오면 색이 바뀝니다. tertiary만 평소에 밑줄이 없고 hover에서만 생깁니다.",
+    variants: [
+      {
+        label: "계열 (type) — hover하면 tertiary에 밑줄이 생긴다",
+        node: (
+          <>
+            <FoxLinkButton size="md" type="primary" label="primary" href="https://example.com" />
+            <FoxLinkButton
+              size="md"
+              type="secondary"
+              label="secondary"
+              href="https://example.com"
+            />
+            <FoxLinkButton size="md" type="tertiary" label="tertiary" href="https://example.com" />
+          </>
+        ),
+      },
+      {
+        label: "크기 (size)",
+        node: (
+          <>
+            <FoxLinkButton size="lg" label="lg" href="https://example.com" />
+            <FoxLinkButton size="md" label="md" href="https://example.com" />
+            <FoxLinkButton size="sm" label="sm" href="https://example.com" />
+          </>
+        ),
+      },
+      {
+        label: "아이콘 — 라벨 뒤에만 놓인다",
+        node: (
+          <>
+            <FoxLinkButton size="lg" label="lg" href="https://example.com" icon={<DemoIcon />} />
+            <FoxLinkButton size="md" label="md" href="https://example.com" icon={<DemoIcon />} />
+            <FoxLinkButton size="sm" label="sm" href="https://example.com" icon={<DemoIcon />} />
+          </>
+        ),
+      },
+      {
+        label: "href 없음 — <button>으로 렌더된다(방문 색이 켜지지 않는다)",
+        node: (
+          <>
+            <FoxLinkButton size="md" label="onAction만" onAction={() => undefined} />
+            <FoxLinkButton size="md" type="tertiary" label="tertiary" onAction={() => undefined} />
+          </>
+        ),
+      },
+      {
+        label: "상태 — disabled (앵커는 href가 빠져 포커스·클릭이 함께 사라진다)",
+        node: (
+          <>
+            <FoxLinkButton size="md" label="disabled (a)" href="https://example.com" disabled />
+            <FoxLinkButton size="md" label="disabled (button)" disabled />
+          </>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-icon-button",
+    name: "FoxIconButton",
+    description:
+      "Figma 시안(btn-ico 400:6312 + btn-ico-toggle 404:5247)을 토큰만으로 옮겼습니다. 두 시안이 계열·크기·모양이 같고 켜짐 상태만 달라 한 컴포넌트로 합쳤습니다 — active를 주면 토글(aria-pressed)이 됩니다. ghost는 테두리가 없는 대신 아이콘이 한 단계 큽니다.",
+    variants: [
+      {
+        label: "계열 (type) — overlay는 사진 위에서 봐야 한다",
+        node: (
+          <>
+            <FoxIconButton size="md" type="default" label="default" icon={<DemoIcon />} />
+            <FoxIconButton size="md" type="ghost" label="ghost" icon={<DemoIcon />} />
+            <OnPhoto>
+              <FoxIconButton size="md" type="overlay" label="overlay" icon={<DemoIcon />} />
+            </OnPhoto>
+          </>
+        ),
+      },
+      {
+        label: "크기 (size) — default 계열",
+        node: (
+          <>
+            <FoxIconButton size="lg" label="lg" icon={<DemoIcon />} />
+            <FoxIconButton size="md" label="md" icon={<DemoIcon />} />
+            <FoxIconButton size="sm" label="sm" icon={<DemoIcon />} />
+            <FoxIconButton size="xsm" label="xsm" icon={<DemoIcon />} />
+          </>
+        ),
+      },
+      {
+        label: "크기 (size) — ghost는 같은 상자에 아이콘만 한 단계 크다",
+        node: (
+          <>
+            <FoxIconButton size="lg" type="ghost" label="lg" icon={<DemoIcon />} />
+            <FoxIconButton size="md" type="ghost" label="md" icon={<DemoIcon />} />
+            <FoxIconButton size="sm" type="ghost" label="sm" icon={<DemoIcon />} />
+            <FoxIconButton size="xsm" type="ghost" label="xsm" icon={<DemoIcon />} />
+          </>
+        ),
+      },
+      {
+        label: "토글 — 눌러서 켜고 끈다 (active)",
+        node: <IconToggleDemo />,
+      },
+      {
+        label: "토글 켜짐 고정 — 계열별 색 비교",
+        node: (
+          <>
+            <FoxIconButton size="md" type="default" label="on" icon={<DemoStar />} active />
+            <FoxIconButton size="md" type="ghost" label="on" icon={<DemoStar />} active />
+            <OnPhoto>
+              <FoxIconButton size="md" type="overlay" label="on" icon={<DemoStar />} active />
+            </OnPhoto>
+          </>
+        ),
+      },
+      {
+        label: "상태 — disabled (켜짐보다 우선한다)",
+        node: (
+          <>
+            <FoxIconButton size="md" label="disabled" icon={<DemoIcon />} disabled />
+            <FoxIconButton size="md" type="ghost" label="disabled" icon={<DemoIcon />} disabled />
+            <FoxIconButton
+              size="md"
+              type="overlay"
+              label="disabled"
+              icon={<DemoIcon />}
+              disabled
+            />
+            <FoxIconButton
+              size="md"
+              label="active + disabled"
+              icon={<DemoStar />}
+              active
+              disabled
+            />
+          </>
+        ),
+      },
+    ],
+  },
 ];
 
@fox/styles/_fox-icon-button.scss (added)
+++ @fox/styles/_fox-icon-button.scss
@@ -0,0 +1,179 @@
+// FoxIconButton — 시안: 관리자페이지 Figma btn-ico (400:6312) + btn-ico-toggle (404:5247)
+//
+// 두 시안은 계열·크기·모양이 같고 토글에만 `active` 상태가 하나 더 있다. 그래서 한 블록으로
+// 두고, 켜짐은 `aria-pressed="true"`로 표시한다 — 토글 버튼의 표준 속성이라 스타일 훅과
+// 접근성 표시를 겸한다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <button class="fox-icon-button fox-icon-button--ghost fox-icon-button--md" aria-label="공유">
+//     <span class="fox-icon-button__icon">…svg…</span>
+//   </button>
+// 토글이면 `aria-pressed="true|false"`를 함께 준다. 아이콘만 있으므로 `aria-label`이 필수다.
+// 아이콘 SVG는 `currentColor`로 그려야 계열별 색이 적용된다.
+//
+// 시안에 `active`와 hover·pressed가 겹치는 조합이 없어(상태가 단일 축이다) 켜진 동안에는
+// 켜짐 색이 유지된다.
+//
+// 토큰이 아닌 값(FoxButton과 동일한 근거로 승인된 항목):
+//   - box-sizing / cursor / flex-shrink: 구조·상호작용 규칙.
+//   - `blur()`: backdrop-filter의 필수 함수 표기. 반지름은 토큰이다.
+
+@use "abstracts" as fox;
+
+.fox-icon-button {
+  box-sizing: border-box;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+  cursor: pointer;
+
+  &:disabled {
+    cursor: default;
+  }
+
+  &__icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+  }
+
+  &__icon > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  // ── 크기 (정사각형) ────────────────────────────────────────────────────────
+  // 아이콘 슬롯은 ghost만 한 단계 크다. 테두리가 없는 만큼 시안이 아이콘을 키웠다.
+  &--lg {
+    inline-size: fox.form(height-lg);
+    block-size: fox.form(height-lg);
+    border-radius: fox.form(radius-lg);
+
+    .fox-icon-button__icon {
+      inline-size: fox.icon(6);
+      block-size: fox.icon(6);
+    }
+
+    &.fox-icon-button--ghost .fox-icon-button__icon {
+      inline-size: fox.icon(7);
+      block-size: fox.icon(7);
+    }
+  }
+
+  &--md {
+    inline-size: fox.form(height-md);
+    block-size: fox.form(height-md);
+    border-radius: fox.form(radius-md);
+
+    .fox-icon-button__icon {
+      inline-size: fox.icon(5);
+      block-size: fox.icon(5);
+    }
+
+    &.fox-icon-button--ghost .fox-icon-button__icon {
+      inline-size: fox.icon(6);
+      block-size: fox.icon(6);
+    }
+  }
+
+  &--sm {
+    inline-size: fox.form(height-sm);
+    block-size: fox.form(height-sm);
+    border-radius: fox.form(radius-sm);
+
+    .fox-icon-button__icon {
+      inline-size: fox.icon(4);
+      block-size: fox.icon(4);
+    }
+
+    &.fox-icon-button--ghost .fox-icon-button__icon {
+      inline-size: fox.icon(5);
+      block-size: fox.icon(5);
+    }
+  }
+
+  &--xsm {
+    inline-size: fox.form(height-xsm);
+    block-size: fox.form(height-xsm);
+    border-radius: fox.form(radius-xsm);
+
+    .fox-icon-button__icon {
+      inline-size: fox.icon(3);
+      block-size: fox.icon(3);
+    }
+
+    &.fox-icon-button--ghost .fox-icon-button__icon {
+      inline-size: fox.icon(4);
+      block-size: fox.icon(4);
+    }
+  }
+
+  // ── 계열 ──────────────────────────────────────────────────────────────────
+  &--default {
+    border: fox.border(1) solid fox.color(button-default-border);
+    background: fox.color(button-default-surface);
+    color: fox.color(icon-neutral-default);
+
+    &:hover:not(:disabled) {
+      background: fox.color(button-default-surface-hover);
+    }
+
+    &:active:not(:disabled) {
+      background: fox.color(button-default-surface-pressed);
+    }
+  }
+
+  &--ghost {
+    background: fox.color(button-default-surface);
+    color: fox.color(icon-neutral-default);
+
+    &:hover:not(:disabled) {
+      background: fox.color(button-default-surface-hover);
+    }
+
+    &:active:not(:disabled) {
+      background: fox.color(button-default-surface-pressed);
+    }
+  }
+
+  &--overlay {
+    background: fox.color(alpha-default-inverse50);
+    backdrop-filter: blur(fox.backdrop(blur1));
+    color: fox.color(icon-neutral-strong-inverse);
+
+    &:hover:not(:disabled),
+    &:active:not(:disabled) {
+      background: fox.color(alpha-default-inverse75);
+    }
+  }
+
+  // ── 켜짐 (토글) ────────────────────────────────────────────────────────────
+  // 계열 hover·pressed 뒤에 와서 켜진 동안 색이 흔들리지 않는다.
+  &--default[aria-pressed="true"]:not(:disabled) {
+    border: none;
+    background: fox.color(button-primary-surface);
+    color: fox.color(icon-neutral-strong-inverse);
+  }
+
+  // ghost는 배경이 그대로 투명하고 아이콘 색만 바뀐다.
+  &--ghost[aria-pressed="true"]:not(:disabled) {
+    background: fox.color(button-default-surface);
+    color: fox.color(icon-theme-primary);
+  }
+
+  &--overlay[aria-pressed="true"]:not(:disabled) {
+    border: fox.border(1) solid fox.color(border-neutral-subtle);
+    background: fox.color(action-default);
+    color: fox.color(icon-theme-primary);
+  }
+}
+
+// 계열과 무관하게 같고 테두리·배경흐림이 없다.
+.fox-icon-button:disabled {
+  border: none;
+  background: fox.color(button-disabled-surface);
+  backdrop-filter: none;
+  color: fox.color(icon-neutral-disabled-strong);
+}
 
@fox/styles/_fox-text-button.scss (added)
+++ @fox/styles/_fox-text-button.scss
@@ -0,0 +1,145 @@
+// FoxTextButton — 시안: 관리자페이지 Figma btn-txt (컴포넌트 세트 106:2468)
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <button class="fox-text-button fox-text-button--primary fox-text-button--md">
+//     <span class="fox-text-button__icon">…svg…</span>   <!-- 선택 -->
+//     <span class="fox-text-button__label">버튼</span>
+//     <span class="fox-text-button__icon">…svg…</span>   <!-- 선택 -->
+//   </button>
+// 비활성은 네이티브 `disabled`. 아이콘 SVG는 `currentColor`로 그려야 계열별 색이 적용된다.
+//
+// 높이를 고정하지 않는다 — 시안이 패딩과 line-height로만 크기를 잡는다.
+//
+// 토큰이 아닌 값(FoxButton과 동일한 근거로 승인된 항목):
+//   - line-height / letter-spacing: 시안 텍스트 스타일 label/*-strong·em의 값이나 Figma가
+//     변수로 export하지 않는다.
+//   - box-sizing / cursor / white-space / flex-shrink: 구조·상호작용 규칙.
+//
+// 아이콘 크기는 시안이 변수를 걸지 않아 리터럴(20/16/16/12px)로 나온다. 값이 정확히
+// 일치하는 `icon-*`(모드 무관)에 매핑했다. `form-icon-*`은 PC에서 24/20/16/12라 어긋난다.
+
+@use "abstracts" as fox;
+
+.fox-text-button {
+  box-sizing: border-box;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+  cursor: pointer;
+  background: fox.color(button-text-surface);
+
+  font-family: fox.font-family(body);
+  line-height: 1;
+  letter-spacing: -0.025em;
+  white-space: nowrap;
+
+  &:disabled {
+    cursor: default;
+  }
+
+  &__label {
+    min-inline-size: 0;
+  }
+
+  &__icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+  }
+
+  &__icon > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  // ── 크기 ──────────────────────────────────────────────────────────────────
+  // lg만 상하 패딩과 간격이 한 단계 넓다. 나머지는 네 방향이 같은 값이다.
+  &--lg {
+    padding-block: fox.padding(3);
+    padding-inline: fox.padding(2);
+    border-radius: fox.form(radius-sm);
+    gap: fox.gap(2);
+    font-size: fox.font-size(label-lg);
+
+    .fox-text-button__icon {
+      inline-size: fox.icon(4);
+      block-size: fox.icon(4);
+    }
+  }
+
+  &--md {
+    padding: fox.padding(2);
+    border-radius: fox.form(radius-sm);
+    gap: fox.gap(1);
+    font-size: fox.font-size(label-md);
+
+    .fox-text-button__icon {
+      inline-size: fox.icon(3);
+      block-size: fox.icon(3);
+    }
+  }
+
+  &--sm {
+    padding: fox.padding(2);
+    border-radius: fox.form(radius-sm);
+    gap: fox.gap(1);
+    font-size: fox.font-size(label-sm);
+
+    .fox-text-button__icon {
+      inline-size: fox.icon(3);
+      block-size: fox.icon(3);
+    }
+  }
+
+  &--xsm {
+    padding: fox.padding(2);
+    border-radius: fox.form(radius-sm);
+    gap: fox.gap(1);
+    font-size: fox.font-size(label-xsm);
+
+    .fox-text-button__icon {
+      inline-size: fox.icon(2);
+      block-size: fox.icon(2);
+    }
+  }
+
+  // ── 계열 ──────────────────────────────────────────────────────────────────
+  // 계열은 색과 굵기만 바꾼다. 배경은 상태에 따라서만 달라진다.
+  &--primary {
+    font-weight: fox.font-weight(bold);
+    color: fox.color(font-theme-primary);
+
+    .fox-text-button__icon {
+      color: fox.color(icon-theme-primary-strong);
+    }
+  }
+
+  &--secondary {
+    font-weight: fox.font-weight(medium);
+    color: fox.color(font-neutral-default);
+
+    .fox-text-button__icon {
+      color: fox.color(icon-neutral-default);
+    }
+  }
+
+  &:hover:not(:disabled) {
+    background: fox.color(button-text-surface-hover);
+  }
+
+  &:active:not(:disabled) {
+    background: fox.color(button-text-surface-pressed);
+  }
+}
+
+// 계열과 무관하게 같다. 굵기는 계열 값을 유지한다(시안에서 disabled도 계열별로 다르다).
+.fox-text-button:disabled {
+  background: fox.color(button-text-surface);
+  color: fox.color(font-neutral-disabled);
+
+  .fox-text-button__icon {
+    color: fox.color(icon-neutral-disabled);
+  }
+}
@fox/styles/components.scss
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
@@ -2,3 +2,6 @@
 // 직접 @use 한다. 둘을 같이 써도 Sass가 모듈을 한 번만 로드해 중복되지 않는다.
 
 @use "fox-button";
+@use "fox-text-button";
+@use "fox-link-button";
+@use "fox-icon-button";
Add a comment
List