임동욱 임동욱 08-18
merge: FoxChip·FoxSelectText·FoxSegmentedControl·FoxToggleQuantity 계열 통합
카탈로그 등록 파일에서 충돌 일곱 구역이 났다. 양쪽이 같은 배열 끝에 항목을 붙였는데
git이 서로 다른 항목의 앞뒤를 맞물려 갈라 놓아, 구역별로 이어 붙이면 남의 항목 머리에
내 항목 꼬리가 붙는다.

그래서 충돌 파일을 손보지 않고 양쪽 원본을 꺼내 항목 단위로 다시 합쳤다. 중괄호를 세어
최상위 항목을 잘라내므로 JSX 안의 괄호나 타입 표기의 대괄호에 속지 않는다.

  - 겹치는 항목 없음: 우리 12개(breadcrumb·pagination·carousel 3·bullet-list·badge 3·
    table·tab·spinner), 상대 6개(chip·chip-area·select-text·segmented-control 2·
    toggle-quantity)
  - fox-select는 상대만 고쳤으므로(다중 선택 지원) 그쪽 판을 쓴다
  - fox-toggle-switch는 둘 다 고친 것으로 보였지만 뒤에 항목이 붙으며 닫는 괄호가 옮겨진
    것뿐이라 실제 충돌이 아니다

components.scss는 자동 병합되며 `@use "fox-toggle-switch"`가 두 번 들어가 Sass가 같은
네임스페이스 중복으로 컴파일을 멈췄다. 중복을 지웠다.

검증: 카탈로그 45개 항목이 모두 뜨고 양쪽 새 컴포넌트가 화면에 그려진다. 파티셜 50개가
빠짐없이 등록돼 있고 배럴도 폴더 수와 같다. sass 630규칙 · tsc · eslint 통과.

Co-Authored-By: Claude Opus 5 
@0f6c0e79d11fcf68f0c56e4021efb3308f8e88e6
 
@fox/core/components/fox-chip-area/fox-chip-area.tsx (added)
+++ @fox/core/components/fox-chip-area/fox-chip-area.tsx
@@ -0,0 +1,101 @@
+"use client";
+
+import {
+  Children,
+  Fragment,
+  cloneElement,
+  isValidElement,
+  type ReactElement,
+  type ReactNode,
+  type Ref,
+} from "react";
+import { cx } from "../../utils";
+import type { FoxChipSize } from "../fox-chip";
+
+export interface FoxChipAreaProps {
+  /**
+   * 자식 칩의 `size`를 전부 이 값으로 덮어쓴다. 넘기지 않으면 자식이 각자 정한다.
+   *
+   * `FoxTagList`와 달리 `type`은 덮어쓰지 않는다 — 한 줄에 계열이 섞이는 것이 칩에서는
+   * 정상이기 때문이다(예: `기간 ∨` 옆에 `전체`·`입금`). 크기만 통일한다.
+   */
+  size?: FoxChipSize;
+  /** 묶음을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */
+  label?: string;
+  /** 묶음 이름 역할을 하는 요소의 id. `label`보다 우선한다 — 화면의 글자와 어긋나지 않는다. */
+  labelledBy?: string;
+  /** `FoxChip`들. */
+  children?: ReactNode;
+  /** 배치 조정용. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+type Overrides = { size?: FoxChipSize };
+
+/**
+ * 자식 칩에 묶음 값을 강제로 덮어쓴다.
+ *
+ * - 호스트 요소(`div` 등)는 건드리지 않는다 — DOM에 없는 속성이 넘어가면 React가 경고한다.
+ * - `Children.map`은 프래그먼트 안으로 들어가지 않아 직접 내려간다. 그러지 않으면
+ *   `<>…</>`로 감싼 칩들이 덮어쓰기를 피해 가고 Fragment에 `size`가 붙어 경고가 난다.
+ *
+ * `FoxTagList`의 같은 함수와 규칙이 같다 — 다른 점은 덮어쓰는 값이 `size` 하나라는 것뿐이다.
+ */
+function overrideChildren(children: ReactNode, overrides: Overrides): ReactNode {
+  return Children.map(children, (child) => {
+    if (!isValidElement(child)) {
+      return child;
+    }
+
+    if (child.type === Fragment) {
+      const fragment = child as ReactElement<{ children?: ReactNode }>;
+      return overrideChildren(fragment.props.children, overrides);
+    }
+
+    if (typeof child.type === "string") {
+      return child;
+    }
+
+    return cloneElement(child as ReactElement<Record<string, unknown>>, overrides);
+  });
+}
+
+/**
+ * @fox 칩 묶음. 배치와 `size` 통일만 책임지고 상태를 갖지 않는다 — 무엇이 켜져 있는지는
+ * 각 칩이(또는 칩에게 값을 주는 호출부가) 소유한다.
+ *
+ * 방향 수식어가 없다 — 칩은 가로로 나란히 놓이는 것이 유일한 배치다.
+ *
+ * `role="radiogroup"`이 아니라 `role="group"`이다 — 하나만 고르는 묶음이 아니고, 각 칩이
+ * 독립적으로 Tab을 받는 버튼이다(`FoxTagList`와 같은 근거).
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxChipArea({
+  size,
+  label,
+  labelledBy,
+  children,
+  className,
+  ref,
+}: FoxChipAreaProps) {
+  // 값을 준 것만 넣는다 — `undefined`를 그대로 넘기면 자식이 스스로 정한 값을 지운다.
+  const overrides: Overrides = {};
+  if (size !== undefined) {
+    overrides.size = size;
+  }
+
+  return (
+    <div
+      ref={ref}
+      role="group"
+      aria-label={labelledBy ? undefined : label}
+      aria-labelledby={labelledBy}
+      className={cx("fox-chip-area", className)}
+    >
+      {overrideChildren(children, overrides)}
+    </div>
+  );
+}
 
@fox/core/components/fox-chip-area/index.ts (added)
+++ @fox/core/components/fox-chip-area/index.ts
@@ -0,0 +1,1 @@
+export { FoxChipArea, type FoxChipAreaProps } from "./fox-chip-area";
 
@fox/core/components/fox-chip-select-option/fox-chip-select-option.tsx (added)
+++ @fox/core/components/fox-chip-select-option/fox-chip-select-option.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import { useId, type ReactNode, type Ref } from "react";
+import { cx } from "../../utils";
+import { FoxCheckbox } from "../fox-checkbox";
+import { FoxCheckboxGroup } from "../fox-checkbox-group";
+import { FoxRadio } from "../fox-radio";
+import { FoxRadioGroup } from "../fox-radio-group";
+
+export type FoxChipSelectionMode = "single" | "multiple";
+
+/** 칩 목록의 항목 하나. 그리는 일은 `FoxCheckbox`·`FoxRadio`가 한다. */
+export interface FoxChipItem {
+  value: string;
+  label: ReactNode;
+  /** 이 항목만 고를 수 없게 한다. */
+  disabled?: boolean;
+}
+
+export interface FoxChipSelectOptionProps {
+  /** 목록을 여는 요소의 `aria-controls`가 가리킬 값. */
+  id?: string;
+  /** 목록에 이름을 주는 요소의 id — 보통 칩 자신이다. */
+  labelledBy?: string;
+  /**
+   * 하나만 고를지 여러 개를 고를지. 항목의 모양이 여기서 갈린다 —
+   * `single`은 라디오 목록, `multiple`은 체크박스 목록이다.
+   */
+  selectionMode?: FoxChipSelectionMode;
+  options: FoxChipItem[];
+  /**
+   * 라디오 묶음의 `name`. `single`에서만 쓰이고, 넘기지 않으면 자동으로 만든다.
+   *
+   * 같은 화면에 칩 목록이 둘 이상 열릴 일은 없지만, `name`이 겹치면 브라우저가 한 묶음으로
+   * 보고 서로를 끄기 때문에 자동 생성이라도 반드시 있어야 한다.
+   */
+  name?: string;
+  /** 지금 켜진 값들. `single`이면 길이가 0 또는 1이다 — 두 계열의 상태를 한 모양으로 받는다. */
+  values?: string[];
+  /** 항목을 켜거나 끌 때 호출된다. 무엇을 어떻게 반영할지는 받는 쪽이 정한다. */
+  onSelect?: (value: string) => void;
+  /**
+   * 모바일에만 보이는 확인 버튼의 글자. 버튼 자체는 언제나 렌더되고 **CSS가 데스크톱에서
+   * 숨긴다** — 조건부 렌더로 하면 창 폭이 바뀔 때마다 DOM이 들락거려서, 열린 채로 화면을
+   * 돌리면 포커스가 날아간다.
+   */
+  confirmLabel?: string;
+  /** 확인 버튼을 눌렀을 때. 보통 목록을 닫는다. */
+  onConfirm?: () => void;
+  /**
+   * 항목(체크박스·라디오)의 크기. 여는 칩의 크기를 그대로 받는다 — 두 컴포넌트의 크기
+   * 이름이 같고 글자 크기도 같은 값이라(lg 17px, md 15px) 그대로 이어진다. 칩만 크고
+   * 목록은 작으면 같은 필터인데 두 벌처럼 보인다.
+   */
+  size?: "lg" | "md";
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 모디파이어를 추가한다. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+/**
+ * @fox 칩의 `select`가 열렸을 때 뜨는 목록. 배치만 책임지고 상태를 갖지 않는다 —
+ * 열림 여부·고른 값은 전부 목록을 여는 칩이 소유한다.
+ *
+ * `FoxSelectOption`과 **다른 컴포넌트다.** 저쪽은 `role="listbox"` + `role="option"`이고
+ * 항목이 글자 한 줄이지만, 이쪽은 시안이 **체크박스·라디오 목록**이라 이미 있는
+ * `FoxCheckboxGroup`·`FoxRadioGroup`을 그대로 쓴다. 네이티브 입력이라 단일 선택의
+ * 화살표 이동·폼 전송을 브라우저가 처리하고, 항목 모양이 다른 화면의 체크박스와 어긋나지
+ * 않는다.
+ *
+ * 항목이 진짜 입력이라 `FoxSelectOption`처럼 `mousedown`을 막지 않는다 — 눌러서 그 입력에
+ * 포커스가 가는 것이 맞다. 목록이 닫히지 않는 이유는 포커스가 여는 쪽과 **같은 조상 안**에
+ * 머물러서다(칩의 focusout 처리가 그것을 본다).
+ *
+ * ⚠️ 스스로 위치를 잡지 못한다 — `position: absolute`라 위치 기준이 되는 조상이 필요하다.
+ * `FoxChip` 안에서는 `fox-chip-select`가 그 역할을 한다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxChipSelectOption({
+  id,
+  labelledBy,
+  selectionMode = "single",
+  options,
+  name,
+  values = [],
+  onSelect,
+  confirmLabel = "확인",
+  onConfirm,
+  size = "md",
+  className,
+  ref,
+}: FoxChipSelectOptionProps) {
+  const generatedName = useId();
+  const groupName = name ?? generatedName;
+
+  const isMultiple = selectionMode === "multiple";
+
+  return (
+    <div
+      ref={ref}
+      id={id}
+      // 목록 전체는 그냥 묶음이다. 단일 선택의 `radiogroup` 의미는 안쪽
+      // `FoxRadioGroup`이 갖는다 — 확인 버튼이 그 묶음 밖에 있어야 하기 때문이다.
+      role="group"
+      aria-labelledby={labelledBy}
+      className={cx("fox-chip-select-option", className)}
+    >
+      <div className="fox-chip-select-option__list">
+        {isMultiple ? (
+          <FoxCheckboxGroup
+            orientation="vertical"
+            labelledBy={labelledBy}
+            // 항목 사이 간격만 묶음 기본값과 다르다 — 스타일 파일의 같은 이름 규칙 참고.
+            className="fox-chip-select-option__group"
+          >
+            {options.map((option) => (
+              <FoxCheckbox
+                key={option.value}
+                size={size}
+                value={option.value}
+                label={option.label}
+                checked={values.includes(option.value)}
+                disabled={option.disabled}
+                onChange={() => onSelect?.(option.value)}
+              />
+            ))}
+          </FoxCheckboxGroup>
+        ) : (
+          <FoxRadioGroup
+            orientation="vertical"
+            name={groupName}
+            labelledBy={labelledBy}
+            className="fox-chip-select-option__group"
+          >
+            {options.map((option) => (
+              <FoxRadio
+                key={option.value}
+                size={size}
+                value={option.value}
+                label={option.label}
+                checked={values.includes(option.value)}
+                disabled={option.disabled}
+                onChange={() => onSelect?.(option.value)}
+              />
+            ))}
+          </FoxRadioGroup>
+        )}
+      </div>
+
+      {/*
+        확인 버튼은 **다중 선택에만** 있다. 이 버튼이 하는 일은 "고르기를 마쳤다"고 알려 목록을
+        닫는 것인데, 단일 선택은 하나 고르는 순간 목록이 닫히므로 버튼이 할 일이 없다 — 남겨
+        두면 눌러도 아무 일이 없는 버튼이 모바일 화면 아래를 차지한다.
+
+        값이 반영되는 시점은 플랫폼과 무관하게 같다(고르는 즉시). 모바일에서 달라지는 것은
+        **닫는 수단이 하나 더 생기는 것**뿐이고, 데스크톱에서는 CSS가 이 버튼을 숨긴다.
+      */}
+      {isMultiple ? (
+        <button
+          type="button"
+          className="fox-chip-select-option__confirm"
+          onClick={onConfirm}
+        >
+          {confirmLabel}
+        </button>
+      ) : null}
+    </div>
+  );
+}
 
@fox/core/components/fox-chip-select-option/index.ts (added)
+++ @fox/core/components/fox-chip-select-option/index.ts
@@ -0,0 +1,6 @@
+export {
+  FoxChipSelectOption,
+  type FoxChipItem,
+  type FoxChipSelectOptionProps,
+  type FoxChipSelectionMode,
+} from "./fox-chip-select-option";
 
