임동욱 임동욱 08-13
feat: FoxInput 추가
Figma input(135:621)의 2타입 × 3크기 × 6상태를 옮긴다. 네이티브 을
감싸므로 시안에 없는 password·email·number 등 모든 타입이 그대로 통한다.

상태는 두 갈래다. 기본은 브라우저가 판단한다 — 포커스는 :focus-within,
비활성·읽기전용은 네이티브 속성, 오류는 aria-invalid. 시안의 completed는
default와 글자색만 다른데 그건 플레이스홀더와 입력값의 네이티브 차이라 규칙이
필요 없다. 모양을 못박아야 하면 루트에 data-state를 준다.

date는 브라우저가 그리는 달력 아이콘과 피커를 그대로 쓴다(사용자 승인).
지우기 버튼은 포커스 중이고 값이 있을 때만 뜨고 누르면 값을 비운다 —
비제어 입력은 DOM을 직접 비우고 제어 입력은 onChange()로 알린다.

헬퍼 메시지 색은 상태를 따라간다(기본 회색 · 포커스 파랑 · 완료 초록 ·
오류 빨강). 아이콘은 @fox에 아이콘 세트가 없어 슬롯으로 두었다.

Co-Authored-By: Claude Opus 5 
@f7f3037acb401297b7015cb0f1cd17bc111640fd
@fox/README.md
--- @fox/README.md
+++ @fox/README.md
@@ -100,6 +100,15 @@
   <div class="fox-button-group fox-button-group--horizontal fox-button-group--start">…</div>
   <div class="fox-button-group fox-button-group--horizontal fox-button-group--end">…</div>
 </div>
