폼

폼 (Form)

사용자로부터 정보를 입력받는 데 쓰는 컴포넌트예요. 데이터 수집, 검증, 제출까지 한 번에 처리해요. 로그인, 회원가입, 설정 페이지처럼 사용자 입력이 필요한 곳이면 어디든 쓸 수 있어요.

출처: 문서

본문

언제 사용하나요 (When To Use)

  • 사용자로부터 정보를 입력받아야 할 때 사용해요.
  • 데이터 수집과 검증, 제출 흐름이 필요할 때 사용해요.

예시 (Examples)

기본 (Basic)

기본적인 폼이에요. 제출 버튼을 누르면 입력받은 값이 콘솔에 출력돼요.

import React from 'react';
import { Button, Checkbox, Form, Input, InputNumber } from 'antd';

const App: React.FC = () => {
  const [form] = Form.useForm();

  const onFinish = (values: any) => {
    console.log('Success:', values);
  };

  const onFinishFailed = (errorInfo: any) => {
    console.log('Failed:', errorInfo);
  };

  return (
    <Form
      name="basic"
      form={form}
      labelCol={{ span: 8 }}
      wrapperCol={{ span: 16 }}
      style={{ maxWidth: 600 }}
      initialValues={{ remember: true }}
      onFinish={onFinish}
      onFinishFailed={onFinishFailed}
      autoComplete="off"
    >
      <Form.Item
        label="Username"
        name="username"
        rules={[{ required: true, message: 'Please input your username!' }]}
      >
        <Input />
      </Form.Item>

      <Form.Item
        label="Password"
        name="password"
        rules={[{ required: true, message: 'Please input your password!' }]}
      >
        <Input.Password />
      </Form.Item>

      <Form.Item name="remember" valuePropName="checked" wrapperCol={{ offset: 8, span: 16 }}>
        <Checkbox>Remember me</Checkbox>
      </Form.Item>

      <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

export default App;

기본 사용법 (Basic usage)

Form은 제어되지 않는 폼(Form)과 제어되는 폼(FormControl) 두 가지 사용법을 제공해요. 제어되는 폼이 더 많은 기능을 제공하지만, 기본 데모에서는 오래된 제어되지 않는 방식을 먼저 보여 줘요.

import React from 'react';
import { Button, Form, Input } from 'antd';

const App: React.FC = () => (
  <Form name="basic" labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }}>
    <Form.Item label="Username" name="username" rules={[{ required: true }]}>
      <Input />
    </Form.Item>

    <Form.Item label="Password" name="password" rules={[{ required: true }]}>
      <Input.Password />
    </Form.Item>

    <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
      <Button type="primary" htmlType="submit">
        Submit
      </Button>
    </Form.Item>
  </Form>
);

export default App;

폼 컨트롤 (Form Control)

제어되는 폼(FormControl)으로는 동적 필드 추가, 실시간 검증, 폼 값 감시 등 더 풍부한 기능을 쓸 수 있어요.

import React from 'react';
import { Button, Form, Input } from 'antd';

const App: React.FC = () => {
  const [form] = Form.useForm();

  const onFinish = (values: any) => {
    console.log('Success:', values);
  };

  const onFill = () => {
    form.setFieldsValue({
      user: 'Ant Design',
      note: 'Hello world!',
    });
  };

  return (
    <Form
      form={form}
      onFinish={onFinish}
      variant="filled"
      style={{ maxWidth: 600 }}
    >
      <Form.Item label="Note" name="note" rules={[{ required: true }]}>
        <Input />
      </Form.Item>

      <Form.Item label="User" name="user" rules={[{ required: true }]}>
        <Input />
      </Form.Item>

      <Form.Item>
        <Button htmlType="submit" type="primary">
          Submit
        </Button>
        <Button htmlType="button" onClick={onFill}>
          Fill form
        </Button>
      </Form.Item>
    </Form>
  );
};

export default App;

레이아웃 (Layout)

폼에는 horizontal, vertical, inline 세 가지 레이아웃이 있어요. layout prop으로 설정하고, 기본값은 horizontal이에요.

