본문으로 건너뛰기
Amineslab UI

Component

FormStack

여러 필드의 간격과 반응형 열 배치를 담당합니다.

Playground

옵션을 조절하며 화면과 사용 코드를 함께 확인하세요.

PC 1280px
Controls

필드 사이 간격 · 기본 테마 8 / 16 / 24px

PC 열 수 · 모바일은 한 열

Code
import { FormStack, TextField, NumberField, TextareaField } from '@amineslab/ui'

function FormLayoutExample({ compact = false, gap = 'md', columns = '1', }: {
    compact?: boolean;
    gap?: string;
    columns?: string;
}) {
    return (<FormStack gap={compact ? 'sm' : gap === 'sm' || gap === 'lg' ? gap : 'md'} columns={columns === '2' ? 2 : 1}>
      <TextField label="프로젝트 이름" placeholder="Amineslab UI" required description="팀에서 식별할 이름입니다."/>
      <NumberField label="예산" placeholder="금액을 입력해 주세요"/>
      <TextareaField label="설명" placeholder="프로젝트를 소개해 주세요" rows={3}/>
    </FormStack>);
}

export default function ExamplePreview() {
  return (<FormLayoutExample {...({"gap":"md","columns":"1"} as const)}/>)
}

Props

@amineslab/ui에서 FormStack를 가져옵니다.

import { FormStack } from '@amineslab/ui'

FormStack

필드 간격과 반응형 열 배치를 적용합니다.

NameTypeDefaultDescription
gap'sm' | 'md' | 'lg''md'필드 사이 간격입니다. 기본 테마에서 8 / 16 / 24px입니다.
columns1 | 21PC 열 수입니다. 모바일은 항상 한 열입니다.
classNamestring-stack 간격을 조정할 때 공개 wrapper에 적용합니다.

Managed fields

기본 입력부터 날짜·시간 필드까지 같은 id·ARIA·변경 콜백 계약을 사용하는 어댑터입니다.

NameTypeDefaultDescription
TextField / PasswordField / NumberField / PhoneField / TextareaFieldmanaged field props + native control props-value, onValueChange, onAfterChange와 label, description, error를 조합합니다.
defaultValue / defaultCheckednative control defaults-Text·Password·Number·Phone·Textarea·Radio·Select·Combobox는 defaultValue, Checkbox는 defaultChecked를 지원합니다. value를 전달하면 부모가 값을 관리합니다. 날짜·시간 필드는 value/onChange로 관리합니다.
required / aria-requiredboolean / AriaAttributes-required는 필수 표시와 지원하는 native 컨트롤의 유효성 검사에 전달합니다. 명시적인 aria-required도 보존합니다. 날짜·시간·파일 선택의 필수 검증은 소비 앱에서 처리합니다.
labelId / descriptionId / errorIdstring-기존 managed field의 라벨·설명·오류 슬롯에 명시적인 ID를 지정합니다. 생략하면 자동으로 생성하고 실제 컨트롤과 연결합니다.
submitOnEnterbooleanfalseText·Password·Number·Phone에서만 Enter 암묵 제출을 허용하는 opt-in입니다.
CheckboxField / RadioGroupField / SelectField / ComboboxFieldmanaged field props + options-선택 컨트롤의 label, aria-labelledby, required, invalid 관계를 연결합니다.
DateField / TimeField / DateRangeFieldmanaged field props + date-time props-PC Popover와 모바일 BottomSheet 전환은 date-time primitive가 소유합니다.

@amineslab/ui/forms/react-hook-form (optional)

React Hook Form을 사용하는 앱에서 같은 managed field를 Controller 규칙으로 연결합니다.

NameTypeDefaultDescription
TextField / NumberField / DateField / FileUploadFieldRHF adapter props-control 또는 FormProvider context, name, rules, defaultValue를 전달합니다.
useRHFSubmitGuard(schema, options) => SubmitGuard-watch 중인 값을 framework-independent submit guard에 연결합니다.

Examples

자주 쓰는 조합을 살펴보고, 필요한 예시의 코드를 펼쳐 확인하세요.

관리 필드와 날짜 계약

Text·Password·Number·Phone·Checkbox·Radio·Select·Combobox와 Date·Time·DateRange 필드가 같은 접근성 관계와 반응형 날짜 표면을 공유합니다.

자동으로 생성된 id와 설명 관계를 확인할 수 있습니다.

체크박스도 필드 설명과 필수 마커를 공유합니다.

import { useState } from 'react'

import { type DateRange, FormStack, TextField, PasswordField, NumberField, PhoneField, TextareaField, CheckboxField, RadioGroupField, SelectField, ComboboxField, DateField, TimeField, DateRangeField } from '@amineslab/ui'

