Skip to content

Add explainer: slotted options in customizable <select>#1357

Open
ffiori wants to merge 5 commits into
MicrosoftEdge:mainfrom
ffiori:slotted-option
Open

Add explainer: slotted options in customizable <select>#1357
ffiori wants to merge 5 commits into
MicrosoftEdge:mainfrom
ffiori:slotted-option

Conversation

@ffiori

@ffiori ffiori commented Jul 6, 2026

Copy link
Copy Markdown
Member

Adds a new SlottedOption/ explainer proposing that a customizable <select> (appearance: base-select) recognize <option>, <optgroup>, and its trigger <button> when they are slotted in from a web component, so they behave as if they were direct children of the <select>.

Today a <select> only treats its direct DOM children as options. If you put a <select> in a component's shadow root and expose a <slot>, the slotted options are ignored and the dropdown is empty. The only options today are to sync options
with a MutationObserver or rebuild the control by hand, which is a lot of code and is usually less accessible than the native control.

Discussion in WHATWG HTML issue: whatwg/html#11535

@ffiori ffiori marked this pull request as ready for review July 6, 2026 23:11
@ffiori ffiori requested a review from Copilot July 6, 2026 23:16

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 a new explainer proposing that customizable <select> (appearance: base-select) should treat <option>, <optgroup>, and the trigger <button> as effective children when they are provided via slotting (including across nested components), enabling reusable select-based web components without JS cloning/workarounds.

Changes:

  • Introduces a new explainer documenting the current limitation with slotted <option> elements and why it matters for accessibility.
  • Specifies proposed behavior for slotted options/optgroups/trigger button, including nested slot scenarios and JS API expectations.
  • Documents alternative approaches (JS rebuild, MutationObserver cloning, customized built-ins, etc.) and A11y/i18n/privacy/security considerations.

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

Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md Outdated
@ffiori ffiori requested review from anaskim, dandclark and leotlee July 6, 2026 23:21

@anaskim anaskim left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall, this seems like a good start to me. I wonder if we need to mention the issues that we could encounter if we implement Joey's solution: "I would solve this by changing the select element to look for options in its flat tree." Just so that we can point folks to a section in this doc if asked.

Comment thread SlottedOption/explainer.md
component; this proposal doesn't let a shadow tree reach content it couldn't
already compose.

## Stakeholder Feedback / Opposition

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we also want to include browser's positions here? (Since I assume Chromium's should be positive)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't know if we should do that now, as far as I know we request positions after having an explainer so we can reference it in the proposals, right?

@kbabbitt kbabbitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great to see this! A few comments, mostly editorial.

Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md Outdated
Comment on lines +95 to +97
> **Note on scope:** This applies only to `<select>` in customizable mode
> (`appearance: base-select`). A `<select>` with the default native appearance is
> unchanged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is default appearance out of scope? Couldn't I put a default appearance <select> inside a shadow DOM?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to have custom and non-custom select behave the same way for this. The biggest concern would be web compat. We might need use counters to see if this is a feasible change to make.

Maybe this should be an open question rather than something that's definitively scoped out?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I was worried about web compat. Definitely open to more discussion about it and include it if necessary. @chrisdholt what are your thoughts?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think we actually lose the progressive enhancement story completely if this doesn't work generally for the non-customizable select. We can't fallback where it's not supported yet, we'd get an error in the console and it wouldn't render.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks all for the comments. I agree, I also think it'd be nice to have default select work as well, but I also think we should have some data to know this isn't gonna break stuff before pushing it (which seems like it'd be fine, but better safe than sorry).

What about this path forward?

  1. Start with base-select. It's the motivating use case and it's low risk (base-select adoption is only ~0.0003% of page loads today and this change would only affect the subset of those that use slotted options).
  2. Measure web compat for extending to the default select in parallel (no counter exists for this yet).
  3. Decide the default select scope in the WHATWG thread The select element doesn't support slotted option elements whatwg/html#11535 with that data plus the progressive enhancement argument. We can raise it there for discussion as soon as this explainer is merged.
Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md Outdated

@dandclark dandclark left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a good explainer!

Comment thread SlottedOption/explainer.md
Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md Outdated
Comment on lines +95 to +97
> **Note on scope:** This applies only to `<select>` in customizable mode
> (`appearance: base-select`). A `<select>` with the default native appearance is
> unchanged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to have custom and non-custom select behave the same way for this. The biggest concern would be web compat. We might need use counters to see if this is a feasible change to make.

Maybe this should be an open question rather than something that's definitively scoped out?

Comment thread SlottedOption/explainer.md
Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md Outdated
Comment thread SlottedOption/explainer.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

7 participants