Skip to content

[combobox] New Combobox and Autocomplete components#2105

Merged
atomiks merged 276 commits intomui:masterfrom
atomiks:feat/Combobox
Sep 3, 2025
Merged

[combobox] New Combobox and Autocomplete components#2105
atomiks merged 276 commits intomui:masterfrom
atomiks:feat/Combobox

Conversation

@atomiks
Copy link
Contributor

@atomiks atomiks commented Jun 12, 2025

Closes #222

Combobox

Preview: https://deploy-preview-2105--base-ui.netlify.app/react/components/combobox

Perf: https://deploy-preview-2105--base-ui.netlify.app/experiments/combobox-perf

Autocomplete

Preview: https://deploy-preview-2105--base-ui.netlify.app/react/components/autocomplete

FilterableMenu

Preview: https://deploy-preview-2105--base-ui.netlify.app/react/components/filterable-menu (will ship later)


To investigate in the future, possibly as separate components or new parts:

  • Combobox + Tabs - different semantics to the regular Tabs with virtual focus

@atomiks atomiks added component: combobox type: new feature Expand the scope of the product to solve a new problem. labels Jun 12, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jun 12, 2025

Open in StackBlitz

pnpm add https://pkg.pr.new/mui/base-ui/@base-ui-components/react@2105
pnpm add https://pkg.pr.new/mui/base-ui/@base-ui-components/utils@2105

commit: 88e36a7

@mui-bot
Copy link

mui-bot commented Jun 12, 2025

Bundle size report

Total Size Change: 🔺+301KB(+20.91%) - Total Gzip Change: 🔺+103KB(+20.17%)
Files: 71 total (2 added, 0 removed, 19 changed)

Show details for 71 more bundles

@base-ui-components/reactparsed: 🔺+36.7KB(+11.79%) gzip: 🔺+10.6KB(+10.66%)
@base-ui-components/react/navigation-menuparsed: 🔺+1.76KB(+1.96%) gzip: 🔺+731B(+2.33%)
@base-ui-components/react/menuparsed: 🔺+1.68KB(+1.48%) gzip: 🔺+660B(+1.68%)
@base-ui-components/react/selectparsed: 🔺+1.67KB(+1.42%) gzip: 🔺+658B(+1.61%)
@base-ui-components/react/context-menuparsed: 🔺+1.67KB(+1.48%) gzip: 🔺+668B(+1.71%)
@base-ui-components/react/tabsparsed: 🔺+808B(+3.09%) gzip: 🔺+326B(+3.46%)
@base-ui-components/react/toolbarparsed: 🔺+806B(+4.12%) gzip: 🔺+344B(+4.92%)
@base-ui-components/react/popoverparsed: 🔺+776B(+0.91%) gzip: 🔺+319B(+1.06%)
@base-ui-components/react/radio-groupparsed: 🔺+753B(+3.55%) gzip: 🔺+324B(+4.03%)
@base-ui-components/react/menubarparsed: 🔺+751B(+3.47%) gzip: 🔺+325B(+4.08%)
@base-ui-components/react/toggle-groupparsed: 🔺+751B(+5.03%) gzip: 🔺+331B(+5.83%)
@base-ui-components/react/preview-cardparsed: 🔺+489B(+0.85%) gzip: 🔺+212B(+1.03%)
@base-ui-components/react/tooltipparsed: 🔺+482B(+0.76%) gzip: 🔺+202B(+0.90%)
@base-ui-components/react/alert-dialogparsed: 🔺+287B(+0.57%) gzip: 🔺+89B(+0.50%)
@base-ui-components/react/dialogparsed: 🔺+286B(+0.56%) gzip: 🔺+100B(+0.56%)
@base-ui-components/react/accordionparsed: 🔺+55B(+0.25%) gzip: 🔺+15B(+0.19%)
@base-ui-components/react/radioparsed: 🔺+55B(+0.38%) gzip: 🔺+5B(+0.09%)
@base-ui-components/react/sliderparsed: 🔺+55B(+0.21%) gzip: 🔺+27B(+0.27%)
@base-ui-components/react/toggleparsed: 🔺+55B(+0.64%) gzip: 🔺+14B(+0.39%)
@base-ui-components/react/comboboxparsed: 🔺+128KB(new) gzip: 🔺+43.8KB(new)
@base-ui-components/react/autocompleteparsed: 🔺+124KB(new) gzip: 🔺+42.9KB(new)
@base-ui-components/react/avatarparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/checkboxparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/checkbox-groupparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/collapsibleparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/direction-providerparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/fieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/fieldsetparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/formparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/inputparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/merge-propsparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/meterparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/number-fieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/progressparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/scroll-areaparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/separatorparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/switchparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/toastparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/typesparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/unstable-no-ssrparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/unstable-use-media-queryparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/react/use-renderparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/detectBrowserparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/errorparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/fastObjectShallowCompareparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/generateIdparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/getReactElementRefparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/inertValueparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/isElementDisabledparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/isMouseWithinBoundsparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/mergeObjectsparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/ownerparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/reactVersionparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/safeReactparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/storeparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useAnimationFrameparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useControlledparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useEnhancedClickHandlerparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useEventCallbackparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useForcedRerenderingparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useIdparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useIntervalparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useIsoLayoutEffectparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useLatestRefparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useMergedRefsparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useOnFirstRenderparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useOnMountparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useRefWithInitparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/useTimeoutparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/visuallyHiddenparsed: 0B(0.00%) gzip: 0B(0.00%)
@base-ui-components/utils/warnparsed: 0B(0.00%) gzip: 0B(0.00%)

