Skip to content

feat: add dropdown customization slots to Select and Multiselect - #4856

Draft
gethinwebster wants to merge 4 commits into
mainfrom
dev-v3-gethinw-select-multiselect-dropdown-customization
Draft

feat: add dropdown customization slots to Select and Multiselect#4856
gethinwebster wants to merge 4 commits into
mainfrom
dev-v3-gethinw-select-multiselect-dropdown-customization

Conversation

@gethinwebster

@gethinwebster gethinwebster commented Aug 3, 2026

Copy link
Copy Markdown
Member

Description

Adds render-prop slots to Select and Multiselect for customizing the dropdown, plus two props for consumers to own the accessibility semantics of that content:

  • renderDropdownHeader — content pinned at the top of the dropdown, above the filter input and options list.
  • renderDropdownFooter — content pinned at the bottom, always sticky (never scrolls), additive to the built-in status region.
  • dropdownRole?: 'auto' | 'dialog' — lets a consumer opt the dropdown into dialog semantics for interactive custom content, independent of whether filtering is enabled.
  • dropdownAriaDescribedby?: string — adds aria-describedby to the dropdown's listbox/dialog content.

Each render prop receives DropdownContentProps { filterText, closeDropdown }. The props live on the shared BaseSelectProps, so Select and Multiselect inherit them; a small composeDropdownContent helper wraps the custom header/footer around the filter and the built-in status region. The two short header/footer wrapper styles are declared in each component's stylesheet (the header is lifted with z-index since it can sit directly above the options list; the footer needs no lift).

Backward compatible: all new props are optional. When omitted, the composed output is unchanged from today's behavior (verified — additive-only documenter/selectors snapshots, no removals).

Related links, issue #, if available: n/a

How has this been tested?

  • Unit tests: new dropdown-customization suites for Select and Multiselect (composition/order, closeDropdown closes + returns focus to the trigger exactly once, filteringType='none', dropdownRole, dropdownAriaDescribedby, expandToViewport). Full Select/Multiselect/dropdown scope passes (503 tests).
  • Integration tests (keyboard reachability): new integ tests confirm on the real browser harness that an interactive footer is reachable via a forward Tab and an interactive header via a backward Shift+Tab, with the dropdown staying open, in both dialog (filteringType='auto') and plain listbox (filteringType='none') modes — so interactive header/footer content is keyboard-reachable without requiring dropdownRole='dialog'.
  • Type / build / snapshots: tsc --noEmit clean; npm run build succeeds; documenter and test-utils-selectors snapshots regenerated (additive only).
  • Manual: dev pages under pages/select and pages/multiselect exercise the header/footer slots, including the no-filter header case, forced dropdownRole="dialog" with dropdownAriaDescribedby, and expandToViewport.
Review checklist

The following items are to be evaluated by the author(s) and the reviewer(s).

Correctness

  • Changes include appropriate documentation updates.
  • Changes are backward-compatible if not indicated, see CONTRIBUTING.md.
  • Changes do not include unsupported browser features, see CONTRIBUTING.md.
  • Changes were manually tested for accessibility, see accessibility guidelines.

Security

Testing

  • Changes are covered with new/existing unit tests?
  • Changes are covered with new/existing integration tests?

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Add renderDropdownHeader, renderDropdownFooter, and renderFilteringActions render props, plus dropdownRole and dropdownAriaDescribedby, to Select and Multiselect for customizing dropdown content. All new props are optional and backward compatible.
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.63%. Comparing base (ac81163) to head (c63d537).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/select/utils/use-select.ts 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4856      +/-   ##
==========================================
- Coverage   97.63%   97.63%   -0.01%     
==========================================
  Files         957      958       +1     
  Lines       31097    31134      +37     
  Branches    11435    11459      +24     
==========================================
+ Hits        30361    30397      +36     
- Misses        689      690       +1     
  Partials       47       47              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Remove the dedicated dropdown-customization.scss. filter-row/filter-input/filtering-actions now live in the shared select/parts/styles.scss and are rendered by the Filter component (via a new filteringActions prop); the header/footer styles are declared directly in each component stylesheet (footer needs no z-index). No public API or behavior change.

Copilot AI 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.

Pull request overview

