GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

アコーディオンリスト UI 9選 — チェックリストまで

アコーディオンリストとは、行をクリックするとその場で高さがなめらかに伸びて詳細内容が開くUIで、別ページに移動せずリスト画面の中で情報を増やしたいときに使います。タップすると行がふわっと開くあの感じを、実際に動くコードとして九つ用意しました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

九つは人気順ではなく、リストの一行が実際にたどる順番で並べています。まず行を一つ開いて詳細を見る一番基本の動き(01)から始め、リストをグループにまとめてスクロールを助ける動き(02)、画面ごと切り替わる動き(03)へ進みます。続いてもっと見るボタンでデータをさらに読み込むとき(04)、あまり使わない操作を隠しておくとき(05)、順番そのものを変えるとき(06)、値をその場で直すとき(07)、過去の出来事を記録するとき(08)、最後にやることを完了にするとき(09)の順です。06番のドラッグ並べ替えと同じく指で行を扱う側はスワイプ削除リストUIにまとめてあり、開く中身そのものが無いときの画面はエンプティステート デザインで扱っています。

01行のインライン展開

html アコーディオンリストで何が見えるか

注文履歴のような行をクリックすると矢印アイコンが90度回転し、その下に詳細情報が現れます。

行のインライン展開
新しいタブで開く

01 · 行のインライン展開 — 計測 累積面積 19.034% · 動いたフレーム 14/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-rows 0fr→1fraria-expandedcaret rotate transform

コードのどこを見るか

.rowx-item__body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .3s cubic-bezier(.2,.8,.2,1);
}
.rowx-item.is-open .rowx-item__body { grid-template-rows: 1fr; }
.rowx-item__body-inner { overflow: hidden; }

height: autoはアニメーションできませんが、grid-template-rowsは0frから1frへなめらかに補間されるため、中身の高さが事前にわからなくても自然に開きます。

02グループ固定セクションヘッダー

画面で起きること

連絡先アプリのように頭文字でまとめたリストで、今スクロールしているグループの見出しが上部に固定され続け、次のグループが来ると押し上げられます。

グループ固定セクションヘッダー
新しいタブで開く

02 · グループ固定セクションヘッダー — 計測 累積面積 25.533% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

position:sticky top:0그룹별 wrapper파스텔 톤

コードの読み方

.grp-group__header {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #ff9ebb;
}

position: stickyは親要素の高さの中でしか固定されないため、グループごとに囲む要素を分けて初めて次のグループが前のヘッダーを押し出せます。

03リスト→詳細スライド遷移

どんな動きか

リスト項目を押すとリストパネルが左へスライドし、詳細パネルが右から入ってくる、設定画面でよく見る遷移です。

リスト→詳細スライド遷移
新しいタブで開く

03 · リスト→詳細スライド遷移 — 計測 累積面積 6.846% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

두 패널 transform:translateXoverflow:hidden 컨테이너history 뒤로가기 대응

どこを変えるか

.ltd-track {
  display: flex;
  width: 200%;
  transition: transform .3s cubic-bezier(.2,.8,.2,1);
}
.ltd-viewport.is-open .ltd-track { transform: translateX(-50%); }

リストと詳細を並べた一本のtrackをまとめて動かすので、パネルごとに別々にアニメーションせず一つのtransformだけで済みます。

04もっと見るボタンリスト

何が見えるか

もっと見るを押すと一瞬スピナーが回り、新しい3行が一度にではなく順番に少しずつ下からせり上がって現れます。

もっと見るボタンリスト
新しいタブで開く

04 · もっと見るボタンリスト — 計測 累積面積 43.192% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

stagger translateY+opacity버튼 로딩 상태 토글신뢰감 있는 밝은 톤

コードのどこを見るか

btn.addEventListener('click', () => {
  list.classList.add('is-loading');
  setTimeout(() => {
    list.classList.remove('is-loading');
    list.classList.add('has-loaded');
  }, 500);
});

