Skip to content

feat(headless): remove data-cl-slot, de-prefix state data attributes#9237

Merged
alexcarpenter merged 1 commit into
mainfrom
card/headless-data-cl
Jul 24, 2026
Merged

feat(headless): remove data-cl-slot, de-prefix state data attributes#9237
alexcarpenter merged 1 commit into
mainfrom
card/headless-data-cl

Conversation

@alexcarpenter

Copy link
Copy Markdown
Member

Headless primitives no longer emit data-cl-slot. Slot identity comes from consumer/Mosaic classNames, not an emitted attr.

All primitive state/variant attrs de-prefixed: data-cl-opendata-open, data-cl-sidedata-side, etc. Matches Mosaic data-<axis> convention. Presence attrs stay split (data-open/data-closed), not Radix data-state.

--cl-* CSS custom properties unchanged (separate namespace).

Internal selectors that keyed off slot attrs rewired to stable attrs (svg[data-side], [aria-expanded], etc). Tests locate by data-testid/roles instead of slot; pure slot-existence tests deleted.

Docs updated: headless READMEs + swingset primitive stories/mdx + swingset house-style. Mosaic layer (data-cl-slot on styled slots) untouched.

Empty changeset: @clerk/headless private/unpublished, no user-facing published impact.

565 tests pass, typecheck + lint clean.

@vercel

vercel Bot commented Jul 24, 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 Jul 24, 2026 12:27am
swingset Ready Ready Preview, Comment Jul 24, 2026 12:27am

Request Review

@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2acb5df

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

@pkg-pr-new

pkg-pr-new Bot commented Jul 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9237

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9237

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9237

@clerk/expo

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

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9237

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 2acb5df

@alexcarpenter
alexcarpenter requested a review from wobsoriano July 24, 2026 00:28
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The headless primitives now emit generic data-* state, transition, placement, and drawer attributes instead of data-cl-* markers, while structural slot markers are removed. Animation hooks, floating-element queries, keyboard navigation, and exported drawer attribute constants were updated accordingly. Tests now use explicit data-testid or semantic role queries, and README/Swingset documentation reflects the revised styling contract.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.41% 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
Title check ✅ Passed The title clearly summarizes the main change: removing data-cl-slot and renaming headless state data attributes.
Description check ✅ Passed The description is directly aligned with the changeset and accurately describes the attribute renames, selector updates, and docs changes.
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.

Warning

Review ran into problems

🔥 Problems

Linked repositories: Couldn't analyze clerk/clerk-android - clone failed: Clone operation failed: Cloning into '/home/jailuser/git'...
warning: templates not found in /usr/share/git-core/templates
From https://github.com/clerk/clerk-android

Errors logged to '/home/jailuser/git/.git/lfs/logs/20260724T002932.877485564.log'.
Use git lfs logs last to view the log.
error: external filter 'git-lfs filter-process' failed
fatal: config/bin/detekt-cli: smudge filter lfs failed
Downloading config/bin/detekt-cli (71 MB)
Error downloading object: config/bin/detekt-cli (2655f48): Smudge error: Error downloading config/bin/detekt-cli (2655f48c7c303a5f9bf920a33229408b8571bb1d29e4e57cf7be3e151bceecb1): LFS: Client error: https://github-cloud.githubusercontent.com/alambic/media/877189286/26/55/2655f48c7c303a5f9bf920a33229408b8571bb1d29e4e57cf7be3e151bceecb1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA5BA2674WPWWEFGQ5%2F20260724%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260724T003053Z&X-Amz-Expires=3600&X-Amz-Signature=5ab1a3cb16a50887bf45b13fac085087ef7bdf653be47ae8bea64478e7cced13&X-Amz-SignedHeaders=host&actor_id=136622811&key_id=0&repo_id=980409545&token=1

Errors logged to '/home/jailuser/git/.git/lfs/logs/20260724T003053.699745391.log'.
Use git lfs logs last to view the log.
error: external filter 'git-lfs filter-process' failed
fatal: config/bin/detekt-cli: smudge filter lfs failed


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

@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: 10

