Skip to content

feat: add NativeSelect component#12

Closed
mattrothenberg wants to merge 1 commit intocloudflare:mainfrom
mattrothenberg:feat/native-select-gh
Closed

feat: add NativeSelect component#12
mattrothenberg wants to merge 1 commit intocloudflare:mainfrom
mattrothenberg:feat/native-select-gh

Conversation

@mattrothenberg
Copy link
Contributor

@mattrothenberg mattrothenberg commented Feb 4, 2026

Summary

Adds a NativeSelect component - a native HTML <select> element styled to match the Kumo design system.

Screenshot 2026-02-04 at 10 56 40 AM

Motivation

While Kumo's Select component provides a fully customizable dropdown experience, there are scenarios where native browser behavior is preferable:

  • Mobile UX: Native OS pickers provide familiar, optimized interfaces on iOS/Android
  • Browser autofill: Critical for address/payment forms where users expect saved data to populate
  • Simpler use cases: When you don't need custom option rendering, icons, or multi-select
  • Accessibility: Native selects have built-in screen reader support

Features

  • Visual parity: Identical appearance to Select trigger when closed
  • Size variants: xs, sm, base (default), lg - matching other form controls
  • Field wrapper integration: Pass label, description, error, labelTooltip props for automatic Field wrapping with proper accessibility associations
  • Optional field indicator: Set required={false} to show "(optional)" badge
  • Full autofill support: Works with autoComplete attribute for address, payment, and other standard form fields

Usage

// Basic usage
<NativeSelect value={country} onChange={(e) => setCountry(e.target.value)}>
  <option value="">Select a country</option>
  <option value="us">United States</option>
  <option value="ca">Canada</option>
</NativeSelect>

// With Field wrapper
<NativeSelect
  label="Country"
  description="Where you're located"
  error={errors.country}
  value={country}
  onChange={handleChange}
>
  <option value="us">United States</option>
</NativeSelect>

// With browser autofill
<NativeSelect
  label="Country"
  name="country"
  autoComplete="country"
>
  <option value="US">United States</option>
</NativeSelect>

When to use NativeSelect vs Select

Use NativeSelect when... Use Select when...
Mobile UX is a priority Custom option rendering needed (icons, avatars)
Browser autofill is required Complex object values
Simple string options Multi-select with chips
Lighter-weight markup preferred Loading states needed
Native form submission Consistent desktop/mobile appearance required

Checklist

  • Component implementation with TypeScript types
  • Size variants (xs, sm, base, lg)
  • Field wrapper integration (label, description, error)
  • Documentation page with examples
  • Demo components
  • Component registry updated
  • Package exports configured
  • Changeset added
  • All tests passing

A native HTML select element styled to match the Kumo design system.
Use when you need native OS behavior (especially on mobile) or
browser autofill support via the autoComplete attribute.

Features:
- Visually matches the Select component trigger
- Size variants: xs, sm, base, lg
- Field wrapper support with label, description, error
- Full support for browser autofill (autoComplete attribute)
- Documentation and demos
@oliviertassinari
Copy link

oliviertassinari commented Feb 6, 2026

Browser autofill: Critical for address/payment forms where users expect saved data to populate

In theory, this should work with Base UI Select (and Material UI Select) using a hidden input. If it doesn't work anymore, we should probably restore it.

@mattrothenberg
Copy link
Contributor Author

@oliviertassinari oh cool! I'd love to see a reference implementation to make sure I'm not just holding it wrong

@mattrothenberg
Copy link
Contributor Author

@oliviertassinari merci! I will take a look at our Select implementation and see where we may have drifted.

@mattrothenberg
Copy link
Contributor Author

mattrothenberg commented Feb 6, 2026

@oliviertassinari I set up a fresh test with @base-ui/react@1.1.0 to verify:

<Select.Root name="country" value={country} onValueChange={setCountry}>
  <Select.Trigger>
    <Select.Value placeholder="Select a country" />
  </Select.Trigger>
  <Select.Portal>
    <Select.Positioner>
      <Select.Popup>
        <Select.Item value="US">United States</Select.Item>
        <Select.Item value="CA">Canada</Select.Item>
        ...
      </Select.Popup>
    </Select.Positioner>
  </Select.Portal>
</Select.Root>

The hidden input renders as:

<input name="country" value="" tabindex="-1" aria-hidden="true" style="...">

I placed this alongside a native <select autocomplete="country"> in the same form. When I trigger browser autofill (Chrome), the native select gets populated but the Base UI Select does not.

I believe the issue is that the hidden input is missing the autocomplete attribute. The name attribute identifies the field for form submission, but browsers need autocomplete="country" to know what data to fill.

The test at SelectRoot.test.tsx#L561 (https://github.com/mui/base-ui/blob/65c2d3ae03442fc69b042f3f92bbe4fd873e083b/packages/react/src/select/root/SelectRoot.test.tsx#L561) confirms the Select handles autofill events (via fireEvent.change), but without the autocomplete attribute, browsers don't trigger those events in the first place.

Would you be open to adding an autoComplete prop that gets passed to the hidden input? Happy to open a PR if that would help! Also, very like I'm just holding it wrong.

@mattrothenberg
Copy link
Contributor Author

Implemented in mui/base-ui#4005 as a conversation starter, assuming I haven't misdiagnosed the issue here.

@mattrothenberg
Copy link
Contributor Author

Superseded by mui/base-ui#4005 and a forthcoming PR to bump Base UI version and consume this change.

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