Skip to content

feat: add data-slot attributes across the entire design system - #872

Open
ravisuhag wants to merge 6 commits into
mainfrom
feat/data-slot-attributes
Open

feat: add data-slot attributes across the entire design system#872
ravisuhag wants to merge 6 commits into
mainfrom
feat/data-slot-attributes

Conversation

@ravisuhag

@ravisuhag ravisuhag commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Every element every component renders now carries a stable data-slot attribute — a kebab-case, component-prefixed identifier (filter-chip-remove, data-view-list-loader-row, dialog-close). This gives consumers a supported hook for styling and testing inner parts without targeting hashed CSS-module classes.

Started as a 3-component pilot (Search, FilterChip, DataView) and grew into full coverage across the design system per follow-up discussion.

Related: #846 (closes its styling half — filter-chip internals and loader rows are now reachable). The prop-forwarding half of #848 is separate and unaffected.

Coverage

76 of 77 components get slots. data-table is deliberately excluded — it's slated for deprecation in favor of DataView, so it doesn't get new public API surface.

Every component ships with:

  • data-slot on every element it renders, placed before any {...props} spread so a caller-supplied override always wins
  • a __tests__/data-slots.test.tsx asserting the slot contract via the shared expectSlots/getSlot/getAllSlots helper (packages/raystack/test-utils/data-slots.ts), including that conditional slots are absent when their part isn't rendered
  • a ### Slots section on its docs page listing slot names

DataView.List cells also carry data-column={accessorKey}, so a single column can be targeted without a per-column classNames entry: [data-slot="data-view-list-cell"][data-column="amount"].

Convention