+
+<div class="fox-input fox-input--md">
+  <label class="fox-input__label" for="name">레이블</label>
+  <div class="fox-input__box">
+    <input class="fox-input__field" id="name" placeholder="내용을 입력하세요.">
+    <span class="fox-input__icon"><!-- svg (선택) --></span>
+  </div>
+  <p class="fox-input__message"><!-- 헬퍼 (선택) --></p>
+</div>
 ```
 
 - 비활성은 네이티브 `disabled` 속성으로 표현합니다(별도 클래스 없음). 앵커에는 `disabled`가
@@ -112,6 +121,9 @@
 - 버튼 패널은 그룹이 여럿이면 양끝으로, 하나면 가운데로 둡니다 — 자식 수는 CSS `:only-child`가
   판단하므로 따로 표시할 게 없습니다. 패널 안에서는 그룹 간격이 넓어지고 버튼에 최소 폭이
   붙으며, 모바일에서는 패널·그룹이 모두 세로로 쌓입니다.
+- 입력의 상태는 기본적으로 브라우저가 판단합니다 — 포커스는 `:focus-within`, 비활성·읽기전용은
+  네이티브 속성, 오류는 `aria-invalid="true"`. 모양을 못박으려면 루트에 `data-state`를 줍니다
+  (`default` `focused` `completed` `error` `disabled` `view`).
 - 아이콘 SVG는 `currentColor`로 그려야 계열별 색이 적용됩니다. 크기는 슬롯이 정합니다.
 
 `core/`의 React 컴포넌트는 이 클래스를 조립해주는 **얇은 래퍼**일 뿐입니다 — 쓰지 않아도
 
@fox/core/components/fox-input/fox-input.tsx (added)
+++ @fox/core/components/fox-input/fox-input.tsx
@@ -0,0 +1,193 @@
+"use client";
+
+import {
+  useId,
+  useRef,
+  useState,
+  type ChangeEvent,
+  type ComponentPropsWithRef,
+  type FormEvent,
+  type ReactNode,
+} from "react";
+import { cx } from "../../utils";
+
+export type FoxInputSize = "lg" | "md" | "sm";
+export type FoxInputState = "default" | "focused" | "completed" | "error" | "disabled" | "view";
+
+/**
+ * 네이티브 `<input>`이 받는 것을 그대로 넘겨받는다 — type·name·autoComplete·maxLength·
+ * min·max·step·pattern·inputMode·required·readOnly·disabled 등. 이름이 겹치거나 의미가
+ * 다른 것만 걷어내고 아래에서 다시 정의한다.
+ */
+type NativeInputProps = Omit<
+  ComponentPropsWithRef<"input">,
+  "size" | "onChange" | "onInput" | "className" | "children" | "hidden"
+>;
+
+export interface FoxInputProps extends NativeInputProps {
+  size?: FoxInputSize;
+  /** 값이 첫 인자다. 지우기 버튼은 이벤트 없이 빈 문자열로 호출한다. */
+  onChange?: (value: string, event?: ChangeEvent<HTMLInputElement>) => void;
+  onInput?: (value: string, event: FormEvent<HTMLInputElement>) => void;
+  label?: string;
+  /** 헬퍼 메시지. 색은 상태를 따라간다. */
+  message?: string;
+  /** 헬퍼 메시지 앞 아이콘. `currentColor`로 그려야 상태 색을 따라간다. */
+  messageIcon?: ReactNode;
+  /** 입력 오른쪽 아이콘. `currentColor`로 그린 SVG여야 색이 적용된다. */
+  icon?: ReactNode;
+  /** 포커스 중이고 값이 있으면 지우기 버튼을 보여준다. */
+  clearable?: boolean;
+  clearLabel?: string;
+  /** 오류 표시. `aria-invalid`가 되고 테두리·헬퍼 색이 바뀐다. */
+  invalid?: boolean;
+  /** 주면 모양이 그 상태로 고정된다. 없으면 포커스·값·비활성으로 브라우저가 판단한다. */
+  state?: FoxInputState;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  /** 배치 조정용. */
+  className?: string;
+}
+
+const SIZE_CLASS: Record<FoxInputSize, string> = {
+  lg: "fox-input--lg",
+  md: "fox-input--md",
+  sm: "fox-input--sm",
+};
+
+/**
+ * @fox 입력 필드. 네이티브 `<input>`을 감싸므로 date·password·email 등 모든 타입을 받는다.
+ *
+ * date 타입은 브라우저가 그리는 달력 아이콘과 피커를 그대로 쓴다 — 시안의 달력 아이콘으로
+ * 바꾸지 않는다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxInput({
+  size = "md",
+  onChange,
+  onInput,
+  label,
+  message,
+  messageIcon,
+  icon,
+  clearable = false,
+  clearLabel = "입력 지우기",
+  invalid = false,
+  state,
+  hidden = false,
+  className,
+  id,
+  value,
+  defaultValue,
+  ref,
+  ...rest
+}: FoxInputProps) {
+  const autoId = useId();
+
+  // 지우기 버튼은 값이 있을 때만 뜬다. 비제어 입력은 DOM만 알고 있으므로 직접 따라간다.
+  const [typedIn, setTypedIn] = useState(() => String(defaultValue ?? "").length > 0);
+  const fieldRef = useRef<HTMLInputElement>(null);
+
+  if (hidden) {
+    return null;
+  }
+
+  const fieldId = id ?? `${autoId}-field`;
+  const messageId = `${autoId}-message`;
+  const hasValue = value !== undefined ? String(value).length > 0 : typedIn;
+  const errored = invalid || state === "error";
+
+  // 호출부 ref와 내부 ref를 함께 채운다 — 지우기가 비제어 입력의 DOM 값을 비워야 한다.
+  const attachField = (node: HTMLInputElement | null) => {
+    fieldRef.current = node;
+    if (typeof ref === "function") {
+      ref(node);
+    } else if (ref) {
+      (ref as { current: HTMLInputElement | null }).current = node;
+    }
+  };
+
+  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
+    if (value === undefined) {
+      setTypedIn(event.target.value.length > 0);
+    }
+    onChange?.(event.target.value, event);
+  };
+
+  const handleInput = (event: FormEvent<HTMLInputElement>) => {
+    onInput?.((event.target as HTMLInputElement).value, event);
+  };
+
+  const handleClear = () => {
+    const field = fieldRef.current;
+    if (field) {
+      // 비제어 입력은 DOM을 직접 비운다. 제어 입력이면 onChange를 받은 호출부가 비운다.
+      field.value = "";
+      field.focus();
+    }
+    setTypedIn(false);
+    onChange?.("");
+  };
+
+  return (
+    <div
+      className={cx("fox-input", SIZE_CLASS[size], className)}
+      data-state={state}
+    >
+      {label && (
+        <label className="fox-input__label" htmlFor={fieldId}>
+          {label}
+        </label>
+      )}
+
+      <div className="fox-input__box">
+        <input
+          {...rest}
+          ref={attachField}
+          id={fieldId}
+          className="fox-input__field"
+          value={value}
+          defaultValue={defaultValue}
+          aria-invalid={errored || undefined}
+          aria-describedby={message ? messageId : undefined}
+          onChange={handleChange}
+          onInput={handleInput}
+        />
+
+        {clearable && hasValue && (
+          <button
+            type="button"
+            className="fox-input__clear"
+            aria-label={clearLabel}
+            onClick={handleClear}
+          >
+            {/* 시안의 XCircle. @fox에 아이콘 세트가 없어 같은 모양을 직접 그린다. */}
+            <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
+              <circle cx="8" cy="8" r="6.25" />
+              <path d="M6 6l4 4M10 6l-4 4" />
+            </svg>
+          </button>
+        )}
+
+        {icon && (
+          <span className="fox-input__icon" aria-hidden="true">
+            {icon}
+          </span>
+        )}
+      </div>
+
+      {message && (
+        <p className="fox-input__message" id={messageId}>
+          {messageIcon && (
+            <span className="fox-input__message-icon" aria-hidden="true">
+              {messageIcon}
+            </span>
+          )}
+          {message}
+        </p>
+      )}
+    </div>
+  );
+}
 
@fox/core/components/fox-input/index.ts (added)
+++ @fox/core/components/fox-input/index.ts
@@ -0,0 +1,1 @@
+export { FoxInput, type FoxInputProps, type FoxInputSize, type FoxInputState } from "./fox-input";
@fox/core/components/index.ts
--- @fox/core/components/index.ts
+++ @fox/core/components/index.ts
@@ -4,3 +4,4 @@
 export * from "./fox-icon-button";
 export * from "./fox-button-group";
 export * from "./fox-button-panel";
+export * from "./fox-input";
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -7,6 +7,7 @@
 import { FoxIconButton } from "../core/components/fox-icon-button";
 import { FoxButtonGroup } from "../core/components/fox-button-group";
 import { FoxButtonPanel } from "../core/components/fox-button-panel";
+import { FoxInput, type FoxInputState } from "../core/components/fox-input";
 import styles from "./dev-test.module.scss";
 
 export interface ComponentExample {
@@ -50,6 +51,40 @@
     >
       {children}
     </span>
+  );
+}
+
+// 입력 예제는 폭이 있어야 시안(360px)처럼 보인다.
+function InputStage({ children }: { children: ReactNode }) {
+  return <div className={styles.demoField}>{children}</div>;
+}
+
+// 지우기 버튼과 자동 상태 판정을 실제로 눌러 보려면 예제가 값을 들고 있어야 한다.
+function InputLiveDemo() {
+  const [text, setText] = useState("지우기를 눌러보세요");
+  return (
+    <>
+      <InputStage>
+        <FoxInput
+          size="md"
+          label="자동 상태"
+          placeholder="입력하면 글자가 진해집니다"
+          message="포커스하면 도움말이 파랗게 바뀝니다"
+          value={text}
+          onChange={setText}
+          clearable
+        />
+      </InputStage>
+      <InputStage>
+        <FoxInput
+          size="md"
+          label="오류 (invalid)"
+          placeholder="내용을 입력하세요."
+          message="필수 항목입니다"
+          invalid
+        />
+      </InputStage>
+    </>
   );
 }
 
@@ -513,4 +548,105 @@
       },
     ],
   },
+  {
+    id: "fox-input",
+    name: "FoxInput",
+    description:
+      "Figma 시안(input, 컴포넌트 세트 135:621)을 옮겼습니다. 네이티브 <input>을 감싸므로 시안에 없는 password·email·number 등 모든 타입을 그대로 받습니다. 상태는 기본적으로 브라우저가 판단하고(포커스·값·비활성·aria-invalid), state를 주면 그 모양으로 고정됩니다. 라벨은 입력 크기와 무관하게 항상 같은 크기입니다.",
+    variants: [
+      {
+        label: "직접 조작 — 입력·포커스·지우기",
+        node: <InputLiveDemo />,
+      },
+      {
+        label: "크기 (size) — 상자·글자·아이콘이 함께 줄고 라벨만 고정",
+        node: (
+          <>
+            {(["lg", "md", "sm"] as const).map((size) => (
+              <InputStage key={size}>
+                <FoxInput size={size} label={`레이블 (${size})`} placeholder="내용을 입력하세요." />
+              </InputStage>
+            ))}
+          </>
+        ),
+      },
+      {
+        label: "상태 고정 (state) — 시안 6종을 그대로 비교",
+        node: (
+          <>
+            {(
+              ["default", "focused", "completed", "error", "disabled", "view"] as FoxInputState[]
+            ).map((state) => (
+              <InputStage key={state}>
+                <FoxInput
+                  size="md"
+                  state={state}
+                  label={state}
+                  placeholder="내용을 입력하세요."
+                  message="힌트 메시지"
+                />
+              </InputStage>
+            ))}
+          </>
+        ),
+      },
+      {
+        label: "타입 — 시안에 없는 것도 네이티브 그대로 동작한다",
+        node: (
+          <>
+            {(["text", "date", "password", "email", "number", "time"] as const).map((type) => (
+              <InputStage key={type}>
+                <FoxInput size="md" type={type} label={type} placeholder="내용을 입력하세요." />
+              </InputStage>
+            ))}
+          </>
+        ),
+      },
+      {
+        label: "아이콘 · 헬퍼 아이콘 슬롯",
+        node: (
+          <>
+            <InputStage>
+              <FoxInput
+                size="md"
+                label="오른쪽 아이콘"
+                placeholder="내용을 입력하세요."
+                icon={<DemoIcon />}
+              />
+            </InputStage>
+            <InputStage>
+              <FoxInput
+                size="md"
+                label="헬퍼 아이콘"
+                placeholder="내용을 입력하세요."
+                message="아이콘은 호출부가 넣습니다"
+                messageIcon={<DemoIcon />}
+              />
+            </InputStage>
+          </>
+        ),
+      },
+      {
+        label: "비활성 · 읽기 전용 — 네이티브 속성으로 자동 판정",
+        node: (
+          <>
+            <InputStage>
+              <FoxInput size="md" label="disabled" defaultValue="수정할 수 없음" disabled />
+            </InputStage>
+            <InputStage>
+              <FoxInput size="md" label="readOnly" defaultValue="읽기 전용" readOnly />
+            </InputStage>
+          </>
+        ),
+      },
+      {
+        label: "라벨·헬퍼 없이도 쓸 수 있다",
+        node: (
+          <InputStage>
+            <FoxInput size="md" placeholder="내용을 입력하세요." />
+          </InputStage>
+        ),
+      },
+    ],
+  },
 ];
@fox/dev-test/dev-test.module.scss
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
@@ -439,3 +439,9 @@
   inline-size: 20rem;
   max-inline-size: 100%;
 }
+
+// 입력 예제용. 시안 폭(360px)에 맞춰야 실제 비율로 보인다.
+.demoField {
+  inline-size: 36rem;
+  max-inline-size: 100%;
+}
 
@fox/styles/_fox-input.scss (added)
+++ @fox/styles/_fox-input.scss
@@ -0,0 +1,285 @@
+// FoxInput — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F)
+//                   Figma input (컴포넌트 세트 135:621)
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-input fox-input--md">
+//     <label class="fox-input__label" for="…">레이블</label>
+//     <div class="fox-input__box">
+//       <input class="fox-input__field" id="…" placeholder="…">
+//       <button class="fox-input__clear" type="button">…svg…</button>   <!-- 선택 -->
+//       <span class="fox-input__icon">…svg…</span>                      <!-- 선택 -->
+//     </div>
+//     <p class="fox-input__message">…</p>                               <!-- 선택 -->
+//   </div>
+//
+// 상태는 두 갈래다.
+//   - 기본(자동): 브라우저가 판단한다. 포커스는 `:focus-within`, 비활성·읽기전용은 네이티브
+//     속성, 오류는 `aria-invalid="true"`. 값이 들어찬 모습(시안의 completed)은 따로 규칙이
+//     필요 없다 — 시안에서 default와 completed의 차이가 글자색뿐인데 그건 플레이스홀더와
+//     입력값의 네이티브 차이다.
+//   - 고정: 루트에 `data-state`를 주면 그 모양으로 못박힌다. 자동 규칙은 전부 꺼진다.
+//     카탈로그·스냅샷처럼 상태를 눈으로 비교해야 할 때 쓴다.
+//
+// 라벨은 입력 크기와 무관하게 항상 label/md다(시안의 form-label이 공용 컴포넌트라 크기를
+// 따라가지 않는다).
+//
+// 아이콘 크기 24/20/16은 시안이 변수를 걸지 않았지만 상자의 나머지 치수가 전부 `form/*`
+// 반응형 토큰이라 아이콘도 `form-icon-*`(PC 24/20/16 · 모바일 20/16/12)에 맞춘다.
+//
+// 토큰이 아닌 값(FoxButton과 동일한 근거로 승인된 항목):
+//   - line-height / letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로 export하지 않는다.
+//   - 헬퍼 메시지의 line-height 1.5: 위와 같다.
+//   - box-sizing / cursor / flex / appearance 관련: 구조·상호작용 규칙.
+
+@use "abstracts" as fox;
+
+.fox-input {
+  display: flex;
+  flex-direction: column;
+  gap: fox.gap(3);
+  inline-size: 100%;
+
+  &__label {
+    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;
+    letter-spacing: -0.025em;
+  }
+
+  &__box {
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    gap: fox.gap(3);
+    inline-size: 100%;
+    background: fox.color(input-surface);
+    border: fox.border(1) solid fox.color(input-border);
+  }
+
+  &__field {
+    flex: 1 0 0;
+    min-inline-size: 0;
+    margin: 0;
+    padding: 0;
+    border: none;
+    background: none;
+    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;
+
+    &::placeholder {
+      color: fox.color(font-neutral-subtler);
+    }
+
+    // 포커스는 상자 테두리가 표시하므로 기본 외곽선을 지운다.
+    &:focus {
+      outline: none;
+    }
+
+    &:disabled {
+      color: fox.color(font-neutral-disabled-strong);
+      cursor: default;
+    }
+  }
+
+  &__icon,
+  &__clear {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: center;
+    color: fox.color(icon-neutral-default);
+  }
+
+  &__icon > *,
+  &__clear > * {
+    inline-size: 100%;
+    block-size: 100%;
+  }
+
+  &__clear {
+    margin: 0;
+    padding: 0;
+    border: none;
+    background: none;
+    cursor: pointer;
+  }
+
+  &__message {
+    display: flex;
+    gap: fox.number(3);
+    align-items: flex-start;
+    margin: 0;
+    color: fox.color(font-neutral-subtle);
+    font-family: fox.font-family(body);
+    font-size: fox.font-size(body-sm);
+    font-weight: fox.font-weight(regular);
+    line-height: 1.5;
+    letter-spacing: -0.025em;
+  }
+
+  &__message-icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    align-items: flex-start;
+    padding-block-start: fox.padding(1);
+
+    > * {
+      inline-size: fox.icon(3);
+      block-size: fox.icon(3);
+    }
+  }
+
+  // ── 크기 ──────────────────────────────────────────────────────────────────
+  &--lg &__box {
+    block-size: fox.form(height-lg);
+    padding-inline: fox.form(padding-lg);
+    border-radius: fox.form(radius-lg);
+  }
+
+  &--lg &__field {
+    font-size: fox.font-size(label-lg);
+  }
+
+  &--lg &__icon,
+  &--lg &__clear {
+    inline-size: fox.form(icon-lg);
+    block-size: fox.form(icon-lg);
+  }
+
+  &--md &__box {
+    block-size: fox.form(height-md);
+    padding-inline: fox.form(padding-md);
+    border-radius: fox.form(radius-md);
+  }
+
+  &--md &__field {
+    font-size: fox.font-size(label-md);
+  }
+
+  &--md &__icon,
+  &--md &__clear {
+    inline-size: fox.form(icon-md);
+    block-size: fox.form(icon-md);
+  }
+
+  &--sm &__box {
+    block-size: fox.form(height-sm);
+    padding-inline: fox.form(padding-sm);
+    border-radius: fox.form(radius-sm);
+  }
+
+  &--sm &__field {
+    font-size: fox.font-size(label-sm);
+  }
+
+  &--sm &__icon,
+  &--sm &__clear {
+    inline-size: fox.form(icon-sm);
+    block-size: fox.form(icon-sm);
+  }
+
+  // 지우기 버튼은 포커스 중에만 보인다(시안에서 focused 변형에만 있다).
+  &__clear {
+    display: none;
+  }
+
+  &__box:focus-within &__clear {
+    display: inline-flex;
+  }
+}
+
+// ── 상태 · 자동 ─────────────────────────────────────────────────────────────
+// 뒤에 오는 규칙이 이긴다: 포커스 < 오류 < 읽기전용 < 비활성.
+.fox-input:not([data-state]) {
+  .fox-input__box:focus-within {
+    border-width: fox.border(2);
+    border-color: fox.color(input-border-active);
+  }
+
+  .fox-input__box:has(.fox-input__field[aria-invalid="true"]) {
+    border-width: fox.border(2);
+    border-color: fox.color(input-border-error);
+  }
+
+  // 비활성도 `:read-only`에 걸리므로 떼어낸다.
+  .fox-input__box:has(.fox-input__field:read-only:not(:disabled)) {
+    border-width: fox.border(1);
+    border-color: fox.color(input-border-disabled);
+    background: fox.color(input-surface-disabled);
+  }
+
+  .fox-input__box:has(.fox-input__field:disabled) {
+    border-width: fox.border(1);
+    border-color: fox.color(input-border-disabled);
+    background: fox.color(input-surface-disabled);
+  }
+
+  &:focus-within .fox-input__message {
+    color: fox.color(font-system-information);
+  }
+
+  &:has(.fox-input__field[aria-invalid="true"]) .fox-input__message {
+    color: fox.color(font-system-danger);
+  }
+}
+
+// ── 상태 · 고정 ─────────────────────────────────────────────────────────────
+// 시안의 focused·completed·error·view는 값이 들어찬 모습이라 플레이스홀더도 진하게 둔다.
+.fox-input[data-state="default"] .fox-input__box,
+.fox-input[data-state="completed"] .fox-input__box {
+  border-width: fox.border(1);
+  border-color: fox.color(input-border);
+  background: fox.color(input-surface);
+}
+
+.fox-input[data-state="focused"] .fox-input__box {
+  border-width: fox.border(2);
+  border-color: fox.color(input-border-active);
+  background: fox.color(input-surface);
+}
+
+.fox-input[data-state="error"] .fox-input__box {
+  border-width: fox.border(2);
+  border-color: fox.color(input-border-error);
+  background: fox.color(input-surface);
+}
+
+.fox-input[data-state="view"] .fox-input__box,
+.fox-input[data-state="disabled"] .fox-input__box {
+  border-width: fox.border(1);
+  border-color: fox.color(input-border-disabled);
+  background: fox.color(input-surface-disabled);
+}
+
+.fox-input[data-state="completed"] .fox-input__field::placeholder,
+.fox-input[data-state="focused"] .fox-input__field::placeholder,
+.fox-input[data-state="error"] .fox-input__field::placeholder,
+.fox-input[data-state="view"] .fox-input__field::placeholder {
+  color: fox.color(font-neutral-default);
+}
+
+.fox-input[data-state="disabled"] .fox-input__field,
+.fox-input[data-state="disabled"] .fox-input__field::placeholder {
+  color: fox.color(font-neutral-disabled-strong);
+}
+
+.fox-input[data-state="focused"] .fox-input__message {
+  color: fox.color(font-system-information);
+}
+
+.fox-input[data-state="completed"] .fox-input__message {
+  color: fox.color(font-system-success);
+}
+
+.fox-input[data-state="error"] .fox-input__message {
+  color: fox.color(font-system-danger);
+}
+
+.fox-input[data-state="focused"] .fox-input__clear {
+  display: inline-flex;
+}
@fox/styles/components.scss
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
@@ -7,3 +7,4 @@
 @use "fox-icon-button";
 @use "fox-button-group";
 @use "fox-button-panel";
+@use "fox-input";
Add a comment
List