모던 웹 애플리케이션을 구축할 때 가장 큰 고통 중 하나는 선택 메뉴, 드롭다운, 토글, 모달, 탭, 라디오 그룹과 같은 커스텀 컴포넌트를 만드는 것입니다. 이러한 컴포넌트는 프로젝트마다 매우 비슷하지만 결코 완전히 _동일_하지는 않습니다.
기성 패키지를 사용할 수도 있지만, 이들은 보통 자체 제공 스타일과 밀접하게 결합되어 있습니다. 결국 프로젝트의 디자인과 느낌에 맞추기가 매우 어려워지며, 거의 항상 CSS 오버라이드를 작성해야 합니다. 이는 Tailwind CSS로 작업할 때 큰 후퇴로 느껴집니다.
다른 옵션은 처음부터 직접 컴포넌트를 만드는 것입니다. 처음에는 쉬워 보이지만, 키보드 네비게이션 지원, ARIA 속성 관리, 포커스 트래핑 등을 추가해야 한다는 것을 기억하면 갑자기 3-4주를 투자해 완벽한 드롭다운 메뉴를 만들려고 노력하게 됩니다.
우리는 더 나은 옵션이 있다고 생각하며, 그것을 만들고 있습니다.
Headless UI는 React와 Vue _(곧 Alpine.js도)_를 위한 완전히 스타일이 적용되지 않은 접근성 있는 UI 컴포넌트 세트입니다. 이를 통해 복잡한 구현 세부 사항을 걱정하지 않고도 이러한 종류의 커스텀 컴포넌트를 쉽게 구축할 수 있으며, 간단한 유틸리티 클래스로 스타일을 처음부터 적용할 수 있는 능력을 잃지 않습니다.

