[React] Downshift로 드롭다운(dropdown) 구현

[React] Downshift로 드롭다운(dropdown) 구현

웹 접근성(accessibility)을 준수하는 드롭다운(dropdown)를 구현하는 것은 생각보다 쉽지 않은 일입니다.

사실 가장 쉬운 방법은 지난 포스팅에서 소개했던 것처럼 HTML의 <select> 엘리먼트를 사용하는 것인데요. <select> 엘리먼트를 사용하면 내부에 있는 <option> 엘리먼트에 커스텀 스타일을 적용할 방법이 없기 때문에 스타일링에 한계가 있습니다.

그래서 여러 가지 엘리먼트를 이용해서 직접 드롭다운를 만드는 경우가 많은데요. 이 때, 시각적으로는 원하는 모습의 UI를 얻을지 몰라도, 웹 접근성 측면에서는 부족한 부분이 생기는 경우를 많이 보게 됩니다. 예를 들어, 웹 접근성을 준수하는 드롭다운는 키보드로도 조작이 가능해야하며, 스크린리더를 위해 ARIA 속성도 적지적소에 설정이 되어있어야 합니다.

Downshift는 이렇게 까다로운 드롭다운를 구현을 쉽게 할 수 있도록 도와주는 React 라이브러리입니다.

React 컴포넌트 작성

먼저 React로 <label>, <input>, <button>, <ul>, <li> 엘리먼트로 이루어진 드롭다운 UI의 기본 골격을 잡아보겠습니다. 각 HTML 엘리먼트는 레이블, 입력란, 토글 버튼, 선택 목록, 선택 항목을 나타내게 됩니다.

import React from "react";

function Combobox({ label, placeholder, items }) {
  return (
    <>
      <label>{label}</label>
      <div>
        <input readOnly placeholder={placeholder} />
        <button>&gt;</button>
      </div>
      <ul>
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </>
  );
}

export default Combobox;

위와 같이 HTML 마크업을 하면 레이블과 입력란, 토글 버튼 아래에 선택 목록이 정적으로 표시될 것입니다. 웹 접근성 스팩에 따르면 <input> 엘리먼트와 <button> 엘리먼트를 보통 묶어서 combobox의 역할을 갖게 되며, 다수의 <li> 엘리먼트로 이뤄진 <ul> 엘리먼트는 listbox의 역할을 갖게 됩니다.

Downshift 설치

본인의 React 프로젝트에 Downshift를 설치합니다.

$ npm i downshift

Downshift 구조

Downshift 라이브러리는 Render Prop과 Hooks 방식을 모두 지원한는데요. 본 포스팅에서는 최근 트랜드에 맞춰 후자 방식으로 사용해보겠습니다. Downshift의 useCombobox hook 함수에 선택 가능한 값 목록을 넘기면 다양한 상태값과 유틸리티 함수를 반환합니다.

import { useCombobox } from "downshift"

function Combobox({ label, placeholder, items }) {
  const { /* 상태값, 유틸리티 함수 */ } = useCombobox({ items });

  return ( /* 생략 */ )
}

Downshift 적용

이제 위에서 작성한 React 컴포넌트에 Downshift를 적용해보도록 하겠습니다.

isOpen 상태값은 선택 목록을 선택적으로 보이게 하기 위해서 사용하고, highlightedIndex 상태값은 각 선택 항목에 하이라이트 효과를 주기위해서 사용합니다.

그리고 get으로 시작하는 유틸리티 함수들은 React prop을 반환하기 때문에, 스프레드 연산자(...)를 이용해서 각 HTML 컴포넌트에 적용해줍니다.

import React from "react";
import { useCombobox } from "downshift";

function Combobox({ label, placeholder, items }) {
  const {
    isOpen,
    highlightedIndex,
    getLabelProps,
    getComboboxProps,
    getInputProps,
    getToggleButtonProps,
    getMenuProps,
    getItemProps,
  } = useCombobox({
    items,
  });

  return (
    <>
      <label {...getLabelProps()}>{label}</label>
      <div {...getComboboxProps()}>
        <input readOnly placeholder={placeholder} {...getInputProps()} />
        <button {...getToggleButtonProps()}>&gt;</button>
      </div>
      <ul {...getMenuProps()}>
        {isOpen &&
          items.map((item, index) => (
            <li
              {...getItemProps({ item, index })}
              key={item}
              style={{ background: index === highlightedIndex && "lightgray" }}
            >
              {item}
            </li>
          ))}
      </ul>
    </>
  );
}

이제 토글 버튼을 클릭하거나 키보드의 방향키를 위나 아래로 눌러보면 선택 목록이 화면에 표시될 것입니다. 또한, 특정 선택 항목을 클릭하거나 키보드 방향키로 이동 후 엔터 버튼을 누르면 해당 항목이 선택이 될 것입니다.

뿐만 아니라, Downshift는 웹 접근성 스팩에 따라 <div> 엘리먼트의 role 속성을 combobox 설정해주고, <ul> 엘리먼트의 role 속성을 listbox로 설정해줍니다. 그 밖에도 일일이 신경쓰기 어려운 ARIA 속성들도 적지적소에 알아서 설정을 해줍니다.

브라우저에서 소스 코드 보기를 해보면 Downshift는가 자동으로 추가해주는 속성들을 쉽게 확인해볼 수 있습니다.

<label id="downshift-3-label" for="downshift-2-input">예약 시간</label>
<div
  role="combobox"
  aria-haspopup="listbox"
  aria-owns="downshift-3-menu"
  aria-expanded="false"
>
  <input
    readonly=""
    placeholder="--:--"
    id="downshift-2-input"
    aria-autocomplete="list"
    aria-controls="downshift-3-menu"
    aria-labelledby="downshift-3-label"
    autocomplete="off"
    value=""
  /><button id="downshift-3-toggle-button" tabindex="-1">&gt;</button>
</div>
<ul
  id="downshift-3-menu"
  role="listbox"
  aria-labelledby="downshift-3-label"
></ul>

전체 코드

마치며

지금까지 Downshift 라이브러리를 이용하여 React로 간단한 드롭다운 UI 컴포넌트를 구현해보았습니다. Downshift 라이브러리의 가장 큰 장점은 사용자로 하여금 어떤 HTML 엘리먼트와 CSS 속성을 사용할지에 대해서 어떠한 제약도 가하지 않는다는 것입니다. 따라서 본인이 원하는 어떤 모양의 드롭다운 컴포넌트에도 Downshift 라이브러리를 활용할 수 있습니다.

예를 들어, 본 포스팅에서는 최대한 간단한 예제를 위해서 <input> 엘리먼트를 읽기전용 처리하였지만, 상황에 따라 사용자의 입력을 허용하고 자동 완성을 지원할 수도 있습니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord