Post

react - 예쁜 체크박스를 만들어보자

🧡 체크박스와 어드민

admin 페이지를 만들다 보면 가장 자주 마주하는 UI 중 하나가 바로 테이블과 체크박스(checkbox) / 라디오 버튼(radio)이다. 관리자 페이지는 실제 사용자가 빠르게 정보를 파악하고 입력할 수 있어야 하기 때문에, UI의 작은 요소 하나도 “한눈에 들어오는가?”가 매우 중요하다.

하지만 기본 제공되는 <input type="checkbox"> 형태는 디자인 커스터마이징에 제약이 있어, 대부분 별도의 스타일을 입히거나 완전히 커스텀 컴포넌트를 만들어 사용하게 된다.

최근 체크박스를 커스텀할 일이 자주 있어서, 그동안 자주 사용했던 두 가지 방식을 간단하게 정리해본다

⭐ state를 사용하자

첫 번째 방법은 state로 체크 여부를 직접 관리하는 방식이다. 가장 간단하고, 원하는 형태로 UI를 자유롭게 만들기 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { useState } from "react"
import { Check } from "lucide-react"
import { cn } from "@/utils/cn"

interface CheckboxProps {
  label?: string
  defaultChecked?: boolean
  onChange?: (checked: boolean) => void
}

export default function Checkbox({
  label = "",
  defaultChecked = false,
  onChange,
}: CheckboxProps) {
  const [checked, setChecked] = useState(defaultChecked)

  const handleClick = () => {
    const newValue = !checked
    setChecked(newValue)
    onChange?.(newValue)
  }

  return (
    <label
      className="flex items-center gap-2 cursor-pointer select-none"
      onClick={handleClick}
    >
      <span
        className={cn(
          "w-5 h-5 rounded-md border flex items-center justify-center transition-all",
          checked
            ? "bg-indigo-600 border-indigo-600"
            : "bg-white border-gray-400"
        )}
      >
        {checked && <Check size={14} className="text-white" />}
      </span>
      {label}
    </label>
  )
}

🌟 input을 사용하자

두 번째 방식은 기본 input 요소를 유지하면서 외형만 커스텀하는 방식이다. 내가 선호하는 방식인데 장점은 다음과 같다.

  • register를 통해 react-hook-form과 자연스럽게 연결됨
  • label 클릭으로 체크가 toggled 되어 접근성이 좋음
  • input은 숨겨두고, 디자인은 label 또는 span에 입히는 구조라서 유지보수에 좋음

구현 핵심은 input은 sr-only로 감추고, 시각적 체크박스는 label 내부의 span으로 구현하는 것이다.

1
2
3
4
5
<label className="flex items-center gap-2 cursor-pointer">
  <input type="checkbox" {...register("isActive")} className="sr-only" />
  <span className="custom-checkbox"></span>
  활성화 여부
</label>
This post is licensed under CC BY 4.0 by the author.