function ManagedFieldsExample() {
    const [name, setName] = useState('Amineslab');
    const [password, setPassword] = useState('secret');
    const [amount, setAmount] = useState<number | null>(12000);
    const [phone, setPhone] = useState('010-1234-5678');
    const [enabled, setEnabled] = useState(true);
    const [role, setRole] = useState('admin');
    const [team, setTeam] = useState('design');
    const [owner, setOwner] = useState('seunghun');
    const [date, setDate] = useState('2026-09-10');
    const [time, setTime] = useState('09:30');
    const [range, setRange] = useState<DateRange>({ from: '2026-09-10', to: '2026-09-14' });
    return (<FormStack className="grid-cols-1 md:grid-cols-2">
      <TextField label="이름" value={name} onValueChange={setName} description="자동으로 생성된 id와 설명 관계를 확인할 수 있습니다." required/>
      <PasswordField label="비밀번호" value={password} onValueChange={setPassword} showToggle/>
      <NumberField label="예산" value={amount} onValueChange={setAmount} showThousandsSeparator textAlign="end"/>
      <PhoneField label="연락처" value={phone} onValueChange={setPhone}/>
      <TextareaField label="메모" value="폼 컨트롤은 동일한 오류·설명 연결을 사용합니다." onValueChange={() => { }}/>
      <CheckboxField label="알림 받기" value={enabled} onValueChange={setEnabled} description="체크박스도 필드 설명과 필수 마커를 공유합니다."/>
      <RadioGroupField label="권한" value={role} onValueChange={setRole} options={[
            { value: 'admin', label: '관리자' },
            { value: 'member', label: '멤버' },
        ]}/>
      <SelectField label="팀" value={team} onValueChange={setTeam} options={[
            { value: 'design', label: '디자인' },
            { value: 'engineering', label: '엔지니어링' },
        ]}/>
      <ComboboxField label="담당자" value={owner} onValueChange={setOwner} options={[
            { value: 'seunghun', label: '박승훈' },
            { value: 'mina', label: '김미나' },
            { value: 'jisu', label: '이 지수' },
        ]}/>
      <DateField label="시작일" value={date} onValueChange={setDate}/>
      <TimeField label="시작 시간" value={time} onValueChange={setTime} step={30}/>
      <DateRangeField label="기간" value={range} onValueChange={setRange}/>
    </FormStack>);
}

export default function ExamplePreview() {
  return (<ManagedFieldsExample />)
}

RHF compound 구조 (optional)

React Hook Form을 사용하는 앱은 optional subpath의 Form compound와 field adapters를 사용해 같은 오류·설명·반응형 날짜 계약을 유지합니다.

목록에 표시할 이름입니다.

import { useState } from 'react'

import { useForm } from 'react-hook-form'

import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage, DateRangeField } from '@amineslab/ui/forms/react-hook-form'

import { Input, Button } from '@amineslab/ui'

function ReactHookFormExample() {
    const [saved, setSaved] = useState('');
    const form = useForm({ defaultValues: { name: '', range: { from: '', to: '' } } });
    return (<Form {...form}>
      <form className="grid w-full gap-4" onSubmit={form.handleSubmit((value) => setSaved(value.name))}>
        <FormField control={form.control} name="name" rules={{ required: '이름을 입력하세요' }} render={({ field }) => (<FormItem>
              <FormLabel>프로젝트 이름</FormLabel>
              <FormControl>
                <Input {...field} aria-required="true"/>
              </FormControl>
              <FormDescription>목록에 표시할 이름입니다.</FormDescription>
              <FormMessage />
            </FormItem>)}/>
        <DateRangeField control={form.control} name="range" label="활동 기간"/>
        <Button type="submit">{saved ? '저장됨' : '검증 후 저장'}</Button>
      </form>
    </Form>);
}

export default function ExamplePreview() {
  return (<ReactHookFormExample />)
}

사용 기준

권장하는 사용
  • FormStack의 자식은 자유롭게 구성합니다.
  • TextField·TextareaField처럼 라벨과 설명을 포함한 필드는 바로 넣으세요.
  • Input·Textarea를 직접 쓰며 라벨·설명·오류를 묶고 싶을 때만 FormField를 사용합니다.
  • FormActions로 저장·취소를 붙일 수 있습니다.
  • 페이지 전체에 PC 카드와 모바일 하단 액션이 필요할 때 FormSurface로 감쌉니다.
  • 값과 검증 상태는 native form 또는 폼 라이브러리가 관리합니다.
피해야 할 사용
  • 겉모양만으로 사용을 결정하지 마세요.
  • 의미와 키보드 동작, 모바일에서의 흐름을 함께 확인하세요.

접근성

화면에 맞는 이름을 제공하고, 키보드만으로 작업을 마칠 수 있는지 확인하세요.

  • Tab: 라벨이 연결한 실제 컨트롤로 이동합니다.