import React, { useState } from 'react';
import { Button, Form, Input, Radio } from 'antd';

type LayoutType = Parameters<typeof Form>[0]['layout'];

const App: React.FC = () => {
  const [form] = Form.useForm();
  const [formLayout, setFormLayout] = useState<LayoutType>('horizontal');

  const onFormLayoutChange = ({ layout }: { layout: LayoutType }) => {
    setFormLayout(layout);
  };

  return (
    <Form
      layout={formLayout}
      form={form}
      initialValues={{ layout: formLayout }}
      onValuesChange={onFormLayoutChange}
      style={{ maxWidth: formLayout === 'inline' ? 'none' : 600 }}
    >
      <Form.Item label="Form Layout" name="layout">
        <Radio.Group value={formLayout}>
          <Radio.Button value="horizontal">Horizontal</Radio.Button>
          <Radio.Button value="vertical">Vertical</Radio.Button>
          <Radio.Button value="inline">Inline</Radio.Button>
        </Radio.Group>
      </Form.Item>
      <Form.Item label="Field A">
        <Input placeholder="input placeholder" />
      </Form.Item>
      <Form.Item label="Field B">
        <Input placeholder="input placeholder" />
      </Form.Item>
      <Form.Item>
        <Button type="primary">Submit</Button>
      </Form.Item>
    </Form>
  );
};

export default App;

검증 규칙 (Validate Rules)

rules로 폼 필드의 검증을 설정할 수 있어요. 필수 여부, 최소/최대 길이, 이메일 형식, 커스텀 검증 함수 등을 지원해요.

import React from 'react';
import { Button, Form, Input, Select } from 'antd';

const { Option } = Select;

const App: React.FC = () => {
  const [form] = Form.useForm();

  const onFinish = (values: any) => {
    console.log('Received values of form:', values);
  };

  const checkPrice = (_: any, value: { number: number }) => {
    if (value.number > 0) {
      return Promise.resolve();
    }
    return Promise.reject(new Error('Price must be greater than zero!'));
  };

  return (
    <Form
      form={form}
      name="register"
      onFinish={onFinish}
      initialValues={{ residence: ['zhejiang', 'hangzhou', 'xihu'] }}
      style={{ maxWidth: 600 }}
      scrollToFirstError
    >
      <Form.Item
        name="email"
        label="E-mail"
        rules={[
          { type: 'email', message: 'The input is not valid E-mail!' },
          { required: true, message: 'Please input your E-mail!' },
        ]}
      >
        <Input />
      </Form.Item>

      <Form.Item
        name="password"
        label="Password"
        rules={[{ required: true, message: 'Please input your password!' }]}
        hasFeedback
      >
        <Input.Password />
      </Form.Item>

      <Form.Item label="Captcha" extra="We must make sure that your are a human.">
        <div>
          <Input />
          <Button>Get captcha</Button>
        </div>
      </Form.Item>

      <Form.Item
        name="price"
        label="Price"
        rules={[{ required: true, message: 'Please input price!' }, { validator: checkPrice }]}
      >
        <Input type="number" />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">
          Register
        </Button>
      </Form.Item>
    </Form>
  );
};

export default App;

동적 폼 항목 (Dynamic Form Item)

폼 항목을 동적으로 추가·제거할 수 있어요. Form.List를 사용해 항목 배열을 관리해요.

import React from 'react';
import { Button, Form, Input, Space } from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';

