Form

Form

검증 규칙을 사용해 사용자로부터 정보를 수집하는 컴포넌트예요.

출처: 문서

본문

네이티브 브라우저 constraint validation API 기반으로 동작하는 폼 컴포넌트예요. 내장 검증과 커스텀 검증을 모두 지원하고, 검증 메시지를 자유롭게 커스터마이즈하며 접근성 있는 검증 메시지를 제공해요. 클라이언트/서버 양쪽 시나리오를 모두 지원해요.

Features

  • 네이티브 브라우저 constraint validation API 기반.
  • 내장 검증 지원.
  • 커스텀 검증 지원.
  • 검증 메시지의 완전한 커스터마이즈.
  • 접근성 있는 검증 메시지.
  • 클라이언트/서버 양쪽 시나리오 지원.
  • 포커스 완전 관리.

Anatomy

모든 파트를 임포트해 조립해요.

import { Form } from "radix-ui";

export default () => (
  <Form.Root>
    <Form.Field>
      <Form.Label />

      <Form.Control />

      <Form.Message />

      <Form.ValidityState />
    </Form.Field>

    <Form.Message />

    <Form.ValidityState />

    <Form.Submit />
  </Form.Root>
);

API Reference

Root

폼의 모든 파트를 담아요.

Prop Type Default
asChild boolean false
onClearServerErrors function No default value

Field

필드의 래퍼예요. id/name과 라벨 접근성을 자동으로 처리해요.

Prop Type Default
asChild boolean false
name* string No default value
serverInvalid boolean No default value
Data attribute Values
[data-invalid] Present when the field is invalid
[data-valid] Present when the field is valid

Label

Field 파트 안에 중첩되면 자동으로 연결되는 라벨 요소예요.

Prop Type Default
asChild boolean false
Data attribute Values
[data-invalid] Present when the field is invalid
[data-valid] Present when the field is valid

Control

Field 파트 안에 중첩되면 자동으로 연결되는 컨트롤 요소(기본은 input)예요.

Prop Type Default
asChild boolean false
Data attribute Values
[data-invalid] Present when the field is invalid
[data-valid] Present when the field is valid

Message

Field 파트 안에 중첩되면 특정 컨트롤에 (기능과 접근성 모두) 자동으로 연결되는 검증 메시지예요. 내장/커스텀 클라이언트 검증과 서버 검증에 모두 쓰일 수 있어요. Field 밖에서 쓰면 필드와 일치하는 name prop을 전달해야 해요.

Form.Message는 메시지가 언제 표시될지 결정하는 match prop을 받아요. 네이티브 HTML validity state(MDN의 ValidityState)와 일치하며, required, min, max 같은 속성에 대해 검증해요. 컨트롤의 validity state에서 주어진 match가 true이면 메시지가 표시돼요.

match에 함수를 전달해 커스텀 검증 규칙을 제공할 수도 있어요.

Prop Type Default
asChild boolean false
match Matcher No default value
forceMatch boolean false
name string No default value

ValidityState

렌더링 중 특정 필드의 validity state에 접근하려면 이 render-prop 컴포넌트를 사용해요(MDN의 ValidityState 참고). Field 파트 안에 중첩되면 필드의 validity가 자동으로 제공되고, 그렇지 않으면 name prop으로 연결해야 해요.

Prop Type Default
children function No default value
name string No default value

Submit

제출 버튼이에요.

Prop Type Default
asChild boolean false

Examples

자신의 컴포넌트와 조합하기

asChild를 사용하면 Form primitive 파트를 자신의 컴포넌트와 조합할 수 있어요.

<Form.Field name="name">
  <Form.Label>Full name</Form.Label>

  <Form.Control asChild>
    <TextField.Input variant="primary" />
  </Form.Control>
</Form.Field>

select 같은 다른 타입의 컨트롤과도 조합할 수 있어요:

<Form.Field name="country">
  <Form.Label>Country</Form.Label>

  <Form.Control asChild>
    <select>
      <option value="uk">United Kingdom</option>…
    </select>
  </Form.Control>
</Form.Field>

참고: 현재 Radix의 다른 폼 primitive(예: Checkbox, Select 등)와 Form을 조합하는 것은 불가능해요. 이에 대한 해결책을 작업 중이에요.

자신만의 검증 메시지 제공하기

children이 제공되지 않으면 Form.Message는 주어진 match에 대한 기본 오류 메시지를 렌더링해요.

// will yield "This value is missing"

<Form.Message match="valueMissing" />

자신의 children을 전달해 더 의미 있는 메시지를 제공할 수 있어요. 국제화에도 유용해요.

// will yield "Please provide a name"

<Form.Message match="valueMissing">Please provide a name</Form.Message>

커스텀 검증

위에서 설명한 모든 내장 클라이언트 검증 match에 더해, 플랫폼의 검증 능력을 그대로 활용하면서 자신만의 커스텀 검증을 제공할 수도 있어요. constraint validation API에 있는 customError 타입을 사용해요.

Form.Message의 match prop에 자신만의 검증 함수를 전달할 수 있어요. 예시:

<Form.Field name="name">
  <Form.Label>Full name</Form.Label>

  <Form.Control />

  <Form.Message match={(value, formData) => value !== "John"}>Only John is allowed.</Form.Message>
</Form.Field>