다음은 @headlessui/react
를 사용하여 완전한 키보드 네비게이션 지원과 ARIA 속성 관리를 갖춘 커스텀 드롭다운 _(라이브러리에 포함된 많은 컴포넌트 중 하나)_을 Tailwind CSS 유틸리티로 스타일링한 예제입니다:
import { Menu } from "@headlessui/react";function MyDropdown() { return ( <Menu as="div" className="relative"> <Menu.Button className="rounded bg-blue-600 px-4 py-2 text-white ...">Options</Menu.Button> <Menu.Items className="absolute right-0 mt-1"> <Menu.Item> {({ active }) => ( <a className={`${active && "bg-blue-500 text-white"} ...`} href="/account-settings"> Account settings </a> )} </Menu.Item> <Menu.Item> {({ active }) => ( <a className={`${active && "bg-blue-500 text-white"} ...`} href="/documentation"> Documentation </a> )} </Menu.Item> <Menu.Item disabled> <span className="opacity-75 ...">Invite a friend (coming soon!)</span> </Menu.Item> </Menu.Items> </Menu> );}
이 예제에서 여러분은 다음과 같은 기능을 무료로 얻을 수 있으며, 관련 코드를 한 줄도 작성할 필요가 없습니다:
- 드롭다운 패널은 클릭, 스페이스바, 엔터, 또는 화살표 키를 사용하여 열립니다.
- 드롭다운은 ESC를 누르거나 외부를 클릭하면 닫힙니다.
- 위아래 화살표 키를 사용하여 항목을 탐색할 수 있습니다.
Home
키를 사용하여 첫 번째 항목으로,End
키를 사용하여 마지막 항목으로 이동할 수 있습니다.- 키보드로 탐색할 때 비활성화된 항목은 자동으로 건너뜁니다.
- 키보드로 탐색한 후 마우스로 항목 위에 올리면 마우스 위치 기반 포커싱으로 전환됩니다.
- 키보드로 탐색할 때 스크린 리더가 항목을 올바르게 알립니다.
- 드롭다운 버튼은 스크린 리더에게 메뉴를 제어하는 버튼으로 올바르게 알려집니다.
- ...그리고 제가 잊어버린 많은 기능들.
이 모든 것을 aria
라는 글자를 코드 어디에도 작성하지 않고, 단일 이벤트 리스너도 작성하지 않고 얻을 수 있습니다. 그리고 여전히 디자인을 완전히 제어할 수 있습니다!
이 컴포넌트에는 3000줄 이상의 테스트 코드가 있습니다. 여러분이 직접 그런 테스트를 작성하지 않아도 된다니 정말 좋죠?
다음은 완전히 스타일링된 라이브 데모입니다 (Tailwind UI에서 가져옴). 실제로 동작하는 모습을 확인할 수 있습니다:
키보드나 스크린 리더로 직접 사용해 보면 그 진가를 알 수 있습니다!
우리는 방금 v0.2.0을 태그했으며, 현재 다음 컴포넌트를 포함하고 있습니다:
- Menu Button (또는 드롭다운)
- Listbox (또는 커스텀 선택)
- Switch (또는 토글)
- ...그리고 더 많은 컴포넌트가 추가될 예정입니다.
더 알아보고 시작하려면 Headless UI 웹사이트로 이동하여 문서를 읽어보세요.
지난 몇 년 동안 제 온라인 작업을 따라오셨다면, 제가 렌더리스 UI 컴포넌트에 매료된 것을 기억하실 겁니다. 저는 2017년 말부터 이에 대해 깊이 파고들기 시작했습니다. 저는 이런 라이브러리가 존재하기를 바랐지만, 팀을 키우기 전까지는 이를 실현할 리소스가 없었습니다.
올해 초 우리는 Robin Malfait를 고용했고, 그는 그 이후로 Headless UI를 풀타임으로 작업해 왔습니다.
이 프로젝트의 가장 큰 동기는 Tailwind UI에 프로덕션 준비가 된 JS 예제를 추가하고 싶다는 것입니다. 현재 Tailwind UI는 HTML만 제공하고, 자바스크립트는 직접 가져와야 하는 프로젝트입니다. 이는 모든 것을 완전히 제어하고 싶은 많은 고객에게는 좋지만, 다른 많은 사람들에게는 마찰점이 됩니다.
우리는 모든 컴포넌트 예제에 200줄의 복잡한 JS를 추가하고 싶지 않았기 때문에, 실제 UI 디자인에서 어떤 유연성도 포기하지 않고도 그 모든 소음을 _추출_하는 방법으로 Headless UI 작업을 시작했습니다.
왜 바퀴를 다시 발명하는가?
이 문제를 해결하려고 시도한 첫 번째 사람들은 우리가 아닙니다. Downshift는 2017년에 이 아이디어에 대해 흥미를 느끼게 한 첫 번째 라이브러리였고, Reach UI와 Reakit는 2018년에 개발을 시작했습니다. 가장 최근에는 올해 초에 React Aria가 출시되었습니다.
우리는 몇 가지 이유로 이 문제에 대해 우리만의 접근 방식을 시도하기로 결정했습니다:
- 기존 솔루션은 거의 전적으로 React에 초점이 맞춰져 있었고, 우리는 이러한 아이디어를 Vue, Alpine과 같은 다른 생태계에 적용하고, 앞으로 더 많은 생태계에 적용하고 싶었습니다.
- 이 라이브러리들은 Tailwind UI에 JS 지원을 추가하는 데 기본이 될 것이며, 이는 비즈니스를 유지하는 데 중요한 부분이기 때문에 라이브러리가 어떻게 작동하고 무엇을 지원할지에 대한 완전한 결정권을 갖는 것이 중요하다고 느꼈습니다.
- 우리는 이러한 컴포넌트의 API가 어떻게 보여야 하는지에 대한 우리만의 아이디어가 있으며, 그 아이디어를 자유롭게 탐구할 수 있기를 원했습니다.
- 우리는 Tailwind를 사용하여 이러한 컴포넌트를 스타일링하는 것이 항상 매우 쉬워야 한다고 생각하며, 커스텀 CSS를 작성해야 하는 번거로움을 피하고 싶었습니다.
지금까지 우리가 만들어낸 것이 유연성과 개발자 경험 사이에서 훌륭한 균형을 이루고 있다고 생각하며, 비슷한 문제를 해결하려는 다른 사람들이 있어서 그들의 아이디어를 배우고 우리의 아이디어를 공유할 수 있다는 점에 감사드립니다.
다음 단계
Headless UI를 위해 개발할 컴포넌트가 아직 많이 남아 있습니다. 예를 들어:
- 모달
- 라디오 그룹
- 탭
- 아코디언
- 콤보박스
- 데이트피커
...그리고 더 많은 컴포넌트가 있을 것입니다. 또한 Alpine.js 지원 작업을 곧 시작할 예정이며, 올해 말쯤 React, Vue, Alpine용 v1.0 버전을 출시할 계획입니다.
그 후에는 다른 프레임워크도 탐구할 예정입니다. Svelte, Angular, Ember와 같은 생태계에서도 동일한 도구를 제공할 수 있도록, 퍼스트클래스 기능으로 직접 지원하거나 커뮤니티 파트너와 협력할 계획입니다.
저희의 진행 상황을 계속 따라가고 싶다면, GitHub에서 프로젝트를 팔로우해 주세요.
이 글에 대해 이야기하고 싶으신가요? GitHub에서 토론하기 →