@fox/core/components/fox-chip/fox-chip.tsx (added)
+++ @fox/core/components/fox-chip/fox-chip.tsx
@@ -0,0 +1,415 @@
+"use client";
+
+import {
+  useEffect,
+  useId,
+  useRef,
+  useState,
+  type KeyboardEvent,
+  type ReactNode,
+  type Ref,
+} from "react";
+import { cx } from "../../utils";
+import {
+  FoxChipSelectOption,
+  type FoxChipItem,
+  type FoxChipSelectionMode,
+} from "../fox-chip-select-option";
+
+export type FoxChipType = "check" | "select" | "action";
+export type FoxChipSize = "lg" | "md";
+
+// 목록이 소유하는 타입이라 그쪽에 두고 여기서 다시 내보낸다 — 호출부는 칩만 알면 된다.
+export type { FoxChipItem, FoxChipSelectionMode };
+
+export interface FoxChipProps {
+  /**
+   * 아이콘의 유무와 누를 때 벌어지는 일을 정한다.
+   *
+   * - `check`: 켜고 끄는 조각. **켜졌을 때만** 글자 앞에 체크 아이콘이 붙는다.
+   * - `select`: 눌러서 목록을 여는 조각. 글자 뒤에 아래 화살표가 늘 붙고, 글자 앞
+   *   아이콘은 `icon`을 넘긴 때만 나온다. 값을 고르면 켜짐으로 보인다.
+   * - `action`: 아이콘 없이 켜고 끄는 조각. 상태별 모양은 `check`와 같다.
+   */
+  type?: FoxChipType;
+  size?: FoxChipSize;
+  /**
+   * 조각에 보이는 글자.
+   *
+   * `select`·단일 선택에서는 아무것도 고르지 않았을 때의 문구다 — 값을 고르면 그 옵션의
+   * 라벨이 대신 보인다(고른 것이 무엇인지가 조각 위에 남아야 필터로 쓸 수 있다).
+   *
+   * ⚠️ 다중 선택에서는 언제나 이 글자가 그대로 보인다. 고른 것이 여럿이라 하나를 골라
+   * 보여 줄 수 없고, 개수를 붙이는 형식(`기간 2`)은 시안에 없어 임의로 만들지 않았다.
+   * 켜짐(남색 채움)이 "무언가 걸려 있다"는 표시를 대신한다. **확인 필요.**
+   */
+  label: ReactNode;
+  /**
+   * 보이는 글자를 대신할 읽어 줄 이름. 넘기지 않으면 글자가 그대로 이름이 된다.
+   *
+   * 조각 여러 개가 같은 글자를 쓰는 자리에 적어 준다 — 예를 들어 기간·지역 필터가 나란히
+   * 있고 둘 다 "전체"라면, 소리로는 구분되지 않는다.
+   */
+  ariaLabel?: string;
+  disabled?: boolean;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+
+  // ── check·action 전용 ────────────────────────────────────────────────────────
+  /** 주면 제어 컴포넌트가 된다 — 켜짐 여부를 호출부가 소유하고 조각이 스스로 바꾸지 않는다. */
+  checked?: boolean;
+  /** 비제어로 쓸 때의 초기 켜짐 여부. `checked`를 주면 무시된다. */
+  defaultChecked?: boolean;
+  /**
+   * 켜지거나 꺼질 때 호출된다.
+   *
+   * ⚠️ 이름이 `onChange`가 아니다. 이 패키지의 기준은 "네이티브 요소를 감싸면 `onChange`,
+   * 감싸는 요소가 없는 커스텀 위젯이면 `on…Change`"인데, 이 조각은 네이티브
+   * `<input type="checkbox">`가 아니라 `aria-pressed`를 든 버튼이라 뒤쪽이다
+   * (`FoxSelect.onValueChange`와 같은 이유).
+   */
+  onCheckedChange?: (checked: boolean) => void;
+
+  // ── select 전용 ─────────────────────────────────────────────────────────────
+  /** 글자 앞에 붙는 아이콘. 넘기지 않으면 자리를 만들지 않는다. `select`에서만 쓴다. */
+  icon?: ReactNode;
+  /** 열었을 때 보여 줄 목록. 비어 있으면 목록을 열지 않는다. */
+  options?: FoxChipItem[];
+  /**
+   * 하나만 고를지 여러 개를 고를지 정한다. 값 prop이 계열마다 다르다 —
+   * `single`은 `value`·`defaultValue`·`onValueChange`(문자열), `multiple`은
+   * `values`·`defaultValues`·`onValuesChange`(문자열 배열)를 쓴다.
+   *
+   * 목록 항목의 모양도 여기서 갈린다(단일은 라디오, 다중은 체크박스). 고른 뒤 닫히는
+   * 규칙도 마찬가지다 — 컴포넌트 주석의 "닫히는 규칙" 참고.
+   */
+  selectionMode?: FoxChipSelectionMode;
+  /** 단일 선택에서만 쓴다. 주면 제어 컴포넌트가 된다 — 고른 값을 호출부가 소유한다. */
+  value?: string;
+  /** 단일 선택에서만 쓴다. 비제어일 때의 초기 값. `value`를 주면 무시된다. */
+  defaultValue?: string;
+  /** 단일 선택에서만 쓴다. 값이 바뀔 때 호출된다. */
+  onValueChange?: (value: string) => void;
+  /** 다중 선택에서만 쓴다. 주면 제어 컴포넌트가 된다. */
+  values?: string[];
+  /** 다중 선택에서만 쓴다. 비제어일 때의 초기 값들. `values`를 주면 무시된다. */
+  defaultValues?: string[];
+  /** 다중 선택에서만 쓴다. 켜고 끌 때마다 **바뀐 뒤의 전체 목록**이 넘어온다. */
+  onValuesChange?: (values: string[]) => void;
+  /** 모바일에만 보이는 확인 버튼의 글자. */
+  confirmLabel?: string;
+
+  id?: string;
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 `type`이나 모디파이어를 추가한다. */
+  className?: string;
+  /** 조각(버튼)을 가리킨다 — 호출부가 포커스를 옮길 때 쓴다. */
+  ref?: Ref<HTMLButtonElement>;
+}
+
+/** `Record`로 고정해 계열을 추가하면 항목 누락이 타입 에러가 되게 한다. */
+const TYPE_CLASS: Record<FoxChipType, string> = {
+  check: "fox-chip--check",
+  select: "fox-chip--select",
+  action: "fox-chip--action",
+};
+
+const SIZE_CLASS: Record<FoxChipSize, string> = {
+  lg: "fox-chip--lg",
+  md: "fox-chip--md",
+};
+
+/**
+ * @fox 칩. 알약보다 각진 작은 조각이고 **켜짐 여부를 갖는다** — 이것이 `FoxTag`와의 차이다.
+ * 태그는 눌러서 사라지거나 어디로 넘어가는 조각이라 상태가 남지 않고, 칩은 누른 결과가
+ * 자기 자신에게 남는다.
+ *
+ * 켜짐은 계열에 따라 다른 것에서 온다: `check`·`action`은 눌러서 직접 켜고 끄고,
+ * `select`는 값을 고르면 켜진 것으로 보인다. 어느 쪽이든 모양 규칙은 한 벌이다.
+ *
+ * `check`·`action`은 `aria-pressed`를 든 버튼이다 — 체크박스가 아니다. 폼으로 값을
+ * 보내야 하면 호출부가 `<input type="hidden">`을 함께 놓는다(칩은 여러 개가 한 묶음으로
+ * 하나의 값을 이루는 자리에 쓰여서, 이름 한 개를 칩 하나에 묶을 수 없다).
+ *
+ * `select`는 `FoxSelect`와 달리 **콤보박스가 아니다.** 열리는 `FoxChipSelectOption`이
+ * listbox가 아니라 체크박스·라디오 목록이라, 칩은 그것을 여닫는 버튼(`aria-expanded`)일
+ * 뿐이고 항목 이동은 네이티브 입력이 알아서 한다. `aria-activedescendant`로 가상 커서를
+ * 옮기던 규칙이 여기에는 없다.
+ *
+ * ── 닫히는 규칙 (`select`) ───────────────────────────────────────────────────
+ *
+ * `selectionMode`가 정한다. 데스크톱 기준이다.
+ *
+ * - `single`: 하나를 고르면 **그 자리에서 반영되고 목록이 닫힌다.** 더 고를 것이 없으니
+ *   닫지 않을 이유가 없다.
+ * - `multiple`: 켜고 꺼도 **목록이 열린 채로 남는다.** 여러 개를 연달아 고르는 것이 목적이라
+ *   하나 누를 때마다 닫히면 쓸 수 없다. 닫는 것은 **바깥을 누르는 것**이고(Escape·Tab으로
+ *   빠져나가는 것도 같다), 그때까지의 체크는 이미 `onValuesChange`로 반영돼 있다 — 닫기가
+ *   곧 확정이 아니라서 취소 개념이 없다.
+ *
+ * 모바일에는 목록 맨 아래에 확인 버튼이 붙는다(시안 주석 "모바일만 노출"). 그 버튼은 값을
+ * 확정하는 것이 아니라 **닫기만 한다** — 위와 같은 이유로 이미 반영돼 있기 때문이다.
+ * 데스크톱에서는 CSS가 그 버튼을 숨긴다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxChip({
+  type = "check",
+  size = "lg",
+  label,
+  ariaLabel,
+  disabled = false,
+  hidden = false,
+  checked,
+  defaultChecked = false,
+  onCheckedChange,
+  icon,
+  options,
+  selectionMode = "single",
+  value,
+  defaultValue,
+  onValueChange,
+  values,
+  defaultValues,
+  onValuesChange,
+  confirmLabel,
+  id,
+  className,
+  ref,
+}: FoxChipProps) {
+  const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);
+  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? "");
+  const [uncontrolledValues, setUncontrolledValues] = useState<string[]>(defaultValues ?? []);
+  const [open, setOpen] = useState(false);
+  const rootRef = useRef<HTMLDivElement>(null);
+  const triggerRef = useRef<HTMLButtonElement>(null);
+
+  const isSelect = type === "select";
+  const isMultiple = selectionMode === "multiple";
+  const items = options ?? [];
+
+  const currentValue = value ?? uncontrolledValue;
+  const currentValues = values ?? uncontrolledValues;
+
+  // 두 계열의 상태를 한 모양(배열)으로 모아 목록에 넘긴다 — 목록은 계열마다 다른 prop을
+  // 알 필요가 없다.
+  const selectedValues = isMultiple
+    ? currentValues
+    : currentValue
+      ? [currentValue]
+      : [];
+
+  const selectedItem = !isMultiple
+    ? items.find((item) => item.value === currentValue)
+    : undefined;
+
+  const generatedId = useId();
+  const chipId = id ?? generatedId;
+  const listId = `${chipId}-list`;
+
+  // 켜짐의 출처가 계열마다 다르다. select는 고른 값이 하나라도 있으면 켜진 것이고(다중에서도
+  // 개수와 무관하게 켜짐/꺼짐 둘뿐이다), 나머지는 호출부가 준 `checked`(없으면 내부 상태)다.
+  const isChecked = isSelect ? selectedValues.length > 0 : (checked ?? uncontrolledChecked);
+
+  useEffect(() => {
+    if (!open) return;
+    function handlePointerDown(event: PointerEvent) {
+      if (!rootRef.current?.contains(event.target as Node)) {
+        setOpen(false);
+      }
+    }
+    // 포인터만 보면 Tab으로 빠져나갈 때 목록이 열린 채 남는다. 포커스가 아무 데도 가지
+    // 않은 경우(relatedTarget이 null)는 위 pointerdown이 이미 처리한다.
+    //
+    // 목록 안의 체크박스·라디오로 포커스가 옮겨 가는 것은 닫기가 아니다 — 그것들도
+    // `rootRef` 안이라 아래 조건에 걸리지 않는다.
+    function handleFocusOut(event: FocusEvent) {
+      const next = event.relatedTarget as Node | null;
+      if (next && !rootRef.current?.contains(next)) {
+        setOpen(false);
+      }
+    }
+    document.addEventListener("pointerdown", handlePointerDown);
+    const root = rootRef.current;
+    root?.addEventListener("focusout", handleFocusOut);
+    return () => {
+      document.removeEventListener("pointerdown", handlePointerDown);
+      root?.removeEventListener("focusout", handleFocusOut);
+    };
+  }, [open]);
+
+  function toggle() {
+    const next = !isChecked;
+    if (checked === undefined) setUncontrolledChecked(next);
+    onCheckedChange?.(next);
+  }
+
+  /**
+   * 항목 하나를 고른다. 닫기까지 여기서 갈린다 — 컴포넌트 주석의 "닫히는 규칙" 참고.
+   *
+   * 제어 컴포넌트일 때는 내부 값을 건드리지 않는다 — 건드리면 호출부가 값을 거부해도
+   * 내부에만 반영되어 둘이 조용히 갈라진다.
+   */
+  function select(next: string) {
+    if (isMultiple) {
+      // 이미 켜져 있으면 끈다. 순서는 `options`가 정한 순서를 따라가게 다시 추린다 —
+      // 누른 순서대로 쌓으면 같은 조합인데도 배열이 달라져 비교·저장이 흔들린다.
+      const nextValues = currentValues.includes(next)
+        ? currentValues.filter((item) => item !== next)
+        : items
+            .filter((item) => item.value === next || currentValues.includes(item.value))
+            .map((item) => item.value);
+
+      if (values === undefined) setUncontrolledValues(nextValues);
+      onValuesChange?.(nextValues);
+      // 목록을 닫지 않는다. 여러 개를 연달아 고르는 것이 목적이라, 하나 누를 때마다 닫히면
+      // 쓸 수 없다. 닫는 것은 바깥 클릭·Escape·Tab이고 그건 위 effect와 키 처리가 맡는다.
+      return;
+    }
+
+    if (value === undefined) setUncontrolledValue(next);
+    onValueChange?.(next);
+    // 단일 선택은 고르는 순간 할 일이 끝나므로 닫는다.
+    close();
+  }
+
+  function close() {
+    setOpen(false);
+    // 목록 안에 포커스가 있었다면 갈 곳이 사라진다 — 여는 버튼으로 돌려놓는다.
+    triggerRef.current?.focus();
+  }
+
+  function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {
+    if (disabled || !isSelect) return;
+
+    if (!open) {
+      if (event.key === "ArrowDown") {
+        event.preventDefault();
+        setOpen(true);
+      }
+      // Enter·Space는 막지 않는다 — 네이티브 버튼의 클릭이 그대로 열어 준다.
+      return;
+    }
+
+    if (event.key === "Escape") {
+      event.preventDefault();
+      close();
+    }
+  }
+
+  // 훅을 전부 부른 뒤에 빠져나간다 — 위쪽에서 반환하면 훅 호출 순서가 깨진다.
+  if (hidden) {
+    return null;
+  }
+
+  // 호출부 ref와 내부 ref를 함께 채운다 — 목록을 닫을 때 포커스를 돌려놓으려면 안에서도
+  // 버튼을 쥐어야 한다.
+  const attachTrigger = (node: HTMLButtonElement | null) => {
+    triggerRef.current = node;
+    if (typeof ref === "function") {
+      ref(node);
+    } else if (ref) {
+      (ref as { current: HTMLButtonElement | null }).current = node;
+    }
+  };
+
+  const chip = (
+    <button
+      ref={isSelect ? attachTrigger : ref}
+      type="button"
+      id={chipId}
+      disabled={disabled}
+      aria-label={ariaLabel}
+      // select는 목록을 여닫는 버튼이다 — listbox가 아니라 체크박스·라디오 묶음을 열므로
+      // `role="combobox"`가 아니라 그냥 버튼 + `aria-expanded`다.
+      aria-expanded={isSelect ? open : undefined}
+      // 목록은 열렸을 때만 렌더되므로, 닫힌 동안 없는 id를 가리키지 않게 한다.
+      aria-controls={isSelect && open ? listId : undefined}
+      aria-pressed={isSelect ? undefined : isChecked}
+      className={cx(
+        "fox-chip",
+        TYPE_CLASS[type],
+        SIZE_CLASS[size],
+        isChecked && "fox-chip--checked",
+        // 목록을 감싸는 자리가 따로 있어서, select는 바깥 div가 className을 받는다.
+        !isSelect && className
+      )}
+      onClick={isSelect ? () => setOpen((current) => !current) : toggle}
+      onKeyDown={handleKeyDown}
+    >
+      {/* 앞 아이콘. check는 켜졌을 때만 체크를, select는 호출부가 준 것을 놓는다. */}
+      {type === "check" && isChecked ? (
+        <span className="fox-chip__icon" aria-hidden="true">
+          <CheckMark />
+        </span>
+      ) : null}
+      {isSelect && icon ? (
+        <span className="fox-chip__icon" aria-hidden="true">
+          {icon}
+        </span>
+      ) : null}
+
+      <span className="fox-chip__label">{selectedItem?.label ?? label}</span>
+
+      {isSelect ? (
+        <span className="fox-chip__icon fox-chip__arrow" aria-hidden="true">
+          <ChevronMark />
+        </span>
+      ) : null}
+    </button>
+  );
+
+  if (!isSelect) {
+    return chip;
+  }
+
+  // `FoxChipSelectOption`은 `position: absolute`라 위치 기준이 되는 조상이 필요하다.
+  return (
+    <div ref={rootRef} className={cx("fox-chip-select", className)}>
+      {chip}
+      {open && items.length > 0 ? (
+        <FoxChipSelectOption
+          id={listId}
+          labelledBy={chipId}
+          // 칩의 크기를 그대로 물려준다 — 크기 이름이 같고, 항목의 글자 크기도 칩과 같은
+          // 값으로 떨어진다(lg 17px, md 15px).
+          size={size}
+          selectionMode={selectionMode}
+          options={items}
+          values={selectedValues}
+          onSelect={select}
+          confirmLabel={confirmLabel}
+          onConfirm={close}
+        />
+      ) : null}
+    </div>
+  );
+}
+
+// 시안 아이콘 둘. `fill`을 시안의 리터럴(흰색·#6D7882) 대신 `currentColor`로 두면 색을
+// 상태 규칙이 정한다 — 남색 배경 위에서 흰색으로, 비활성에서 흐린 회색으로 따라간다.
+// `width`·`height`를 쓰지 않는다 — 크기는 슬롯이 정하고 SVG는 100%로 따라온다.
+
+// check 계열의 체크 표시.
+function CheckMark() {
+  return (
+    <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
+      <path
+        d="M14.5306 5.03057L6.5306 13.0306C6.46092 13.1005 6.37813 13.156 6.28696 13.1938C6.1958 13.2317 6.09806 13.2512 5.99935 13.2512C5.90064 13.2512 5.8029 13.2317 5.71173 13.1938C5.62057 13.156 5.53778 13.1005 5.4681 13.0306L1.9681 9.53057C1.89833 9.4608 1.84299 9.37798 1.80524 9.28683C1.76748 9.19568 1.74805 9.09798 1.74805 8.99932C1.74805 8.90066 1.76748 8.80296 1.80524 8.71181C1.84299 8.62066 1.89833 8.53783 1.9681 8.46807C2.03786 8.3983 2.12069 8.34296 2.21184 8.30521C2.30299 8.26745 2.40069 8.24802 2.49935 8.24802C2.59801 8.24802 2.69571 8.26745 2.78686 8.30521C2.87801 8.34296 2.96083 8.3983 3.0306 8.46807L5.99997 11.4374L13.4693 3.96932C13.6102 3.82842 13.8013 3.74927 14.0006 3.74927C14.1999 3.74927 14.391 3.82842 14.5318 3.96932C14.6727 4.11021 14.7519 4.30131 14.7519 4.50057C14.7519 4.69983 14.6727 4.89092 14.5318 5.03182L14.5306 5.03057Z"
+        fill="currentColor"
+      />
+    </svg>
+  );
+}
+
+// select 계열의 아래 화살표.
+function ChevronMark() {
+  return (
+    <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
+      <path
+        d="M13.5306 6.53061L8.5306 11.5306C8.46092 11.6005 8.37813 11.656 8.28696 11.6939C8.1958 11.7317 8.09806 11.7512 7.99935 11.7512C7.90064 11.7512 7.8029 11.7317 7.71173 11.6939C7.62057 11.656 7.53778 11.6005 7.4681 11.5306L2.4681 6.53061C2.3272 6.38972 2.24805 6.19862 2.24805 5.99936C2.24805 5.80011 2.3272 5.60901 2.4681 5.46811C2.60899 5.32722 2.80009 5.24806 2.99935 5.24806C3.19861 5.24806 3.3897 5.32722 3.5306 5.46811L7.99997 9.93749L12.4693 5.46749C12.6102 5.32659 12.8013 5.24744 13.0006 5.24744C13.1999 5.24744 13.391 5.32659 13.5318 5.46749C13.6727 5.60838 13.7519 5.79948 13.7519 5.99874C13.7519 6.198 13.6727 6.38909 13.5318 6.52999L13.5306 6.53061Z"
+        fill="currentColor"
+      />
+    </svg>
+  );
+}
 
@fox/core/components/fox-chip/index.ts (added)
+++ @fox/core/components/fox-chip/index.ts
@@ -0,0 +1,7 @@
+export {
+  FoxChip,
+  type FoxChipItem,
+  type FoxChipProps,
+  type FoxChipSize,
+  type FoxChipType,
+} from "./fox-chip";
 
