Headless UI: 스타일 없이 접근성 높은 UI 컴포넌트

Date

모던 웹 애플리케이션을 구축할 때 가장 큰 고통 중 하나는 선택 메뉴, 드롭다운, 토글, 모달, 탭, 라디오 그룹과 같은 커스텀 컴포넌트를 만드는 것입니다. 이러한 컴포넌트는 프로젝트마다 비슷하지만 완전히 동일하지는 않습니다.

기성 패키지를 사용할 수도 있지만, 이들은 보통 자체 제공 스타일과 강하게 결합되어 있습니다. 결국 프로젝트의 디자인과 일치시키기 위해 많은 CSS 오버라이드를 작성해야 하며, Tailwind CSS를 사용할 때는 이 과정이 큰 후퇴처럼 느껴집니다.

다른 옵션은 처음부터 직접 컴포넌트를 만드는 것입니다. 처음에는 쉬워 보이지만, 키보드 네비게이션 지원, ARIA 속성 관리, 포커스 트래핑 등을 추가해야 한다는 것을 떠올리면, 완벽한 드롭다운 메뉴를 만들기 위해 3-4주를 소모하게 됩니다.

우리는 더 나은 옵션이 있다고 생각했고, 그래서 그것을 만들고 있습니다.

Headless UI는 React와 Vue를 위한 완전히 스타일이 없고 접근성이 높은 UI 컴포넌트 세트입니다 (곧 Alpine.js도 지원 예정). 이를 통해 복잡한 구현 세부 사항을 걱정하지 않고도 이러한 종류의 커스텀 컴포넌트를 쉽게 만들 수 있으며, 간단한 유틸리티 클래스로 스타일을 처음부터 적용할 수 있는 유연성을 유지합니다.

Headless UI 로고

다음은 @headlessui/react를 사용하여 완전한 키보드 네비게이션 지원과 ARIA 속성 관리를 포함한 커스텀 드롭다운을 만드는 예제입니다. Tailwind CSS 유틸리티로 스타일을 적용했습니다:

import { Menu } from '@headlessui/react'

function MyDropdown() {
  return (
    <Menu as="div" className="relative">
      <Menu.Button className="px-4 py-2 rounded bg-blue-600 text-white ...">Options</Menu.Button>
      <Menu.Items className="absolute mt-1 right-0">
        <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을 출시했으며, 현재 다음 컴포넌트를 포함하고 있습니다:

더 알아보고 싶다면, Headless UI 웹사이트로 이동하여 문서를 읽어보세요.


지난 몇 년 동안 제 온라인 작업을 따라왔다면, 제가 렌더리스 UI 컴포넌트에 매료되었던 것을 기억할지도 모릅니다. 이는 제가 2017년 말에 본격적으로 관심을 가지기 시작한 주제입니다. 저는 이런 라이브러리가 존재하기를 바랐지만, 팀을 키우기 전까지는 이를 실현할 리소스가 없었습니다.

올해 초 우리는 Robin Malfait를 고용했고, 그는 그 이후로 Headless UI를 전담하여 작업해 왔습니다.

이 프로젝트의 가장 큰 동기는 Tailwind UI에 프로덕션 준비가 된 JS 예제를 추가하고 싶다는 것입니다. 현재 Tailwind UI는 HTML만 제공하며, 자바스크립트는 직접 가져와야 하는 형태입니다. 이는 모든 것을 완전히 제어하고 싶은 많은 고객에게는 좋지만, 다른 많은 사람들에게는 불편함을 초래합니다.

우리는 모든 컴포넌트 예제에 200줄의 복잡한 JS 코드를 추가하고 싶지 않았기 때문에, 실제 UI 디자인에서의 유연성을 포기하지 않으면서도 그 모든 복잡성을 추출할 방법으로 Headless UI 작업을 시작했습니다.

왜 바퀴를 다시 발명하는가?

우리는 이 문제를 해결하려고 시도한 첫 번째 사람들이 아닙니다. Downshift는 2017년에 이 아이디어에 대해 흥미를 느끼게 해준 첫 번째 라이브러리였고, Reach UIReakit는 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에서 토론에 참여해 보세요 →