const App: React.FC = () => {
  const onFinish = (values: any) => {
    console.log('Received values of form:', values);
  };

  return (
    <Form name="dynamic_form_nest_item" onFinish={onFinish} style={{ maxWidth: 600 }}>
      <Form.List name="users">
        {(fields, { add, remove }) => (
          <>
            {fields.map(({ key, name, ...restField }) => (
              <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
                <Form.Item {...restField} name={[name, 'first']} rules={[{ required: true, message: 'Missing first name' }]}>
                  <Input placeholder="First Name" />
                </Form.Item>
                <Form.Item {...restField} name={[name, 'last']} rules={[{ required: true, message: 'Missing last name' }]}>
                  <Input placeholder="Last Name" />
                </Form.Item>
                <MinusCircleOutlined onClick={() => remove(name)} />
              </Space>
            ))}
            <Form.Item>
              <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
                Add field
              </Button>
            </Form.Item>
          </>
        )}
      </Form.List>
      <Form.Item>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

export default App;

커스터마이즈된 폼 컨트롤 (Customized Form Controls)

value와 onChange만 처리하면 어떤 컴포넌트든 폼 컨트롤로 쓸 수 있어요.

import React, { useState } from 'react';
import { Button, Form, Input, Select } from 'antd';

const { Option } = Select;

type Currency = 'RMB' | 'USD';

interface PriceValue {
  number?: number;
  currency?: Currency;
}

interface PriceInputProps {
  value?: PriceValue;
  onChange?: (value: PriceValue) => void;
}

const PriceInput: React.FC<PriceInputProps> = ({ value = {}, onChange }) => {
  const [number, setNumber] = useState(0);
  const [currency, setCurrency] = useState<Currency>('RMB');

  const triggerChange = (changedValue: PriceValue) => {
    onChange?.({ number, currency, ...value, ...changedValue });
  };

  const onNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newNumber = parseInt(e.target.value || '0', 10);
    if (Number.isNaN(number)) {
      return;
    }
    setNumber(newNumber);
    triggerChange({ number: newNumber });
  };

  const onCurrencyChange = (newCurrency: Currency) => {
    setCurrency(newCurrency);
    triggerChange({ currency: newCurrency });
  };

  return (
    <span>
      <Input type="text" value={value.number || number} onChange={onNumberChange} placeholder="Input number" />
      <Select value={value.currency || currency} style={{ width: 80 }} onChange={onCurrencyChange}>
        <Option value="RMB">RMB</Option>
        <Option value="USD">USD</Option>
      </Select>
    </span>
  );
};

const App: React.FC = () => {
  const onFinish = (values: any) => {
    console.log('Received values from form: ', values);
  };

  const checkPrice = (_: any, value: { number: number }) => {
    if (value.number >= 1) {
      return Promise.resolve();
    }
    return Promise.reject(new Error('Price must be greater than 0!'));
  };

  return (
    <Form name="customized_form_controls" onFinish={onFinish} style={{ maxWidth: 600 }}>
      <Form.Item name="price" label="Price" rules={[{ validator: checkPrice }]}>
        <PriceInput />
      </Form.Item>
      <Form.Item>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

export default App;

API

공통 props 참조: Common props

Form

Form 컴포넌트의 기본 props예요.

속성 설명 타입 기본값
component 커스텀 컴포넌트로 <form>을 대체 ComponentType | false form
colon label 뒤에 콜론을 표시할지 여부 boolean true
disabled 폼 안의 모든 하위 컴포넌트를 비활성화 boolean false
form Form.useForm()으로 만든 폼 인스턴스 FormInstance -
labelAlign label의 정렬 방식 left | right right
labelCol label의 레이아웃 object -
name 폼 이름 string -
preserve 필드가 제거되었을 때 값을 보존할지 여부 boolean true
requiredMark 필수 필드 표시 방식 boolean | optional | (label, info) => ReactNode true
scrollToFirstError 제출 실패 시 첫 번째 에러로 스크롤 boolean | options false
size 폼 안 컴포넌트의 크기 large | middle | small -
validateMessages 검증 메시지 템플릿 ValidateMessages -
validateTrigger 검증을 트리거할 이벤트 string | string[] onChange
variant 컴포넌트의 변형 outlined | borderless | filled | underlined -
wrapperCol 입력 컨트롤의 레이아웃 object -
onFinish 제출 성공 시 호출 (values) => void -
onFinishFailed 제출 실패 시 호출 ({ values, errorFields, outOfDate }) => void -
onValuesChange 값이 변경될 때 호출 (changedValues, allValues) => void -

Form.Item

속성 설명 타입 기본값
dependencies 다른 필드의 값을 의존 NamePath[] -
extra 추가 도움말을 표시 ReactNode -
getValueFromEvent 이벤트에서 값을 추출하는 함수 (..args) => any -
getValueProps 입력 컴포넌트에 넘기는 props를 변환 (value) => Record<string, any> -
hasFeedback 검증 피드백 아이콘을 표시 boolean false
help 도움말 텍스트 ReactNode -
hidden 필드를 숨길지 여부 boolean false
htmlFor label의 htmlFor 속성 string -
initialValue 초기값 any -
label 필드의 라벨 ReactNode -
name 필드 이름 NamePath -
normalize 값 변경 시 정규화 함수 (value, prevValue, allValues) => any -
noStyle 스타일 없는 필드 boolean false
preserve 값 보존 여부 boolean true
required 필수 여부(라벨에 별표 표시) boolean false
rules 검증 규칙 Rule[] -
shouldUpdate 다른 필드 변경에 반응해 다시 렌더링 boolean | (prevValues, curValues) => boolean false
trigger 값을 트리거할 이벤트 string onChange
validateDebounce 검증 디바운스 number -
validateFirst 첫 번째 실패에서 멈출지 여부 boolean | parallel false
validateStatus 검증 상태 강제 지정 success | warning | error | validating -
validateTrigger 검증 트리거 이벤트 string | string[] onChange
valuePropName 값이 담긴 prop 이름 string value
wrapperCol 입력 컨트롤 레이아웃 object -

Form.List

속성 설명 타입 기본값
children 리스트 렌더링 함수 (fields, { add, remove, move, insert, replace }) => ReactNode -
initialValue 초기값 any[] -
name 폼 리스트 이름 NamePath -
rules 검증 규칙 Rule[] -

FormInstance

Form.useForm()으로 얻는 인스턴스의 메서드예요.

메서드 설명
getFieldValue(name) 필드 값을 가져옴
getFieldsValue() 모든 필드 값을 가져옴
getFieldsError() 모든 필드의 에러를 가져옴
isFieldsTouched() 필드가 터치되었는지 확인
isFieldTouched(name) 특정 필드가 터치되었는지 확인
isFieldValidating(name) 특정 필드가 검증 중인지 확인
resetFields(fields?) 필드 값을 초기값으로 리셋
scrollToField(name, options?) 특정 필드로 스크롤
setFields(fields) 필드 상태를 설정
setFieldsValue(values) 필드 값을 설정
submit() 폼을 제출
validateFields(nameList?) 필드를 검증. Promise 반환

Rule

검증 규칙 객체의 속성이에요.

속성 설명 타입
enum 열거형 값과 일치하는지 검증 any[]
len 정확한 길이 검증 number
max 최대 길이/값 검증 number
message 에러 메시지 ReactNode
min 최소 길이/값 검증 number
pattern 정규식 패턴 검증 RegExp
required 필수 여부 boolean
transform 검증 전 값 변환 (value) => any
type 값 타입 검증 string
validator 커스텀 검증 함수 (rule, value) => Promise | void
whitespace 공백만 있는지 검증 boolean

Semantic DOM

https://ant.design/components/form/semantic.md

Design Token

Component Token (Form)

토큰 이름 설명 타입 기본값
itemMarginBottom 필드 항목 사이의 아래 여백 number

FAQ

context, redux, ConfigProvider locale/prefixCls에 접근할 수 없는 이유는 뭔가요? {#faq-context-redux}

정적 메서드(message, notification, Modal.confirm 등)를 사용할 때는 antd가 ReactDOM.render로 별도의 React 인스턴스를 만들어 컨텍스트를 공유하지 못할 수 있어요. 폼 안에서 이런 메서드를 쓸 때는 Form.useFormInstance()나 App 컴포넌트의 인스턴스를 사용하면 컨텍스트 문제를 피할 수 있어요.

검증 규칙의 validator를 비동기로 사용할 수 있나요?

네, validator는 Promise를 반환할 수 있어요. resolve하면 통과, reject하면 실패로 처리돼요.

더 알아보기 (Learn more)