껍데기는 가라, 알맹이만 남긴다 (Headless UI)

812025년 11월 04일4

전통적인 UI 라이브러리들은 개발자들에게 잘 디자인된 버튼, 모달, 드롭다운 메뉴 같은 컴포넌트들을 제공했다. 편리했지만, 동시에 제약이 따랐다. 디자이너가 아주 독특한 모양의 UI를 요구할 때, 기존 라이브러리의 스타일을 덮어쓰거나 커스터마이징하는 것은 종종 원래의 코드를 해킹하는 것에 가까운, 고통스러운 작업이었다.

반대로 모든 것을 처음부터 직접 만들자니, 보이지 않는 복잡성이 발목을 잡았다. 키보드 네비게이션, 포커스 관리, 스크린 리더를 위한 접근성(WAI-ARIA) 속성 관리, 열림/닫힘 상태 관리 등, 제대로 된 UI 컴포넌트를 만드는 것은 겉보기보다 훨씬 더 어려운 일이었다.

이 딜레마 속에서, ‘헤드리스(Headless) UI’라는 새로운 개념이 떠오르기 시작했다.
이름 그대로, ‘머리(Head)’, 즉 시각적인 부분을 완전히 제거한 UI 라이브러리였다.

헤드리스 UI 라이브러리는 스타일이 전혀 입혀지지 않은, 순수한 ‘기능’과 ‘상태’ 로직만을 제공했다. 그리고 이 로직을 제공하는 가장 완벽한 방법이 바로 커스텀 훅이었다.

Headless UIRadix UI, Downshift 같은 라이브러리들이 이 개념을 선도했다.

한 개발팀이 매우 독특한 디자인의 드롭다운 메뉴를 구현해야 하는 과제를 받았다. 기존 라이브러리로는 구현이 불가능했다.
그때, 한 개발자가 Headless UIuseMenu 훅(개념적 예시)을 사용하자고 제안했다.

import { useMenu } from '@headlessui/react';
import { useRef } from 'react';

function CustomDropdown() {
  const buttonRef = useRef(null);
  const itemsRef = useRef(null);

  // useMenu 훅을 호출하여, 메뉴 기능에 필요한 모든 것을 받는다.
  const {
    isOpen,
    getButtonProps,
    getMenuProps,
    getItemProps,
  } = useMenu({ buttonRef, itemsRef });

  return (
    <div>
      {/* getButtonProps가 aria 속성, onClick 핸들러 등을 모두 제공한다. */}
      <button {...getButtonProps()} ref={buttonRef}>
        Options
      </button>

      {/* getMenuProps가 role, 키보드 이벤트 핸들러 등을 제공한다. */}
      <ul {...getMenuProps()} ref={itemsRef}>
        {isOpen && (
          <>
            {/* getItemProps가 각 아이템의 상태와 이벤트를 관리한다. */}
            <li {...getItemProps({ value: 'edit' })}>Edit</li>
            <li {...getItemProps({ value: 'duplicate' })}>Duplicate</li>
            <li {...getItemProps({ value: 'delete' })}>Delete</li>
          </>
        )}
      </ul>
    </div>
  );
}

useMenu 훅은 드롭다운 메뉴를 구현하는 데 필요한 모든 복잡한 로직을 내부적으로 처리했다.

  • 메뉴가 열렸는지 닫혔는지 알려주는 isOpen 상태.
  • 버튼과 메뉴, 각 아이템에 필요한 모든 aria-* 속성과 이벤트 핸들러(클릭, 키보드 조작 등)를 담고 있는 get...Props 헬퍼 함수들.

개발자가 할 일은, 이 훅이 제공하는 ‘알맹이’들(상태와 props)을 가지고, 자신이 원하는 어떤 HTML 구조와 CSS 스타일로든 100% 자유롭게 껍데기를 ‘그리기만’ 하면 되는 것이었다.

결과적으로, 기능과 표현의 완벽한 분리가 이루어졌다.

  • 기능(Functionality): 복잡한 상태 관리, 이벤트 처리, 접근성은 모두 헤드리스 UI 라이브러리의 훅이 책임진다.
  • 표현(Presentation): UI가 어떻게 보일지는 100% 개발자와 디자이너의 손에 달려있다.

이것은 UI 개발의 패러다임을 바꾸는 것이었다.
더 이상 완제품 컴포넌트에 자신을 맞추는 것이 아니라, 순수한 기능이라는 부품을 가져와 자신만의 완제품을 조립하는 방식이었다.

훅은 이 헤드리스 UI 혁명을 가능하게 한 핵심 기술이었다.
상태와 로직을 UI로부터 분리하여 재사용 가능한 함수로 캡슐화하는 훅의 본질적인 특성이, 헤드리스 UI가 추구하는 철학과 완벽하게 일치했기 때문이다.

리액트 생태계는 이제 개발자들에게 더 넓은 선택의 폭을 제공하고 있었다.
빠른 개발을 원한다면 전통적인 UI 라이브러리를, 완전한 디자인 자유도를 원한다면 헤드리스 UI 라이브러리를 선택할 수 있게 된 것이다. 그리고 이 모든 것의 중심에는, ‘훅’이라는 공통된 언어가 있었다.