Skip to Content

Input

텍스트 입력을 받는 기본 폼 컴포넌트


개요

Input 컴포넌트는 사용자로부터 텍스트 입력을 받는 기본 폼 요소입니다. HTML <input> 요소를 확장하여 일관된 스타일링과 접근성을 제공합니다.

주요 특징

  • 다양한 타입: text, email, password, number, tel, url 등
  • 유효성 검사 상태: default, error, success 표시
  • 다양한 크기: sm, default, lg
  • 접근성 우선: WCAG 2.1 AA 준수
  • TypeScript: 완벽한 타입 지원

설치

npx @vortex/cli add input

기본 사용법

import { Input } from "@vortex/ui-foundation"; export default function App() { return <Input type="email" placeholder="이메일을 입력하세요" />; }

입력 타입

Input은 다양한 HTML input 타입을 지원합니다.

Text (기본)

<Input type="text" placeholder="이름을 입력하세요" />

Email

<Input type="email" placeholder="email@example.com" />

Password

<Input type="password" placeholder="비밀번호" />

Number

<Input type="number" placeholder="나이" min={0} max={120} />

Tel

<Input type="tel" placeholder="010-1234-5678" />

URL

<Input type="url" placeholder="https://example.com" />

Sizes

Input은 3가지 size를 제공합니다.

Small

<Input size="sm" placeholder="Small input" />

Default

<Input placeholder="Default input" />

Large

<Input size="lg" placeholder="Large input" />

언제 사용하는가

✅ 권장 사용 사례

  • 단일 라인 텍스트: 이름, 이메일, 전화번호 등
  • 숫자 입력: 나이, 수량, 가격 등
  • 비밀번호: 로그인, 회원가입 폼
  • 검색: 검색 입력 필드
  • 폼 입력: 모든 종류의 폼 데이터

실전 예제

로그인 폼

<form> <div className="space-y-4"> <div> <label htmlFor="email" className="block text-sm font-medium mb-2"> 이메일 </label> <Input id="email" type="email" placeholder="email@example.com" required /> </div> <div> <label htmlFor="password" className="block text-sm font-medium mb-2"> 비밀번호 </label> <Input id="password" type="password" placeholder="••••••••" required /> </div> </div> </form>

언제 사용하지 말아야 하는가

상황대신 사용할 컴포넌트
여러 줄 텍스트 입력<Textarea>
옵션 선택<Select>
날짜 선택<DatePicker>
파일 업로드<FileInput>
On/Off 토글<Switch>

Advanced Usage

유효성 검사 상태

import { Input } from "@vortex/ui-foundation"; import { useState } from "react"; function ValidatedInput() { const [email, setEmail] = useState(""); const [error, setError] = useState(""); const validate = (value: string) => { if (!value.includes("@")) { setError("유효한 이메일을 입력하세요"); } else { setError(""); } }; return ( <div> <Input type="email" value={email} onChange={(e) => { setEmail(e.target.value); validate(e.target.value); }} aria-invalid={!!error} aria-describedby={error ? "email-error" : undefined} className={error ? "border-destructive" : ""} /> {error && ( <p id="email-error" className="text-sm text-destructive mt-1"> {error} </p> )} </div> ); }

아이콘과 함께 사용

import { Input } from "@vortex/ui-foundation"; import { Search } from "lucide-react"; function SearchInput() { return ( <div className="relative"> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input placeholder="검색..." className="pl-10" /> </div> ); }

Controlled vs Uncontrolled

// Controlled (권장) function ControlledInput() { const [value, setValue] = useState(""); return <Input value={value} onChange={(e) => setValue(e.target.value)} />; } // Uncontrolled function UncontrolledInput() { const inputRef = useRef<HTMLInputElement>(null); return <Input ref={inputRef} defaultValue="초기값" />; }

접근성 (Accessibility)

Input 컴포넌트는 WCAG 2.1 AA 기준을 준수합니다.

ARIA Attributes

자동으로 제공:

  • type: 입력 타입 자동 설정
  • aria-invalid: 유효성 검사 실패 시 true

필수 사항:

// ✅ Good: label과 연결 <label htmlFor="email">이메일</label> <Input id="email" type="email" /> // ✅ Good: 에러 메시지와 연결 <Input aria-invalid={hasError} aria-describedby="error-msg" /> <p id="error-msg">에러 메시지</p> // ❌ Bad: label 없음 <Input type="email" />

키보드 네비게이션

  • Tab: 다음 입력 필드로 이동
  • Shift + Tab: 이전 입력 필드로 이동
  • Enter: 폼 제출 (type=“submit” 버튼이 있는 경우)

Best Practices

1. 명확한 레이블 제공

// ✅ Good <label htmlFor="username" className="block text-sm font-medium mb-2"> 사용자 이름 </label> <Input id="username" type="text" /> // ❌ Bad <Input placeholder="사용자 이름" /> // placeholder만으로는 부족

2. Placeholder 사용 가이드

// ✅ Good: 예시 제공 <Input type="email" placeholder="example@email.com" /> // ❌ Bad: label 대신 사용 <Input placeholder="이메일" /> // label이 필요함

3. 유효성 검사 피드백

// ✅ Good: 즉각적인 피드백 <Input aria-invalid={hasError} aria-describedby="error" className={hasError ? "border-destructive" : ""} />; { hasError && ( <p id="error" className="text-sm text-destructive mt-1"> {errorMessage} </p> ); }

4. Required 필드 표시

<label htmlFor="email"> 이메일 <span className="text-destructive">*</span> </label> <Input id="email" type="email" required />

TypeScript

Props 타입

import { InputProps } from "@vortex/ui-foundation"; interface CustomInputProps extends InputProps { label?: string; error?: string; } function CustomInput({ label, error, ...props }: CustomInputProps) { return ( <div> {label && ( <label htmlFor={props.id} className="block text-sm font-medium mb-2"> {label} </label> )} <Input {...props} aria-invalid={!!error} /> {error && <p className="text-sm text-destructive mt-1">{error}</p>} </div> ); }

성능 최적화

디바운싱

import { Input } from "@vortex/ui-foundation"; import { useState, useEffect } from "react"; import { useDebounce } from "@/hooks/useDebounce"; function SearchInput() { const [searchTerm, setSearchTerm] = useState(""); const debouncedSearchTerm = useDebounce(searchTerm, 500); useEffect(() => { if (debouncedSearchTerm) { // API 호출 fetchResults(debouncedSearchTerm); } }, [debouncedSearchTerm]); return ( <Input placeholder="검색..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} /> ); }

관련 컴포넌트

  • Textarea: 여러 줄 텍스트 입력
  • Select: 드롭다운 선택
  • Checkbox: 다중 선택
  • Radio: 단일 선택
  • Button: 폼 제출

참고 자료


지원 및 피드백

문제가 발생하거나 개선 제안이 있으신가요?

Last updated on