컴포넌트

Input

한 줄의 텍스트를 입력합니다.

설치

pnpm dlx ply-ui add input field icon

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

사용법

입력 이름은 label 또는 Field.Label로 연결합니다. variant는 표면, controlSize는 높이입니다. 기본값은 standard와 lg입니다.

Standard

variant="standard" — 투명한 배경과 테두리를 사용합니다. 기본값입니다.

Contrast

variant="contrast" — 배경 대비를 높인 입력 영역입니다.

Utility

variant="utility" — 배경과 테두리가 없는 입력 영역입니다.

크기

controlSize로 높이를 지정합니다. 기본값은 lg입니다. HTML의 size는 글자 수에 따른 너비이므로 구분해서 사용합니다.

비활성

disabled는 입력과 포커스 이동을 막습니다.

읽기 전용

readOnly는 값을 수정하지 못하게 합니다. 값 선택과 복사는 가능합니다.

오류

Field.Root의 invalid로 오류 상태를 지정합니다. 같은 Root 안의 Input과 Field.Error는 오류 상태와 설명을 공유합니다.

앞쪽 아이콘

leading에 아이콘이나 내용을 넣습니다. 입력과 아이콘을 감싸는 요소는 Input이 구성합니다.

뒤쪽 아이콘

trailing에 아이콘이나 내용을 넣습니다. 입력과 아이콘을 감싸는 요소는 Input이 구성합니다.

속성

이 프로젝트에서 추가하거나 제한한 속성입니다.

Input

속성

타입

오류가 있는 입력 그룹

아이콘이나 숫자 장식이 있어도 입력과 바깥 테두리는 같은 오류 상태를 표시합니다. aria-describedby로 오류 설명을 연결하세요.

구현 코드

components/ui/input.tsx
input.tsx
"use client";
import { hasContent } from "../../lib/content";
import { cx } from "../../lib/cx";
import { useRender } from "@base-ui/react/use-render";
import type { ComponentProps, CSSProperties, ReactNode } from "react";
import { Input as Primitive } from "@base-ui/react/input";
import "./input.css";

export type InputProps = ComponentProps<typeof Primitive> & {
  /** 컨트롤 높이입니다. @defaultValue "lg" */
  controlSize?: "xs" | "sm" | "md" | "lg";
  /** 입력 표면의 배경과 테두리입니다. */
  variant?: "standard" | "contrast" | "utility";
  /** 입력 앞의 아이콘 또는 내용입니다. */
  leading?: ReactNode;
  /** 입력 뒤의 아이콘 또는 내용입니다. */
  trailing?: ReactNode;
  /** 장식 유무와 관계없이 네이티브 input에만 적용합니다. */
  inputClassName?: string;
  inputStyle?: CSSProperties;
};
/** className/style은 보이는 전체 표면, ref·이벤트·입력 속성은 실제 input에 연결합니다. */
export function Input({
  controlSize = "lg",
  variant = "standard",
  leading,
  trailing,
  className,
  style,
  inputClassName,
  inputStyle,
  render,
  ...props
}: InputProps) {
  return (
    <Primitive
      {...props}
      render={(elementProps, state) => (
        <InputSurface
          elementProps={elementProps}
          state={state}
          render={render}
          controlSize={controlSize}
          variant={variant}
          leading={leading}
          trailing={trailing}
          className={className}
          style={style}
          inputClassName={inputClassName}
          inputStyle={inputStyle}
        />
      )}
    />
  );
}
function InputSurface({
  elementProps,
  state,
  render,
  controlSize,
  variant,
  leading,
  trailing,
  className,
  style,
  inputClassName,
  inputStyle,
}: Pick<
  InputProps,
  | "render"
  | "controlSize"
  | "variant"
  | "leading"
  | "trailing"
  | "className"
  | "style"
  | "inputClassName"
  | "inputStyle"
> & { elementProps: ComponentProps<"input">; state: Primitive.State }) {
  const grouped = hasContent(leading) || hasContent(trailing);
  const custom = typeof className === "function" ? className(state) : className;
  const resolvedStyle = typeof style === "function" ? style(state) : style;
  const { ref, ...props } = elementProps;
  const invalid =
    state.valid === false ||
    props["aria-invalid"] === true ||
    props["aria-invalid"] === "true";
  const input = useRender({
    defaultTagName: "input",
    render,
    ref,
    state: { ...state },
    props: {
      ...props,
      "data-size": controlSize,
      "data-variant": variant,
      className: cx(
        "rbx-input",
        props.className,
        !grouped && custom,
        inputClassName,
      ),
      style: {
        ...props.style,
        ...(!grouped ? resolvedStyle : undefined),
        ...inputStyle,
      },
    },
  });
  if (!grouped) return input;
  return (
    <div
      className={cx("rbx-input-group", custom)}
      style={resolvedStyle}
      data-size={controlSize}
      data-variant={variant}
      data-disabled={state.disabled || undefined}
      data-invalid={invalid || undefined}
    >
      {hasContent(leading) && (
        <span className="rbx-input-adornment">{leading}</span>
      )}
      {input}
      {hasContent(trailing) && (
        <span className="rbx-input-adornment">{trailing}</span>
      )}
    </div>
  );
}
components/ui/input.css
input.css
@layer theme, base, components, utilities;
@layer components.ply-base, components.ply;

@layer components.ply {
  /* Input: 공통 구조는 theme.css, 컴포넌트 전용 조정은 아래에 작성합니다. */
}

장식과 너비 지정

leading·trailing 유무와 관계없이 className과 style은 전체 입력 표면에 적용합니다. ref, 이벤트와 입력 속성은 실제 input에 연결합니다. 내부 글자나 입력 자체만 꾸밀 때는 inputClassName·inputStyle을 사용합니다.

<Input leading={<Icon name="icon-regular-person" />} className="max-w-64" />

목차