Accordion opens the last item on initial mount instead of the first

Open
#861 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@Shinyaigeek is already working on this.

Since Jul 25, 2026.

  • #862 by @Shinyaigeek — open

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
25/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
react, typescript
Domain
frontend

Research direction

Start in packages/react-ui/src/genui-lib/Accordion/index.tsx and compare the auto-open behavior with the Tabs fix for issue #819. Check for existing Accordion tests or add coverage for a static mount and a streaming value that completes after its item appears. Done means initial mount does not open the last item, and streaming does not leave a partial value selected.

Written by the indexing model from the issue text.

Description

bug

Summary

<Accordion> from @openuidev/react-ui opens the last item on initial mount, even for a fully static (non-streaming) response. Expected is the first item open (as the pre-refactor implementation did), or all items closed.

This is the same defect shape as the <Tabs> issue #819: a streaming "follow the newest content" heuristic that doesn't exclude the initial mount.

Reproduction

Standalone repro (published packages only, npm install && npm run dev):
https://github.com/Shinyaigeek/openui-accordion-repro

root = Accordion([i1, i2, i3])
i1 = AccordionItem("a", "Apple", [c1])
i2 = AccordionItem("b", "Banana", [c2])
i3 = AccordionItem("c", "Cherry", [c3])
c1 = TextContent("Apple section content")
c2 = TextContent("Banana section content")
c3 = TextContent("Cherry section content")
  • Expected: the first section (Apple) is open on mount, or all sections start closed.
  • Actual: the last section (Cherry) is open on mount.

Cherry section open on mount

Versions: @openuidev/[email protected], @openuidev/[email protected], @openuidev/[email protected], React 19.

Root cause

In packages/react-ui/src/genui-lib/Accordion/index.tsx, the auto-open logic runs during render and treats any growth in item count as "a new item arrived during streaming":

const [openItem, setOpenItem] = React.useState<string>("");
const userHasInteracted = React.useRef(false);
const prevItemCount = React.useRef(0);

// Auto-open: only when a NEW item arrives during streaming
if (!userHasInteracted.current && items.length > prevItemCount.current) {
  const newest = items[items.length - 1];
  if (newest) setOpenItem(newest.props.value);
}
prevItemCount.current = items.length;

On the very first render prevItemCount.current is 0, so a fully static N-item accordion satisfies items.length > 0 and the branch opens items[items.length - 1] — the last section. The baseline pass is indistinguishable from streaming growth.

The earlier implementation had a mount effect that opened the first item; the refactor in #485 removed it, leaving only the auto-open-newest branch.

Related streaming latch in the same branch

While actually streaming there is a second problem: when a new item appears before its value string has finished streaming, setOpenItem latches a prefix of the value (e.g. "ov" for "overview"). The only re-sync trigger is item-count growth, so the completed value never re-syncs; the latched prefix matches no item and the accordion ends up with no section open until the user clicks. (Items are not filtered for complete values — contrast Tabs, which filters value != null.)

Suggested fix

Mirror the Tabs fix (c2b1e7ef for #819):

  • Skip the auto-open pass on the baseline render (e.g. seed prevItemCount from the first non-empty items.length), so a static mount opens the first item (or none) instead of the last.
  • Only auto-open items whose value is complete, and re-sync when a previously-partial value completes, so the streaming auto-open doesn't latch a prefix.
Dominant language
TypeScript
Stars
10k
Forks
698
Avg merge
1d 6h
Merged PRs (30d)
148

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from thesysdev/openui

All issues in thesysdev/openui

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.