Design System 101 - Accordion

什麼是 Accordion?

手風琴組件 (Accordion) 是一系列垂直堆疊的互動標題所組成,通常會有 標題 (Header)內容 (Content) 兩個部分,並且可以透過點擊 標題 (Header) 展開或收合 內容 (Content) 區塊,其主要是用在內容過多時,可以透過該組件來隱藏內容。

Accordion 的特性:

  1. 當內容收合時,所有標題會連續地排列,讓使用者能夠快速瀏覽所有大綱,而不需要大量地滾動頁面。
  2. 鍵盤使用者不需要經過頁面上的所有可聚焦 (focusable) 元素來到達想前往的地方,收合的內容是不可聚焦的。

Accordion 的使用

何時使用

  • 當要呈現的資訊結構相似,並且內容過多時,可以透過 Accordion 來隱藏內容。例如: 常見問題區塊

  • 當每次只想要顯示一個區塊的內容時,主要目的是為了避免使用者一次看到太多內容感到不知所措 (Overwhelmed),可以透過 Accordion 逐步展示內容。

何時不使用

  • 當要一次性在畫面上顯示所有內容 — 使用卡片 (Card) 組件。
  • 當需要隱藏的資訊是簡單且沒有重複的結構 — 使用摺疊 (Collapsible) 組件。

Anatomy

組件結構

組件描述
Accordion管理所有 Accordion 展開與收合的狀態。
Accordion.Item管理單一 Accordion 的所有狀態。
Accordion.HeaderAccordion 的標題。
Accordion.Trigger能夠觸發 Accordion 展開與收合。
Accordion.ContentAccordion 的內容。

使用方式

() => {
<Accordion>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>Header</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Content</Accordion.Content>
</Accordion.Item>
</Accordion>;
};

Data Model

狀態描述
index, defaultIndex展開收合可以是 controlled 或 uncontrolled 狀態
allowMultiple可以同時展開多個 Accordion。

*關於 controlled 與 uncontrolled 狀態,可以參考 Design System 常用的 Hooks,這邊就不再贅述。

組件 API

General Props

屬性名稱型別描述
childrenReact.ReactNode組件的子組件
classNamestring自定義 class
asReact.ElementType自定義 HTML tag

`Accordion`

屬性名稱型別描述
indexstring展開的 Accordion ID
defaultIndexstring預設展開的 Accordion ID
onIndexChange(value: string) => void當展開的 Accordion ID 改變時觸發的 callback
allowMultipleboolean是否可以同時展開多個 Accordion
disabledboolean是否禁用 Accordion
dirltr | rtlAccordion 的方向

`Item`

屬性名稱型別描述
indexstringAccordion 的 ID

`Content`

屬性名稱型別描述
keepMountedboolean是否保留在 DOM 中

`Header` & `Trigger`

N/A

實作

Accordion 實作的重點整理:

Accordion

  1. 透過 React Context API 建立 AccordionContext 來管理當前活動 (active) 的 index
  2. 透過 Collection API 管理所有可聚焦 (focusable) 的元素,並且根據對應的鍵盤事件來控制聚焦 (focus) 的狀態。

AccordionItem

  1. 透過 AccordionItemContext,來管理單一 Accordion 的狀態,例如: 是否展開 (open) 等資訊。
import { Accordion, AccordionItem } from './Accordion.jsx';
import './styles.css';

export default () => {
  return (
    <section>
      <Accordion defaultIndex={0}>
        <Accordion.Item>
          <Accordion.Trigger>
            <Accordion.Header>
              <span>Is it accessible?</span>
              <ChevronDownIcon />
            </Accordion.Header>
          </Accordion.Trigger>
          <Accordion.Content>Yes. It adheres to the WAI-ARIA design pattern. </Accordion.Content>
        </Accordion.Item>
        <Accordion.Item>
          <Accordion.Trigger>
            <Accordion.Header>
              <span>Is it unstyled?</span>
              <ChevronDownIcon />
            </Accordion.Header>
          </Accordion.Trigger>
          <Accordion.Content>
            Yes. It's unstyled by default, giving you freedom over the look and feel.
          </Accordion.Content>
        </Accordion.Item>
      </Accordion>
    </section>
  );
};

const ChevronDownIcon = () => (
  <svg
    width="15"
    height="15"
    viewBox="0 0 15 15"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="AccordionChevron"
  >
    <path
      d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
      fill="currentColor"
      fill-rule="evenodd"
      clip-rule="evenodd"
    ></path>
  </svg>
);

Accessibility

以下皆是參考 WAI-ARIA 的 Accordion 規範所整理的。

  1. 鍵盤事件 (這部分都已在上面 focus-manager.js 中實作)
鍵盤事件描述 (當 focus 在 Trigger 上時)
Enter & Spaec按下 Enter 可以展開或收合 Content
ArrowUp按下 ArrowUp 可以聚焦上一個 Trigger
ArrowDown按下 ArrowDown 可以聚焦下一個 Trigger
Home按下 Home 可以聚焦第一個 Trigger
End按下 End 可以聚焦最後一個 Trigger
  1. ARIA 屬性

Accrodion.Trigger

屬性名稱型別描述
rolebuttonTriggerrolebutton,因為 Trigger 可以透過鍵盤事件觸發
aria-controlsstring指向 Content 的 ID,當 Trigger 被聚焦時,可以透過 aria-controls
aria-expandedbooleanTrigger 是否展開
aria-disabledbooleanTrigger 是否被禁用

Accrodion.Content

屬性名稱型別描述
regionstringContentroleregion,因為 Content 是一個獨立的區塊
aria-labelledbystring指向 Header 的 ID,當 Content 被聚焦時,可以透過 aria-labelledby

參考資料

  1. Collapsible Section
  2. Radix UI - Accordion