반응형
고급 디자인 패턴 시리즈의 마지막 주인공은 최근 프론트엔드 생태계에서 가장 뜨거운 관심을 받고 있는 Headless UI 패턴입니다.
디자인 시스템을 구축하거나 복잡한 상호작용이 필요한 UI(드롭다운, 달력, 모달 등)를 만들 때, "기능은 똑같은데 디자인만 살짝 다른 컴포넌트"를 매번 다시 만드느라 고생한 적이 있다면 이 패턴이 해답이 될 것입니다.
1. 😶 Headless UI란 무엇인가?
'Head(머리, 즉 UI/스타일)'가 없는 컴포넌트를 의미합니다. 컴포넌트에서 **상태 관리, 키보드 인터랙션, 웹 접근성(A11y)**과 같은 복잡한 로직만 담당하고, 실제로 화면에 어떻게 보일지는 개발자에게 전적으로 맡기는 방식입니다.
- 기존 방식: 버튼 색상, 패딩 등을 props로 넘겨서 제어 (확장성의 한계)
- Headless 방식: 로직이 담긴 훅(Hook)이나 컴포넌트가 '속성(Props)'만 제공하고, 스타일은 내가 원하는 CSS(Tailwind, Styled-components 등)로 직접 입힘
2. 🛠️ 왜 Headless 패턴인가?
- 완벽한 스타일 자유도: 디자인 가이드가 바뀌어도 로직을 건드릴 필요가 없습니다. 클래스 이름이나 인라인 스타일만 바꾸면 됩니다.
- 웹 접근성 준수: aria-expanded, role="listbox" 같은 복잡한 접근성 속성을 라이브러리가 알아서 계산해 줍니다.
- 번들 크기 최적화: 스타일 코드가 포함되어 있지 않아 매우 가볍습니다.
3. 🏗️ 실전 구현: 커스텀 훅을 이용한 Headless 패턴
가장 대표적인 방식은 Getter Props 패턴을 활용한 커스텀 훅 방식입니다. 간단한 'Toggle' 로직을 예로 들어보겠습니다.
1단계: 로직 전용 훅 만들기
JavaScript
function useToggle() {
const [on, setOn] = useState(false);
const toggle = () => setOn(!on);
// UI 요소에 입혀줄 '속성 덩어리'를 반환합니다.
const getTogglerProps = (props = {}) => ({
'aria-pressed': on,
onClick: toggle,
...props,
});
return { on, toggle, getTogglerProps };
}
2단계: 실제 UI에서 사용하기 (스타일은 내 마음대로!)
JavaScript
function App() {
const { on, getTogglerProps } = useToggle();
return (
<div>
{/* 스타일은 Tailwind, CSS Modules 등 무엇이든 사용 가능 */}
<button
className={on ? 'bg-blue-500' : 'bg-gray-300'}
{...getTogglerProps({ id: 'my-toggle' })}
>
{on ? 'ON' : 'OFF'}
</button>
</div>
);
}
4. 🌟 주요 Headless UI 라이브러리
직접 로직을 다 짜기엔 웹 접근성(Keyboard Navigation 등)이 너무 복잡하기 때문에, 아래 라이브러리들을 실무에서 많이 사용합니다.
- Radix UI / Headless UI (by Tailwind): 가장 대중적인 컴포넌트 기반 라이브러리.
- TanStack Table (React Table): 테이블의 정렬, 필터링 로직만 제공하는 끝판왕.
- Downshift: 자동완성(Autocomplete), 콤보박스 로직 전용.
- React Aria: 어도비에서 만든, 접근성에 미친(?) 라이브러리.
5. ⚖️ Compound Components vs Headless UI
- Compound Components: 부모-자식 간의 구조적 관계를 정의하고 상태를 공유하는 구조적 패턴.
- Headless UI: UI를 아예 제외하고 기능적 속성만 제공하는 로직 분리 패턴.
최근에는 Headless UI를 기반으로 Compound Components 구조를 입히는 방식이 현대 프론트엔드 아키텍처의 정석으로 자리 잡고 있습니다.
반응형
'개발 > React' 카테고리의 다른 글
| [고급반] Step 3-2. Suspense와 Error Boundary: 선언적 비동기 처리 아키텍처 (0) | 2025.12.23 |
|---|---|
| [고급반] Step 3-1. Concurrent React: useTransition과 useDeferredValue (0) | 2025.12.23 |
| [고급반] Step 2-2. 로직 재사용의 기술: Render Props와 HOC (0) | 2025.12.23 |
| [고급반] Step 2-1. 확장성 있는 디자인 패턴: Compound Components (0) | 2025.12.23 |
| [고급반] Step 1-3. 대용량 데이터 렌더링: Windowing 기법으로 1만 개 리스트 다루기 (0) | 2025.12.23 |