🧹 Nitpick comments (2)
packages/headless/src/primitives/tooltip/tooltip.test.tsx (1)

34-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Testing Library queries for the new test IDs.

Replace document.querySelector(...) with screen.getByTestId(...) for required elements and screen.queryByTestId(...) for absence checks. This keeps the tests aligned with React Testing Library and improves failure diagnostics.

As per coding guidelines, React component tests must use proper React Testing Library queries.

Also applies to: 44-44, 60-60, 89-89, 98-98, 111-111, 134-136, 157-157, 170-170, 179-179, 189-189, 198-198, 205-205

🤖 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/tooltip/tooltip.test.tsx` at line 34,
Replace document.querySelector checks in the tooltip tests with React Testing
Library screen queries: use screen.getByTestId for required tooltip elements and
screen.queryByTestId for absence checks. Update all referenced assertions while
preserving their existing expected outcomes.

Source: Coding guidelines

packages/headless/src/primitives/tabs/tabs.test.tsx (1)

29-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Keep a regression test for slot removal.

After removing these assertions, the suite no longer verifies that Tabs parts do not emit the deprecated data-cl-slot attribute. Add one focused absence assertion covering the tablist, tabs, panels, and indicator so this contract cannot silently regress.

Also applies to: 368-368

🤖 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/tabs/tabs.test.tsx` around lines 29 - 30,
Add a focused regression test within the Tabs “ARIA attributes” suite that
asserts the tablist, tabs, panels, and indicator do not emit the deprecated
data-cl-slot attribute. Use the existing Tabs test setup and absence-assertion
style, preserving the current ARIA behavior checks.
🤖 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-root.tsx`:
- Around line 60-66: Update the Home/End handling in the accordion root’s
onKeyDown handler to collect only this Accordion’s registered or uniquely
identified enabled triggers, excluding elements with aria-disabled="true". Do
not use the broad subtree selector that can include nested accordions or
unrelated aria-expanded controls, and preserve the existing Home/End navigation
behavior for the filtered trigger list.

In `@packages/headless/src/primitives/collapsible/collapsible.test.tsx`:
- Line 31: Extend the collapsible tests around the root, trigger, and panel
assertions to verify removed legacy attributes are absent: data-cl-slot,
data-cl-open, data-cl-closed, and data-cl-disabled. Add negative assertions at
the referenced test cases while preserving the existing positive checks for the
new attributes.

In `@packages/headless/src/primitives/collapsible/README.md`:
- Around line 94-100: Update the collapsible usage example to apply
className="cl-collapsible-panel" to the panel element targeted by the CSS
selector, ensuring the documented example works with the provided animation
styles.

In `@packages/headless/src/primitives/file-upload/file-upload.test.tsx`:
- Around line 303-307: Extend the file-upload tests around the disabled trigger
case and the additional referenced case to assert that legacy data-cl-slot and
data-cl-disabled attributes are absent. Check the hidden input plus the root,
dropzone, and trigger elements, while preserving the existing positive
assertions for the replacement attributes and selectors.

In `@packages/headless/src/primitives/popover/popover-arrow.tsx`:
- Around line 19-20: The computed data-side attribute must take precedence over
consumer props in both arrow components. In
packages/headless/src/primitives/popover/popover-arrow.tsx lines 19-20, move
data-side after the props spread; apply the same ordering change in
packages/headless/src/primitives/select/select-arrow.tsx lines 16-17, preserving
the computed side for styling.

In `@packages/headless/src/primitives/tooltip/tooltip-positioner.tsx`:
- Line 20: The computed data-side value must remain authoritative in both
tooltip surfaces. Update TooltipPositioner in
packages/headless/src/primitives/tooltip/tooltip-positioner.tsx:20 so mergeProps
does not allow otherProps to override data-side, and update TooltipArrow in
packages/headless/src/primitives/tooltip/tooltip-arrow.tsx:19 so the computed
side is assigned after consumer props; preserve all other prop-merging behavior.

In `@packages/swingset/src/stories/accordion.mdx`:
- Around line 114-119: Update the Accordion.Panel usage in
packages/swingset/src/stories/accordion.mdx (lines 114-119) to pass the
cl-accordion-panel class, and update the Tabs.Indicator usage in
packages/swingset/src/stories/tabs.mdx (line 122) to pass the cl-tabs-indicator
class; keep each example’s CSS selector consistent with the class assigned to
its primitive.

In `@packages/swingset/src/stories/autocomplete.stories.tsx`:
- Line 10: The story descriptions incorrectly characterize data-* attributes as
ARIA wiring. Update the wording in the component story descriptions for
Autocomplete, Collapsible, Popover, and Select to describe data-* attributes as
emitted state or styling selectors, while identifying roles and aria-*
attributes as the accessibility wiring; apply this at
packages/swingset/src/stories/autocomplete.stories.tsx:10-10,
packages/swingset/src/stories/collapsible.stories.tsx:7-7,
packages/swingset/src/stories/popover.stories.tsx:7-7, and
packages/swingset/src/stories/select.stories.tsx:7-7.

In `@packages/swingset/src/stories/collapsible.mdx`:
- Around line 80-90: Update the examples so their CSS selectors match the
rendered headless parts: in packages/swingset/src/stories/collapsible.mdx lines
80-90, assign className='cl-collapsible-panel' to Collapsible.Panel; in
packages/swingset/src/stories/popover.mdx lines 121-127, assign
className='cl-popover-popup' to Popover.Popup. Ensure both samples visibly
demonstrate the required class assignments.

In `@packages/swingset/src/stories/dialog.stories.tsx`:
- Line 7: Reword the comments at
packages/swingset/src/stories/dialog.stories.tsx#L7-L7 to state that the
primitive provides ARIA wiring and emits data-* attributes for styling; at
packages/swingset/src/stories/drawer.stories.tsx#L7-L7, distinguish ARIA wiring
from gesture/state data-* hooks; and at
packages/swingset/src/stories/menu.stories.tsx#L7-L7, distinguish ARIA wiring
from positioning/state data-* hooks. No implementation changes are needed.

---

Nitpick comments:
In `@packages/headless/src/primitives/tabs/tabs.test.tsx`:
- Around line 29-30: Add a focused regression test within the Tabs “ARIA
attributes” suite that asserts the tablist, tabs, panels, and indicator do not
emit the deprecated data-cl-slot attribute. Use the existing Tabs test setup and
absence-assertion style, preserving the current ARIA behavior checks.

In `@packages/headless/src/primitives/tooltip/tooltip.test.tsx`:
- Line 34: Replace document.querySelector checks in the tooltip tests with React
Testing Library screen queries: use screen.getByTestId for required tooltip
elements and screen.queryByTestId for absence checks. Update all referenced
assertions while preserving their existing expected outcomes.
🪄 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), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 420617cc-13ad-47f3-8283-80d1117b0bdc

📥 Commits

Reviewing files that changed from the base of the PR and between acb1461 and 2acb5df.

📒 Files selected for processing (120)
  • .changeset/headless-remove-data-cl-slot.md
  • packages/headless/README.md
  • packages/headless/src/hooks/use-animations-finished.test.ts
  • packages/headless/src/hooks/use-animations-finished.ts
  • packages/headless/src/hooks/use-transition.test.ts
  • packages/headless/src/hooks/use-transition.ts
  • packages/headless/src/primitives/accordion/README.md
  • 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/autocomplete/README.md
  • packages/headless/src/primitives/autocomplete/autocomplete-arrow.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete-input.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete-list.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete-option.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete-popup.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete-positioner.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete-root.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete.test.tsx
  • packages/headless/src/primitives/collapsible/README.md
  • packages/headless/src/primitives/collapsible/collapsible-panel.tsx
  • packages/headless/src/primitives/collapsible/collapsible-root.tsx
  • packages/headless/src/primitives/collapsible/collapsible-trigger.tsx
  • packages/headless/src/primitives/collapsible/collapsible.test.tsx
  • packages/headless/src/primitives/dialog/README.md
  • packages/headless/src/primitives/dialog/dialog-backdrop.tsx
  • packages/headless/src/primitives/dialog/dialog-trigger.tsx
  • packages/headless/src/primitives/dialog/dialog-viewport.tsx
  • packages/headless/src/primitives/dialog/dialog.test.tsx
  • packages/headless/src/primitives/drawer/README.md
  • packages/headless/src/primitives/drawer/css-vars.ts
  • packages/headless/src/primitives/drawer/drawer-backdrop.tsx
  • packages/headless/src/primitives/drawer/drawer-handle-grip.tsx
  • packages/headless/src/primitives/drawer/drawer-trigger.tsx
  • packages/headless/src/primitives/drawer/drawer-viewport.tsx
  • packages/headless/src/primitives/drawer/drawer.test.tsx
  • packages/headless/src/primitives/drawer/use-drawer-drag.ts
  • packages/headless/src/primitives/file-upload/README.md
  • packages/headless/src/primitives/file-upload/file-upload-dropzone.tsx
  • packages/headless/src/primitives/file-upload/file-upload-item-delete.tsx
  • packages/headless/src/primitives/file-upload/file-upload-item-preview.tsx
  • packages/headless/src/primitives/file-upload/file-upload-item.tsx
  • packages/headless/src/primitives/file-upload/file-upload-root.tsx
  • packages/headless/src/primitives/file-upload/file-upload-trigger.tsx
  • packages/headless/src/primitives/file-upload/file-upload.test.tsx
  • packages/headless/src/primitives/menu/README.md
  • packages/headless/src/primitives/menu/menu-arrow.tsx
  • packages/headless/src/primitives/menu/menu-item.tsx
  • packages/headless/src/primitives/menu/menu-popup.tsx
  • packages/headless/src/primitives/menu/menu-positioner.tsx
  • packages/headless/src/primitives/menu/menu-separator.tsx
  • packages/headless/src/primitives/menu/menu-trigger.tsx
  • packages/headless/src/primitives/menu/menu.test.tsx
  • packages/headless/src/primitives/otp/README.md
  • packages/headless/src/primitives/otp/otp-input.tsx
  • packages/headless/src/primitives/otp/otp-root.tsx
  • packages/headless/src/primitives/otp/otp.test.tsx
  • packages/headless/src/primitives/popover/README.md
  • packages/headless/src/primitives/popover/popover-arrow.tsx
  • packages/headless/src/primitives/popover/popover-close.tsx
  • packages/headless/src/primitives/popover/popover-description.tsx
  • packages/headless/src/primitives/popover/popover-popup.tsx
  • packages/headless/src/primitives/popover/popover-positioner.tsx
  • packages/headless/src/primitives/popover/popover-title.tsx
  • packages/headless/src/primitives/popover/popover-trigger.tsx
  • packages/headless/src/primitives/popover/popover.test.tsx
  • packages/headless/src/primitives/select/README.md
  • packages/headless/src/primitives/select/select-arrow.tsx
  • packages/headless/src/primitives/select/select-option.tsx
  • packages/headless/src/primitives/select/select-popup.tsx
  • packages/headless/src/primitives/select/select-positioner.tsx
  • packages/headless/src/primitives/select/select-trigger.tsx
  • packages/headless/src/primitives/select/select-value.tsx
  • packages/headless/src/primitives/select/select.test.tsx
  • packages/headless/src/primitives/tabs/README.md
  • packages/headless/src/primitives/tabs/tabs-indicator.tsx
  • packages/headless/src/primitives/tabs/tabs-list.tsx
  • packages/headless/src/primitives/tabs/tabs-panel.tsx
  • packages/headless/src/primitives/tabs/tabs-tab.tsx
  • packages/headless/src/primitives/tabs/tabs-trigger.tsx
  • packages/headless/src/primitives/tabs/tabs.test.tsx
  • packages/headless/src/primitives/tooltip/README.md
  • packages/headless/src/primitives/tooltip/tooltip-arrow.tsx
  • packages/headless/src/primitives/tooltip/tooltip-popup.tsx
  • packages/headless/src/primitives/tooltip/tooltip-positioner.tsx
  • packages/headless/src/primitives/tooltip/tooltip-trigger.tsx
  • packages/headless/src/primitives/tooltip/tooltip.test.tsx
  • packages/headless/src/utils/css-vars.ts
  • packages/headless/src/utils/use-render.test.tsx
  • packages/headless/src/utils/use-render.tsx
  • packages/swingset/CLAUDE.md
  • packages/swingset/src/stories/accordion.mdx
  • packages/swingset/src/stories/accordion.stories.tsx
  • packages/swingset/src/stories/autocomplete.mdx
  • packages/swingset/src/stories/autocomplete.stories.tsx
  • packages/swingset/src/stories/collapsible.mdx
  • packages/swingset/src/stories/collapsible.stories.tsx
  • packages/swingset/src/stories/dialog.component.mdx
  • packages/swingset/src/stories/dialog.mdx
  • packages/swingset/src/stories/dialog.stories.tsx
  • packages/swingset/src/stories/drawer.mdx
  • packages/swingset/src/stories/drawer.stories.tsx
  • packages/swingset/src/stories/file-upload.mdx
  • packages/swingset/src/stories/file-upload.stories.tsx
  • packages/swingset/src/stories/menu.mdx
  • packages/swingset/src/stories/menu.stories.tsx
  • packages/swingset/src/stories/otp.mdx
  • packages/swingset/src/stories/otp.stories.tsx
  • packages/swingset/src/stories/popover.mdx
  • packages/swingset/src/stories/popover.stories.tsx
  • packages/swingset/src/stories/select.mdx
  • packages/swingset/src/stories/select.stories.tsx
  • packages/swingset/src/stories/tabs.component.mdx
  • packages/swingset/src/stories/tabs.mdx
  • packages/swingset/src/stories/tabs.stories.tsx
  • packages/swingset/src/stories/tooltip.mdx
  • packages/swingset/src/stories/tooltip.stories.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
💤 Files with no reviewable changes (13)
  • packages/headless/src/primitives/popover/popover-description.tsx
  • packages/headless/src/primitives/menu/menu-popup.tsx
  • packages/headless/src/primitives/popover/popover-close.tsx
  • packages/headless/src/primitives/menu/menu-separator.tsx
  • packages/headless/src/primitives/file-upload/file-upload-item-preview.tsx
  • packages/headless/src/primitives/select/select-value.tsx
  • packages/headless/src/primitives/tooltip/tooltip-popup.tsx
  • packages/headless/src/primitives/select/select-popup.tsx
  • packages/headless/src/primitives/tabs/tabs-indicator.tsx
  • packages/headless/src/primitives/tabs/tabs-list.tsx
  • packages/headless/src/primitives/autocomplete/autocomplete-popup.tsx
  • packages/headless/src/primitives/popover/popover-title.tsx
  • packages/headless/src/primitives/popover/popover-popup.tsx

Comment on lines 60 to +66
onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => {
if (event.key !== 'Home' && event.key !== 'End') {
return;
}
event.preventDefault();
const items = Array.from(
event.currentTarget.querySelectorAll<HTMLElement>('[data-cl-slot="accordion-trigger"]:not([disabled])'),
event.currentTarget.querySelectorAll<HTMLElement>('[aria-expanded]:not([disabled])'),

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Scope Home/End navigation to this Accordion’s enabled triggers.

[aria-expanded]:not([disabled]) searches the entire subtree, so nested accordions or consumer controls with aria-expanded can be included. It also filters native disabled, while AccordionTrigger represents its disabled state with aria-disabled; disabled triggers may therefore remain in the navigation list. Use the Composite registry or another Accordion-specific selector, and exclude aria-disabled="true".

🤖 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-root.tsx` around lines
60 - 66, Update the Home/End handling in the accordion root’s onKeyDown handler
to collect only this Accordion’s registered or uniquely identified enabled
triggers, excluding elements with aria-disabled="true". Do not use the broad
subtree selector that can include nested accordions or unrelated aria-expanded
controls, and preserve the existing Home/End navigation behavior for the
filtered trigger list.