@fox/core/components/fox-segmented-control-item/fox-segmented-control-item.tsx (added)
+++ @fox/core/components/fox-segmented-control-item/fox-segmented-control-item.tsx
@@ -0,0 +1,92 @@
+"use client";
+
+import { type ChangeEvent, type ComponentPropsWithRef, type ReactNode } from "react";
+import { cx } from "../../utils";
+
+export type FoxSegmentedControlItemSize = "lg" | "md" | "sm" | "xsm";
+
+/**
+ * 네이티브 `<input type="radio">`가 받는 것을 그대로 넘겨받는다 — name·value·checked·
+ * defaultChecked·disabled·required·form 등. 의미가 겹치는 것만 걷어내고 아래에서 다시 정의한다.
+ */
+type NativeRadioProps = Omit<
+  ComponentPropsWithRef<"input">,
+  "type" | "size" | "className" | "children" | "hidden" | "onChange"
+>;
+
+export interface FoxSegmentedControlItemProps extends NativeRadioProps {
+  size?: FoxSegmentedControlItemSize;
+  label: ReactNode;
+  /** 글자 앞에 붙는 아이콘. 넘기지 않으면 자리를 만들지 않는다. */
+  icon?: ReactNode;
+  /**
+   * 이 칸이 선택될 때 부른다. 값이 첫 인자다 — 네이티브 `onChange`를 `Omit`으로 걷어내고
+   * 그 자리를 대신한다(`FoxRadio`와 같은 방식).
+   */
+  onChange?: (value: string, event: ChangeEvent<HTMLInputElement>) => void;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 모디파이어를 추가한다. */
+  className?: string;
+}
+
+/** `Record`로 고정해 크기를 추가하면 항목 누락이 타입 에러가 되게 한다. */
+const SIZE_CLASS: Record<FoxSegmentedControlItemSize, string> = {
+  lg: "fox-segmented-control-item--lg",
+  md: "fox-segmented-control-item--md",
+  sm: "fox-segmented-control-item--sm",
+  xsm: "fox-segmented-control-item--xsm",
+};
+
+/**
+ * @fox 세그먼티드 컨트롤의 칸 하나. 상태를 갖지 않는다 — 켜짐 여부는 네이티브
+ * `<input type="radio">`가 소유한다.
+ *
+ * `name`이 같은 칸끼리는 브라우저가 그룹으로 묶어 단일 선택·화살표 이동·폼 전송을 처리한다.
+ * `FoxSegmentedControl`이 하는 일은 그 `name`을 통일하고 테두리로 감싸는 것뿐이다.
+ *
+ * 루트가 `<label>`이라 글자나 아이콘 어디를 눌러도 선택된다. 배경·글자색은 CSS가 input의
+ * `:checked`·`:disabled`를 보고 그리므로, 비제어로 써도 모양이 어긋나지 않는다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxSegmentedControlItem({
+  size = "lg",
+  label,
+  icon,
+  onChange,
+  hidden = false,
+  className,
+  ...rest
+}: FoxSegmentedControlItemProps) {
+  if (hidden) {
+    return null;
+  }
+
+  // 라디오는 켜질 때만 change가 뛴다(꺼질 때는 안 뛴다). 그래도 명시적으로 걸러 두면
+  // 브라우저가 달라져도 "선택될 때"라는 계약이 흔들리지 않는다(`FoxRadio`와 같은 근거).
+  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
+    if (event.target.checked) {
+      onChange?.(event.target.value, event);
+    }
+  };
+
+  return (
+    <label className={cx("fox-segmented-control-item", SIZE_CLASS[size], className)}>
+      {/* `:has()`가 찾을 대상이고 형제 선택자의 기준이기도 하므로 맨 앞에 둔다. */}
+      <input
+        {...rest}
+        type="radio"
+        className="fox-segmented-control-item__input"
+        onChange={handleChange}
+      />
+      {icon ? (
+        <span className="fox-segmented-control-item__icon" aria-hidden="true">
+          {icon}
+        </span>
+      ) : null}
+      <span className="fox-segmented-control-item__label">{label}</span>
+    </label>
+  );
+}
 
@fox/core/components/fox-segmented-control-item/index.ts (added)
+++ @fox/core/components/fox-segmented-control-item/index.ts
@@ -0,0 +1,5 @@
+export {
+  FoxSegmentedControlItem,
+  type FoxSegmentedControlItemProps,
+  type FoxSegmentedControlItemSize,
+} from "./fox-segmented-control-item";
 
