Skip to content

feat(headless): add Accordion primitive#8475

Merged
alexcarpenter merged 7 commits into
mainfrom
carp/headless-accordion
Jun 9, 2026
Merged

feat(headless): add Accordion primitive#8475
alexcarpenter merged 7 commits into
mainfrom
carp/headless-accordion

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented May 5, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds Accordion primitive with single/multiple expand modes
  • Animated panel height via --cl-accordion-panel-height CSS custom property
  • Roving keyboard navigation via Floating UI Composite

Review Stack

Review order — start at #8474 (foundation) and work up. Each PR adds one primitive on top of the previous.

# PR What it adds Base
1 #8474 Foundation (infra + hooks + utils + Dialog) main
2 #8475 Accordion #8474
3 #8476 Tabs #8475
4 #8477 cssVars + Tooltip #8476
5 #8478 Popover #8477
6 #8479 Select #8478
7 #8480 Menu #8479
8 #8481 Autocomplete + integration tests #8480
9 #8764 Collapsible #8481

Test plan

  • pnpm build succeeds
  • pnpm test passes (+35 accordion tests)
  • CI passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a full Accordion primitive with controlled/uncontrolled modes, single/multiple expand, keyboard navigation, accessibility, and customizable rendering/styling.
  • Documentation
    • Added comprehensive Accordion usage docs, examples, keyboard/ARIA guidance, and animation tips.
  • Tests
    • Added automated tests validating behavior, accessibility, keyboard navigation, and animation lifecycle.
  • Chores
    • Exported and published the Accordion primitive and updated build outputs; minor test setup ordering tweak.

@vercel

vercel Bot commented May 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 9, 2026 7:57pm

Request Review

@changeset-bot

changeset-bot Bot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f13cdb6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@alexcarpenter alexcarpenter force-pushed the carp/headless-foundation branch 4 times, most recently from 6e1dfb8 to 2a5b8e2 Compare May 5, 2026 15:21
@alexcarpenter alexcarpenter force-pushed the carp/headless-accordion branch from 9f473a8 to 5b5ddfa Compare May 5, 2026 15:26
@alexcarpenter alexcarpenter force-pushed the carp/headless-foundation branch from 2a5b8e2 to 16b5d39 Compare May 5, 2026 15:30
@alexcarpenter alexcarpenter force-pushed the carp/headless-accordion branch from 5b5ddfa to 47cce7a Compare May 5, 2026 15:39
@alexcarpenter alexcarpenter force-pushed the carp/headless-accordion branch from 47cce7a to 78ffbf1 Compare May 5, 2026 15:49
@alexcarpenter alexcarpenter force-pushed the carp/headless-accordion branch from 78ffbf1 to 22dc58f Compare May 5, 2026 18:38
@alexcarpenter alexcarpenter force-pushed the carp/headless-foundation branch from 54016b4 to 4430af5 Compare June 3, 2026 16:14
@alexcarpenter alexcarpenter force-pushed the carp/headless-accordion branch from 483637a to a7cbc25 Compare June 3, 2026 17:23
@alexcarpenter alexcarpenter force-pushed the carp/headless-accordion branch from a7cbc25 to 2379a11 Compare June 3, 2026 17:33
Base automatically changed from carp/headless-foundation to main June 9, 2026 14:23
@alexcarpenter alexcarpenter force-pushed the carp/headless-accordion branch from 2379a11 to 8b06f02 Compare June 9, 2026 19:31
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 745a8caa-01a2-4ce5-9441-121403288107

📥 Commits

Reviewing files that changed from the base of the PR and between 5a6d8bb and f13cdb6.

📒 Files selected for processing (1)
  • .changeset/headless-accordion-primitive.md
✅ Files skipped from review due to trivial changes (1)
  • .changeset/headless-accordion-primitive.md

📝 Walkthrough

Walkthrough

Adds a complete Accordion primitive to headless: context plumbing, Root state orchestration, Item/Header/Trigger/Panel components with ARIA/animation, export/build wiring, README, and comprehensive tests.

Changes

Accordion Primitive Implementation