Comment thread packages/headless/src/primitives/collapsible/collapsible.test.tsx
Comment on lines +94 to 100
.cl-collapsible-panel {
overflow: hidden;
height: var(--collapsible-panel-height);
transition: height 200ms ease;
}
[data-cl-slot='collapsible-panel'][data-cl-closed] {
.cl-collapsible-panel[data-closed] {
height: 0;

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Show the class required by the CSS example.

The new selector targets the consumer-provided .cl-collapsible-panel class, but the usage example never applies it. As written, copying both snippets leaves the panel unmatched and the animation does not run. Add className="cl-collapsible-panel" to the example, or scope the CSS to an attribute emitted by the primitive.

As per coding guidelines, README files must provide working usage examples; this example should demonstrate the class used by its CSS.

🤖 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/collapsible/README.md` around lines 94 -
100, Update the collapsible usage example to apply
className="cl-collapsible-panel" to the panel element targeted by the CSS
selector, ensuring the documented example works with the provided animation
styles.

Source: Coding guidelines

Comment thread packages/headless/src/primitives/file-upload/file-upload.test.tsx
Comment on lines +19 to 20
data-side={side}
{...props}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== popover-arrow =="
sed -n '1,120p' packages/headless/src/primitives/popover/popover-arrow.tsx

echo
echo "== select-arrow =="
sed -n '1,120p' packages/headless/src/primitives/select/select-arrow.tsx

echo
echo "== search for mergeProps usage near arrow primitives =="
rg -n "mergeProps|data-side" packages/headless/src/primitives/popover packages/headless/src/primitives/select -g '*.tsx' -g '*.ts'

Repository: clerk/javascript

Length of output: 5721


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  packages/headless/src/primitives/popover/popover-arrow.tsx \
  packages/headless/src/primitives/select/select-arrow.tsx
do
  echo "===== $f ====="
  nl -ba "$f" | sed -n '1,120p'
  echo
done

Repository: clerk/javascript

Length of output: 265


Set data-side after consumer props in both arrow components. props currently come last, so callers can override the computed side and break side-based styling.

  • packages/headless/src/primitives/popover/popover-arrow.tsx
  • packages/headless/src/primitives/select/select-arrow.tsx
📍 Affects 2 files
  • packages/headless/src/primitives/popover/popover-arrow.tsx#L19-L20 (this comment)
  • packages/headless/src/primitives/select/select-arrow.tsx#L16-L17
🤖 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/popover/popover-arrow.tsx` around lines 19 -
20, The computed data-side attribute must take precedence over consumer props in
both arrow components. In
packages/headless/src/primitives/popover/popover-arrow.tsx lines 19-20, move
data-side after the props spread; apply the same ordering change in
packages/headless/src/primitives/select/select-arrow.tsx lines 16-17, preserving
the computed side for styling.

const ownProps = {
'data-cl-slot': 'tooltip-positioner',
'data-cl-side': side,
'data-side': side,

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== tooltip-positioner.tsx ==\n'
cat -n packages/headless/src/primitives/tooltip/tooltip-positioner.tsx

printf '\n== tooltip-arrow.tsx ==\n'
cat -n packages/headless/src/primitives/tooltip/tooltip-arrow.tsx

Repository: clerk/javascript

Length of output: 3061


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' packages/headless/src/primitives/tooltip/tooltip-positioner.tsx
printf '\n---\n'
sed -n '1,120p' packages/headless/src/primitives/tooltip/tooltip-arrow.tsx

Repository: clerk/javascript

Length of output: 2535


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== mergeProps definition ==\n'
rg -n "export .*mergeProps|function mergeProps|const mergeProps" packages/headless/src -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx'

printf '\n== likely utils file ==\n'
fd -a "utils" packages/headless/src | head -n 20

Repository: clerk/javascript

Length of output: 804


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' packages/headless/src/utils/use-render.tsx

Repository: clerk/javascript

Length of output: 5757


Keep the computed data-side authoritative

Consumer props still override this value in both tooltip surfaces: mergeProps(..., otherProps) lets data-side be replaced in TooltipPositioner, and {...props} after data-side={side} does the same in TooltipArrow. Assign the computed value last, or omit it from consumer overrides.

📍 Affects 2 files
  • packages/headless/src/primitives/tooltip/tooltip-positioner.tsx#L20-L20 (this comment)
  • packages/headless/src/primitives/tooltip/tooltip-arrow.tsx#L19-L19
🤖 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/tooltip/tooltip-positioner.tsx` at line 20,
The computed data-side value must remain authoritative in both tooltip surfaces.
Update TooltipPositioner in
packages/headless/src/primitives/tooltip/tooltip-positioner.tsx:20 so mergeProps
does not allow otherProps to override data-side, and update TooltipArrow in
packages/headless/src/primitives/tooltip/tooltip-arrow.tsx:19 so the computed
side is assigned after consumer props; preserve all other prop-merging behavior.

Comment on lines +114 to +119
.cl-accordion-panel {
overflow: hidden;
height: var(--cl-accordion-panel-height);
transition: height 200ms ease;
}
[data-cl-slot='accordion-panel'][data-cl-closed] {
.cl-accordion-panel[data-closed] {

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the CSS examples assign the classes they select.

Both examples now select consumer-owned classes, but neither usage snippet passes those classes to the corresponding primitive. Copying the examples therefore produces no styling.

  • packages/swingset/src/stories/accordion.mdx#L114-L119: show className='cl-accordion-panel' on Accordion.Panel, or use a documented consumer class consistently.
  • packages/swingset/src/stories/tabs.mdx#L122-L122: show className='cl-tabs-indicator' on Tabs.Indicator, or use a documented consumer class consistently.
📍 Affects 2 files
  • packages/swingset/src/stories/accordion.mdx#L114-L119 (this comment)
  • packages/swingset/src/stories/tabs.mdx#L122-L122
🤖 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/swingset/src/stories/accordion.mdx` around lines 114 - 119, Update
the Accordion.Panel usage in packages/swingset/src/stories/accordion.mdx (lines
114-119) to pass the cl-accordion-panel class, and update the Tabs.Indicator
usage in packages/swingset/src/stories/tabs.mdx (line 122) to pass the
cl-tabs-indicator class; keep each example’s CSS selector consistent with the
class assigned to its primitive.

// Headless primitives ship no styles. This single demo renders the primitive raw —
// unstyled — so it faithfully reflects what `@clerk/headless` provides: behavior, state,
// positioning, keyboard navigation, and ARIA wiring via the `data-cl-*` attributes each
// positioning, keyboard navigation, and ARIA wiring via the `data-*` attributes each

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Describe data-* as state hooks, not ARIA wiring. Roles and aria-* attributes provide the accessibility wiring; these data-* attributes are styling/state selectors.

  • packages/swingset/src/stories/autocomplete.stories.tsx#L10-L10: replace “ARIA wiring via” with wording that separates ARIA wiring from emitted state attributes.
  • packages/swingset/src/stories/collapsible.stories.tsx#L7-L7: replace “ARIA wiring via” with wording that separates ARIA wiring from emitted state attributes.
  • packages/swingset/src/stories/popover.stories.tsx#L7-L7: replace “ARIA wiring via” with wording that separates ARIA wiring from emitted state attributes.
  • packages/swingset/src/stories/select.stories.tsx#L7-L7: replace “ARIA wiring via” with wording that separates ARIA wiring from emitted state attributes.
📍 Affects 4 files
  • packages/swingset/src/stories/autocomplete.stories.tsx#L10-L10 (this comment)
  • packages/swingset/src/stories/collapsible.stories.tsx#L7-L7
  • packages/swingset/src/stories/popover.stories.tsx#L7-L7
  • packages/swingset/src/stories/select.stories.tsx#L7-L7
🤖 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/swingset/src/stories/autocomplete.stories.tsx` at line 10, The story
descriptions incorrectly characterize data-* attributes as ARIA wiring. Update
the wording in the component story descriptions for Autocomplete, Collapsible,
Popover, and Select to describe data-* attributes as emitted state or styling
selectors, while identifying roles and aria-* attributes as the accessibility
wiring; apply this at
packages/swingset/src/stories/autocomplete.stories.tsx:10-10,
packages/swingset/src/stories/collapsible.stories.tsx:7-7,
packages/swingset/src/stories/popover.stories.tsx:7-7, and
packages/swingset/src/stories/select.stories.tsx:7-7.

Comment on lines 80 to +90
`Collapsible.Panel` also exposes `--collapsible-panel-height` /
`--collapsible-panel-width` (its measured dimensions) for height/width-based
animations:

```css
[data-cl-slot='collapsible-panel'] {
.cl-collapsible-panel {
overflow: hidden;
height: var(--collapsible-panel-height);
transition: height 200ms ease;
}
[data-cl-slot='collapsible-panel'][data-cl-closed] {
.cl-collapsible-panel[data-closed] {

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the CSS examples assign their selector classes. Headless parts do not guarantee .cl-collapsible-panel or .cl-popover-popup; the samples currently match nothing unless consumers independently add those classes.

  • packages/swingset/src/stories/collapsible.mdx#L80-L90: show className='cl-collapsible-panel' on Collapsible.Panel, or explicitly state that consumers must assign it.
  • packages/swingset/src/stories/popover.mdx#L121-L127: show className='cl-popover-popup' on Popover.Popup, or explicitly state that consumers must assign it.
📍 Affects 2 files
  • packages/swingset/src/stories/collapsible.mdx#L80-L90 (this comment)
  • packages/swingset/src/stories/popover.mdx#L121-L127
🤖 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/swingset/src/stories/collapsible.mdx` around lines 80 - 90, Update
the examples so their CSS selectors match the rendered headless parts: in
packages/swingset/src/stories/collapsible.mdx lines 80-90, assign
className='cl-collapsible-panel' to Collapsible.Panel; in
packages/swingset/src/stories/popover.mdx lines 121-127, assign
className='cl-popover-popup' to Popover.Popup. Ensure both samples visibly
demonstrate the required class assignments.

// Headless primitives ship no styles. This single demo renders the primitive raw —
// unstyled — so it faithfully reflects what `@clerk/headless` provides: behavior, state,
// and ARIA wiring via the `data-cl-*` attributes each part emits, with zero appearance.
// and ARIA wiring via the `data-*` attributes each part emits, with zero appearance.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Separate ARIA wiring from styling attributes.

data-* exposes state/styling hooks; ARIA wiring is provided by aria-* attributes. Reword these comments to describe both independently.

  • packages/swingset/src/stories/dialog.stories.tsx#L7-L7: say the primitive provides ARIA wiring and emits data-* attributes for styling.
  • packages/swingset/src/stories/drawer.stories.tsx#L7-L7: distinguish ARIA wiring from gesture/state data-* hooks.
  • packages/swingset/src/stories/menu.stories.tsx#L7-L7: distinguish ARIA wiring from positioning and state data-* hooks.
📍 Affects 3 files
  • packages/swingset/src/stories/dialog.stories.tsx#L7-L7 (this comment)
  • packages/swingset/src/stories/drawer.stories.tsx#L7-L7
  • packages/swingset/src/stories/menu.stories.tsx#L7-L7
🤖 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/swingset/src/stories/dialog.stories.tsx` at line 7, Reword the
comments at packages/swingset/src/stories/dialog.stories.tsx#L7-L7 to state that
the primitive provides ARIA wiring and emits data-* attributes for styling; at
packages/swingset/src/stories/drawer.stories.tsx#L7-L7, distinguish ARIA wiring
from gesture/state data-* hooks; and at
packages/swingset/src/stories/menu.stories.tsx#L7-L7, distinguish ARIA wiring
from positioning/state data-* hooks. No implementation changes are needed.

@alexcarpenter
alexcarpenter merged commit 1f8d89f into main Jul 24, 2026
49 checks passed
@alexcarpenter
alexcarpenter deleted the card/headless-data-cl branch July 24, 2026 02:09
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