Details of bundle changes

Generated by 🚫 dangerJS against 88e36a7

@netlify
Copy link

netlify bot commented Jun 12, 2025

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 88e36a7
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/68b851b9d2f27d0008545e3a
😎 Deploy Preview https://deploy-preview-2105--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@atomiks atomiks force-pushed the feat/Combobox branch 4 times, most recently from 41a4d0f to 04f2b69 Compare June 12, 2025 12:57
@michael-land
Copy link

it would be really nice this component will also support autoHighlight, similar to MUI Autocomplete.

@atomiks

This comment was marked as outdated.

@atomiks atomiks force-pushed the feat/Combobox branch 3 times, most recently from a851184 to 508c8de Compare June 17, 2025 02:23
@github-actions github-actions bot added PR: out-of-date The pull request has merge conflicts and can't be merged. and removed PR: out-of-date The pull request has merge conflicts and can't be merged. labels Jun 17, 2025
@atomiks atomiks force-pushed the feat/Combobox branch 6 times, most recently from 5de6915 to 27f8e3e Compare June 23, 2025 07:29
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jun 23, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jun 24, 2025
@atomiks atomiks force-pushed the feat/Combobox branch 2 times, most recently from 586ccbc to ca828da Compare June 24, 2025 06:36
Copy link
Member

@michaldudak michaldudak left a comment

Choose a reason for hiding this comment

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

I haven't found any serious issues and the API looks solid. But there are many potential use cases we might not considered, so let's release this and see how the community feels about it. In case there's something fundamentally wrong (which I doubt), we still have time to fix before v1.

@mui-bot
Copy link

mui-bot commented Sep 3, 2025

Bundle size report

Bundle Parsed size Gzip size
@base-ui-components/react 🔺+36.7KB(+11.79%) 🔺+10.6KB(+10.66%)

Details of bundle changes

Copy link
Member

@LukasTy LukasTy left a comment

Choose a reason for hiding this comment

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

The components look amazing! 😍
That's a massive undertaking, awesome work. 💯 🚀
Sorry for a late review. 🙈
I will check a11y behavior next.


I have a behavioral observation:
The Autocomplete closes the items after searching and removing search query.
Is that the expected behavior? Or should it keep open with unfiltered items, just like before opening it?

Screen.Recording.2025-09-02.at.13.51.58.mov

year: number;
}

const top100Movies: Movie[] = [
Copy link
Member

Choose a reason for hiding this comment

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

General nitpick: Is there a way to share this data between CSS Modules and Tailwind examples? 🤔

);

const input = screen.getByRole<HTMLInputElement>('combobox');
fireEvent.click(input);
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick

Suggested change
fireEvent.click(input);
await user.click(input);


expect(input).not.to.have.attribute('data-focused');

fireEvent.focus(input);
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: user.keyboard('{Tab}') would probably work as well. 🤔

Copy link
Member

@mnajdova mnajdova left a comment

Choose a reason for hiding this comment

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

🎉 Well done James!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: autocomplete Changes related to the autocomplete component. component: combobox Changes related to the combobox component. type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[combobox] Implement Combobox