Layer / File(s) Summary
Context and state management foundation
packages/headless/src/primitives/accordion/accordion-context.ts
Two React contexts and hooks (AccordionContext, AccordionItemContext) establish accordion and item state and enforce composition.
Accordion root component with state orchestration
packages/headless/src/primitives/accordion/accordion-root.tsx
AccordionRoot implements controllable/uncontrollable value handling, toggle logic for single/multiple, accordionId generation, and keyboard focus handling via Composite.
Item, Header, Trigger, and Panel components
packages/headless/src/primitives/accordion/accordion-item.tsx, accordion-header.tsx, accordion-trigger.tsx, accordion-panel.tsx
AccordionItem provides per-item context and state attributes; AccordionHeader renders an h3 slot; AccordionTrigger renders a button with ARIA and conditional toggle; AccordionPanel measures height, manages transitions, and emits ARIA/role attributes and data-state attributes.
Public API exports and build wiring
packages/headless/src/primitives/accordion/parts.ts, packages/headless/src/primitives/accordion/index.ts, packages/headless/package.json, packages/headless/vite.config.ts, .changeset/*
Adds barrel exports and aliases (Root/Item/Header/Trigger/Panel), package.json subpath export ./accordion, Vite lib entry for the accordion bundle, and updates the changeset.
User documentation
packages/headless/src/primitives/accordion/README.md
New README documenting component parts, props, controlled/uncontrolled examples, keyboard navigation, data attributes, CSS animation hooks, and ARIA semantics.
Comprehensive accordion test coverage
packages/headless/src/primitives/accordion/accordion.test.tsx, packages/headless/vitest.setup.ts
Extensive tests validating slots, expand/collapse behavior (uncontrolled/controlled/single/multiple), ARIA wiring, animation lifecycle and CSS vars, disabled behavior, keyboard navigation (Arrow/Home/End/Enter/Space), and axe accessibility checks; includes vitest setup import reorder.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AccordionTrigger
  participant AccordionRoot
  participant AccordionContext
  participant AccordionPanel
  User->>AccordionTrigger: click toggle(itemValue)
  AccordionTrigger->>AccordionRoot: invoke toggle(itemValue)
  AccordionRoot->>AccordionContext: update open value / call onValueChange
  AccordionContext->>AccordionPanel: expose open/ids to render
  AccordionPanel->>AccordionPanel: measure height (ResizeObserver)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • wobsoriano
  • austincalvelage
  • Ephem

Poem

🐰 I hopped in bytes to bring delight,

A Root that holds the toggles tight,
Triggers click, panels measure high,
Docs and tests ensure no sigh,
A tiny accordion—soft and right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(headless): add Accordion primitive' accurately and concisely describes the main change—introducing a new Accordion primitive component to the headless package.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Jun 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8475

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8475

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8475

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8475

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8475

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8475

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8475

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8475

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8475

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8475

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8475

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8475

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8475

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8475

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8475

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8475

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8475

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8475

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8475

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8475

commit: f13cdb6

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
packages/headless/src/primitives/accordion/accordion-panel.tsx (1)

8-8: ⚡ Quick win

Add JSDoc documentation for public interface.

This public interface needs JSDoc documentation for consumers and potentially generated reference docs.

📝 Suggested JSDoc
+/**
+ * Props for the AccordionPanel component.
+ * Extends all standard HTML div attributes.
+ */
 export interface AccordionPanelProps extends ComponentProps<'div'> {}