@fox/core/components/fox-segmented-control/fox-segmented-control.tsx (added)
+++ @fox/core/components/fox-segmented-control/fox-segmented-control.tsx
@@ -0,0 +1,112 @@
+"use client";
+
+import {
+  Children,
+  Fragment,
+  cloneElement,
+  isValidElement,
+  type ReactElement,
+  type ReactNode,
+  type Ref,
+} from "react";
+import { cx } from "../../utils";
+import type { FoxSegmentedControlItemSize } from "../fox-segmented-control-item";
+
+export type FoxSegmentedControlSize = FoxSegmentedControlItemSize;
+
+export interface FoxSegmentedControlProps {
+  /** 자식 칸의 `size`를 전부 이 값으로 덮어쓴다. 묶음의 모서리 크기도 이 값이 정한다. */
+  size?: FoxSegmentedControlSize;
+  /**
+   * 자식 칸의 `name`을 전부 이 값으로 덮어쓴다. 묶음 안에서 `name`이 어긋나면 브라우저가
+   * 다른 묶음으로 보고 여러 칸이 동시에 켜지므로, 묶음이 단일 진실 공급원이다.
+   */
+  name?: string;
+  /** 묶음을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */
+  label?: string;
+  /** 묶음 이름 역할을 하는 요소의 id. `label`보다 우선한다 — 화면의 글자와 어긋나지 않는다. */
+  labelledBy?: string;
+  /** `FoxSegmentedControlItem`들. */
+  children?: ReactNode;
+  /** 배치 조정용. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+const SIZE_CLASS: Record<FoxSegmentedControlSize, string> = {
+  lg: "fox-segmented-control--lg",
+  md: "fox-segmented-control--md",
+  sm: "fox-segmented-control--sm",
+  xsm: "fox-segmented-control--xsm",
+};
+
+type Overrides = { name?: string; size?: FoxSegmentedControlSize };
+
+/**
+ * 자식 칸에 묶음 값을 강제로 덮어쓴다.
+ *
+ * - 호스트 요소(`div` 등)는 건드리지 않는다 — DOM에 없는 속성이 넘어가면 React가 경고한다.
+ * - `Children.map`은 프래그먼트 안으로 들어가지 않아 직접 내려간다. 그러지 않으면
+ *   `<>…</>`로 감싼 칸들이 덮어쓰기를 피해 가고 Fragment에 `name`이 붙어 경고가 난다.
+ *
+ * `FoxRadioGroup`의 같은 함수와 규칙이 같다.
+ */
+function overrideChildren(children: ReactNode, overrides: Overrides): ReactNode {
+  return Children.map(children, (child) => {
+    if (!isValidElement(child)) {
+      return child;
+    }
+
+    if (child.type === Fragment) {
+      const fragment = child as ReactElement<{ children?: ReactNode }>;
+      return overrideChildren(fragment.props.children, overrides);
+    }
+
+    if (typeof child.type === "string") {
+      return child;
+    }
+
+    return cloneElement(child as ReactElement<Record<string, unknown>>, overrides);
+  });
+}
+
+/**
+ * @fox 세그먼티드 컨트롤. 칸들을 테두리 하나로 감싸고 `name`·`size`를 통일한다. 상태를
+ * 갖지 않는다 — 어느 칸이 켜졌는지는 각 칸의 네이티브 `<input type="radio">`가 소유한다.
+ *
+ * `size`는 두 곳에 쓰인다: 묶음의 모서리 크기(`form-radius-*`)와 자식 칸의 크기다. 둘이
+ * 어긋나면 칸 배경이 둥근 모서리와 맞지 않으므로 값을 하나로 묶어 둔다.
+ *
+ * 방향 수식어가 없다 — 칸들은 가로로 붙어 늘어서는 것이 유일한 배치다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxSegmentedControl({
+  size = "lg",
+  name,
+  label,
+  labelledBy,
+  children,
+  className,
+  ref,
+}: FoxSegmentedControlProps) {
+  // 크기는 묶음이 언제나 정한다 — 모서리와 칸 높이가 갈라지면 안 된다. `name`은 값을 준
+  // 때만 넣는다(`undefined`를 그대로 넘기면 자식이 스스로 정한 값을 지운다).
+  const overrides: Overrides = { size };
+  if (name !== undefined) {
+    overrides.name = name;
+  }
+
+  return (
+    <div
+      ref={ref}
+      role="radiogroup"
+      aria-label={labelledBy ? undefined : label}
+      aria-labelledby={labelledBy}
+      className={cx("fox-segmented-control", SIZE_CLASS[size], className)}
+    >
+      {overrideChildren(children, overrides)}
+    </div>
+  );
+}
 
@fox/core/components/fox-segmented-control/index.ts (added)
+++ @fox/core/components/fox-segmented-control/index.ts
@@ -0,0 +1,5 @@
+export {
+  FoxSegmentedControl,
+  type FoxSegmentedControlProps,
+  type FoxSegmentedControlSize,
+} from "./fox-segmented-control";
@fox/core/components/fox-select-option/fox-select-option.tsx
--- @fox/core/components/fox-select-option/fox-select-option.tsx
+++ @fox/core/components/fox-select-option/fox-select-option.tsx
@@ -8,6 +8,14 @@
   id?: string;
   /** 목록에 이름을 주는 요소의 id — 보통 셀렉트의 라벨이다. */
   labelledBy?: string;
+  /**
+   * 여러 항목을 동시에 고를 수 있는 목록(`aria-multiselectable`).
+   *
+   * 모양은 달라지지 않는다 — 고른 항목 표시는 항목이 각자 `aria-selected`로 하고, 이 속성은
+   * "몇 개까지 고를 수 있는지"를 보조기술에 알리는 것뿐이다. 이게 없으면 여러 개가 켜져
+   * 있어도 하나만 고르는 목록으로 읽혀, 두 번째를 고르면 첫 번째가 꺼진 줄 안다.
+   */
+  multiselectable?: boolean;
   /** `FoxSelectOptionItem`들. */
   children?: ReactNode;
   /** 배치 조정용. 모양이 달라야 하면 여기 말고 모디파이어를 추가한다. */
@@ -32,6 +40,7 @@
 export function FoxSelectOption({
   id,
   labelledBy,
+  multiselectable = false,
   children,
   className,
   ref,
@@ -42,6 +51,9 @@
       id={id}
       role="listbox"
       aria-labelledby={labelledBy}
+      // 기본값(하나만 고르는 목록)일 때는 속성을 아예 붙이지 않는다 — `false`를 적어 두는
+      // 것과 결과는 같지만, 없는 쪽이 DOM에서 읽기 쉽다.
+      aria-multiselectable={multiselectable || undefined}
       className={cx("fox-select-option", className)}
       onMouseDown={(event) => event.preventDefault()}
     >
 
@fox/core/components/fox-select-text/fox-select-text.tsx (added)
+++ @fox/core/components/fox-select-text/fox-select-text.tsx
@@ -0,0 +1,264 @@
+"use client";
+
+import {
+  useEffect,
+  useId,
+  useRef,
+  useState,
+  type KeyboardEvent,
+  type ReactNode,
+  type Ref,
+} from "react";
+import { cx } from "../../utils";
+import type { FoxSelectItem } from "../fox-select";
+import { FoxSelectOption } from "../fox-select-option";
+import { FoxSelectOptionItem } from "../fox-select-option-item";
+
+export type FoxSelectTextSize = "lg" | "md" | "sm";
+
+export interface FoxSelectTextProps {
+  size: FoxSelectTextSize;
+  /** `FoxSelect`와 같은 데이터 모양이다 — 열리는 목록이 같은 조각이므로 타입도 같이 쓴다. */
+  options: FoxSelectItem[];
+  /** 주면 제어 컴포넌트가 된다 — 호출부가 소유하고, 이 요소가 스스로 바꾸지 않는다. */
+  value?: string;
+  /** 비제어로 쓸 때의 초기 값. `value`를 주면 무시된다. */
+  defaultValue?: string;
+  /** 값이 바뀔 때 호출된다(`FoxSelect.onValueChange`와 같은 이름·같은 근거). */
+  onValueChange?: (value: string) => void;
+  /** 아무 옵션도 선택되지 않았을 때 보여줄 문구. */
+  placeholder?: ReactNode;
+  disabled?: boolean;
+  /**
+   * 글자를 대신할 읽어 줄 이름.
+   *
+   * 폼 라벨이 없는 컴포넌트라 보이는 글자가 곧 이름이다. 그런데 그 글자가 고른 값
+   * ("2026년 8월")이라 무엇을 고르는 자리인지는 소리로 전달되지 않는다 — `"조회 기간"`처럼
+   * 적어 준다.
+   */
+  ariaLabel?: string;
+  /** 이름 역할을 하는 요소의 id. `ariaLabel`보다 우선한다. */
+  labelledBy?: string;
+  id?: string;
+  /** 배치 조정용. 모양이 달라야 하면 여기 말고 모디파이어를 추가한다. */
+  className?: string;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  /** 트리거를 가리킨다 — 호출부가 포커스를 옮길 때 쓴다. */
+  ref?: Ref<HTMLButtonElement>;
+}
+
+const SIZE_CLASS: Record<FoxSelectTextSize, string> = {
+  lg: "fox-select-text--lg",
+  md: "fox-select-text--md",
+  sm: "fox-select-text--sm",
+};
+
+/**
+ * @fox 글자만 있는 셀렉트. 테두리도 고정 높이도 없고, 고른 값이 그대로 글자로 보인다.
+ *
+ * `FoxSelect`와 같은 콤보박스지만 **폼 한 칸이 아니다** — 라벨·힌트·에러·필수 표시가 없고,
+ * lg는 글자가 heading/md(제목용 20px bold)다. 화면의 제목이나 목록 위 정렬 기준처럼 "지금
+ * 무엇을 보고 있는지"를 글자로 보여 주면서 그 자리에서 바꾸게 하는 자리에 쓴다
+ * (예: `2026년 8월 ∨`, `최신순 ∨`). 값을 입력받아 서버로 보내는 자리라면 `FoxSelect`다.
+ *
+ * 열리는 목록은 `FoxSelect`와 같은 `FoxSelectOption`·`FoxSelectOptionItem`을 쓴다 — 트리거
+ * 모양만 다르고 목록은 같아야 한다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxSelectText({
+  size,
+  options,
+  value,
+  defaultValue,
+  onValueChange,
+  placeholder = "선택해주세요",
+  disabled = false,
+  ariaLabel,
+  labelledBy,
+  id,
+  className,
+  hidden = false,
+  ref,
+}: FoxSelectTextProps) {
+  const [open, setOpen] = useState(false);
+  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? "");
+  const [activeIndex, setActiveIndex] = useState(-1);
+  const rootRef = useRef<HTMLDivElement>(null);
+
+  const currentValue = value ?? uncontrolledValue;
+  const selectedIndex = options.findIndex((option) => option.value === currentValue);
+  const selectedOption = selectedIndex >= 0 ? options[selectedIndex] : undefined;
+
+  const generatedId = useId();
+  const triggerId = id ?? generatedId;
+  const listboxId = `${triggerId}-listbox`;
+  // 값이 아니라 순번으로 만든다 — 옵션 값에 공백·특수문자가 들어와도 id가 깨지지 않는다.
+  const optionId = (index: number) => `${triggerId}-opt-${index}`;
+
+  useEffect(() => {
+    if (!open) return;
+    function handlePointerDown(event: PointerEvent) {
+      if (!rootRef.current?.contains(event.target as Node)) {
+        setOpen(false);
+      }
+    }
+    // 포인터만 보면 Tab으로 빠져나갈 때 목록이 열린 채 남는다. 포커스가 아무 데도 가지
+    // 않은 경우(relatedTarget이 null)는 위 pointerdown이 이미 처리한다 — 여기서 닫으면
+    // 옵션을 누르는 순간에도 닫혀 클릭이 성사되지 않는다(`FoxSelect`와 같은 근거).
+    function handleFocusOut(event: FocusEvent) {
+      const next = event.relatedTarget as Node | null;
+      if (next && !rootRef.current?.contains(next)) {
+        setOpen(false);
+      }
+    }
+    document.addEventListener("pointerdown", handlePointerDown);
+    const root = rootRef.current;
+    root?.addEventListener("focusout", handleFocusOut);
+    return () => {
+      document.removeEventListener("pointerdown", handlePointerDown);
+      root?.removeEventListener("focusout", handleFocusOut);
+    };
+  }, [open]);
+
+  function openList() {
+    setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
+    setOpen(true);
+  }
+
+  function commit(next: string) {
+    // 제어 컴포넌트일 때는 내부 값을 건드리지 않는다 — 건드리면 호출부가 값을 거부해도
+    // 내부에만 반영되어 둘이 조용히 갈라진다.
+    if (value === undefined) setUncontrolledValue(next);
+    onValueChange?.(next);
+    setOpen(false);
+  }
+
+  function moveActive(delta: number) {
+    setActiveIndex((current) => {
+      const count = options.length;
+      if (count === 0) return current;
+      return (current + delta + count) % count;
+    });
+  }
+
+  function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {
+    if (disabled) return;
+
+    if (!open) {
+      if (event.key === "ArrowDown" || event.key === "ArrowUp") {
+        event.preventDefault();
+        openList();
+      }
+      // Enter·Space는 막지 않는다 — 네이티브 버튼의 클릭이 그대로 열어 준다.
+      return;
+    }
+
+    switch (event.key) {
+      case "ArrowDown":
+        event.preventDefault();
+        moveActive(1);
+        break;
+      case "ArrowUp":
+        event.preventDefault();
+        moveActive(-1);
+        break;
+      case "Home":
+        event.preventDefault();
+        setActiveIndex(0);
+        break;
+      case "End":
+        event.preventDefault();
+        setActiveIndex(options.length - 1);
+        break;
+      case "Enter":
+      case " ":
+        event.preventDefault();
+        if (activeIndex >= 0) {
+          commit(options[activeIndex].value);
+        }
+        break;
+      case "Escape":
+        event.preventDefault();
+        setOpen(false);
+        break;
+      default:
+        break;
+    }
+  }
+
+  // 훅을 전부 부른 뒤에 빠져나간다 — 위쪽에서 반환하면 훅 호출 순서가 깨진다.
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    // `FoxSelectOption`은 `position: absolute`라 위치 기준이 되는 조상이 필요하다
+    // (`fox-select__control`과 같은 역할).
+    <div ref={rootRef} className={cx("fox-select-text", SIZE_CLASS[size], className)}>
+      <button
+        ref={ref}
+        type="button"
+        id={triggerId}
+        role="combobox"
+        disabled={disabled}
+        aria-haspopup="listbox"
+        aria-expanded={open}
+        // 목록은 열렸을 때만 렌더되므로, 닫힌 동안 없는 id를 가리키지 않게 한다.
+        aria-controls={open ? listboxId : undefined}
+        // 화살표로 옮겨 다니는 옵션을 보조기술에 알린다. 이게 없으면 `--active` 강조가
+        // 시각 전용이라 스크린리더 사용자는 이동을 알 수 없다.
+        aria-activedescendant={open && activeIndex >= 0 ? optionId(activeIndex) : undefined}
+        aria-label={labelledBy ? undefined : ariaLabel}
+        aria-labelledby={labelledBy}
+        className="fox-select-text__trigger"
+        onClick={() => (open ? setOpen(false) : openList())}
+        onKeyDown={handleKeyDown}
+      >
+        <span className="fox-select-text__value">{selectedOption?.label ?? placeholder}</span>
+        <span className="fox-select-text__icon" aria-hidden="true">
+          <ChevronMark />
+        </span>
+      </button>
+
+      {open ? (
+        <FoxSelectOption
+          id={listboxId}
+          labelledBy={labelledBy}
+          className="fox-select-text__list"
+        >
+          {options.map((option, index) => (
+            <FoxSelectOptionItem
+              key={option.value}
+              id={optionId(index)}
+              label={option.label}
+              icon={option.icon}
+              danger={option.danger}
+              selected={option.value === currentValue}
+              active={index === activeIndex}
+              onHover={() => setActiveIndex(index)}
+              onSelect={() => commit(option.value)}
+            />
+          ))}
+        </FoxSelectOption>
+      ) : null}
+    </div>
+  );
+}
+
+// 시안 아이콘. `FoxChip`의 것과 같은 글리프다 — 이 패키지는 컴포넌트마다 자기 글리프를
+// 안에 두고(`FoxTag`·`FoxSelect`도 그렇다), 아이콘 모듈을 따로 만들지 않는다.
+// `fill`은 시안의 리터럴 대신 `currentColor`로 두어 색을 크기 규칙이 정하게 한다.
+// `width`·`height`를 쓰지 않는다 — 크기는 슬롯이 정하고 SVG는 100%로 따라온다.
+function ChevronMark() {
+  return (
+    <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
+      <path
+        d="M13.5306 6.53061L8.5306 11.5306C8.46092 11.6005 8.37813 11.656 8.28696 11.6939C8.1958 11.7317 8.09806 11.7512 7.99935 11.7512C7.90064 11.7512 7.8029 11.7317 7.71173 11.6939C7.62057 11.656 7.53778 11.6005 7.4681 11.5306L2.4681 6.53061C2.3272 6.38972 2.24805 6.19862 2.24805 5.99936C2.24805 5.80011 2.3272 5.60901 2.4681 5.46811C2.60899 5.32722 2.80009 5.24806 2.99935 5.24806C3.19861 5.24806 3.3897 5.32722 3.5306 5.46811L7.99997 9.93749L12.4693 5.46749C12.6102 5.32659 12.8013 5.24744 13.0006 5.24744C13.1999 5.24744 13.391 5.32659 13.5318 5.46749C13.6727 5.60838 13.7519 5.79948 13.7519 5.99874C13.7519 6.198 13.6727 6.38909 13.5318 6.52999L13.5306 6.53061Z"
+        fill="currentColor"
+      />
+    </svg>
+  );
+}
 
@fox/core/components/fox-select-text/index.ts (added)
+++ @fox/core/components/fox-select-text/index.ts
@@ -0,0 +1,5 @@
+export {
+  FoxSelectText,
+  type FoxSelectTextProps,
+  type FoxSelectTextSize,
+} from "./fox-select-text";
@fox/core/components/fox-select/fox-select.tsx
--- @fox/core/components/fox-select/fox-select.tsx
+++ @fox/core/components/fox-select/fox-select.tsx
@@ -1,6 +1,7 @@
 "use client";
 
 import {
+  Fragment,
   useEffect,
   useId,
   useRef,
@@ -15,6 +16,7 @@
 import { FoxSelectOptionItem } from "../fox-select-option-item";
 
 export type FoxSelectSize = "sm" | "md" | "lg";
+export type FoxSelectSelectionMode = "single" | "multiple";
 
 /** 옵션 하나의 데이터. 그리는 일은 `FoxSelectOptionItem`이 한다. */
 export interface FoxSelectItem {
@@ -29,7 +31,17 @@
 export interface FoxSelectProps {
   size: FoxSelectSize;
   options: FoxSelectItem[];
-  /** 주면 제어 컴포넌트가 된다 — 호출부가 소유하고, select가 스스로 바꾸지 않는다. */
+  /**
+   * 하나만 고를지 여러 개를 고를지 정한다. 값 prop이 계열마다 다르다 —
+   * `single`은 `value`·`defaultValue`·`onValueChange`(문자열), `multiple`은
+   * `values`·`defaultValues`·`onValuesChange`(문자열 배열)를 쓴다.
+   *
+   * `multiple`이면 트리거에 고른 옵션의 라벨이 **쉼표로 이어져** 보이고, 하나를 골라도
+   * 목록이 닫히지 않는다 — 여러 개를 연달아 고르는 것이 목적이라 누를 때마다 닫히면 쓸 수
+   * 없다. 닫는 것은 바깥 클릭·Escape·Tab이다(`FoxChip`의 select와 같은 규칙).
+   */
+  selectionMode?: FoxSelectSelectionMode;
+  /** 단일 선택에서만 쓴다. 주면 제어 컴포넌트가 된다 — 호출부가 소유하고, select가 스스로 바꾸지 않는다. */
   value?: string;
   /**
    * 비제어로 쓸 때의 초기 값. `value`를 주면 무시된다.
@@ -49,6 +61,12 @@
    * 게다가 `FoxEmail`·`FoxPhoneNumber`가 이 이름으로 호출하고 있어 바꾸면 함께 깨진다.
    */
   onValueChange?: (value: string) => void;
+  /** 다중 선택에서만 쓴다. 주면 제어 컴포넌트가 된다. */
+  values?: string[];
+  /** 다중 선택에서만 쓴다. 비제어일 때의 초기 값들. `values`를 주면 무시된다. */
+  defaultValues?: string[];
+  /** 다중 선택에서만 쓴다. 켜고 끌 때마다 **바뀐 뒤의 전체 목록**이 넘어온다. */
+  onValuesChange?: (values: string[]) => void;
   /** 아무 옵션도 선택되지 않았을 때 트리거에 보여줄 문구. */
   placeholder?: ReactNode;
   /** Figma 시안의 error 상태. */
@@ -97,9 +115,13 @@
 export function FoxSelect({
   size,
   options,
+  selectionMode = "single",
   value,
   defaultValue,
   onValueChange,
+  values,
+  defaultValues,
+  onValuesChange,
   placeholder = "선택해주세요",
   error,
   disabled = false,
@@ -115,14 +137,38 @@
   ref,
 }: FoxSelectProps) {
   const isDisabled = disabled || readOnly;
+  const isMultiple = selectionMode === "multiple";
   const [open, setOpen] = useState(false);
   const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? "");
+  const [uncontrolledValues, setUncontrolledValues] = useState<string[]>(defaultValues ?? []);
   const [activeIndex, setActiveIndex] = useState(-1);
   const rootRef = useRef<HTMLDivElement>(null);
 
   const currentValue = value ?? uncontrolledValue;
-  const selectedIndex = options.findIndex((option) => option.value === currentValue);
-  const selectedOption = selectedIndex >= 0 ? options[selectedIndex] : undefined;
+  const currentValues = values ?? uncontrolledValues;
+
+  const isSelected = (optionValue: string) =>
+    isMultiple ? currentValues.includes(optionValue) : optionValue === currentValue;
+
+  // 화살표가 어디서 시작할지 정한다. 다중 선택에서는 켜진 것 중 첫 번째다.
+  const selectedIndex = options.findIndex((option) => isSelected(option.value));
+  const selectedOptions = options.filter((option) => isSelected(option.value));
+
+  // 트리거에 보이는 것. 다중이면 고른 라벨을 쉼표로 잇는다 — 라벨이 ReactNode일 수 있어
+  // `join`을 쓰지 못하므로 사이에 구분자를 끼워 넣는다.
+  const displayValue =
+    selectedOptions.length === 0 ? (
+      placeholder
+    ) : isMultiple ? (
+      selectedOptions.map((option, index) => (
+        <Fragment key={option.value}>
+          {index > 0 ? ", " : null}
+          {option.label}
+        </Fragment>
+      ))
+    ) : (
+      selectedOptions[0].label
+    );
 
   const generatedId = useId();
   const triggerId = id ?? generatedId;
@@ -163,9 +209,27 @@
     setOpen(true);
   }
 
+  /**
+   * 제어 컴포넌트일 때는 내부 값을 건드리지 않는다 — 건드리면 호출부가 값을 거부해도
+   * 내부에만 반영되어 둘이 조용히 갈라진다.
+   */
   function commit(next: string) {
-    // 제어 컴포넌트일 때는 내부 값을 건드리지 않는다 — 건드리면 호출부가 값을 거부해도
-    // 내부에만 반영되어 둘이 조용히 갈라진다.
+    if (isMultiple) {
+      // 이미 켜져 있으면 끈다. 순서는 `options`가 정한 순서를 따라가게 다시 추린다 —
+      // 누른 순서대로 쌓으면 같은 조합인데도 배열이 달라져 비교·저장이 흔들리고, 트리거에
+      // 이어 붙는 글자 순서도 목록과 어긋난다.
+      const nextValues = currentValues.includes(next)
+        ? currentValues.filter((item) => item !== next)
+        : options
+            .filter((option) => option.value === next || currentValues.includes(option.value))
+            .map((option) => option.value);
+
+      if (values === undefined) setUncontrolledValues(nextValues);
+      onValuesChange?.(nextValues);
+      // 목록을 닫지 않는다 — 여러 개를 연달아 고르는 것이 목적이다.
+      return;
+    }
+
     if (value === undefined) setUncontrolledValue(next);
     onValueChange?.(next);
     setOpen(false);
@@ -259,17 +323,21 @@
           aria-readonly={readOnly || undefined}
           className={cx(
             "fox-select__trigger",
-            selectedOption && "fox-select__trigger--completed"
+            selectedOptions.length > 0 && "fox-select__trigger--completed"
           )}
           onClick={() => (open ? setOpen(false) : openList())}
           onKeyDown={handleTriggerKeyDown}
         >
-          <span className="fox-select__value">{selectedOption?.label ?? placeholder}</span>
+          <span className="fox-select__value">{displayValue}</span>
           <ChevronIcon className="fox-select__icon" />
         </button>
 
         {open ? (
-          <FoxSelectOption id={listboxId} labelledBy={label ? labelId : undefined}>
+          <FoxSelectOption
+            id={listboxId}
+            labelledBy={label ? labelId : undefined}
+            multiselectable={isMultiple}
+          >
             {options.map((option, index) => (
               <FoxSelectOptionItem
                 key={option.value}
@@ -277,7 +345,7 @@
                 label={option.label}
                 icon={option.icon}
                 danger={option.danger}
-                selected={option.value === currentValue}
+                selected={isSelected(option.value)}
                 active={index === activeIndex}
                 onHover={() => setActiveIndex(index)}
                 onSelect={() => commit(option.value)}
 
@fox/core/components/fox-toggle-quantity/fox-toggle-quantity.tsx (added)
+++ @fox/core/components/fox-toggle-quantity/fox-toggle-quantity.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import { useState, type Ref } from "react";
+import { cx } from "../../utils";
+
+export interface FoxToggleQuantityProps {
+  /** 주면 제어 컴포넌트가 된다 — 호출부가 소유하고, 이 요소가 스스로 바꾸지 않는다. */
+  value?: number;
+  /** 비제어로 쓸 때의 초기 값. `value`를 주면 무시된다. */
+  defaultValue?: number;
+  /**
+   * 값이 바뀔 때 호출된다. 이미 `min`·`max`로 자른 뒤의 값이 넘어온다.
+   *
+   * ⚠️ 이름이 `onChange`가 아니다 — 네이티브 입력을 감싸는 게 아니라 버튼 둘로 만든 커스텀
+   * 위젯이라 `FoxSelect.onValueChange`와 같은 쪽이다.
+   */
+  onValueChange?: (value: number) => void;
+  /** 내려갈 수 있는 하한. 값이 여기 닿으면 빼기 버튼이 비활성된다. */
+  min?: number;
+  /** 올라갈 수 있는 상한. 넘기지 않으면 상한이 없어 더하기 버튼이 비활성되지 않는다. */
+  max?: number;
+  /** 한 번에 오르내리는 폭. */
+  step?: number;
+  /**
+   * 폼으로 값을 보낼 이름. 주면 같은 값을 담은 `<input type="hidden">`을 함께 렌더한다.
+   *
+   * `FoxChip`은 이걸 열지 않는다 — 칩은 여러 개가 한 묶음으로 하나의 값을 이뤄서 이름 하나를
+   * 칩 하나에 묶을 수 없기 때문이다. 이쪽은 값이 정확히 하나라 그 문제가 없다.
+   */
+  name?: string;
+  /** 두 버튼을 모두 비활성한다. `min`·`max`에 의한 개별 비활성과는 별개다. */
+  disabled?: boolean;
+  /** 묶음을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */
+  label?: string;
+  /** 묶음 이름 역할을 하는 요소의 id. `label`보다 우선한다. */
+  labelledBy?: string;
+  /** 빼기 버튼을 읽어 줄 이름. */
+  decreaseLabel?: string;
+  /** 더하기 버튼을 읽어 줄 이름. */
+  increaseLabel?: string;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  /** 배치 조정용. 시안의 폭(140px)을 바꿔야 할 때도 여기로 준다. */
+  className?: string;
+  ref?: Ref<HTMLDivElement>;
+}
+
+/**
+ * @fox 수량 조절기. 빼기 버튼 · 숫자 · 더하기 버튼이 회색 판 위에 놓인다.
+ *
+ * 값은 제어·비제어 둘 다 된다(`FoxSelect`와 같은 방식). `min`·`max`에 닿으면 해당 버튼이
+ * 네이티브 `disabled`가 되므로, 누를 수 없다는 것이 커서와 보조기술에 함께 전달된다.
+ *
+ * 숫자는 입력칸이 아니라 읽기 전용 글자다 — 시안에 입력칸의 테두리도 배경도 없다. 그래서
+ * `role="spinbutton"`을 붙이지 않고 `role="group"` 안의 버튼 둘로 두고, 바뀐 값은
+ * `aria-live`로 알린다. 키보드로 값을 직접 치는 자리가 필요해지면 시안에 먼저 추가한다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxToggleQuantity({
+  value,
+  defaultValue = 0,
+  onValueChange,
+  min = 0,
+  max,
+  step = 1,
+  name,
+  disabled = false,
+  label,
+  labelledBy,
+  decreaseLabel = "수량 줄이기",
+  increaseLabel = "수량 늘리기",
+  hidden = false,
+  className,
+  ref,
+}: FoxToggleQuantityProps) {
+  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
+
+  const currentValue = value ?? uncontrolledValue;
+
+  // 훅을 전부 부른 뒤에 빠져나간다 — 위쪽에서 반환하면 훅 호출 순서가 깨진다.
+  if (hidden) {
+    return null;
+  }
+
+  // 호출부가 범위 밖의 값을 줄 수도 있으므로 표시 직전에 한 번 자른다 — 자르지 않으면
+  // 버튼 비활성 여부와 보이는 숫자가 어긋난다.
+  const clamp = (next: number) => Math.min(max ?? Infinity, Math.max(min, next));
+
+  const atMin = currentValue <= min;
+  const atMax = max !== undefined && currentValue >= max;
+
+  function commit(next: number) {
+    const clamped = clamp(next);
+    if (clamped === currentValue) {
+      return;
+    }
+    // 제어 컴포넌트일 때는 내부 값을 건드리지 않는다 — 건드리면 호출부가 값을 거부해도
+    // 내부에만 반영되어 둘이 조용히 갈라진다.
+    if (value === undefined) setUncontrolledValue(clamped);
+    onValueChange?.(clamped);
+  }
+
+  return (
+    <div
+      ref={ref}
+      role="group"
+      aria-label={labelledBy ? undefined : label}
+      aria-labelledby={labelledBy}
+      className={cx("fox-toggle-quantity", className)}
+    >
+      <button
+        type="button"
+        className="fox-toggle-quantity__button"
+        disabled={disabled || atMin}
+        aria-label={decreaseLabel}
+        onClick={() => commit(currentValue - step)}
+      >
+        <span className="fox-toggle-quantity__icon" aria-hidden="true">
+          <MinusMark />
+        </span>
+      </button>
+
+      {/* 값이 바뀐 것을 소리로도 알린다 — 버튼 이름은 "수량 줄이기"라 결과를 담지 않는다. */}
+      <span className="fox-toggle-quantity__value" aria-live="polite">
+        {clamp(currentValue)}
+      </span>
+
+      <button
+        type="button"
+        className="fox-toggle-quantity__button"
+        disabled={disabled || atMax}
+        aria-label={increaseLabel}
+        onClick={() => commit(currentValue + step)}
+      >
+        <span className="fox-toggle-quantity__icon" aria-hidden="true">
+          <PlusMark />
+        </span>
+      </button>
+
+      {name ? <input type="hidden" name={name} value={clamp(currentValue)} /> : null}
+    </div>
+  );
+}
+
+// 시안 아이콘 둘. `fill`을 시안의 리터럴(#58616A) 대신 `currentColor`로 두면 색을 버튼
+// 규칙이 정한다(그쪽에서 같은 값을 `primitive(neutral-60)`으로 가리킨다).
+// `width`·`height`를 쓰지 않는다 — 크기는 슬롯이 정하고 SVG는 100%로 따라온다.
+
+function MinusMark() {
+  return (
+    <svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
+      <path
+        d="M17.5 10C17.5 10.1658 17.4342 10.3247 17.3169 10.4419C17.1997 10.5592 17.0408 10.625 16.875 10.625H3.125C2.95924 10.625 2.80027 10.5592 2.68306 10.4419C2.56585 10.3247 2.5 10.1658 2.5 10C2.5 9.83424 2.56585 9.67527 2.68306 9.55806C2.80027 9.44085 2.95924 9.375 3.125 9.375H16.875C17.0408 9.375 17.1997 9.44085 17.3169 9.55806C17.4342 9.67527 17.5 9.83424 17.5 10Z"
+        fill="currentColor"
+      />
+    </svg>
+  );
+}
+
+function PlusMark() {
+  return (
+    <svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
+      <path
+        d="M17.5 10C17.5 10.1658 17.4342 10.3247 17.3169 10.4419C17.1997 10.5592 17.0408 10.625 16.875 10.625H10.625V16.875C10.625 17.0408 10.5592 17.1997 10.4419 17.3169C10.3247 17.4342 10.1658 17.5 10 17.5C9.83424 17.5 9.67527 17.4342 9.55806 17.3169C9.44085 17.1997 9.375 17.0408 9.375 16.875V10.625H3.125C2.95924 10.625 2.80027 10.5592 2.68306 10.4419C2.56585 10.3247 2.5 10.1658 2.5 10C2.5 9.83424 2.56585 9.67527 2.68306 9.55806C2.80027 9.44085 2.95924 9.375 3.125 9.375H9.375V3.125C9.375 2.95924 9.44085 2.80027 9.55806 2.68306C9.67527 2.56585 9.83424 2.5 10 2.5C10.1658 2.5 10.3247 2.56585 10.4419 2.68306C10.5592 2.80027 10.625 2.95924 10.625 3.125V9.375H16.875C17.0408 9.375 17.1997 9.44085 17.3169 9.55806C17.4342 9.67527 17.5 9.83424 17.5 10Z"
+        fill="currentColor"
+      />
+    </svg>
+  );
+}
 
@fox/core/components/fox-toggle-quantity/index.ts (added)
+++ @fox/core/components/fox-toggle-quantity/index.ts
@@ -0,0 +1,1 @@
+export { FoxToggleQuantity, type FoxToggleQuantityProps } from "./fox-toggle-quantity";
@fox/core/components/index.ts
--- @fox/core/components/index.ts
+++ @fox/core/components/index.ts
@@ -15,6 +15,9 @@
 export * from "./fox-check-icon";
 export * from "./fox-checkbox";
 export * from "./fox-checkbox-group";
+export * from "./fox-chip";
+export * from "./fox-chip-area";
+export * from "./fox-chip-select-option";
 export * from "./fox-conditional-option";
 export * from "./fox-email";
 export * from "./fox-file-upload";
@@ -27,9 +30,12 @@
 export * from "./fox-radio";
 export * from "./fox-radio-group";
 export * from "./fox-radio-icon";
+export * from "./fox-segmented-control";
+export * from "./fox-segmented-control-item";
 export * from "./fox-select";
 export * from "./fox-select-option";
 export * from "./fox-select-option-item";
+export * from "./fox-select-text";
 export * from "./fox-spinner";
 export * from "./fox-tab";
 export * from "./fox-tab-item";
@@ -40,4 +46,6 @@
 export * from "./fox-tag-list";
 export * from "./fox-text-area";
 export * from "./fox-text-button";
+export * from "./fox-toggle-quantity";
 export * from "./fox-toggle-switch";
+
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -50,6 +50,12 @@
 import { FoxTab } from "../core/components/fox-tab";
 import { FoxTabItem } from "../core/components/fox-tab-item";
 import { FoxSpinner } from "../core/components/fox-spinner";
+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";
+import { FoxSegmentedControl } from "../core/components/fox-segmented-control";
+import { FoxSegmentedControlItem } from "../core/components/fox-segmented-control-item";
+import { FoxToggleQuantity } from "../core/components/fox-toggle-quantity";
 import styles from "./dev-test.module.scss";
 
 export interface ComponentExample {
@@ -560,6 +566,36 @@
   );
 }
 
+const CHIP_PERIOD_OPTIONS: FoxChipItem[] = [
+  { value: "7d", label: "최근 7일" },
+  { value: "1m", label: "최근 1개월" },
+  { value: "3m", label: "최근 3개월" },
+  { value: "custom", label: "직접 입력" },
+];
+
+const SELECT_TEXT_MONTHS: FoxSelectItem[] = [
+  { value: "2026-06", label: "2026년 6월" },
+  { value: "2026-07", label: "2026년 7월" },
+  { value: "2026-08", label: "2026년 8월" },
+];
+
+const SELECT_TEXT_SORTS: FoxSelectItem[] = [
+  { value: "latest", label: "최신순" },
+  { value: "oldest", label: "오래된순" },
+  { value: "amount", label: "금액 높은순" },
+];
+
+// FoxChip select 계열의 앞 아이콘 자리를 확인하기 위한 달력 도형(시안이 그 자리에 달력을
+// 쓴다). `currentColor`로 그려야 상태별 글자 색을 따라간다.
+function DemoCalendar() {
+  return (
+    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" aria-hidden="true">
+      <rect x="2" y="3" width="12" height="11" rx="1.5" />
+      <path d="M2 6.5h12M5.5 1.5v3M10.5 1.5v3" />
+    </svg>
+  );
+}
+
 /** 컴포넌트를 만들 때마다 항목 하나를 추가한다 — 다른 파일은 고치지 않는다. */
 export const COMPONENT_EXAMPLES: ComponentExample[] = [
   {
@@ -985,7 +1021,7 @@
       },
     ],
   },
-    {
+  {
     id: "fox-form-label",
     name: "FoxFormLabel",
     description:
@@ -1222,6 +1258,22 @@
           </div>
         ),
       },
+      {
+        label:
+          "selectionMode=\"multiple\" — 고른 라벨이 쉼표로 이어지고, 목록은 바깥을 눌러야 닫힙니다",
+        node: (
+          <div className={styles.demoFields}>
+            <FoxSelect size="md" label="아무것도 안 고름" selectionMode="multiple" options={SELECT_ITEMS} />
+            <FoxSelect
+              size="md"
+              label="여러 개 고름"
+              selectionMode="multiple"
+              options={SELECT_ITEMS}
+              defaultValues={SELECT_ITEMS.slice(0, 2).map((option) => option.value)}
+            />
+          </div>
+        ),
+      },
     ],
   },
   {
@@ -1409,7 +1461,7 @@
       },
     ],
   },
-    {
+  {
     id: "fox-conditional-option",
     name: "FoxConditionalOption",
     description:
@@ -1912,7 +1964,8 @@
         ),
       },
     ],