Adds new dropdown customization slots to Select/Multiselect (header, footer, and inline filtering actions) along with accessibility-oriented props to control dropdown semantics (dropdownRole) and descriptions (dropdownAriaDescribedby). The implementation factors shared dropdown header/footer composition into a helper and updates styles, test utils, unit/integration tests, snapshots, and demo pages.

Changes:

  • Add renderDropdownHeader/renderDropdownFooter/renderFilteringActions render-props and dropdownRole/dropdownAriaDescribedby to shared select props, wiring them into Select/Multiselect dropdown rendering.
  • Introduce shared composeDropdownContent helper and new styling hooks for custom header/footer and filtering-actions layout.
  • Add/extend test-utils finders, unit + browser integration tests, snapshots, and dev-pages showcasing the new slots.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/test-utils/dom/select/index.ts Adds DOM test-utils finders for dropdown header/footer and filtering actions.
src/test-utils/dom/multiselect/index.ts Adds DOM test-utils finders for dropdown header/footer and filtering actions.
src/select/utils/use-select.ts Adds dropdownRole handling and exposes a closeDropdown callback for customization slots.
src/select/utils/dropdown-customization.tsx New shared helper to compose dropdown header/footer nodes.
src/select/styles.scss Adds Select dropdown header/footer styling hooks.
src/select/parts/styles.scss Adds layout styles for filter row + inline filtering actions.
src/select/parts/filter.tsx Renders optional inline filtering actions next to the filter input.
src/select/internal.tsx Wires new customization props into Select dropdown header/footer and ARIA describedby joining.
src/select/interfaces.ts Extends public Select/BaseSelect props and types for dropdown customization + ARIA props.
src/select/tests/dropdown-customization.test.tsx New unit tests validating composition, props, and focus/close behavior.
src/select/integ/dropdown-customization.test.ts New browser tests for keyboard reachability of interactive header/footer.
src/multiselect/use-multiselect.tsx Passes through dropdownRole/dropdownAriaDescribedby and exposes closeDropdown.
src/multiselect/styles.scss Adds Multiselect dropdown header/footer styling hooks.
src/multiselect/internal.tsx Wires new customization props into Multiselect dropdown header/footer and ARIA describedby joining.
src/multiselect/interfaces.ts Re-exports dropdown customization types via Select types for Multiselect.
src/multiselect/tests/dropdown-customization.test.tsx New unit tests mirroring Select customization behavior for Multiselect.
src/tests/snapshot-tests/snapshots/test-utils-selectors.test.tsx.snap Snapshot updates for new selector classnames.
src/tests/snapshot-tests/snapshots/documenter.test.ts.snap Documenter snapshot updates for new public props/types and test-utils APIs.
pages/select/dropdown-customization.page.tsx New dev page demonstrating Select dropdown customization variants.
pages/multiselect/dropdown-customization.page.tsx New dev page demonstrating Multiselect dropdown customization variants.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/select/styles.scss
Comment thread src/select/utils/use-select.ts
closeDropdownAndRefocus focused the trigger (whose onFocus already closes the dropdown) and then called closeDropdown() again, firing the close side effects twice. Focus the trigger to close, and only call closeDropdown() explicitly when the trigger is already focused.

Copilot AI 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.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/select/styles.scss:15

  • PR description mentions extracting the header/footer styling into a shared SCSS mixin to avoid duplication, but the .dropdown-header / .dropdown-footer rules are still duplicated here and in src/multiselect/styles.scss (and there are no other SCSS occurrences). Either implement the shared mixin (and consume it from both files) or adjust the PR description to match the actual approach.
// Custom dropdown header/footer regions (renderDropdownHeader / renderDropdownFooter). Duplicated in the
// Multiselect stylesheet. The header can sit directly above the options list, so it is lifted with z-index
// to keep focusable content's focus ring above the list; the footer sits below the list with natural
// clearance and needs no lift.

…pdown customization

Remove the inline filtering-actions slot, which coupled consumer content to the built-in filter input's layout and was fragile to style. Custom controls near the filter can use renderDropdownHeader instead. Filter reverts to the bare input; renderDropdownHeader/renderDropdownFooter, dropdownRole and dropdownAriaDescribedby are unchanged.
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