3行は最初max-height: 0で畳まれていて、行ごとにtransition-delayを少しずつ変えることで、一斉にではなく順番に現れます。

05ロングプレスアクションメニュー

画面で起きること

行を0.5秒以上長押しすると、お気に入り・アーカイブ・削除のアイコンメニューが弾むように現れます。

ロングプレスアクションメニュー
新しいタブで開く

05 · ロングプレスアクションメニュー — 計測 累積面積 2.742% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointerdown 타이머 500msscale bounce keyframes키보드용 더보기 버튼 대체

コードの読み方

row.addEventListener('pointerdown', (e) => {
  if (e.target.closest('button')) return;
  timer = setTimeout(openMenu, 500);
});
['pointerup', 'pointerleave'].forEach((ev) =>
  row.addEventListener(ev, () => clearTimeout(timer))
);

指を離すとタイマーがすぐ解除されるため、スクロールのつもりで軽く触れただけではメニューは開きません。長押しが難しい人のために、常に見える「···」ボタンでも同じメニューを開けます。

06ドロップ位置表示の並び替え

どんな動きか

六点ハンドルをドラッグすると、項目をただ入れ替えるのではなく、置かれる場所に青い帯状の線が先に現れて位置を示します。

ドロップ位置表示の並び替え
新しいタブで開く

06 · ドロップ位置表示の並び替え — 計測 累積面積 19.851% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

drop-indicator 엘리먼트pointermove 중간 인덱스 계산화살표 키보드 이동

どこを変えるか

ハンドルにフォーカスしてAlt+矢印キーでも同じ並び替えができます。

handle.addEventListener('keydown', (e) => {
  if (!e.altKey || (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')) return;
  e.preventDefault();
  const targetIdx = order().indexOf(item) + (e.key === 'ArrowUp' ? -1 : 1);
  list.insertBefore(item, order()[targetIdx]);
});

マウスのドラッグに頼らず、ハンドルにフォーカスしてAlt+矢印キーだけで同じ結果を作れることが、並び替えリストをアクセシブルにする条件です。

07リストのインライン編集

何が見えるか

名前欄をクリックするとその場で入力欄に変わり、Enterか外側クリックで保存され、鉛筆アイコンがチェックに変わります。

リストのインライン編集
新しいタブで開く

07 · リストのインライン編集 — 計測 累積面積 6.801% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

contenteditable 대신 input 치환Escape 로 되돌리기신뢰감 있는 밝은 SaaS 톤

コードのどこを見るか

input.addEventListener('click', () => { if (input.readOnly) startEdit(); });
input.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') save();
  else if (e.key === 'Escape') cancel();
});
input.addEventListener('blur', () => { if (!input.readOnly) save(); });

contenteditableの代わりに普通の<input readonly>を使うと、値の検証やEscapeでの取り消しをinput標準の動作にそのまま任せられ、コードが短くなります。

08アクティビティタイムライン

画面で起きること

縦線に沿ったアイコンの点が、上から順に少しずつふわっと浮き上がるように現れます。

アクティビティタイムライン
新しいタブで開く

08 · アクティビティタイムライン — 計測 累積面積 6.644% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

stagger fade+translateY타임라인 세로선 + 아이콘 점파스텔 톤

コードの読み方

.tl-item {
  opacity: 0;
  transform: translateY(10px);
  animation: tl-in .6s ease-out forwards;
  animation-delay: calc(var(--n) * 140ms);
}
@keyframes tl-in { to { opacity: 1; transform: translateY(0); } }

各項目に--nカスタムプロパティで順番を渡しcalc()で遅延を計算すれば、項目数がいくつでもループなしで順番に浮き上がらせられます。

09チェックリスト取り消し線

どんな動きか

チェックボックスを押すと色が満ちてチェックが飛び出し、テキストには左から右へ取り消し線が引かれて薄くなります。