match는 첫 번째 인자로 컨트롤의 현재 값, 두 번째 인자로 전체 FormData를 받고 호출돼요. match는 비동기 검증을 위해 async 함수(또는 promise 반환)일 수도 있어요.

유효성 기반 스타일링

관련 파트에 data-valid와 data-invalid 속성을 추가해요. 그에 맞게 컴포넌트를 스타일링할 수 있어요. Label 파트를 스타일링하는 예시예요.

//index.jsx

import * as React from "react";

import { Form } from "radix-ui";

export default () => (
  <Form.Root>
    <Form.Field name="email">
      <Form.Label className="FormLabel">Email</Form.Label>

      <Form.Control type="email" />
    </Form.Field>
  </Form.Root>
);
/* styles.css */

.FormLabel[data-invalid] {
  color: red;
}

.FormLabel[data-valid] {
  color: green;
}

더 많은 제어를 위해 validity state 접근하기

필드의 raw validity state에 접근해 자신만의 아이콘을 표시하거나, 정의된 props를 통해 컴포넌트 라이브러리와 인터페이스해야 할 수 있어요. Form.ValidityState 파트로 할 수 있어요:

<Form.Field name="name">
  <Form.Label>Full name</Form.Label>

  <Form.ValidityState>
    {(validity) => (
      <Form.Control asChild>
        <TextField.Input variant="primary" state={getTextFieldInputState(validity)} />
      </Form.Control>
    )}
  </Form.ValidityState>
</Form.Field>

서버 사이드 검증

컴포넌트는 동일한 Form.Message 컴포넌트로 서버 사이드 검증도 지원해요. 클라이언트 오류로 정의한 것과 같은 메시지를 forceMatch prop으로 재사용할 수 있는데, 이 prop은 클라이언트 match 로직과 무관하게 메시지를 강제로 표시해요.

메시지가 클라이언트에 없으면 match 없는 Form.Message도 렌더링할 수 있어요. 필드는 Form.Field 파트에 serverInvalid boolean prop을 넘겨 invalid로 표시돼요.

서버 오류 처리 예시:

import * as React from "react";

import { Form } from "radix-ui";

function Page() {
  const [serverErrors, setServerErrors] = React.useState({
    email: false,

    password: false,
  });

  return (
    <Form.Root
      // `onSubmit` only triggered if it passes client-side validation
      onSubmit={(event) => {
        const data = Object.fromEntries(new FormData(event.currentTarget));

        // Submit form data and catch errors in the response
        submitForm(data)
          .then(() => {})
          /**
           * Map errors from your server response into a structure you'd like to work with.
           * In this case resulting in this object: `{ email: false, password: true }`
           */
          .catch((errors) => setServerErrors(mapServerErrors(errors)));

        // prevent default form submission
        event.preventDefault();
      }}
      onClearServerErrors={() => setServerErrors({ email: false, password: false })}
    >
      <Form.Field name="email" serverInvalid={serverErrors.email}>
        <Form.Label>Email address</Form.Label>

        <Form.Control type="email" required />

        <Form.Message match="valueMissing">Please enter your email.</Form.Message>

        <Form.Message match="typeMismatch" forceMatch={serverErrors.email}>
          Please provide a valid email.
        </Form.Message>
      </Form.Field>

      <Form.Field name="password" serverInvalid={serverErrors.password}>
        <Form.Label>Password</Form.Label>

        <Form.Control type="password" required />

        <Form.Message match="valueMissing">Please enter a password.</Form.Message>

        {serverErrors.password && (
          <Form.Message>
            Please provide a valid password. It should contain at least 1 number and 1 special
            character.
          </Form.Message>
        )}
      </Form.Field>

      <Form.Submit>Submit</Form.Submit>
    </Form.Root>
  );
}

서버 오류는 Form.Root 파트의 onClearServerErrors 콜백 prop으로 지워야 해요. 폼이 다시 제출되기 전과 폼이 reset될 때 서버 오류를 지워요.

추가로 이는 단일 서버 오류를 언제 리셋할지 제어할 수 있게 해 줘요. 예를 들어 사용자가 편집하는 즉시 email 서버 오류를 리셋할 수 있어요:

<Form.Field name="email" serverInvalid={serverErrors.email}>
  <Form.Label>Email address</Form.Label>

  <Form.Control
    type="email"
    onChange={() => setServerErrors((prev) => ({ ...prev, email: false }))}
  />

  <Form.Message match="valueMissing">Please enter your email.</Form.Message>

  <Form.Message match="typeMismatch" forceMatch={serverErrors.email}>
    Please provide a valid email.
  </Form.Message>
</Form.Field>

Accessibility

컴포넌트는 검증의 "inline errors" 패턴을 따르며:

  • 라벨과 컨트롤은 Form.Field에 제공한 name으로 연결돼요.
  • 하나 이상의 클라이언트 오류 메시지가 표시되면 해당 컨트롤과 자동으로 연결되어 그에 맞게 공지돼요.
  • 포커스는 첫 번째 invalid 컨트롤로 이동해요.

더 알아보기 (Learn more)

  • 서버 검증 오류는 Form.Field의 serverInvalid와 Form.Message의 forceMatch를 조합해 표시해요.
  • Form.ValidityState render prop으로 필드의 raw validity state에 접근해 커스텀 UI/로직을 구성할 수 있어요.