Documented in the styling guide (With data-slot, including the has-[]/[&_] composition patterns) and enforced going forward by the add-new-component skill checklist:

  • kebab-case, component-prefixed: filter-chip-remove, data-view-list-row
  • subparts extend the prefix; a repeated part reuses one name
  • slot names are public API, covered by semver
  • Popover.Content/Menu.Content/Dialog.Popup-style wrappers that split props between a Base UI Positioner and Popup get the slot hardcoded directly on the Popup — not routed through forwarded props, since those land on the positioner instead
  • existing internal precedent (menu-trigger's data-slot='menu-subtrigger') is preserved unchanged
  • a component reaching into a different component's rendered markup by tag name (.container input, .root button) is exactly the fragile pattern data-slot replaces — the 7 places this existed in-scope are now [data-slot=...] selectors instead

classNames cleanup (closing the loop on #846)

#846 asked to grow classNames with more keys (a filter-chip object, a loader key). Instead we audited every classNames-style prop in the design system against the now-complete data-slot coverage:

  • Deprecated (@deprecated JSDoc, non-breaking — actual removal is a future major version) every key with an exact 1:1 data-slot match: DataView.List, DataView.Timeline, DataView.Filters (filterChips/addFilter), Indicator, Input, EmptyState, Table.SectionHeader, Sidebar.NavigationGroup, Sidebar.Item, and Sidebar.More's root/leadingIcon/text.
  • Left alone, deliberately: Sidebar.More.classNames.menuContentMenu.Content portals to document.body, so a data-slot selector can't be scoped to one specific dropdown instance; this prop remains the only way to target it. Same reasoning applies to any other portaled per-instance content.
  • Fixed a real bug: DataView.Filters.classNames.addFilter was declared in the type but never applied anywhere — setting it silently did nothing. Wired it onto the default trigger.
  • Closed two doc gaps found while cross-referencing: Table had no ### Slots section at all, and DataView.List/Timeline/Sidebar.NavigationGroup's classNames types were undocumented or oversimplified in the docs-site mirror (apps/www/.../props.ts).

Fixes made while integrating

A few real issues surfaced while wiring this up across the whole system, fixed as part of this PR:

  • calendar: a test wrongly assumed DatePicker's error span unmounts when there's no error — it always mounts and toggles via data-visible, so screen readers hear it the instant an error appears. Fixed the test, not the component.
  • avatar: a test asserted the fallback and loaded-image slots simultaneously — Base UI unmounts the fallback once the image loads. Split into two tests.
  • chat: a test rendered Chat.JumpButton outside its required Chat.Messages ancestor. Fixed the test harness.
  • text, headline, label, grid, grid-item: Base UI's mergeProps types its parameter against HTMLAttributes, which doesn't include data-slot — passing it as a fresh object literal fails TypeScript's excess-property check. Fixed by extracting the props into a named const first (matching the existing pattern already used in flex.tsx), which sidesteps the check without a cast.

Testing

  • Full package suite: 2389 tests passing (1 pre-existing skip), across all 76 covered components
  • Typecheck: unchanged from the pre-existing baseline (6 errors, all in data-table/menu/popover test files, none introduced by this change)
  • @raystack/apsara rollup build: passes
  • www docs site build: passes
  • Biome: clean (only formatting auto-fixes applied)

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview Jul 31, 2026 4:46pm

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 319 files, which is 219 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ae2cfbe-8bca-4bdb-8478-fec229b242ad

📥 Commits

Reviewing files that changed from the base of the PR and between 5a388ac and 2614fdd.

📒 Files selected for processing (319)
  • .agents/skills/add-new-component/SKILL.md
  • apps/www/src/content/docs/(overview)/styling.mdx
  • apps/www/src/content/docs/components/alert-dialog/index.mdx
  • apps/www/src/content/docs/components/amount/index.mdx
  • apps/www/src/content/docs/components/badge/index.mdx
  • apps/www/src/content/docs/components/breadcrumb/index.mdx
  • apps/www/src/content/docs/components/button/index.mdx
  • apps/www/src/content/docs/components/checkbox/index.mdx
  • apps/www/src/content/docs/components/chip/index.mdx
  • apps/www/src/content/docs/components/command/index.mdx
  • apps/www/src/content/docs/components/container/index.mdx
  • apps/www/src/content/docs/components/context-menu/index.mdx
  • apps/www/src/content/docs/components/copy-button/index.mdx
  • apps/www/src/content/docs/components/dataview/index.mdx
  • apps/www/src/content/docs/components/dataview/props.ts
  • apps/www/src/content/docs/components/dialog/index.mdx
  • apps/www/src/content/docs/components/drawer/index.mdx
  • apps/www/src/content/docs/components/empty-state/props.ts
  • apps/www/src/content/docs/components/filter-chip/index.mdx
  • apps/www/src/content/docs/components/flex/index.mdx
  • apps/www/src/content/docs/components/floating-actions/index.mdx
  • apps/www/src/content/docs/components/grid/index.mdx
  • apps/www/src/content/docs/components/headline/index.mdx
  • apps/www/src/content/docs/components/icon-button/index.mdx
  • apps/www/src/content/docs/components/image/index.mdx
  • apps/www/src/content/docs/components/indicator/index.mdx
  • apps/www/src/content/docs/components/indicator/props.ts
  • apps/www/src/content/docs/components/input/index.mdx
  • apps/www/src/content/docs/components/label/index.mdx
  • apps/www/src/content/docs/components/link/index.mdx
  • apps/www/src/content/docs/components/menu/index.mdx
  • apps/www/src/content/docs/components/menubar/index.mdx
  • apps/www/src/content/docs/components/meter/index.mdx
  • apps/www/src/content/docs/components/navbar/index.mdx
  • apps/www/src/content/docs/components/number-field/index.mdx
  • apps/www/src/content/docs/components/otp-field/index.mdx
  • apps/www/src/content/docs/components/popover/index.mdx
  • apps/www/src/content/docs/components/preview-card/index.mdx
  • apps/www/src/content/docs/components/progress/index.mdx
  • apps/www/src/content/docs/components/radio/index.mdx
  • apps/www/src/content/docs/components/search/index.mdx
  • apps/www/src/content/docs/components/separator/index.mdx
  • apps/www/src/content/docs/components/sidebar/index.mdx
  • apps/www/src/content/docs/components/sidebar/props.ts
  • apps/www/src/content/docs/components/sidepanel/index.mdx
  • apps/www/src/content/docs/components/skeleton/index.mdx
  • apps/www/src/content/docs/components/slider/index.mdx
  • apps/www/src/content/docs/components/spinner/index.mdx
  • apps/www/src/content/docs/components/switch/index.mdx
  • apps/www/src/content/docs/components/table/index.mdx
  • apps/www/src/content/docs/components/table/props.ts
  • apps/www/src/content/docs/components/tabs/index.mdx
  • apps/www/src/content/docs/components/text/index.mdx
  • apps/www/src/content/docs/components/textarea/index.mdx
  • apps/www/src/content/docs/components/toast/index.mdx
  • apps/www/src/content/docs/components/toggle/index.mdx
  • apps/www/src/content/docs/components/toolbar/index.mdx
  • apps/www/src/content/docs/components/tooltip/index.mdx
  • apps/www/src/content/docs/components/tour/index.mdx
  • packages/raystack/components/accordion/__tests__/data-slots.test.tsx
  • packages/raystack/components/accordion/accordion-content.tsx
  • packages/raystack/components/accordion/accordion-item.tsx
  • packages/raystack/components/accordion/accordion-root.tsx
  • packages/raystack/components/accordion/accordion-trigger.tsx
  • packages/raystack/components/alert-dialog/__tests__/data-slots.test.tsx
  • packages/raystack/components/alert-dialog/alert-dialog-content.tsx
  • packages/raystack/components/alert-dialog/alert-dialog-misc.tsx
  • packages/raystack/components/amount/__tests__/data-slots.test.tsx
  • packages/raystack/components/amount/amount.tsx
  • packages/raystack/components/announcement-bar/__tests__/data-slots.test.tsx
  • packages/raystack/components/announcement-bar/announcement-bar.tsx
  • packages/raystack/components/avatar/__tests__/data-slots.test.tsx
  • packages/raystack/components/avatar/avatar.tsx
  • packages/raystack/components/badge/__tests__/data-slots.test.tsx
  • packages/raystack/components/badge/badge.module.css
  • packages/raystack/components/badge/badge.tsx
  • packages/raystack/components/badge/index.tsx
  • packages/raystack/components/box/__tests__/data-slots.test.tsx
  • packages/raystack/components/box/box.tsx
  • packages/raystack/components/box/index.tsx
  • packages/raystack/components/breadcrumb/__tests__/data-slots.test.tsx
  • packages/raystack/components/breadcrumb/breadcrumb-item.tsx
  • packages/raystack/components/breadcrumb/breadcrumb-misc.tsx
  • packages/raystack/components/breadcrumb/breadcrumb-root.tsx
  • packages/raystack/components/button/__tests__/data-slots.test.tsx
  • packages/raystack/components/button/button.tsx
  • packages/raystack/components/calendar/__tests__/data-slots.test.tsx
  • packages/raystack/components/calendar/calendar.module.css
  • packages/raystack/components/calendar/calendar.tsx
  • packages/raystack/components/calendar/date-picker.tsx
  • packages/raystack/components/calendar/range-picker.tsx
  • packages/raystack/components/callout/__tests__/data-slots.test.tsx
  • packages/raystack/components/callout/callout.tsx
  • packages/raystack/components/callout/index.tsx
  • packages/raystack/components/chat-panel/__tests__/data-slots.test.tsx
  • packages/raystack/components/chat-panel/chat-panel-parts.tsx
  • packages/raystack/components/chat-panel/chat-panel-root.tsx
  • packages/raystack/components/chat-panel/chat-panel-trigger.tsx
  • packages/raystack/components/chat/__tests__/data-slots.test.tsx
  • packages/raystack/components/chat/chat-attachment.tsx
  • packages/raystack/components/chat/chat-composer.tsx
  • packages/raystack/components/chat/chat-item.tsx
  • packages/raystack/components/chat/chat-messages.tsx
  • packages/raystack/components/chat/chat-separator.tsx
  • packages/raystack/components/chat/chat.tsx
  • packages/raystack/components/checkbox/__tests__/data-slots.test.tsx
  • packages/raystack/components/checkbox/checkbox.tsx
  • packages/raystack/components/chip/__tests__/data-slots.test.tsx
  • packages/raystack/components/chip/chip.tsx
  • packages/raystack/components/chip/index.tsx
  • packages/raystack/components/code-block/__tests__/data-slots.test.tsx
  • packages/raystack/components/code-block/code-block-code.tsx
  • packages/raystack/components/code-block/code-block-language-select.tsx
  • packages/raystack/components/code-block/code-block-misc.tsx
  • packages/raystack/components/code-block/code-block-root.tsx
  • packages/raystack/components/collapsible/__tests__/data-slots.test.tsx
  • packages/raystack/components/collapsible/collapsible.tsx
  • packages/raystack/components/color-picker/__tests__/data-slots.test.tsx
  • packages/raystack/components/color-picker/color-picker-alpha.tsx
  • packages/raystack/components/color-picker/color-picker-area.tsx
  • packages/raystack/components/color-picker/color-picker-hue.tsx
  • packages/raystack/components/color-picker/color-picker-input.tsx
  • packages/raystack/components/color-picker/color-picker-mode.tsx
  • packages/raystack/components/color-picker/color-picker-root.tsx
  • packages/raystack/components/combobox/__tests__/data-slots.test.tsx
  • packages/raystack/components/combobox/combobox-content.tsx
  • packages/raystack/components/combobox/combobox-input.tsx
  • packages/raystack/components/combobox/combobox-item.tsx
  • packages/raystack/components/combobox/combobox-misc.tsx
  • packages/raystack/components/command/__tests__/data-slots.test.tsx
  • packages/raystack/components/command/command-content.tsx
  • packages/raystack/components/command/command-dialog.tsx
  • packages/raystack/components/command/command-empty.tsx
  • packages/raystack/components/command/command-input.tsx
  • packages/raystack/components/command/command-item.tsx
  • packages/raystack/components/command/command-misc.tsx
  • packages/raystack/components/command/command-root.tsx
  • packages/raystack/components/command/index.tsx
  • packages/raystack/components/container/__tests__/data-slots.test.tsx
  • packages/raystack/components/container/container.tsx
  • packages/raystack/components/context-menu/__tests__/data-slots.test.tsx
  • packages/raystack/components/context-menu/context-menu-content.tsx
  • packages/raystack/components/context-menu/context-menu-item.tsx
  • packages/raystack/components/context-menu/context-menu-misc.tsx
  • packages/raystack/components/context-menu/context-menu-trigger.tsx
  • packages/raystack/components/copy-button/__tests__/data-slots.test.tsx
  • packages/raystack/components/copy-button/copy-button.tsx
  • packages/raystack/components/copy-button/index.tsx
  • packages/raystack/components/data-view/__tests__/data-slots.test.tsx
  • packages/raystack/components/data-view/components/clear-filters.tsx
  • packages/raystack/components/data-view/components/display-controls.tsx
  • packages/raystack/components/data-view/components/display-properties.tsx
  • packages/raystack/components/data-view/components/empty-state.tsx
  • packages/raystack/components/data-view/components/filters.tsx
  • packages/raystack/components/data-view/components/grouping.tsx
  • packages/raystack/components/data-view/components/list.tsx
  • packages/raystack/components/data-view/components/ordering.tsx
  • packages/raystack/components/data-view/components/timeline.tsx
  • packages/raystack/components/data-view/components/toolbar.tsx
  • packages/raystack/components/data-view/components/view-switcher.tsx
  • packages/raystack/components/data-view/components/zero-state.tsx
  • packages/raystack/components/data-view/data-view.module.css
  • packages/raystack/components/data-view/data-view.types.tsx
  • packages/raystack/components/dialog/__tests__/data-slots.test.tsx
  • packages/raystack/components/dialog/dialog-content.tsx
  • packages/raystack/components/dialog/dialog-misc.tsx
  • packages/raystack/components/dialog/index.ts
  • packages/raystack/components/drawer/__tests__/data-slots.test.tsx
  • packages/raystack/components/drawer/drawer-content.tsx
  • packages/raystack/components/drawer/drawer-misc.tsx
  • packages/raystack/components/empty-state/__tests__/data-slots.test.tsx
  • packages/raystack/components/empty-state/empty-state.module.css
  • packages/raystack/components/empty-state/empty-state.tsx
  • packages/raystack/components/empty-state/index.tsx
  • packages/raystack/components/field/__tests__/data-slots.test.tsx
  • packages/raystack/components/field/field-misc.tsx
  • packages/raystack/components/field/field-root.tsx
  • packages/raystack/components/fieldset/__tests__/data-slots.test.tsx
  • packages/raystack/components/fieldset/fieldset.tsx
  • packages/raystack/components/filter-chip/__tests__/data-slots.test.tsx
  • packages/raystack/components/filter-chip/filter-chip-operation.tsx
  • packages/raystack/components/filter-chip/filter-chip.module.css
  • packages/raystack/components/filter-chip/filter-chip.tsx
  • packages/raystack/components/flex/__tests__/data-slots.test.tsx
  • packages/raystack/components/flex/flex.tsx
  • packages/raystack/components/flex/index.tsx
  • packages/raystack/components/floating-actions/__tests__/data-slots.test.tsx
  • packages/raystack/components/floating-actions/floating-actions.tsx
  • packages/raystack/components/form/__tests__/data-slots.test.tsx
  • packages/raystack/components/form/form.tsx
  • packages/raystack/components/grid/__tests__/data-slots.test.tsx
  • packages/raystack/components/grid/grid-item.tsx
  • packages/raystack/components/grid/grid.tsx
  • packages/raystack/components/grid/index.ts
  • packages/raystack/components/grid/types.ts
  • packages/raystack/components/headline/__tests__/data-slots.test.tsx
  • packages/raystack/components/headline/headline.tsx
  • packages/raystack/components/headline/index.tsx
  • packages/raystack/components/icon-button/__tests__/data-slots.test.tsx
  • packages/raystack/components/icon-button/icon-button.tsx
  • packages/raystack/components/image/__tests__/data-slots.test.tsx
  • packages/raystack/components/image/image.tsx
  • packages/raystack/components/indicator/__tests__/data-slots.test.tsx
  • packages/raystack/components/indicator/index.tsx
  • packages/raystack/components/indicator/indicator.tsx
  • packages/raystack/components/input/__tests__/data-slots.test.tsx
  • packages/raystack/components/input/input.tsx
  • packages/raystack/components/label/__tests__/data-slots.test.tsx
  • packages/raystack/components/label/label.tsx
  • packages/raystack/components/link/__tests__/data-slots.test.tsx
  • packages/raystack/components/link/link.tsx
  • packages/raystack/components/list/__tests__/data-slots.test.tsx
  • packages/raystack/components/list/list.tsx
  • packages/raystack/components/menu/__tests__/data-slots.test.tsx
  • packages/raystack/components/menu/cell.tsx
  • packages/raystack/components/menu/menu-content.tsx
  • packages/raystack/components/menu/menu-item.tsx
  • packages/raystack/components/menu/menu-misc.tsx
  • packages/raystack/components/menu/menu-trigger.tsx
  • packages/raystack/components/menubar/__tests__/data-slots.test.tsx
  • packages/raystack/components/menubar/menubar.tsx
  • packages/raystack/components/message/__tests__/data-slots.test.tsx
  • packages/raystack/components/message/message-bubble.tsx
  • packages/raystack/components/message/message.tsx
  • packages/raystack/components/meter/__tests__/data-slots.test.tsx
  • packages/raystack/components/meter/meter-misc.tsx
  • packages/raystack/components/meter/meter-root.tsx
  • packages/raystack/components/meter/meter-track.tsx
  • packages/raystack/components/navbar/__tests__/data-slots.test.tsx
  • packages/raystack/components/navbar/navbar-root.tsx
  • packages/raystack/components/navbar/navbar-sections.tsx
  • packages/raystack/components/number-field/__tests__/data-slots.test.tsx
  • packages/raystack/components/number-field/number-field.tsx
  • packages/raystack/components/otp-field/__tests__/data-slots.test.tsx
  • packages/raystack/components/otp-field/otp-field.tsx
  • packages/raystack/components/popover/__tests__/data-slots.test.tsx
  • packages/raystack/components/popover/index.tsx
  • packages/raystack/components/popover/popover.tsx
  • packages/raystack/components/preview-card/__tests__/data-slots.test.tsx
  • packages/raystack/components/preview-card/preview-card.tsx
  • packages/raystack/components/progress/__tests__/data-slots.test.tsx
  • packages/raystack/components/progress/progress-misc.tsx
  • packages/raystack/components/progress/progress-root.tsx
  • packages/raystack/components/progress/progress-track.tsx
  • packages/raystack/components/prompt-input/__tests__/data-slots.test.tsx
  • packages/raystack/components/prompt-input/prompt-input-parts.tsx
  • packages/raystack/components/prompt-input/prompt-input-root.tsx
  • packages/raystack/components/prompt-input/prompt-input-submit.tsx
  • packages/raystack/components/prompt-input/prompt-input-textarea.tsx
  • packages/raystack/components/radio/__tests__/data-slots.test.tsx
  • packages/raystack/components/radio/radio.tsx
  • packages/raystack/components/reasoning/__tests__/data-slots.test.tsx
  • packages/raystack/components/reasoning/reasoning.tsx
  • packages/raystack/components/scroll-area/__tests__/data-slots.test.tsx
  • packages/raystack/components/scroll-area/scroll-area-scrollbar.tsx
  • packages/raystack/components/scroll-area/scroll-area.tsx
  • packages/raystack/components/search/__tests__/data-slots.test.tsx
  • packages/raystack/components/search/index.ts
  • packages/raystack/components/search/search.module.css
  • packages/raystack/components/search/search.tsx
  • packages/raystack/components/select/__tests__/data-slots.test.tsx
  • packages/raystack/components/select/select-content.tsx
  • packages/raystack/components/select/select-item.tsx
  • packages/raystack/components/select/select-misc.tsx
  • packages/raystack/components/select/select-multiple-value.tsx
  • packages/raystack/components/select/select-trigger.tsx
  • packages/raystack/components/select/select-value.tsx
  • packages/raystack/components/separator/__tests__/data-slots.test.tsx
  • packages/raystack/components/separator/separator.tsx
  • packages/raystack/components/side-panel/__tests__/data-slots.test.tsx
  • packages/raystack/components/side-panel/side-panel.tsx
  • packages/raystack/components/sidebar/__tests__/data-slots.test.tsx
  • packages/raystack/components/sidebar/sidebar-footer.tsx
  • packages/raystack/components/sidebar/sidebar-group.tsx
  • packages/raystack/components/sidebar/sidebar-header.tsx
  • packages/raystack/components/sidebar/sidebar-item.tsx
  • packages/raystack/components/sidebar/sidebar-leading-visual.tsx
  • packages/raystack/components/sidebar/sidebar-main.tsx
  • packages/raystack/components/sidebar/sidebar-more.tsx
  • packages/raystack/components/sidebar/sidebar-root.tsx
  • packages/raystack/components/sidebar/sidebar-trailing-visual.tsx
  • packages/raystack/components/sidebar/sidebar-trigger.tsx
  • packages/raystack/components/skeleton/__tests__/data-slots.test.tsx
  • packages/raystack/components/skeleton/skeleton.tsx
  • packages/raystack/components/slider/__tests__/data-slots.test.tsx
  • packages/raystack/components/slider/slider.tsx
  • packages/raystack/components/spinner/__tests__/data-slots.test.tsx
  • packages/raystack/components/spinner/spinner.tsx
  • packages/raystack/components/switch/__tests__/data-slots.test.tsx
  • packages/raystack/components/switch/index.ts
  • packages/raystack/components/switch/switch.tsx
  • packages/raystack/components/table/__tests__/data-slots.test.tsx
  • packages/raystack/components/table/table.tsx
  • packages/raystack/components/tabs/__tests__/data-slots.test.tsx
  • packages/raystack/components/tabs/tabs.tsx
  • packages/raystack/components/text-area/__tests__/data-slots.test.tsx
  • packages/raystack/components/text-area/text-area.tsx
  • packages/raystack/components/text/__tests__/data-slots.test.tsx
  • packages/raystack/components/text/index.tsx
  • packages/raystack/components/text/text.tsx
  • packages/raystack/components/theme-provider/__tests__/data-slots.test.tsx
  • packages/raystack/components/theme-provider/switcher.tsx
  • packages/raystack/components/theme-provider/theme.tsx
  • packages/raystack/components/toast/__tests__/data-slots.test.tsx
  • packages/raystack/components/toast/toast-provider.tsx
  • packages/raystack/components/toast/toast-root.tsx
  • packages/raystack/components/toggle/__tests__/data-slots.test.tsx
  • packages/raystack/components/toggle/toggle.tsx
  • packages/raystack/components/toolbar/__tests__/data-slots.test.tsx
  • packages/raystack/components/toolbar/toolbar.module.css
  • packages/raystack/components/toolbar/toolbar.tsx
  • packages/raystack/components/tooltip/__tests__/data-slots.test.tsx
  • packages/raystack/components/tooltip/tooltip-content.tsx
  • packages/raystack/components/tooltip/tooltip-misc.tsx
  • packages/raystack/components/tour/__tests__/data-slots.test.tsx
  • packages/raystack/components/tour/tour-content.tsx
  • packages/raystack/components/tour/tour-overlay.tsx
  • packages/raystack/components/tour/tour-parts.tsx
  • packages/raystack/test-utils/data-slots.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

@ravisuhag ravisuhag changed the title feat: add data-slot attributes to Search, FilterChip, and DataView feat: add data-slot attributes across the entire design system Jul 31, 2026
Every element these components render now carries a stable, kebab-case,
component-prefixed data-slot identifier (e.g. filter-chip-remove,
data-view-list-loader-row). This gives consumers a supported styling and
testing hook for inner parts that have no classNames key, without
targeting hashed CSS-module classes.

- Add test-utils/data-slots with getSlot/getAllSlots/expectSlots and
  slot-contract tests for all three components
- Document the convention in the styling guide and list slot names on
  each component's docs page
- Require data-slot coverage in the add-new-component skill checklist

First batch of the design-system-wide rollout. Closes the styling half
of #846 (filter-chip internals and loader rows are now reachable).
Popover.Content/Menu.Content call sites are deliberately skipped: their
extra props land on Base UI's positioner, not the popup — those slots
ship with the popover/menu batch.
@ravisuhag
ravisuhag requested a review from rohanchkrabrty July 31, 2026 16:30
Every element every component renders now carries a stable, kebab-case,
component-prefixed data-slot identifier — the full rollout following the
first batch (Search, FilterChip, DataView).

76 of 77 components covered; data-table is deliberately excluded (it's
slated for deprecation in favor of DataView). Each component gets a
__tests__/data-slots.test.tsx asserting its slot contract via the
shared expectSlots/getSlot helper, and a docs "### Slots" section
listing its slot names.

Existing classNames props are untouched — this is purely additive.
Popover/Menu/Dialog-style Content wrappers that split props between a
Base UI Positioner and Popup get their slot hardcoded directly on the
Popup, not routed through forwarded props, so it lands on the right
element. Existing internal precedent (menu-trigger's
data-slot='menu-subtrigger') is preserved unchanged.

Fixes made while integrating parallel batches:
- calendar: corrected a test's wrong assumption that DatePicker's error
  span unmounts when absent (it always mounts, toggled via
  data-visible, so screen readers hear it immediately on error)
- avatar: corrected a test asserting the fallback and loaded image
  slots simultaneously (Base UI unmounts the fallback once the image
  loads)
- chat: fixed a test rendering Chat.JumpButton outside its required
  Chat.Messages ancestor
- text, headline, label, grid, grid-item: mergeProps' generic props
  type doesn't include data-slot, so passing it as a fresh object
  literal fails TypeScript's excess-property check. Extracted the
  props into a named const first (matching the existing pattern in
  flex.tsx), which resolves it without a cast.
…ency

Checked the data-slot rollout against components.build's data-attributes
guide. Two follow-ups:

- Renamed Command's `command-input-wrapper` slot to
  `command-input-container`, matching the "-container" suffix used
  elsewhere (input-container, toast-viewport) instead of a bare
  "wrapper" name.
- Documented the has-[]/[&_] compound-selector patterns in the styling
  guide — the main technique the guide highlights for parent-aware and
  descendant styling via data-slot, which wasn't shown before. No
  component change needed; existing slots already support it.

Also updated the outdated "rolling out component by component" coverage
note now that all components ship slots.
Some components' CSS reached into a different component's rendered
markup by HTML tag (.container input, .root button, > span) — exactly
the fragile pattern data-slot exists to replace, per
components.build/data-attributes. Swapped each for the actual
data-slot the nested element carries, verified empirically per case
(a Base UI render-prop composition like Toolbar.Button wrapping Button
isn't always obvious which slot wins without checking):

- search: .container input -> [data-slot="search-input"]
- filter-chip: .chip/.inputFieldWrapper/.dateField input ->
  [data-slot="input"] / [data-slot="date-picker-input"] (the chip-level
  reset needs both, since it resets any input variant inside the chip)
- data-view: .display-popover-properties-select > span ->
  [data-slot="select-value"]
- toolbar: .root[data-orientation="vertical"] button ->
  [data-slot="toolbar-button"]
- calendar: .dropdowns > span -> [data-slot="select-value"]

data-table's equivalent selector is left as-is — that component is
excluded from the data-slot rollout since it's being deprecated.
Left in place: selectors that reach into a component's own directly-
rendered icon/pseudo-element (own markup, not cross-component), and
code-block's :global(.token...) rules (targeting Prism.js's own
public class API, not something we control).
…ecate redundant classNames props

Follow-up from auditing #846 against the data-slot rollout: every
component's classNames-style hook props were checked for redundancy
now that data-slot covers the same ground, and three concrete gaps
were closed.

New: data-column
- DataView.List's header cells, body cells, and loader cells now carry
  data-column={accessorKey} alongside their data-slot, so one column
  can be targeted without a per-column classNames entry:
  [data-slot="data-view-list-cell"][data-column="amount"].

Fixed: DataView.Filters classNames.addFilter
- Declared in the type since the classNames API existed, but never
  applied anywhere in filters.tsx — setting it had no effect. Wired it
  onto the default trigger (Button/IconButton), same as classNames.filterChips
  already applies to FilterChip.

Deprecated (JSDoc @deprecated, non-breaking — removal is a future
major version): every classNames key with an exact 1:1 data-slot
match — DataView.List, DataView.Timeline, DataView.Filters
(filterChips/addFilter), Indicator, Input, EmptyState,
Table.SectionHeader, Sidebar.NavigationGroup, Sidebar.Item, and
Sidebar.More's root/leadingIcon/text (not menuContent — Menu.Content
portals to document.body, so a data-slot selector can't be scoped to
one specific instance; that key stays as the only way to target it).
DataViewListColumn's per-column classNames is deprecated too, pointing
at the new data-column combo, since data-slot alone can't distinguish
one column from another.

Also closed two pre-existing doc gaps found while cross-referencing
props against their mirrored apps/www props.ts: Table never had a
Slots section, and DataView.List/Timeline/Sidebar.NavigationGroup's
classNames types were undocumented or oversimplified there.
@ravisuhag
ravisuhag force-pushed the feat/data-slot-attributes branch from 56d14b7 to bbcdb35 Compare July 31, 2026 16:40
…Sidebar.Trigger

Rebase fallout: the sidebar refactor (#867) replaced the boolean
collapsible prop with 'icon' | 'hidden' | 'none', so
collapsible={false} no longer disables the toggle (canCollapse checks
!== 'none'). Switched the test to collapsible='none' and added
coverage for the new Sidebar.Trigger's data-slot.
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.

1 participant