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="이름을 입력하세요" /><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: 폼 제출
참고 자료
지원 및 피드백
문제가 발생하거나 개선 제안이 있으신가요?
- GitLab Issues: vortex/platform/issues
- Slack: #vortex-design-system
Last updated on