As per coding guidelines, all public APIs in packages must be documented with JSDoc.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/headless/src/primitives/accordion/accordion-panel.tsx` at line 8,
Add a JSDoc block for the public interface AccordionPanelProps: document the
purpose of the interface (props for an Accordion panel component), note that it
extends ComponentProps<'div'>, describe expected/important behaviors or keys
consumers might rely on (e.g., children, className, id), mark it as public API,
and include a short usage example and `@extends` tag referencing
ComponentProps<'div'>; update the AccordionPanelProps declaration accordingly in
accordion-panel.tsx.

Source: Coding guidelines

packages/headless/src/primitives/accordion/accordion-trigger.tsx (1)

8-8: ⚡ Quick win

Add JSDoc documentation for public interface.

This public interface is part of the accordion primitive's API surface and needs JSDoc documentation for consumers and potentially generated reference docs.

📝 Suggested JSDoc
+/**
+ * Props for the AccordionTrigger component.
+ * Extends all standard HTML button attributes.
+ */
 export interface AccordionTriggerProps extends ComponentProps<'button'> {}

As per coding guidelines, all public APIs in packages must be documented with JSDoc, and public/reference-facing APIs should be treated as customer-facing documentation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/headless/src/primitives/accordion/accordion-trigger.tsx` at line 8,
Add JSDoc to the exported AccordionTriggerProps public interface: document what
this prop type represents, its relationship to ComponentProps<'button'>, typical
consumers, and any important notes (e.g., forwarded props or accessibility
considerations). Place the JSDoc immediately above the declaration of
AccordionTriggerProps so generated docs pick it up and ensure the description is
concise and mentions it extends ComponentProps<'button'> and is intended for the
Accordion trigger button element.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/headless/src/primitives/accordion/accordion-context.ts`:
- Around line 12-18: The exported hook functions lack explicit return types;
update the function signatures for useAccordionContext and
useAccordionItemContext to include their respective return annotations
(useAccordionContext: AccordionContextValue and useAccordionItemContext:
AccordionItemContextValue), ensuring the functions still perform the runtime
null-check and throw the same Error when ctx is missing; adjust any imports or
type references if needed so AccordionContextValue and AccordionItemContextValue
are available to the module.

In `@packages/headless/src/primitives/accordion/accordion-header.tsx`:
- Line 7: The exported component AccordionHeader lacks an explicit return type;
update the function signature for AccordionHeader to include an explicit return
annotation of React.JSX.Element | null (since renderElement can return null when
enabled is false) so the public API has a declared return type; locate the
AccordionHeader function declaration and add the type annotation to its return
type accordingly.

In `@packages/headless/src/primitives/accordion/accordion-item.tsx`:
- Line 14: The exported component function AccordionItem currently lacks an
explicit return type; update its signature to include a component return type
(for example append ": JSX.Element" or ": React.ReactElement") so it becomes
"export function AccordionItem(props: AccordionItemProps): JSX.Element" (or
React.ReactElement) and ensure any necessary imports/types are available (e.g.,
import React types if your TSX config requires them).

In `@packages/headless/src/primitives/accordion/accordion-panel.tsx`:
- Around line 10-82: The public function AccordionPanel lacks an explicit return
type and JSDoc; update the function signature to include an explicit React
return type (e.g., React.ReactElement | null) on the exported
AccordionPanel(props: AccordionPanelProps) declaration and add a JSDoc block
above it that documents the component, its props (`@param` props
AccordionPanelProps), the return value (`@returns` React.ReactElement | null) and
a short `@example` showing usage (e.g., <Accordion.Panel>...</Accordion.Panel>);
keep existing logic (useAccordionItemContext, useTransition, panelRef,
measure/ResizeObserver, renderElement, mergeProps) untouched and only modify the
signature and add the JSDoc comment.

In `@packages/headless/src/primitives/accordion/accordion-trigger.tsx`:
- Around line 10-54: The public component AccordionTrigger currently lacks an
explicit return type and JSDoc; update the function signature to include an
explicit React.ReactElement return type (export function AccordionTrigger(props:
AccordionTriggerProps): React.ReactElement) and add a JSDoc block above it
documenting the component, its props (`@param` props - AccordionTriggerProps), the
return value (`@returns` React.ReactElement), and a short `@example` showing usage
of <Accordion.Trigger> with children; keep references to the relevant symbols in
the doc (AccordionTrigger, AccordionTriggerProps) and do not change behavior of
useAccordionContext, useAccordionItemContext, CompositeItem, mergeProps, or
renderElement.

---

Nitpick comments:
In `@packages/headless/src/primitives/accordion/accordion-panel.tsx`:
- Line 8: Add a JSDoc block for the public interface AccordionPanelProps:
document the purpose of the interface (props for an Accordion panel component),
note that it extends ComponentProps<'div'>, describe expected/important
behaviors or keys consumers might rely on (e.g., children, className, id), mark
it as public API, and include a short usage example and `@extends` tag referencing
ComponentProps<'div'>; update the AccordionPanelProps declaration accordingly in
accordion-panel.tsx.

In `@packages/headless/src/primitives/accordion/accordion-trigger.tsx`:
- Line 8: Add JSDoc to the exported AccordionTriggerProps public interface:
document what this prop type represents, its relationship to
ComponentProps<'button'>, typical consumers, and any important notes (e.g.,
forwarded props or accessibility considerations). Place the JSDoc immediately
above the declaration of AccordionTriggerProps so generated docs pick it up and
ensure the description is concise and mentions it extends
ComponentProps<'button'> and is intended for the Accordion trigger button
element.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: c18d7ed6-2b5d-4624-84f3-d4e272334791

📥 Commits

Reviewing files that changed from the base of the PR and between 90bc732 and 8b06f02.

📒 Files selected for processing (12)
  • packages/headless/package.json
  • packages/headless/src/primitives/accordion/README.md
  • packages/headless/src/primitives/accordion/accordion-context.ts
  • packages/headless/src/primitives/accordion/accordion-header.tsx
  • packages/headless/src/primitives/accordion/accordion-item.tsx
  • packages/headless/src/primitives/accordion/accordion-panel.tsx
  • packages/headless/src/primitives/accordion/accordion-root.tsx
  • packages/headless/src/primitives/accordion/accordion-trigger.tsx
  • packages/headless/src/primitives/accordion/accordion.test.tsx
  • packages/headless/src/primitives/accordion/index.ts
  • packages/headless/src/primitives/accordion/parts.ts
  • packages/headless/vite.config.ts

Comment thread packages/headless/src/primitives/accordion/accordion-context.ts
Comment thread packages/headless/src/primitives/accordion/accordion-header.tsx
Comment thread packages/headless/src/primitives/accordion/accordion-item.tsx
Comment thread packages/headless/src/primitives/accordion/accordion-panel.tsx
Comment thread packages/headless/src/primitives/accordion/accordion-trigger.tsx
Apply Frederick's Dialog review feedback to Accordion:
- Merge consumer ref with the internal panel ref via useMergeRefs so a
  consumer-supplied ref no longer clobbers the ref used for height
  measurement (matches Dialog.Popup).
- Force the derived trigger/panel ids to win over consumer-supplied ids so
  the trigger/panel aria pairing can't be silently broken.
- Resolve lint errors (empty interfaces, non-null assertions, curly, imports).
@alexcarpenter alexcarpenter merged commit eb303a6 into main Jun 9, 2026
46 checks passed
@alexcarpenter alexcarpenter deleted the carp/headless-accordion branch June 9, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants