GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 Accordion List UI Patterns (Copy-Paste)

An accordion list is a UI where clicking a row grows its height in place to reveal detail content.

Auto-plays · click a tile to jump to its section · all nine in one zip

These nine are ordered the way a real list row actually gets used, not by popularity. It starts with the plainest move — expanding one row for detail (01) — then groups rows for easier scanning (02), and swaps the whole screen for a detail view (03). After that comes fetching more data (04), tucking away rarely used actions (05), changing the order itself with drag and drop list items (06), editing a value right where it sits (07), logging what already happened (08), and finally marking a task as done (09). The finger-driven cousin of item 06's drag reorder is collected in Swipe to Delete List UI Patterns, and the screen you show when there is nothing to expand lives in Empty State UI Design Cards.

01Row inline expand

What you see in this accordion list ui

Clicking an order-history row spins its arrow icon 90 degrees and unfolds detail content beneath it.

Row inline expand
Open in a new tab

01 · Row inline expand — Measured area 19.034% · moving frames 14/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

Where to look in the code

.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 can't be animated, but grid-template-rows interpolates cleanly between 0fr and 1fr, so the row opens smoothly even when you don't know the content's height in advance.

02Grouped sticky section header

What happens on screen

In a contact-style list grouped by first letter, the header for the group you're currently scrolling through sticks to the top until the next group's header pushes it out of the way.

Grouped sticky section header
Open in a new tab

02 · Grouped sticky section header — Measured area 25.533% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

How to read the code

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

position: sticky only stays pinned within its own parent's box, so each group needs its own wrapper element for the next group to be able to push the previous header off screen.

03List-to-detail slide transition

What moves

Tapping a list item slides the list panel out to the left while the detail panel slides in from the right — the drill-down pattern you see in most settings screens.

List-to-detail slide transition
Open in a new tab

03 · List-to-detail slide transition — Measured area 6.846% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

What to change

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

Both panels sit side by side inside one track, so a single transform moves them together instead of animating each panel separately.

04Load-more button list

What you see

Clicking the load-more button briefly spins a loader, then three new rows slide up into place one after another instead of popping in all at once.

Load-more button list
Open in a new tab

04 · Load-more button list — Measured area 43.192% · moving frames 13/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

Where to look in the code

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

The three rows start collapsed at max-height: 0, and each gets a slightly different transition-delay so they reveal in sequence rather than all at once.

05Long-press action menu

What happens on screen

Holding a row for half a second pops a menu of star, archive, and delete icons into view with a bouncy scale-in.

Long-press action menu
Open in a new tab

05 · Long-press action menu — Measured area 2.742% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

How to read the code

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

Releasing early cancels the timer, so a quick tap meant for scrolling never accidentally opens the menu. An always-visible "···" button opens the same menu for anyone who can't long-press.

06Drop-gap indicator reorder

What moves

Dragging the six-dot handle shows a highlighted empty gap line where the item will land, instead of just shoving the other rows aside.

Drop-gap indicator reorder
Open in a new tab

06 · Drop-gap indicator reorder — Measured area 19.851% · moving frames 16/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

What to change

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]);
});

Anyone who can't drag with a mouse can focus the handle and reorder with Alt+Arrow keys instead — that's what makes a reorderable list actually accessible.

07List inline edit

What you see

Clicking a name field turns it into a text input right there; pressing Enter or clicking away saves it and swaps the pencil icon for a check mark.

List inline edit
Open in a new tab

07 · List inline edit — Measured area 6.801% · moving frames 12/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

Where to look in the code

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(); });

Using a plain <input readonly> instead of contenteditable means validation and Escape-to-cancel come from the input's normal behavior for free, which keeps the code short.

08Activity timeline log

What happens on screen

Icon dots along a vertical line fade and rise into place one after another, from top to bottom.

Activity timeline log
Open in a new tab

08 · Activity timeline log — Measured area 6.644% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

How to read the code

.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); } }

Passing each item's index in as a --n custom property and computing the delay with calc() lets any number of entries stagger in order without a loop in the animation itself.

09Checklist strikethrough complete

What moves

Tapping the checkbox fills it with color and pops in a check mark, while the text gets a left-to-right strikethrough and fades.

Checklist strikethrough complete
Open in a new tab

09 · Checklist strikethrough complete — Measured area 0.621% · moving frames 13/23Original by GODRICH · SCSS and React ship in the zip · See the password

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

What to change

.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 doesn't animate smoothly across browsers, so instead this uses a fake underline scaled from scaleX(0) to scaleX(1). Pinning transform-origin: left is what makes the line draw from the left edge.

Where does an accordion list break?

The first trap we actually hit building item 01 was trying to animate grid-template-rows straight to auto — auto isn't a real start or end value the browser can interpolate between, so the row just snaps open instead of easing; it has to animate between two fr values like 0fr and 1fr instead. The second trap showed up in item 04: hiding the new rows with display: none and only then swapping the class would mean the browser finishes the layout change before it has a chance to transition — starting from max-height: 0, which is actually rendered at zero size, is what keeps the animation visible. The same height-animation trap resurfaces on exactly the same principle in Portfolio Gallery HTML CSS, where cards swap places inside a grid. The zip password for these nine files is pw5ht9pc, and unzipping it lets you compare the vanilla and react versions of all nine side by side.

Other list and table topics live under the real-world UI category; the other nine list patterns — swipe, sort, and pagination — are in 9 Swipe to Delete List UI Patterns; and what this site covers is on the about page. How grid-template-rows interpolates fr units is documented on MDN's grid-template-rows page, and how far position: sticky reaches is on MDN's position page.

Accessibility — what each of the nine drops under reduced-motion

With reduced motion turned on, all nine demos turn off transition and animation and jump straight to the end state. Items like 01 and 03, where height or position changes, land directly on the open/transitioned state; items like 05 and 09, where the bounce itself is the point, lose the bounce but keep the result — the menu open, the checkbox checked. Items 06 and 09 are already fully operable by keyboard, so nothing about how they work changes.

FAQ

Can I just use the HTML <details> element for this accordion list design instead of building it myself?

<details>/<summary> gives you open/close for free, but you still have to add the height animation yourself and the default styling varies by browser — that's why item 01 above uses a button plus grid-template-rows for finer control instead. If accessibility is the priority, styling on top of <details> is a reasonable alternative. Showing where the reader stands once a row opens into detail is what Breadcrumb UI Patterns answers.

Do these patterns still work if the list has hundreds of items?

For 01 and 08, where every visible row carries its own animation, and for 02, where every group needs its own sticky-header wrapper element, a list that grows into the hundreds needs extra work like virtualization (rendering only what's on screen). The zip code is built for the tens-of-items scale you'd see on one page.

Does the long-press menu work the same way on mobile?

Yes — pointerdown/pointerup handle mouse, touch, and pen through the same code. Mobile browsers can trigger their own context menu or text selection on a long press, though, so the demo sets touch-action: manipulation to prevent that conflict.

Enter the archive password

The password is inside this article. You will find it as you read.