チェックリスト取り消し線
新しいタブで開く

09 · チェックリスト取り消し線 — 計測 累積面積 0.621% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scale(1.15) 체크scaleX 취소선캔디 파스텔 톤

どこを変えるか

.ck-item__text::after {
  content: "";
  position: absolute; inset: 50% 0 auto 0; height: 2px;
  background: #ff9ebb;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .3s;
}
.ck-item.is-checked .ck-item__text::after { transform: scaleX(1); }

text-decoration-lineはブラウザによってアニメーションが滑らかでないため、代わりに疑似的な下線をscaleX(0)からscaleX(1)へ伸ばす方法を使います。transform-origin: leftで起点を左に固定するので、線は左から引かれます。

アコーディオンリストは、どこで壊れるか

01番の行展開を作る中で実際にぶつかった落とし穴は、grid-template-rowsをいきなりautoへアニメーションしようとして失敗したことです——autoは補間できる始点でも終点でもないため、ふわっと開かず一瞬で切り替わってしまいます。0frと1frのようなグリッド比率の値どうしでアニメーションする必要があります。二つ目は04番のもっと見るボタンで、新しい行をdisplay: noneで隠しておいてからクラスを切り替えると、ブラウザがレイアウトを計算する前に切り替えが終わってしまい、アニメーションなしで突然現れました——max-height: 0のように実際にはレンダリングされつつサイズだけ0の状態から始める必要があります。同じ高さアニメーションの落とし穴は、カードが格子の中で位置を入れ替えるポートフォリオ ギャラリー UIでも同じ原理で現れます。この九つのファイルの圧縮パスワードはpw5ht9pcで、解凍するとvanilla・reactフォルダで同じ九つの動きを並べて比較できます。

ほかのリスト・表のテーマは実用UIカテゴリに、スワイプ・並び替え・ページネーションを扱うもう一つの九選は9 Swipe to Delete List UI Patternsに、このサイトが何を扱っているかはサイト紹介にまとまっています。grid-template-rowsがfr単位をどう補間するかはMDNのgrid-template-rowsページに、position: stickyの効く範囲はMDNのpositionページにまとまっています。

アクセシビリティ — 九つがreduced-motionで何をするか

システムで動きを減らす設定をオンにすると、九つのデモはすべてtransitionとanimationを止め、最終状態だけをすぐに表示します。01・03番のように高さや位置が変わる項目は展開・遷移後の状態へ直接移り、05・09番のように弾む感じ自体が要点の項目はその感じだけが消えて結果(メニューが開く、チェックが付く)は残ります。06番の並び替えと07番のインライン編集はもともとキーボードだけで操作できるため、動作そのものは変わりません。

FAQ

インライン編集 ui まで自作せずHTMLの<details>要素で済ませてもいいですか?

<details>・<summary>は開閉そのものは標準で提供されますが、高さのアニメーションは自分で追加する必要があり、既定の見た目もブラウザによって違います。そのため九つのデモではボタンとgrid-template-rowsでより細かく制御しました。アクセシビリティを優先するなら<details>の上にスタイルを重ねる方法も選択肢です。行を開いて詳細に入った後で現在地を示す話はパンくずリストが引き受けています。

リストの項目が数百個に増えても、このコードはそのまま使えますか?

01・02・08番のように表示中の行すべてにアニメーションが付く場合、項目が数百個規模になると仮想スクロール(画面に見える分だけ描画する手法)のような追加の最適化が必要です。zipのコードは1画面に収まる数十個規模を前提にしています。

ロングプレスメニューはモバイルでも同じように動きますか?

pointerdown・pointerupイベントはマウス・タッチ・ペンを同じコードで扱うため、そのまま動作します。ただしモバイルブラウザは長押しで独自のコンテキストメニューやテキスト選択が出ることがあるため、デモのコードはtouch-action: manipulationでその競合を防いでいます。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。