Skip to content

Conversation

@paoloricciuti
Copy link
Member

Closes #15617

This is not the whole PR but is technically mergeable as is: what this does is that if there's an option with rich content inside instead of producing the code we produce today it produces code that looks like this

$.rich_option(
    () => {
        var span = $.child(option);
        var text = $.child(span, true);

        $.reset(span);
        $.next();
        $.reset(option);
        $.template_effect(() => $.set_text(text, $.get(label_a)));
    },
    () => {
        $.template_effect(() => option.textContent = `${$.get(label_a) ?? ''} A`);
    }
);

rich_option is a function that checks if the new parser that persists elements inside option is in use (unfortunately we need to use this trick of creating an element, setting it and checking the first element because Safari preview ships with the new parser but CSS.supports still doesn't support stylable selects, so the new parser could be in place without the CSS support being there ref) and invokes one of the two branches. The two branches are either "only text" in case of the old parser or the whole tree in case of the new parser.

What's missing for full support:

  • allow button inside select
  • allow selectedcontent inside button
  • probably error if button has state inside because the old parser will just remove it completely
  • allow the same in optgroup (I need to check what's the actual behaviour)

I tested it in browsers that supports the new parser and browsers that don't, and it seems to work.

Warning

Claude Opus 4.5 actually wrote most of the code...I reviewed it and corrected it where I thought it was wrong but please use extra caution in reviewing this PR 🧡

As I've said, this is technically mergeable, and it will allow initial stylization of the select (if you don't use button with selectedcontent)...I plan to fully implement everything but wanted an initial feedback on this.

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

@changeset-bot
Copy link

changeset-bot bot commented Jan 7, 2026

🦋 Changeset detected

Latest commit: 89ed7df

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

Playground

pnpm add https://pkg.pr.new/svelte@17429

@Rich-Harris
Copy link
Member

Any thoughts on how we could handle a case like this?

@paoloricciuti
Copy link
Member Author

Uhhhh that's true...I wonder if we should disallow components all together it's a bit limiting but we do need the text value at compile time which is simply not possible if component are involved no? Maybe we could have a separate state in the walk and we only push expressions and text to it?

@Rich-Harris
Copy link
Member

Do we need it at compile time? Could it be as simple as just bailing out of hydration inside <option> elements if customizable selects aren't supported?

@paoloricciuti
Copy link
Member Author

Ok this is hilarious because that's actually the solution Claude came up with initially but I thought it was bad...I guess that could work tho...we disable hydration, we run the function and then re-enable hydration. It wouldn't even need to have two function because it would work regardless no?

I just wonder if this could mess up hydration in some way but it shouldn't 🤔

@Rich-Harris
Copy link
Member

Yeah, it should work just fine

@paoloricciuti
Copy link
Member Author

Yeah, it should work just fine

Mmm it's not that simple...it could work if the child of an option is only a component but if we have something like this

<option><span>{something}</span> <Component /></option>

the code tries to get the first child of option and the text inside it anyway and since the option come needs to come from the SSRd html it doesn't have that inside and it fails.

@7nik
Copy link
Contributor

7nik commented Jan 7, 2026

Can we do the opposite: hydrate and fix options (back) into rich options? It seems you can insert whatever content, but in browsers without customizable select support, only <option>s and text inside them are displayed.

@paoloricciuti
Copy link
Member Author

Can we do the opposite: hydrate and fix options (back) into rich options? It seems you can insert whatever content, but in browsers without customizable select support, only s and text inside them are displayed.

WDYM the opposite? The point is that we should hydrate in both cases, in one case by actually picking the spans inside the option and setting the text, in the other by setting the textContent of option to whatever texts are rendered (but they could still be dyanamic)

@7nik
Copy link
Contributor

7nik commented Jan 7, 2026

I mean, fix it to only the rich variant (in the example, insert span, if it's missing, regardless of the customizable select support).
But it would be nice to fix only within the select instead of remounting the entire app.

@Rich-Harris
Copy link
Member

opened #17436

@7nik
Copy link
Contributor

7nik commented Jan 9, 2026

One more TODO is, in CSS selectors, treat selectedcontent as an alias of option so rules with it aren't marked as unused.

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.

A <select> can now include rich HTML content

4 participants