ShadCN UI 학습기: form 컴포넌트

박기범·2025년 3월 22일

shadcn/ui

목록 보기
8/10
post-thumbnail

이번에는 shadcn ui form을 설치를 한 후 간단한 로그인 폼을 제작해보았습니다.

https://ui.shadcn.com/docs/components/form#installation
다음 링크를 통해 form을 깔아주면

  • react-hook-form → 폼 상태 관리 라이브러리
  • @hookform/resolvers → react-hook-form과 zod 같은 스키마 검증 라이브러리를 연결하는 역할
  • zod → 입력 값 검증을 위한 스키마 정의 라이브러리
    다음과 같은 라이브러리가 깔린

처음 zod 라이브러리가 깔린 것을 봤을 때 사용해보지 못하여서 개념에 대한 공부를 해보았다.

🛠 zod란? (입력 값 검증 라이브러리)
zod는 입력 값의 유효성을 검사하는 라이브러리로, 폼 입력값이 올바른지 확인하는 데 사용됩니다.

  • zod의 주요 특징
  1. 스키마 기반 검증 → z.object({})를 이용해 데이터 구조를 정의하고 검증 수행
  2. 타입스크립트 친화적 → 입력 데이터의 타입을 자동으로 추론
  3. 체이닝 방식 검증 → .min(), .max(), .email() 등 다양한 메서드를 사용해 유효성 검사 가능

🛠 zod를 사용한 폼 검증 예제

import { z } from 'zod';
export const formSchema = z.object({
  username: z.string().min(2, '이름은 최소 2자 이상이어야 합니다.').max(50, '최대 50자까지 입력 가능합니다.'),
  password: z.string().min(6, '비밀번호는 최소 6자 이상이어야 합니다.'),
});
export type FormSchemaType = z.infer<typeof formSchema>;

이 코드는 zod를 사용해 입력값이 올바른지 검증하는 스키마를 정의한 것입니다.
username은 최소 2자 이상, 최대 50자 이하로 제한
password는 최소 6자 이상 입력해야 유효


ShadCN UI의 Form 컴포넌트

ShadCN UI는 react-hook-form과 함께 사용할 수 있도록 폼 컴포넌트를 제공합니다.
특히 Form, FormField, FormItem, FormLabel, FormControl, FormMessage 등을 활용하면 깔끔하고 일관된 스타일의 폼을 손쉽게 구성할 수 있습니다.

다음은 zod와 훅폼을 사용한 컴포넌트 작성 코드입니다.

//데이터 유효성에 관한 스키마 정의
//formSchema.ts
import { z } from 'zod';

export const formSchema = z.object({
  username: z
    .string()
    .min(2, '이름은 최소 2자 이상이어야 합니다.')
    .max(50, '최대 50자까지 입력 가능합니다.'),
  password: z.string().min(6, '비밀번호는 최소 6자 이상이어야 합니다.'),
});

export type FormSchemaType = z.infer<typeof formSchema>;
//임시로 만든 회원가입 폼
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { FormSchemaType, formSchema } from '@/schemas/formSchema';
import { Button } from '@/components/ui/button';
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import {
  Card,
  CardHeader,
  CardTitle,
  CardContent,
  CardFooter,
} from '@/components/ui/card';
import { Eye, EyeOff, Lock, User } from 'lucide-react';
import { useState } from 'react';

const AuthForm = () => {
  const form = useForm<FormSchemaType>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      username: '',
      password: '',
    },
  });

  const [showPassword, setShowPassword] = useState(false);

  const onSubmit = (values: FormSchemaType) => {
    alert(
      `회원가입에 성공하였습니다!!\n이름 : ${values.username} \n비번 : ${values.password}`,
    );
  };

  return (
    <Card className="w-[400px] border border-gray-200 shadow-lg">
      <CardHeader>
        <CardTitle className="text-center text-2xl font-bold">
          회원가입
        </CardTitle>
      </CardHeader>
      <CardContent>
        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-5">
            {/* Username 필드 */}
            <FormField
              control={form.control}
              name="username"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Username</FormLabel>
                  <FormControl>
                    <div className="relative">
                      <User
                        className="absolute left-3 top-1/2 -translate-y-1/2 transform text-gray-400"
                        size={18}
                      />
                      <Input
                        placeholder="홍길동"
                        {...field}
                        className="pl-10"
                      />
                    </div>
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            {/* Password 필드 */}
            <FormField
              control={form.control}
              name="password"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Password</FormLabel>
                  <FormControl>
                    <div className="relative">
                      <Lock
                        className="absolute left-3 top-1/2 -translate-y-1/2 transform text-gray-400"
                        size={18}
                      />
                      <Input
                        type={showPassword ? 'text' : 'password'}
                        placeholder="비밀번호 입력"
                        {...field}
                        className="pl-10 pr-10"
                      />
                      <button
                        type="button"
                        onClick={() => setShowPassword(!showPassword)}
                        className="absolute right-3 top-1/2 -translate-y-1/2 transform text-gray-400"
                      >
                        {showPassword ? (
                          <EyeOff size={18} />
                        ) : (
                          <Eye size={18} />
                        )}
                      </button>
                    </div>
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            <CardFooter className="flex flex-col gap-3">
              <Button type="submit" className="w-full">
                회원가입
              </Button>
              <p className="text-center text-sm text-gray-500">
                이미 계정이 있으신가요?{' '}
                <a href="" className="text-blue-500 hover:underline">
                  로그인
                </a>
              </p>
            </CardFooter>
          </form>
        </Form>
      </CardContent>
    </Card>
  );
};

export default AuthForm;

추가적인 디자인을 위한 card, button 컴포넌트를 추가했습니다.
또한 학습을 목적으로 했기 때문에 컴포넌트 분리, 최적화 등을 추가적으로 하지 않았습니다.

또 한 가지 흥미로웠던 점이 shadcn-ui의 form 컴포넌트를 추가할 때 자동으로 lucide-react도 함께 설치되어서 아이콘을 바로 사용할 수 있다는 점이다.

마지막으로 코드에 관한 간단한 설명

  • useForm을 활용한 폼 상태 관리
    react-hook-form의 useForm을 사용해 폼 상태를 관리하고,
    zodResolver를 통해 Zod 스키마 검증을 적용
  • 입력 필드 (Username & Password)
    FormField, FormItem, FormControl을 사용해 shadcn/ui 스타일을 적용.
    lucide-react 아이콘(User, Lock, Eye, EyeOff)을 활용하여 UI 개선.
    비밀번호 필드에 토글 버튼을 추가하여 Eye, EyeOff 아이콘으로 비밀번호 가리기/보이기 기능 제공.
  • 카드 UI (Shadcn UI Card 컴포넌트 사용)
    Card, CardHeader, CardTitle, CardContent, CardFooter를 활용해 회원가입 UI 디자인.
  • 폼 제출 (회원가입)
    onSubmit 함수에서 입력값을 받아 alert 창으로 확인하도록 구현.

profile
프론트엔드 개발공부를 하고있습니다.

0개의 댓글