-  },  { 
+  },
+  { 
     id: "fox-check-icon",
     name: "FoxCheckIcon",
     description:
@@ -2848,4 +2901,358 @@
       },
     ],
   },
+  {
+    id: "fox-chip",
+    name: "FoxChip",
+    description:
+      "Figma 시안(chip 264:424) 켜짐 여부를 갖는 작은 조각입니다 — type은 아이콘의 유무와 누를 때 벌어지는 일만 정하고, 상태별 모양(기본·hover·켜짐·비활성)은 세 계열이 똑같습니다. check는 켜졌을 때만 앞에 체크가 붙고, select는 뒤 화살표가 늘 있으며 앞 아이콘은 icon을 넘긴 때만 나옵니다. action은 아이콘이 없습니다. select는 selectionMode로 단일·다중을 고릅니다 — 단일은 하나 고르면 그 자리에서 반영되고 닫히지만, 다중은 열린 채로 남고 바깥을 눌러야 닫힙니다(닫기가 곧 확정이 아니라 체크할 때마다 이미 반영됩니다). 열리는 목록은 FoxSelectOption이 아니라 전용 FoxChipSelectOption이고, 항목이 단일이면 라디오·다중이면 체크박스입니다. 그래서 칩은 콤보박스가 아니라 목록을 여닫는 버튼(aria-expanded)일 뿐이고, 항목 사이 이동은 네이티브 입력이 알아서 합니다. 아래 예제는 전부 실제로 눌러서 확인할 수 있습니다 — hover(파란 테두리 + 연한 파랑 배경)는 마우스를 올려야 보이고, 켜짐 위에서는 hover가 걸리지 않습니다(시안에 없어 임의로 만들지 않았습니다).",
+    variants: [
+      {
+        label: "check — 눌러서 켜고 끕니다. 켜지면 앞에 체크가 붙고 남색으로 채워집니다",
+        node: (
+          <>
+            <FoxChip label="레이블" />
+            <FoxChip label="레이블" defaultChecked />
+          </>
+        ),
+      },
+      {
+        label: "select — 눌러 목록을 엽니다. 고르면 켜짐이 되고 글자가 고른 값으로 바뀝니다",
+        node: (
+          <>
+            <FoxChip type="select" label="기간" icon={<DemoCalendar />} options={CHIP_PERIOD_OPTIONS} />
+            <FoxChip type="select" label="기간" options={CHIP_PERIOD_OPTIONS} />
+            <FoxChip
+              type="select"
+              label="기간"
+              icon={<DemoCalendar />}
+              options={CHIP_PERIOD_OPTIONS}
+              defaultValue="1m"
+            />
+          </>
+        ),
+      },
+      {
+        label:
+          "select · 다중 선택 — 켜고 꺼도 목록이 열린 채로 남고, 바깥을 눌러야 닫힙니다 (칩 글자는 그대로)",
+        node: (
+          <>
+            <FoxChip
+              type="select"
+              selectionMode="multiple"
+              label="기간"
+              icon={<DemoCalendar />}
+              options={CHIP_PERIOD_OPTIONS}
+            />
+            <FoxChip
+              type="select"
+              selectionMode="multiple"
+              label="기간"
+              icon={<DemoCalendar />}
+              options={CHIP_PERIOD_OPTIONS}
+              defaultValues={["7d", "3m"]}
+            />
+          </>
+        ),
+      },
+      {
+        label: "action — 아이콘 없이 켜고 끕니다 (켜져도 체크가 붙지 않습니다)",
+        node: (
+          <>
+            <FoxChip type="action" label="레이블" />
+            <FoxChip type="action" label="레이블" defaultChecked />
+          </>
+        ),
+      },
+      {
+        label: "크기 — lg 높이 40 / md 높이 28. 아이콘도 20 / 16으로 함께 바뀝니다",
+        node: (
+          <>
+            <FoxChip size="lg" label="레이블" defaultChecked />
+            <FoxChip size="md" label="레이블" defaultChecked />
+            <FoxChip size="lg" type="select" label="기간" icon={<DemoCalendar />} options={CHIP_PERIOD_OPTIONS} />
+            <FoxChip size="md" type="select" label="기간" icon={<DemoCalendar />} options={CHIP_PERIOD_OPTIONS} />
+          </>
+        ),
+      },
+      {
+        label: "비활성 — 꺼짐 / 켜짐. 켜짐 여부와 무관하게 한 벌입니다(시안 확인 완료)",
+        node: (
+          <>
+            <FoxChip label="레이블" disabled />
+            <FoxChip label="레이블" disabled defaultChecked />
+            <FoxChip type="select" label="기간" icon={<DemoCalendar />} options={CHIP_PERIOD_OPTIONS} disabled />
+            <FoxChip type="action" label="레이블" disabled defaultChecked />
+          </>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-chip-area",
+    name: "FoxChipArea",
+    description:
+      "Figma 시안(chip-area 264:1671) 칩 여러 개의 배치와 size 통일만 책임집니다. 시안이 주는 규칙이 inline-flex · align-items: center · gap 4px 세 줄뿐이라 스타일 파일도 그만큼입니다. FoxTagList와 달리 type은 덮어쓰지 않습니다 — 한 줄에 계열이 섞이는 것이 칩에서는 정상이기 때문입니다(기간 select 옆에 action 칩들). role이 radiogroup이 아니라 group입니다 — 하나만 고르는 묶음이 아니고 각 칩이 독립적으로 Tab을 받는 버튼입니다. 줄바꿈이 없습니다(flex-wrap 없음) — 시안이 한 줄만 그리므로 폭이 모자라면 넘칩니다.",
+    variants: [
+      {
+        label: "기본 — 칩 사이 간격 4px, 계열이 섞여도 됩니다",
+        node: (
+          <FoxChipArea size="md" label="거래 구분">
+            <FoxChip type="select" label="기간" icon={<DemoCalendar />} options={CHIP_PERIOD_OPTIONS} />
+            <FoxChip type="action" label="전체" defaultChecked />
+            <FoxChip type="action" label="입금" />
+            <FoxChip type="action" label="출금" />
+            <FoxChip type="action" label="환불" />
+          </FoxChipArea>
+        ),
+      },
+      {
+        label: "size — 자식이 준 값은 무시되고 묶음 값으로 통일된다",
+        node: (
+          <FoxChipArea size="md" label="md 크기">
+            {/* 일부러 어긋난 size를 준다 — 셋 다 md로 나와야 정상 */}
+            <FoxChip label="레이블" size="lg" />
+            <FoxChip label="레이블" size="lg" defaultChecked />
+            <FoxChip label="레이블" />
+          </FoxChipArea>
+        ),
+      },
+      {
+        label: "size를 넘기지 않으면 — 자식이 각자 정한 값이 그대로 남는다",
+        node: (
+          <FoxChipArea label="크기 섞임">
+            <FoxChip label="lg" size="lg" />
+            <FoxChip label="md" size="md" />
+          </FoxChipArea>
+        ),
+      },
+      {
+        label: "프래그먼트로 감싼 자식에도 통일이 걸린다",
+        node: (
+          <FoxChipArea size="lg" label="프래그먼트">
+            <>
+              <FoxChip label="레이블" size="md" />
+              <FoxChip label="레이블" size="md" defaultChecked />
+            </>
+          </FoxChipArea>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-select-text",
+    name: "FoxSelectText",
+    description:
+      "Figma 시안(select-txt 112:508) 글자만 있는 셀렉트입니다. FoxSelect와 같은 콤보박스이고 열리는 목록도 같은 FoxSelectOption을 쓰지만, 폼 한 칸이 아닙니다 — 라벨·힌트·에러·필수 표시가 없고 테두리도 고정 높이도 없습니다. 배경 계열도 다릅니다: FoxSelect는 중립(흰 바탕 + 테두리)인데 이쪽은 강조 계열이라 기본이 투명이고 hover·pressed에서만 연한 파랑이 깔립니다 — 그래서 아래 예제의 기본 상태는 배경이 없는 것이 정상이고, 마우스를 올리고 눌러야 색이 보입니다. 크기가 글자 계열까지 바꾸는 것도 여기뿐입니다: lg는 heading/md(제목용 20px bold), md·sm은 label/*-strong(본문용 medium)입니다. '지금 무엇을 보고 있는지'를 글자로 보여 주면서 그 자리에서 바꾸게 하는 자리에 씁니다.",
+    variants: [
+      {
+        label: "크기 — lg(제목 20px bold) / md(15px medium) / sm(13px medium)",
+        node: (
+          <>
+            <FoxSelectText size="lg" ariaLabel="조회 기간" options={SELECT_TEXT_MONTHS} defaultValue="2026-08" />
+            <FoxSelectText size="md" ariaLabel="정렬 기준" options={SELECT_TEXT_SORTS} defaultValue="latest" />
+            <FoxSelectText size="sm" ariaLabel="정렬 기준" options={SELECT_TEXT_SORTS} defaultValue="latest" />
+          </>
+        ),
+      },
+      {
+        label: "아무것도 고르지 않으면 placeholder — 골라 보세요",
+        node: (
+          <>
+            <FoxSelectText size="lg" ariaLabel="조회 기간" options={SELECT_TEXT_MONTHS} placeholder="기간 선택" />
+            <FoxSelectText size="md" ariaLabel="정렬 기준" options={SELECT_TEXT_SORTS} />
+          </>
+        ),
+      },
+      {
+        label: "제목 옆에 놓기 — inline이라 줄 전체를 차지하지 않습니다",
+        node: (
+          <span style={{ display: "inline-flex", alignItems: "center", gap: "0.8rem" }}>
+            <FoxSelectText size="lg" ariaLabel="조회 기간" options={SELECT_TEXT_MONTHS} defaultValue="2026-08" />
+            <FoxSelectText size="sm" ariaLabel="정렬 기준" options={SELECT_TEXT_SORTS} defaultValue="amount" />
+          </span>
+        ),
+      },
+      {
+        label: "비활성 — 시안에 없어 색은 기본과 같고 hover·pressed만 죽습니다",
+        node: (
+          <>
+            <FoxSelectText size="lg" ariaLabel="조회 기간" options={SELECT_TEXT_MONTHS} defaultValue="2026-08" disabled />
+            <FoxSelectText size="md" ariaLabel="정렬 기준" options={SELECT_TEXT_SORTS} defaultValue="latest" disabled />
+          </>
+        ),
+      },
+      {
+        label: "세로 flex 안에 놓아도 목록 폭이 늘어나지 않는다 (트리거는 부모가 늘립니다)",
+        node: (
+          <div className={styles.demoStack}>
+            <FoxSelectText size="md" ariaLabel="조회 기간" options={SELECT_TEXT_MONTHS} defaultValue="2026-08" />
+            <FoxSelectText size="sm" ariaLabel="정렬 기준" options={SELECT_TEXT_SORTS} defaultValue="latest" />
+          </div>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-segmented-control-item",
+    name: "FoxSegmentedControlItem",
+    description:
+      "Figma 시안(segmented-control-item) 세그먼티드 컨트롤의 칸 하나입니다. 상태를 갖지 않습니다 — 켜짐 여부는 네이티브 input[type=radio]가 소유하므로, name만 같으면 단일 선택·화살표 이동·폼 전송을 브라우저가 처리합니다(FoxRadio와 같은 구조). 루트가 label이라 글자든 아이콘이든 어디를 눌러도 선택됩니다. 아래 예제는 묶는 테두리 없이 칸만 보여 주는 것이라 실제 사용 모습은 FoxSegmentedControl 쪽을 보세요 — 칸 오른쪽의 구분선이 이 컴포넌트 것이고, 감싸는 테두리와 모서리는 묶음 것입니다. 켜짐과 비활성은 구분선을 지웁니다 hover·pressed는 꺼져 있을 때만 걸립니다.",
+    variants: [
+      {
+        label: "크기 — lg 56 / md 40 / sm 28 / xsm 24 (아이콘도 24/20/16/12로 함께)",
+        node: (
+          <>
+            <FoxSegmentedControlItem name="sci-lg" size="lg" label="목록" icon={<DemoStar />} defaultChecked />
+            <FoxSegmentedControlItem name="sci-md" size="md" label="목록" icon={<DemoStar />} defaultChecked />
+            <FoxSegmentedControlItem name="sci-sm" size="sm" label="목록" icon={<DemoStar />} defaultChecked />
+            <FoxSegmentedControlItem name="sci-xsm" size="xsm" label="목록" icon={<DemoStar />} defaultChecked />
+          </>
+        ),
+      },
+      {
+        label: "상태 — 꺼짐 / 켜짐 / 비활성+꺼짐 / 비활성+켜짐 (마우스를 올리고 눌러 보세요)",
+        node: (
+          <>
+            <FoxSegmentedControlItem name="sci-a" size="md" label="꺼짐" />
+            <FoxSegmentedControlItem name="sci-b" size="md" label="켜짐" defaultChecked />
+            <FoxSegmentedControlItem name="sci-c" size="md" label="비활성" disabled />
+            <FoxSegmentedControlItem name="sci-d" size="md" label="비활성 켜짐" disabled defaultChecked />
+          </>
+        ),
+      },
+      {
+        label: "아이콘 없이 — 넘기지 않으면 자리를 만들지 않습니다",
+        node: (
+          <>
+            <FoxSegmentedControlItem name="sci-e" size="md" label="글자만" />
+            <FoxSegmentedControlItem name="sci-f" size="md" label="아이콘" icon={<DemoStar />} />
+          </>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-segmented-control",
+    name: "FoxSegmentedControl",
+    description:
+      "Figma 시안(segmented-control) 칸들을 테두리 하나로 감싸고 name·size를 통일합니다. 상태를 갖지 않습니다 — 어느 칸이 켜졌는지는 각 칸의 네이티브 라디오가 소유하므로 아래 예제는 전부 실제로 눌러서 바꿀 수 있고, 화살표 키로도 이동합니다. size는 묶음의 모서리와 칸의 높이를 함께 정합니다: 둘이 어긋나면 칸 배경이 둥근 모서리와 맞지 않아서, 자식이 준 size는 무시되고 묶음 값으로 통일됩니다. overflow: hidden으로 채워진 칸 배경을 모서리에 맞춰 잘라 냅니다. name도 묶음이 단일 진실 공급원입니다 — 한 칸만 빼먹어 두 개가 켜지는 실수가 생기지 않습니다.",
+    variants: [
+      {
+        label: "크기 — lg(모서리 12) / md(8) / sm(4) / xsm(4)",
+        node: (
+          <div className={styles.demoStackStart}>
+            <FoxSegmentedControl size="lg" name="sc-lg" label="보기 방식 lg">
+              <FoxSegmentedControlItem label="전체" value="all" defaultChecked />
+              <FoxSegmentedControlItem label="입금" value="in" />
+              <FoxSegmentedControlItem label="출금" value="out" />
+            </FoxSegmentedControl>
+            <FoxSegmentedControl size="md" name="sc-md" label="보기 방식 md">
+              <FoxSegmentedControlItem label="전체" value="all" defaultChecked />
+              <FoxSegmentedControlItem label="입금" value="in" />
+              <FoxSegmentedControlItem label="출금" value="out" />
+            </FoxSegmentedControl>
+            <FoxSegmentedControl size="sm" name="sc-sm" label="보기 방식 sm">
+              <FoxSegmentedControlItem label="전체" value="all" defaultChecked />
+              <FoxSegmentedControlItem label="입금" value="in" />
+              <FoxSegmentedControlItem label="출금" value="out" />
+            </FoxSegmentedControl>
+            <FoxSegmentedControl size="xsm" name="sc-xsm" label="보기 방식 xsm">
+              <FoxSegmentedControlItem label="전체" value="all" defaultChecked />
+              <FoxSegmentedControlItem label="입금" value="in" />
+              <FoxSegmentedControlItem label="출금" value="out" />
+            </FoxSegmentedControl>
+          </div>
+        ),
+      },
+      {
+        label: "아이콘 + 글자 — 첫 칸·마지막 칸이 켜져도 배경이 모서리를 넘지 않습니다",
+        node: (
+          <FoxSegmentedControl size="md" name="sc-icon" label="정렬 방식">
+            <FoxSegmentedControlItem label="목록" value="list" icon={<DemoStar />} defaultChecked />
+            <FoxSegmentedControlItem label="카드" value="card" icon={<DemoStar />} />
+            <FoxSegmentedControlItem label="표" value="table" icon={<DemoStar />} />
+          </FoxSegmentedControl>
+        ),
+      },
+      {
+        label: "size — 자식이 준 값은 무시되고 묶음 값으로 통일된다",
+        node: (
+          <FoxSegmentedControl size="sm" name="sc-override" label="크기 통일">
+            {/* 일부러 어긋난 size를 준다 — 셋 다 sm으로 나와야 정상 */}
+            <FoxSegmentedControlItem label="전체" value="all" size="lg" defaultChecked />
+            <FoxSegmentedControlItem label="입금" value="in" size="xsm" />
+            <FoxSegmentedControlItem label="출금" value="out" />
+          </FoxSegmentedControl>
+        ),
+      },
+      {
+        label: "칸 하나만 비활성 — 나머지는 그대로 고를 수 있습니다",
+        node: (
+          <FoxSegmentedControl size="md" name="sc-disabled" label="일부 비활성">
+            <FoxSegmentedControlItem label="전체" value="all" defaultChecked />
+            <FoxSegmentedControlItem label="입금" value="in" />
+            <FoxSegmentedControlItem label="준비중" value="soon" disabled />
+          </FoxSegmentedControl>
+        ),
+      },
+      {
+        label: "칸 두 개 / 다섯 개 — 개수와 무관하게 규칙이 같습니다",
+        node: (
+          <div className={styles.demoStackStart}>
+            <FoxSegmentedControl size="md" name="sc-two" label="두 칸">
+              <FoxSegmentedControlItem label="켜짐" value="on" defaultChecked />
+              <FoxSegmentedControlItem label="꺼짐" value="off" />
+            </FoxSegmentedControl>
+            <FoxSegmentedControl size="md" name="sc-five" label="다섯 칸">
+              <FoxSegmentedControlItem label="1월" value="1" />
+              <FoxSegmentedControlItem label="2월" value="2" />
+              <FoxSegmentedControlItem label="3월" value="3" defaultChecked />
+              <FoxSegmentedControlItem label="4월" value="4" />
+              <FoxSegmentedControlItem label="5월" value="5" />
+            </FoxSegmentedControl>
+          </div>
+        ),
+      },
+    ],
+  },
+  {
+    id: "fox-toggle-quantity",
+    name: "FoxToggleQuantity",
+    description:
+      "Figma 시안(toggle-quantity) 수량 조절기입니다. 회색 판 위에 빼기 버튼·숫자·더하기 버튼이 놓이고, 크기 수식어가 없습니다 — 시안이 한 벌만 줍니다. 값은 제어·비제어 둘 다 되고(FoxSelect와 같은 방식) min·max에 닿으면 해당 버튼이 네이티브 disabled가 되므로 누를 수 없다는 것이 커서와 보조기술에 함께 전달됩니다. 숫자는 입력칸이 아니라 읽기 전용 글자입니다 — 시안에 입력칸의 테두리도 배경도 없어서 role=spinbutton을 붙이지 않고, 대신 값에 aria-live를 걸어 바뀐 숫자를 읽어 줍니다. 숫자가 flex: 1 0 0이라 남는 자리를 혼자 가져가므로 값이 한 자리든 세 자리든 버튼 위치가 흔들리지 않습니다. 버튼은 FoxIconButton이 아닙니다 — 크기·모서리·아이콘 슬롯은 sm과 같지만 배경이 투명이 아니라 흰색이어야 회색 판 위에서 보입니다.",
+    variants: [
+      {
+        label: "기본 — 눌러서 바꿔 보세요 (min 0, 상한 없음)",
+        node: <FoxToggleQuantity label="수량" defaultValue={1} />,
+      },
+      {
+        label: "min에 닿으면 빼기가, max에 닿으면 더하기가 비활성됩니다 (min 1 / max 3)",
+        node: (
+          <>
+            <FoxToggleQuantity label="최소값" min={1} max={3} defaultValue={1} />
+            <FoxToggleQuantity label="중간값" min={1} max={3} defaultValue={2} />
+            <FoxToggleQuantity label="최대값" min={1} max={3} defaultValue={3} />
+          </>
+        ),
+      },
+      {
+        label: "step — 한 번에 10씩",
+        node: <FoxToggleQuantity label="10단위" step={10} max={100} defaultValue={10} />,
+      },
+      {
+        label: "자릿수가 늘어도 버튼 위치가 흔들리지 않습니다",
+        node: (
+          <>
+            <FoxToggleQuantity label="한 자리" defaultValue={9} />
+            <FoxToggleQuantity label="세 자리" defaultValue={999} />
+          </>
+        ),
+      },
+      {
+        label: "disabled — 둘 다 막습니다 (min·max에 의한 개별 비활성과는 별개)",
+        node: <FoxToggleQuantity label="비활성" defaultValue={5} disabled />,
+      },
+    ],
+  },
 ];
@fox/dev-test/dev-test.module.scss
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
@@ -417,6 +417,10 @@
 }
 
 // 그룹 예제용. 그룹이 블록 레벨이라 정렬·줄바꿈을 보려면 폭이 정해진 무대가 필요하다.
+//
+// ⚠️ 세로 flex의 기본 `align-items: stretch`가 자식을 가로로 늘린다. 인라인 컨트롤
+// (`inline-flex`·`inline-block`인 것들)을 여러 줄로 쌓아 비교할 때는 아래 `demoStackStart`를
+// 쓴다 — 이걸 쓰면 컴포넌트가 제 폭을 잃어 실제 모습과 다르게 보인다.
 .demoStack {
   display: flex;
   flex-direction: column;
@@ -424,6 +428,14 @@
   inline-size: 100%;
 }
 
+// 인라인 컨트롤을 세로로 쌓아 비교할 때. 자식이 늘어나지 않아 제 폭 그대로 보인다.
+.demoStackStart {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: fox.gap(4);
+}
+
 .demoOutline {
   padding: fox.padding(3);
   border: fox.border(1) dashed fox.color(border-neutral-subtle);
 
@fox/styles/_fox-chip-area.scss (added)
+++ @fox/styles/_fox-chip-area.scss
@@ -0,0 +1,27 @@
+// FoxChipArea — 시안: 통합관리자페이지 디자인시스템 Figma chip-area
+//
+// 칩 여러 개를 나란히 놓는 자리다. 시안이 주는 규칙이 세 줄뿐이라 이 파일도 세 줄로 끝난다 —
+// 칩 사이 간격 말고는 아무것도 정하지 않는다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-chip-area" role="group" aria-label="거래 구분">
+//     <button type="button" class="fox-chip fox-chip--md fox-chip--action">…</button>
+//     <button type="button" class="fox-chip fox-chip--md fox-chip--action">…</button>
+//   </div>
+//
+// `inline-flex`라 묶음이 내용만큼만 넓다 — 옆에 다른 것을 놓을 수 있다. 줄 전체를 차지해야
+// 하면 호출부가 `className`으로 폭을 준다(그것이 `className`을 여는 유일한 이유다).
+//
+// `align-items: center`가 필요한 이유는 칩의 크기(높이)가 섞일 수 있어서다. `FoxChipArea`의
+// `size`로 통일하면 섞이지 않지만, 통일하지 않고 쓰는 것도 막지 않는다.
+//
+// ⚠️ 줄바꿈이 없다(`flex-wrap`을 주지 않는다) — 시안이 한 줄만 그린다. 폭이 모자라면 칩이
+// 눌리는 게 아니라 넘친다. 칩이 여러 줄로 흘러야 하는 자리가 나오면 시안에 먼저 추가한다.
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-chip-area {
+  display: inline-flex;
+  align-items: center;
+  gap: fox.gap(2);
+}
 
@fox/styles/_fox-chip-select-option.scss (added)
+++ @fox/styles/_fox-chip-select-option.scss
@@ -0,0 +1,112 @@
+// FoxChipSelectOption — 시안: 통합관리자페이지 디자인시스템 Figma chip-select-option
+//
+// 칩의 `select`가 열렸을 때 뜨는 목록이다. `fox-select-option`과 **다른 컴포넌트다** —
+// 저쪽은 글자 한 줄짜리 listbox고, 이쪽은 시안이 체크박스·라디오 목록이라 이미 있는
+// `fox-checkbox-group`·`fox-radio-group`을 그대로 담는다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-chip-select-option" role="group">
+//     <div class="fox-chip-select-option__list">
+//       <div class="fox-checkbox-group fox-checkbox-group--vertical">…</div>
+//     </div>
+//     <button type="button" class="fox-chip-select-option__confirm">확인</button>
+//   </div>
+//
+// 폭은 **내용이** 정한다. 시안이 280px을 주지만 고정하지 않는다(사용자 결정) — 항목 글자가
+// 길면 잘리고 짧으면 남는데, 어느 쪽도 목록이 스스로 해결할 수 있는 문제가 아니기 때문이다.
+// 여는 칩의 폭도 따라가지 않는다: 칩은 글자만큼만 좁아서 거기 맞추면 읽을 수 없다
+// (`fox-select-option`의 `inset-inline: 0`을 쓰지 않는 이유이기도 하다).
+//
+// 폭을 못박아야 하는 자리가 생기면 호출부가 `className`으로 준다.
+//
+// ⚠️ 확인 버튼은 **다중 선택 + 모바일**에서만 보인다. 조건이 둘이고 걸리는 자리가 다르다:
+//
+// - **다중 선택에만 있다** — React가 정한다. 단일 선택은 하나 고르는 순간 목록이 닫혀서 이
+//   버튼이 할 일이 없다(눌러도 아무 일이 없는 버튼이 화면 아래를 차지하게 된다).
+// - **모바일에만 보인다**(시안 주석 "모바일만 노출") — 이건 CSS가 정한다. 조건부 렌더로
+//   하면 창 폭이 바뀔 때마다 DOM이 들락거려서, 열린 채로 화면을 돌리면 포커스가 날아간다.
+//
+// 값이 반영되는 시점은 플랫폼과 무관하게 같다(고르는 즉시). 모바일에서 달라지는 것은 닫는
+// 수단이 하나 더 생기는 것뿐이고, 데스크톱에서 닫는 것은 바깥 클릭이다.
+//
+// ⚠️ `overflow: hidden`이 필요하다. 확인 버튼이 `align-self: stretch`라 판 좌우 끝까지
+// 닿는데, 판의 둥근 모서리 밖으로 사각형 배경이 삐져나온다. 버튼에 모서리를 따로 주는 대신
+// 판에서 한 번 잘라 낸다(`fox-segmented-control`과 같은 방식).
+//
+// 토큰이 아닌 값:
+//   - ⚠️ `max-block-size: 24rem`(240px): 항목이 많을 때의 상한. `fox-select-option`이 같은
+//     이유로 잡아 둔 임시값을 그대로 따랐다(그 파일의 TODO 참고). 확정되면 함께 고친다.
+//   - box-sizing / position / display / flex / overflow / z-index / cursor: 구조 규칙.
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-chip-select-option {
+  box-sizing: border-box;
+  position: absolute;
+  inset-inline-start: 0;
+  inset-block-start: calc(100% + #{fox.gap(2)});
+  z-index: 10;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  // 내용이 폭을 정한다(파일 상단 주석 참고). `max-content`라 항목 글자가 줄바꿈되지 않고,
+  // 위치 기준인 `.fox-chip-select`가 좁아도 거기 눌리지 않는다.
+  inline-size: max-content;
+  // 확인 버튼의 사각 배경을 둥근 모서리로 잘라 낸다(파일 상단 ⚠️ 참고).
+  overflow: hidden;
+  border: fox.border(1) solid fox.color(border-neutral-subtle);
+  border-radius: fox.radius(3);
+  background: fox.color(surface-neutral-default);
+
+  // 항목이 놓이는 자리. 판에는 여백을 줄 수 없어(확인 버튼이 끝까지 닿아야 한다) 여기서 준다.
+  &__list {
+    box-sizing: border-box;
+    padding-block: fox.padding(8);
+    padding-inline: fox.padding(9);
+    // 판이 `align-items: flex-start`라 그냥 두면 내용만큼만 넓다. 시안이 이 자리에
+    // `align-self: stretch`를 준다 — 판 폭이 확인 버튼 때문에 더 넓어져도 여백이 따라간다.
+    align-self: stretch;
+    // ⚠️ 확정 전 임시값 — 파일 상단 승인 목록 참고.
+    max-block-size: 24rem;
+    overflow-y: auto;
+  }
+
+  // 모바일 전용. 판 좌우 끝까지 닿는다(`align-self: stretch`).
+  &__confirm {
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    align-self: stretch;
+    flex-shrink: 0;
+    block-size: fox.form(height-lg);
+    padding-inline: fox.form(padding-lg);
+    gap: fox.gap(2);
+    border: none;
+    background: fox.color(button-primary-surface);
+    cursor: pointer;
+
+    color: fox.color(button-primary-font);
+    font-family: fox.font-family(body);
+    font-size: fox.font-size(label-lg);
+    font-weight: fox.font-weight(medium);
+    line-height: 1;
+    letter-spacing: -0.025em;
+
+    // 데스크톱에서는 숨긴다(파일 상단 ⚠️ 참고).
+    @include fox.pc {
+      display: none;
+    }
+  }
+}
+
+// 항목 사이 간격만 묶음의 기본값과 다르다 — 체크박스·라디오 묶음의 세로 기본이 `gap-7`(24px)
+// 인데 이 시안은 `gap-5`(16px)다. 나머지(세로 배치·왼쪽 정렬)는 묶음 것과 같아서 건드리지
+// 않는다. 묶음이 `className`을 여는 목적("배치 조정용")에 정확히 해당한다.
+//
+// 클래스 둘을 겹쳐 적는다 — 묶음의 `--vertical`과 특정도가 같아, 겹치지 않으면 파일을
+// @use 하는 순서가 승부를 가른다.
+.fox-checkbox-group.fox-chip-select-option__group,
+.fox-radio-group.fox-chip-select-option__group {
+  gap: fox.gap(5);
+}
 
@fox/styles/_fox-chip.scss (added)
+++ @fox/styles/_fox-chip.scss
@@ -0,0 +1,171 @@
+// FoxChip — 시안: 통합관리자페이지 디자인시스템 Figma chip
+//
+// 각진 작은 조각이고 **켜짐 여부를 갖는다** — 이것이 `fox-tag`와의 차이다. 태그는 눌러서
+// 사라지거나 어디로 넘어가는 조각이라 상태가 남지 않고, 칩은 누른 결과가 자기 자신에게 남는다.
+// 그래서 알약(`radius: max`)이 아니라 폼 요소와 같은 모서리(`form-radius-*`)를 쓴다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <button type="button" class="fox-chip fox-chip--md fox-chip--check" aria-pressed="false">
+//     <span class="fox-chip__label">레이블</span>
+//   </button>
+//   <button type="button" class="fox-chip fox-chip--md fox-chip--check fox-chip--checked"
+//           aria-pressed="true">
+//     <span class="fox-chip__icon">…체크 svg…</span>      <!-- 켜졌을 때만 -->
+//     <span class="fox-chip__label">레이블</span>
+//   </button>
+//   <div class="fox-chip-select">                          <!-- 목록의 위치 기준 -->
+//     <button type="button" class="fox-chip fox-chip--lg fox-chip--select" role="combobox"
+//             aria-haspopup="listbox" aria-expanded="false">
+//       <span class="fox-chip__icon">…앞 아이콘 svg…</span>  <!-- 넘긴 때만 -->
+//       <span class="fox-chip__label">레이블</span>
+//       <span class="fox-chip__icon fox-chip__arrow">…화살표 svg…</span>
+//     </button>
+//     <ul class="fox-select-option fox-chip-select__list" role="listbox">…</ul>  <!-- 열렸을 때만 -->
+//   </div>
+//
+// 켜짐을 `aria-pressed`가 아니라 `--checked` 클래스가 표시한다. 계열마다 켜짐의 출처가
+// 달라서다 — check·action은 `aria-pressed`를 들지만 select는 `role="combobox"`라 그 속성을
+// 쓸 수 없다(고른 값이 있으면 켜진 것으로 본다). 클래스 하나로 모으면 모양 규칙이 한 벌로 끝난다.
+//
+// 상태 규칙은 계열과 무관하게 같다 — 계열이 정하는 것은 아이콘의 유무와 자리뿐이고, 아이콘이
+// 앞이냐 뒤냐는 **마크업 순서가 정한다**(`fox-tag`와 같은 방식. `order`나 `row-reverse`를 쓰지
+// 않는다).
+//
+// ⚠️ hover는 꺼져 있을 때만 걸린다. 시안이 켜짐 위의 hover를 그리지 않았고, 임의로 만들면
+// 남색 채움이 눌린 채로 흔들린다. 선택자에 `:not(.fox-chip--checked)`를 박아 못박는다.
+//
+// ⚠️ 비활성은 켜짐 여부와 무관하게 한 벌이다(시안 확인 완료 — 비활성+꺼짐 시안이 따로 없다).
+// 체크 아이콘은 계속 `--checked`가 정하므로 비활성+켜짐이면 흐린 회색 체크가 남는다.
+//
+// 크기가 바꾸는 것: 높이·좌우 여백·모서리·글자 크기·아이콘 크기. gap은 두 크기가 같다(`gap-2`).
+// 아이콘만 크기를 따라가는 점이 `fox-tag`와 다르다 — 태그는 세 크기가 모두 16px이지만
+// 시안의 칩은 md 16px(`icon-3`) / lg 20px(`icon-4`)다.
+//
+// 토큰이 아닌 값(`fox-button`·`fox-tag`와 동일한 근거로 승인된 항목):
+//   - line-height / letter-spacing: 시안 텍스트 스타일 label/*의 값이나 Figma가 변수로
+//     export하지 않는다. 두 크기가 모두 `-0.025em`이라 규칙 하나로 끝난다(md는 label/md
+//     15px→-0.375px, lg는 label/lg 17px→-0.425px — 시안 확인 완료). px이 아니라 em인 이유는
+//     글자 크기가 크기 수식어마다, 그리고 브레이크포인트마다 달라지기 때문이다 — px으로
+//     박으면 그 자리에서 비율이 어긋난다.
+//   - box-sizing / display / flex / cursor / white-space / aspect-ratio: 구조·상호작용 규칙.
+//   - `--checked`의 `border-color: transparent`: 시안이 켜짐에 테두리를 주지 않는다. 테두리를
+//     지우는 대신 투명하게 두는 이유는 배경이 그 자리까지 칠해져 모양은 같으면서, 높이·좌우
+//     여백이 계열·상태에 상관없이 한 값으로 유지되기 때문이다.
+//   - `fox-chip-select__list`의 위치·폭: 구조 규칙(아래 주석 참고).
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-chip {
+  // 안쪽 여백과 테두리가 바깥 크기를 늘리지 않게 한다 — 고정 높이가 흔들리지 않는다.
+  box-sizing: border-box;
+  display: inline-flex;
+  align-items: center;
+  border: fox.border(1) solid fox.color(border-neutral-default);
+  background: fox.color(action-default);
+  cursor: pointer;
+
+  color: fox.color(font-neutral-default);
+  font-family: fox.font-family(body);
+  font-weight: fox.font-weight(regular);
+  line-height: 1;
+  letter-spacing: -0.025em;
+  white-space: nowrap;
+
+  // 글자만 줄어들게 해 아이콘이 밀리지 않게 한다.
+  &__label {
+    min-inline-size: 0;
+  }
+
+  &__icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+    // 시안이 아이콘에 정사각 비율을 못박는다. 자리가 좁아 눌려도 글리프가 찌그러지지 않는다.
+    aspect-ratio: 1 / 1;
+  }
+
+  // 넘긴 SVG가 슬롯을 꽉 채운다 — 크기는 슬롯이 정하고 SVG는 따라온다.
+  &__icon > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  // 화살표만 글자보다 한 단계 흐리다(시안 #6D7882 = font-neutral-disabled-strong). 기본
+  // 상태에서만이다 — 아래 상태 규칙이 색을 물려주므로 남색 배경 위에서 회색으로 남지 않는다.
+  &__arrow {
+    color: fox.color(font-neutral-disabled-strong);
+  }
+
+  // ── 크기 ────────────────────────────────────────────────────────────────────
+  &--lg {
+    block-size: fox.form(height-md);
+    padding-inline: fox.padding(4);
+    gap: fox.gap(2);
+    border-radius: fox.form(radius-md);
+    font-size: fox.font-size(label-lg);
+  }
+
+  &--lg &__icon {
+    inline-size: fox.icon(4);
+    block-size: fox.icon(4);
+  }
+
+  &--md {
+    block-size: fox.form(height-sm);
+    padding-inline: fox.padding(3);
+    gap: fox.gap(2);
+    border-radius: fox.form(radius-xsm);
+    font-size: fox.font-size(label-md);
+  }
+
+  &--md &__icon {
+    inline-size: fox.icon(3);
+    block-size: fox.icon(3);
+  }
+
+  // ── 상태 ────────────────────────────────────────────────────────────────────
+  // 순서가 규칙이다: hover → checked → disabled. 뒤에 오는 것이 이긴다.
+
+  // 꺼져 있을 때만 걸린다(파일 상단 ⚠️ 참고).
+  &:not(&--checked):hover:not(:disabled) {
+    border-color: fox.color(border-theme-secondary);
+    background: fox.color(action-secondary-hover);
+    color: fox.color(font-theme-secondary);
+  }
+
+  &--checked {
+    // 배경이 테두리 자리까지 칠해져 모양은 테두리 없는 것과 같다(상단 승인 항목 참고).
+    border-color: transparent;
+    background: fox.color(action-secondary-active);
+    color: fox.color(font-neutral-static-inverse);
+  }
+
+  &:disabled {
+    border-color: fox.color(border-neutral-disabled);
+    background: fox.color(element-neutral-disabled);
+    color: fox.color(font-neutral-disabled-strong);
+    cursor: default;
+  }
+
+  // 화살표는 기본 상태에서만 흐리고, 상태가 바뀌면 글자 색을 그대로 따라간다.
+  &:not(&--checked):hover:not(:disabled) &__arrow,
+  &--checked &__arrow,
+  &:disabled &__arrow {
+    color: inherit;
+  }
+}
+
+// select 계열을 감싸는 자리. `fox-select-option`이 `position: absolute`라 위치 기준이 되는
+// 조상이 필요하다(`fox-select__control`과 같은 역할).
+//
+// `inline-block`인 이유는 칩이 글자만큼만 넓어야 하기 때문이다 — 블록이면 줄 전체를 차지해
+// 칩 여러 개를 나란히 놓을 수 없다.
+.fox-chip-select {
+  position: relative;
+  display: inline-block;
+}
+
+// 목록의 폭·위치 규칙은 여기에 없다 — `fox-chip-select-option`이 280px 고정 폭을 스스로
+// 갖는다. 예전에는 `fox-select-option`을 빌려 쓰면서 폭을 여기서 덮어썼는데, 전용 목록이
+// 생기면서 그 덮어쓰기가 필요 없어졌다.
 
@fox/styles/_fox-segmented-control-item.scss (added)
+++ @fox/styles/_fox-segmented-control-item.scss
@@ -0,0 +1,197 @@
+// FoxSegmentedControlItem — 시안: 통합관리자페이지 디자인시스템 Figma segmented-control-item
+//
+// 세그먼티드 컨트롤의 칸 하나다. 묶는 테두리와 모서리는 `fox-segmented-control`이 갖고,
+// 이 파일은 칸 안쪽(높이·여백·글자·아이콘·상태 배경)과 칸 사이 구분선만 책임진다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <label class="fox-segmented-control-item fox-segmented-control-item--lg">
+//     <input type="radio" class="fox-segmented-control-item__input" name="view" value="list">
+//     <span class="fox-segmented-control-item__icon">…svg…</span>   <!-- 넘긴 때만 -->
+//     <span class="fox-segmented-control-item__label">목록</span>
+//   </label>
+//
+// 루트가 `<label>`이라 어디를 눌러도 브라우저가 선택을 처리한다. 상태는 전부 네이티브
+// `<input type="radio">`가 소유한다 — `name`이 같으면 단일 선택·화살표 이동·폼 전송까지
+// 브라우저가 알아서 한다(규약 3, `fox-radio`와 같은 구조).
+//
+// input은 순서상 **맨 앞**에 와야 한다. 시각적으로만 숨기고 포커스는 살려 둔다 —
+// `display: none`으로 지우면 키보드로 도달할 수 없다.
+//
+// 켜짐·비활성을 `:has()`로 본다. `fox-radio`는 표시가 input의 **형제**라 `~`로 닿지만,
+// 여기서는 색이 바뀌는 것이 input을 **감싼 루트**라 형제 선택자로는 닿을 수 없다
+// (`fox-checkbox`가 루트 커서를 바꿀 때 쓰는 방식과 같다).
+//
+// ── 구분선 ────────────────────────────────────────────────────────────────────
+// 칸마다 **오른쪽에만** 선을 둔다. 칸 N과 N+1 사이의 선은 N이 갖는다.
+//
+// ⚠️ 마지막 칸의 선은 지운다(투명). 시안에 없는 처리지만, 두면 묶음 자신의 테두리 바로
+// 안쪽에 같은 색 선이 하나 더 생겨 두 줄로 보인다. 지우지 않고 투명으로 두는 이유는 폭이
+// 남아 있어야 칸 너비가 위치에 따라 흔들리지 않기 때문이다.
+//
+// 켜짐·비활성은 선을 갖지 않는다 — 배경이 채워지는 상태라 시안이 `border-right`를 주지 않는다.
+//
+// ── 상태 ──────────────────────────────────────────────────────────────────────
+// ⚠️ hover·pressed는 꺼져 있을 때만 걸린다. 시안이 켜짐 위의 hover를 그리지 않았고, 임의로
+// 만들면 채워진 남색이 눌린 채로 흔들린다.
+//
+// 크기가 바꾸는 것: 높이·좌우 여백·글자 크기·아이콘 크기, 그리고 xsm만 간격(`gap-1`)이다.
+//
+// 토큰이 아닌 값(`fox-button`·`fox-tag`와 동일한 근거로 승인된 항목):
+//   - letter-spacing: 시안 텍스트 스타일 label/*-strong의 값이나 Figma가 변수로 export하지
+//     않는다. 네 크기가 모두 `-0.025em`이라 규칙 하나로 끝난다(17px→-0.425px,
+//     15px→-0.375px, 13px→-0.325px, 11px→-0.275px). px이 아니라 em인 이유는 글자 크기가 크기
+//     수식어마다, 그리고 브레이크포인트마다 달라지기 때문이다.
+//   - 포커스 링의 `outline-offset` 음수: 묶음이 `overflow: hidden`이라 바깥으로 그리면
+//     잘린다. 안쪽으로 그려야 첫 칸·마지막 칸에서도 온전히 보인다.
+//   - box-sizing / position / display / flex / cursor / white-space / aspect-ratio:
+//     구조·상호작용 규칙.
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-segmented-control-item {
+  // 여백과 구분선이 바깥 크기를 늘리지 않게 한다 — 고정 높이가 흔들리지 않는다.
+  box-sizing: border-box;
+  // 숨긴 input의 위치 기준.
+  position: relative;
+  display: inline-flex;
+  justify-content: center;
+  align-items: center;
+  border-inline-end: fox.border(1) solid fox.color(button-default-border);
+  background: fox.color(action-secondary);
+  cursor: pointer;
+
+  color: fox.color(font-neutral-default);
+  font-family: fox.font-family(body);
+  font-weight: fox.font-weight(medium);
+  line-height: 1;
+  letter-spacing: -0.025em;
+  text-align: center;
+  white-space: nowrap;
+
+  // 시각적으로만 숨긴다. `opacity: 0`은 포커스를 유지하므로 키보드로 도달할 수 있고,
+  // 클릭은 루트 `<label>`이 받는다.
+  &__input {
+    position: absolute;
+    inline-size: 0;
+    block-size: 0;
+    margin: 0;
+    opacity: 0;
+    pointer-events: none;
+  }
+
+  &__icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+    // 시안이 아이콘에 정사각 비율을 못박는다. 자리가 좁아 눌려도 글리프가 찌그러지지 않는다.
+    aspect-ratio: 1 / 1;
+  }
+
+  // 넘긴 SVG가 슬롯을 꽉 채운다 — 크기는 슬롯이 정하고 SVG는 따라온다.
+  &__icon > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  // 글자만 줄어들게 해 아이콘이 밀리지 않게 한다.
+  &__label {
+    min-inline-size: 0;
+  }
+
+  // ── 크기 ────────────────────────────────────────────────────────────────────
+  &--lg {
+    block-size: fox.form(height-lg);
+    padding-inline: fox.form(padding-lg);
+    gap: fox.gap(2);
+    font-size: fox.font-size(label-lg);
+  }
+
+  &--lg &__icon {
+    inline-size: fox.form(icon-lg);
+    block-size: fox.form(icon-lg);
+  }
+
+  &--md {
+    block-size: fox.form(height-md);
+    padding-inline: fox.form(padding-md);
+    gap: fox.gap(2);
+    font-size: fox.font-size(label-md);
+  }
+
+  &--md &__icon {
+    inline-size: fox.form(icon-md);
+    block-size: fox.form(icon-md);
+  }
+
+  &--sm {
+    block-size: fox.form(height-sm);
+    padding-inline: fox.form(padding-sm);
+    gap: fox.gap(2);
+    font-size: fox.font-size(label-sm);
+  }
+
+  &--sm &__icon {
+    inline-size: fox.form(icon-sm);
+    block-size: fox.form(icon-sm);
+  }
+
+  // xsm만 간격이 한 단계 좁다.
+  &--xsm {
+    block-size: fox.form(height-xsm);
+    padding-inline: fox.form(padding-xsm);
+    gap: fox.gap(1);
+    font-size: fox.font-size(label-xsm);
+  }
+
+  &--xsm &__icon {
+    inline-size: fox.form(icon-xsm);
+    block-size: fox.form(icon-xsm);
+  }
+}
+
+// ── 상태 ──────────────────────────────────────────────────────────────────────
+// 순서가 규칙이다: hover·pressed → 켜짐 → 비활성. 뒤에 오는 것이 이긴다. `fox-radio`처럼
+// 중첩 밖에 평평하게 적는다 — `:has()` 안에 전체 클래스 이름이 그대로 보여야 읽힌다.
+
+// 꺼져 있고 쓸 수 있을 때만 걸린다(파일 상단 ⚠️ 참고).
+.fox-segmented-control-item:not(:has(.fox-segmented-control-item__input:checked)):not(
+    :has(.fox-segmented-control-item__input:disabled)
+  ):hover {
+  background: fox.color(action-secondary-hover);
+}
+
+// 시안의 pressed는 `:active`가 받는다.
+.fox-segmented-control-item:not(:has(.fox-segmented-control-item__input:checked)):not(
+    :has(.fox-segmented-control-item__input:disabled)
+  ):active {
+  background: fox.color(action-secondary-pressed);
+}
+
+// 켜짐. 글자와 아이콘이 함께 반전색이 된다 — 아이콘은 `currentColor`로 그려야 따라온다.
+.fox-segmented-control-item:has(.fox-segmented-control-item__input:checked) {
+  border-inline-end-color: transparent;
+  background: fox.color(action-secondary-active);
+  color: fox.color(font-neutral-static-inverse);
+}
+
+.fox-segmented-control-item:has(.fox-segmented-control-item__input:disabled) {
+  border-inline-end-color: transparent;
+  background: fox.color(element-neutral-disabled);
+  color: fox.color(font-neutral-disabled-strong);
+  cursor: default;
+}
+
+// 마지막 칸은 구분선을 지운다(파일 상단 ⚠️ 참고). 켜짐·비활성 규칙과 특이도가 같아 지지
+// 않도록 그 뒤에 둔다 — 어차피 셋 다 같은 값이라 누가 이겨도 결과는 같다.
+.fox-segmented-control-item:last-child {
+  border-inline-end-color: transparent;
+}
+
+// ── 포커스 ────────────────────────────────────────────────────────────────────
+// input이 시각적으로 숨겨져 브라우저 기본 링이 보이지 않으므로 여기서 대신 그린다.
+// 안쪽으로 그리는 이유는 파일 상단 승인 목록 참고.
+.fox-segmented-control-item:has(.fox-segmented-control-item__input:focus-visible) {
+  outline: fox.border(2) solid fox.color(border-theme-primary);
+  outline-offset: calc(-1 * #{fox.border(2)});
+}
 
@fox/styles/_fox-segmented-control.scss (added)
+++ @fox/styles/_fox-segmented-control.scss
@@ -0,0 +1,51 @@
+// FoxSegmentedControl — 시안: 통합관리자페이지 디자인시스템 Figma segmented-control
+//
+// 칸들을 테두리 하나로 감싸는 자리다. 시안이 주는 규칙이 배치 두 줄과 크기별 모서리뿐이라
+// 이 파일도 그만큼이다 — 칸 안쪽은 `fox-segmented-control-item`이 전부 갖는다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-segmented-control fox-segmented-control--lg" role="radiogroup"
+//        aria-label="보기 방식">
+//     <label class="fox-segmented-control-item fox-segmented-control-item--lg">…</label>
+//     <label class="fox-segmented-control-item fox-segmented-control-item--lg">…</label>
+//   </div>
+//
+// `inline-flex`라 묶음이 내용만큼만 넓다 — 옆에 다른 것을 놓을 수 있다. 줄 전체를 차지해야
+// 하면 호출부가 `className`으로 폭을 준다(그것이 `className`을 여는 유일한 이유다).
+//
+// ⚠️ `overflow: hidden`이 필요하다. 켜진 칸과 비활성 칸은 배경이 채워지는데, 첫 칸·마지막
+// 칸에서 그 사각형 배경이 묶음의 둥근 모서리 밖으로 삐져나온다. 칸마다 모서리를 주는 대신
+// 묶음에서 한 번 잘라 낸다 — 칸이 몇 개든, 어느 칸이 켜지든 규칙이 하나로 끝난다.
+//
+// 크기가 바꾸는 것은 모서리뿐이다. 높이·여백·글자는 칸이 갖는다 — 그래서 묶음과 칸의
+// `size`가 어긋나면 안 되고, React 쪽에서 묶음이 자식 값을 덮어쓴다.
+//
+// 토큰이 아닌 값:
+//   - box-sizing / display / flex / overflow: 구조 규칙.
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-segmented-control {
+  box-sizing: border-box;
+  display: inline-flex;
+  align-items: center;
+  // 채워진 칸 배경을 둥근 모서리로 잘라 낸다(파일 상단 ⚠️ 참고).
+  overflow: hidden;
+  border: fox.border(1) solid fox.color(border-neutral-default);
+
+  &--lg {
+    border-radius: fox.form(radius-lg);
+  }
+
+  &--md {
+    border-radius: fox.form(radius-md);
+  }
+
+  &--sm {
+    border-radius: fox.form(radius-sm);
+  }
+
+  &--xsm {
+    border-radius: fox.form(radius-xsm);
+  }
+}
 
@fox/styles/_fox-select-text.scss (added)
+++ @fox/styles/_fox-select-text.scss
@@ -0,0 +1,163 @@
+// FoxSelectText — 시안: 통합관리자페이지 디자인시스템 Figma select-txt
+//
+// 글자만 있는 셀렉트다. 테두리도 고정 높이도 없고, 배경만 세 상태로 바뀐다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-select-text fox-select-text--lg">          <!-- 목록의 위치 기준 -->
+//     <button type="button" class="fox-select-text__trigger" id="st" role="combobox"
+//             aria-haspopup="listbox" aria-expanded="true" aria-controls="st-listbox">
+//       <span class="fox-select-text__value">2026년 8월</span>
+//       <span class="fox-select-text__icon">…화살표 svg…</span>
+//     </button>
+//     <ul class="fox-select-option fox-select-text__list" id="st-listbox" role="listbox">
+//       <li class="fox-select-option-item" …>…</li>
+//     </ul>                                                     <!-- 열렸을 때만 -->
+//   </div>
+//
+// `fox-select`와 무엇이 다른가: **폼 한 칸이 아니다.** 라벨·힌트·에러·필수 표시가 없고,
+// 배경 계열도 다르다 — `fox-select`는 중립(`action-default`, 흰 바탕에 테두리)이지만 이쪽은
+// 강조(`action-primary`) 계열이라 기본이 **투명**이고 hover·pressed에서만 연한 파랑이 깔린다.
+// 크기가 글자 **계열까지** 바꾸는 것도 여기뿐이다 — lg는 heading/md(제목용 20px bold,
+// `font-family-title`), md·sm은 label/*-strong(본문용 medium)이다. 폼 한 칸이라면 크기를
+// 바꿨다고 제목 폰트가 될 이유가 없다. 즉 이것은 "지금 무엇을 보고 있는지"를 글자로 보여
+// 주면서 그 자리에서 바꾸게 하는 컨트롤이다.
+//
+// 크기가 바꾸는 것: 여백·간격·글자(계열·크기·굵기·줄높이)·아이콘 크기·글자 색. 모서리만
+// 세 크기가 같다(`form-radius-xsm`).
+//
+// ⚠️ lg만 위아래 여백이 없다(`padding: 0 var(--padding-2)`). md·sm은 사방에 준다 — 시안이
+// 그렇게 준다. lg는 줄높이가 140%라 그 자체로 위아래 여백을 만든다.
+//
+// ⚠️ 비활성은 시안에 없다. 색을 임의로 만들지 않고 hover·pressed만 막는다 — 네이티브
+// `disabled`를 주면 커서와 클릭만 죽고 모양은 기본과 같다(`fox-tag`와 같은 처리).
+//
+// 토큰이 아닌 값(`fox-button`·`fox-tag`와 동일한 근거로 승인된 항목):
+//   - letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로 export하지 않는다. 세 크기가
+//     모두 `-0.025em`이라 규칙 하나로 끝난다(20px→-0.5px, 15px→-0.375px, 13px→-0.325px).
+//     px이 아니라 em인 이유는 글자 크기가 크기 수식어마다, 그리고 브레이크포인트마다
+//     달라지기 때문이다 — px으로 박으면 그 자리에서 비율이 어긋난다.
+//   - line-height 1.4 / 1: 시안의 140% / 100%. 비율이라 토큰 스케일에 대응이 없다.
+//   - box-sizing / display / flex / cursor / white-space / aspect-ratio: 구조·상호작용 규칙.
+//   - `fox-select-text__list`의 위치·폭: 구조 규칙(아래 주석 참고).
+
+@use "@fox/styles/abstracts" as fox;
+
+// 목록의 위치 기준. `inline-block`인 이유는 글자만큼만 넓어야 하기 때문이다 — 블록이면 줄
+// 전체를 차지해 제목 옆에 다른 것을 놓을 수 없다.
+.fox-select-text {
+  position: relative;
+  display: inline-block;
+
+  &__trigger {
+    // 여백이 바깥 크기를 늘리지 않게 한다.
+    box-sizing: border-box;
+    display: inline-flex;
+    align-items: center;
+    border: none;
+    border-radius: fox.form(radius-xsm);
+    background: fox.color(action-primary);
+    cursor: pointer;
+
+    letter-spacing: -0.025em;
+    white-space: nowrap;
+
+    &:hover:not(:disabled) {
+      background: fox.color(action-primary-hover);
+    }
+
+    // 시안의 pressed는 `:active`가 받는다.
+    &:active:not(:disabled) {
+      background: fox.color(action-primary-pressed);
+    }
+
+    &:disabled {
+      cursor: default;
+    }
+  }
+
+  // 글자만 줄어들게 해 화살표가 밀리지 않게 한다.
+  &__value {
+    min-inline-size: 0;
+  }
+
+  &__icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+    // 시안이 아이콘에 정사각 비율을 못박는다. 자리가 좁아 눌려도 글리프가 찌그러지지 않는다.
+    aspect-ratio: 1 / 1;
+  }
+
+  // 넘긴 SVG가 슬롯을 꽉 채운다 — 크기는 슬롯이 정하고 SVG는 따라온다.
+  &__icon > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  // ── 크기 ────────────────────────────────────────────────────────────────────
+  // lg만 제목 계열이다(파일 상단 주석 참고).
+  &--lg &__trigger {
+    padding-block: 0;
+    padding-inline: fox.padding(2);
+    gap: fox.gap(2);
+    color: fox.color(font-neutral-strong);
+    font-family: fox.font-family(title);
+    font-size: fox.font-size(heading-md);
+    font-weight: fox.font-weight(bold);
+    line-height: 1.4;
+  }
+
+  &--lg &__icon {
+    inline-size: fox.form(icon-md);
+    block-size: fox.form(icon-md);
+  }
+
+  &--md &__trigger {
+    padding: fox.padding(2);
+    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(medium);
+    line-height: 1;
+  }
+
+  &--md &__icon {
+    inline-size: fox.form(icon-sm);
+    block-size: fox.form(icon-sm);
+  }
+
+  // sm만 여백과 간격이 한 단계 좁다.
+  &--sm &__trigger {
+    padding: fox.padding(1);
+    gap: fox.gap(1);
+    color: fox.color(font-neutral-default);
+    font-family: fox.font-family(body);
+    font-size: fox.font-size(label-sm);
+    font-weight: fox.font-weight(medium);
+    line-height: 1;
+  }
+
+  &--sm &__icon {
+    inline-size: fox.form(icon-xsm);
+    block-size: fox.form(icon-xsm);
+  }
+}
+
+// `fox-select-option`의 기본값은 `inset-inline: 0`, 즉 여는 요소와 같은 폭이다. 폼 한 칸을
+// 채우는 셀렉트에서는 맞지만 이쪽은 글자만큼만 좁아서 목록이 읽을 수 없게 눌린다. 왼쪽만
+// 맞추고 폭은 **내용이** 정하게 한다.
+//
+// ⚠️ `min-inline-size: 100%`(트리거보다 좁아지지 말 것)를 걸지 않는다. 퍼센트는 위치 기준인
+// `.fox-select-text`의 폭을 보는데, 이 요소는 `inline-block`이라 세로 flex·grid 안에 놓이면
+// 부모의 `align-items: stretch`에 늘어난다. 그러면 목록이 그 늘어난 폭을 그대로 물려받아
+// 화면을 가로지른다 — 목록 폭이 호출부 레이아웃에 끌려다니게 된다. 내용 폭만 보면 어디에
+// 놓이든 목록은 같은 크기다.
+//
+// 클래스 둘을 겹쳐 적는다 — 한쪽이 `inset-inline` 단축, 이쪽이 `inset-inline-end` 낱개라
+// 특정도가 같으면 파일을 @use 하는 순서가 승부를 가른다. 겹쳐 두면 순서와 무관하게 이긴다.
+.fox-select-option.fox-select-text__list {
+  inset-inline-end: auto;
+  inline-size: max-content;
+}
 
@fox/styles/_fox-toggle-quantity.scss (added)
+++ @fox/styles/_fox-toggle-quantity.scss
@@ -0,0 +1,115 @@
+// FoxToggleQuantity — 시안: 통합관리자페이지 디자인시스템 Figma toggle-quantity
+//
+// 회색 판 위에 빼기 버튼 · 숫자 · 더하기 버튼이 놓인다. 크기 수식어가 없다 — 시안이 한 벌만
+// 준다.
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-toggle-quantity" role="group" aria-label="수량">
+//     <button type="button" class="fox-toggle-quantity__button" aria-label="수량 줄이기" disabled>
+//       <span class="fox-toggle-quantity__icon">…빼기 svg…</span>
+//     </button>
+//     <span class="fox-toggle-quantity__value" aria-live="polite">1</span>
+//     <button type="button" class="fox-toggle-quantity__button" aria-label="수량 늘리기">
+//       <span class="fox-toggle-quantity__icon">…더하기 svg…</span>
+//     </button>
+//   </div>
+//
+// 숫자가 `flex: 1 0 0`이라 남는 자리를 혼자 가져간다 — 버튼 둘은 크기가 고정이므로 값이
+// 한 자리든 세 자리든 버튼 위치가 흔들리지 않는다.
+//
+// ⚠️ 버튼이 `fox-icon-button`이 아니다. `fox-icon-button--sm`과 크기(28)·모서리(4)·아이콘
+// 슬롯(20)이 전부 같지만 **배경이 다르다** — 아이콘 버튼의 `default` 계열은
+// `button-default-surface`(투명)인데 이 시안은 `action-default`(불투명 흰색)다. 회색 판
+// 위에 놓이므로 투명이면 판 색이 그대로 비쳐 버튼이 사라진다. 아이콘 버튼에 흰 배경 계열이
+// 생기면 그때 이 규칙을 걷어내고 갈아탄다.
+//
+// 비활성은 배경만 바뀐다 — 시안이 disabled에 `border`를 적지 않아 테두리를 지운다(투명으로
+// 두어 폭은 남긴다. 지우면 28×28이 26×26이 되어 판 안에서 자리가 밀린다).
+//
+// 토큰이 아닌 값:
+//   - ⚠️ `inline-size: 14rem`: **시안의 폭(140px)을 일단 그대로 적용해 둔 것이다.** Figma가
+//     변수로 export하지 않았고 토큰 스케일에도 140이 없어서 원시값으로 남아 있다. 토큰이
+//     생기면 `fox.*()`로 바꾸고, 폭을 고정하지 않는 쪽으로 정해지면 여기서 뺀다.
+//     그때까지 폭을 바꿔야 하는 자리는 호출부가 `className`으로 덮는다.
+//   - letter-spacing: 시안 텍스트 스타일 label/lg-em의 값이나 Figma가 변수로 export하지
+//     않는다. `-0.025em`이면 17px에서 -0.425px로 시안과 같고, 글자 크기가 브레이크포인트마다
+//     달라져도 비율이 유지된다(다른 컴포넌트와 같은 근거).
+//   - 아이콘 색 `fox.primitive(neutral-60)`: 시안 값 `#58616A`를 가진 **semantic 토큰이 없다**
+//     (icon 계열에 있는 것은 neutral-70인 `icon-neutral-default`와 neutral-50인
+//     `icon-neutral-subtle`뿐이다). 그래서 시안이 가리키는 원시값을 직접 쓴다 —
+//     `fox-select-option`이 그림자 색에 `alpha-black10`을 직접 쓰는 것과 같은 방식이다.
+//
+//     ⚠️ 원시값이라 **다크 모드에서 뒤집히지 않는다** — 밝은 회색 아이콘이 어두운 버튼 위에
+//     그대로 남는다(`action-default`는 다크에서 neutral-100이라 대비는 확보된다). 반전이
+//     필요해지면 Figma에서 icon 계열에 neutral-60을 추가한 뒤 그 토큰으로 바꾼다.
+//   - box-sizing / display / flex / cursor / aspect-ratio: 구조·상호작용 규칙.
+
+@use "@fox/styles/abstracts" as fox;
+
+.fox-toggle-quantity {
+  // 안쪽 여백이 바깥 폭을 늘리지 않게 한다 — 시안의 140px은 테두리까지 포함한 값이다.
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  // ⚠️ 시안 값을 일단 적용해 둔 상태다 — 파일 상단 주석 참고.
+  inline-size: 14rem;
+  padding: fox.padding(2);
+  gap: fox.gap(2);
+  border-radius: fox.radius(4);
+  background: fox.color(surface-neutral-gray);
+
+  &__button {
+    box-sizing: border-box;
+    display: flex;
+    flex-shrink: 0;
+    justify-content: center;
+    align-items: center;
+    inline-size: fox.form(height-sm);
+    block-size: fox.form(height-sm);
+    // 시안이 버튼에 정사각 비율을 못박는다. 자리가 좁아 눌려도 모양이 무너지지 않는다.
+    aspect-ratio: 1 / 1;
+    padding: 0;
+    border: fox.border(1) solid fox.color(button-default-border);
+    border-radius: fox.form(radius-sm);
+    background: fox.color(action-default);
+    // 시안 값을 가진 semantic 토큰이 없어 원시값을 직접 쓴다(파일 상단 승인 항목 참고).
+    color: fox.primitive(neutral-60);
+    cursor: pointer;
+
+    &:disabled {
+      // 테두리를 지우지 않고 투명으로 둔다(파일 상단 주석 참고).
+      border-color: transparent;
+      background: fox.color(button-disabled-surface);
+      cursor: default;
+    }
+  }
+
+  &__icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+    inline-size: fox.icon(4);
+    block-size: fox.icon(4);
+    aspect-ratio: 1 / 1;
+  }
+
+  // 넘긴 SVG가 슬롯을 꽉 채운다 — 크기는 슬롯이 정하고 SVG는 따라온다.
+  &__icon > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  // 남는 자리를 혼자 가져간다 — `flex-basis: 0`이라 글자 길이가 버튼 위치를 밀지 않는다.
+  &__value {
+    flex: 1 0 0;
+    color: fox.color(font-neutral-strong);
+    font-family: fox.font-family(body);
+    font-size: fox.font-size(label-lg);
+    font-weight: fox.font-weight(bold);
+    line-height: 1;
+    letter-spacing: -0.025em;
+    text-align: center;
+  }
+}
@fox/styles/components.scss
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
@@ -12,6 +12,9 @@
 @use "fox-check-icon";
 @use "fox-checkbox";
 @use "fox-checkbox-group";
+@use "fox-chip";
+@use "fox-chip-area";
+@use "fox-chip-select-option";
 @use "fox-conditional-option";
 @use "fox-email";
 @use "fox-file-upload";
@@ -21,9 +24,12 @@
 @use "fox-radio";
 @use "fox-radio-group";
 @use "fox-radio-icon";
+@use "fox-segmented-control";
+@use "fox-segmented-control-item";
 @use "fox-select";
 @use "fox-select-option";
 @use "fox-select-option-item";
+@use "fox-select-text";
 @use "fox-tag";
 @use "fox-tag-list";
 @use "fox-text-area";
@@ -44,3 +50,4 @@
 @use "fox-tab";
 @use "fox-tab-item";
 @use "fox-spinner";
+@use "fox-toggle-quantity";
Add a comment
List