w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.5k stars 661 forks source link

[css-ui] Pseudo-element for select's UA button #10717

Closed josepharhar closed 1 week ago

josepharhar commented 3 months ago

I proposed a bunch of pseudo-elements in this issue, and I'd like to split each pseudo into a separate issue: https://github.com/w3c/csswg-drafts/issues/10462

The pseudo-element would target the <button> element in the UA shadowroot as described in the original issue as well as the draft HTML spec: https://github.com/whatwg/html/pull/10548

Here are some options for a name:

  1. ::select-button
  2. ::select-fallback-button
nt1m commented 3 months ago

The fact that you can do select { ... } to style the in-page control is pretty well established and backwards-compatible.

Maybe a stupid question, but why introduce a new ::select-button pseudo-element that would only work in newer user agents?

nt1m commented 3 months ago

https://open-ui.org/components/customizableselect/#customizing-basic-styles

The explainer has:

<select>
  <option>one</option>
  <option>two</option>
</select>
<style>
select, select::select-fallback-button {
  font-family: monospace;
  font-size: 12px;
}
</style>

I think that is a really unfortunate developer experience to have to specify both selectors. I really think this "fallback button" should only visible to the user agent. I don't think engineering wise it's an insurmountable challenge and it preserves backwards compatibility, which is nice.

Maybe consider making the fallback button full size, but "invisible" (deferring all of its styling to the parent select):

#fallback-button {
   /* reset some button styles to make button "invisible" (potentially `all: unset` could work here too) */
   background: unset;
   border: unset;
   color: unset; /* essentially the same as inherit / currentColor in this context */

   /* make it take the full size of the in-page box */
   width: 100%;
   height: 100%;
}

Then the appearance: base UA stylesheet would put its base styles (arrow icon, border, padding, etc.) on select:-internal-uses-fallback-button or select:not(:has(> button)).

nt1m commented 3 months ago

It's also worth noting that:

select, select::select-fallback-button {
  font-family: monospace;
  font-size: 12px;
}

doesn't actually work in older browsers, because the whole declaration will be dropped as a result of not being able to parse select, select::select-fallback-button. So you'd end up having to do:

@supports selector(::select-fallback-button) {
   // duplicate styling
}

@supports not selector(::select-fallback-button) {
   // duplicate styling
}

I think it would be really good to try to make styling on select just work directly in the fallback button case at least.

josepharhar commented 3 months ago

Thanks for the feedback!

I like the idea of setting a bunch of properties to unset to make them inherit from the shadow host <select>, that would make the ::select-fallback-button usage as shown in the explainer unneeded.

Maybe consider making the fallback button full size, but "invisible" (deferring all of its styling to the parent select):

The way I've been designing this so far is to try to make the <select>'s box "invisible" rather than the fallback or author buttons invisible, mostly by removing borders from the <select> in appearance:base mode. This has been nice because its consistent between the cases of author provided or fallback buttons.

If we make the fallback button "invisible", then when the author provides their own button should we make that one "invisible" too? Or should we make the author apply more styles to make the <select>'s box "invisible"? For example, we could end up with the situation where by default both the <select> has a border and the author provided <button> has a border. I don't think this would ever be desirable, and ideally we wouldn't put the burden on the author to pick one of the borders and remove the other.

I'm also worried about the focus ring being rendered around the invisible fallback button inside the visible select, which would for example make the focus ring get rendered inside of the border and look wrong. I suppose that's something I could try adding a bunch of special casing for in the rendering of focus rings but I'd prefer not to try that.

Is there a way we could make the non-inherited properties that would affect the box of the <select> to all be delegated to the fallback <button> instead, like borders?

nt1m commented 3 months ago

If we make the fallback button "invisible", then when the author provides their own button should we make that one "invisible" too? Or should we make the author apply more styles to make the has a border and the author provided

You could put the select box appearance: base styles apply to select:not(:has(> button)) (or some internal pseudo-class which would represent when the fallback is used), then you wouldn't have conflicting styles if the developer wants to use their own button. If the developer specifies their own button, we can safely assume they want full control over the button, rather than trying to aim for backwards compatibility (which would likely be impossible in this case).

I'm also worried about the focus ring being rendered around the invisible fallback button inside the visible select, which would for example make the focus ring get rendered inside of the border and look wrong. I suppose that's something I could try adding a bunch of special casing for in the rendering of focus rings but I'd prefer not to try that.

If the fallback button has the full size of the select in-page box, it's likely not super noticeable. Though I think you'd want to defer focus to the parent select in this case, since authors who've previously defined styles for select:focus-visible would likely want them to keep working.

nt1m commented 3 months ago

Is there a way we could make the non-inherited properties that would affect the box of the <select>to all be delegated to the fallback <button> instead, like borders?

select {
    display: contents !important;
}

#fallback-button {
   all: inherit;
   display: inline-block;
}

is what I can think of, but it wouldn't pass over the display value from the select.

josepharhar commented 2 months ago

I'm starting to think that it might be better to remove the fallback UA <button> element entirely and replace it with a text node or a div containing text, which is more like how appearance:auto works.

We would then have to do some other fixes though:

With this in mind, I like your display:contents idea more. Thanks for the suggestion! I'll try implementing it and see what happens.

nt1m commented 2 months ago

This is more of implementation question at this point, but I think you're better off with a div/span or a text node than my display: contents idea honestly. It does potentially pose some challenges, but it's more accurately represents what you're trying to achieve.

fantasai commented 2 months ago
select {
    display: contents !important;
}

#fallback-button {
   all: inherit;
   display: inline-block;
}

This seems a bit weird, why not just

fallback-button, select > button {
   display: contents;
}

?

josepharhar commented 2 months ago
select {
    display: contents !important;
}

#fallback-button {
   all: inherit;
   display: inline-block;
}

This seems a bit weird, why not just

fallback-button, select > button {
   display: contents;
}

?

I tried prototyping this and there were no focus rings, so I'd have to additionally try making changes to how focus rings get rendered. I imagine there would also be other issues since this puts display:contents on the element which is actually getting focused.

This is more of implementation question at this point, but I think you're better off with a div/span or a text node than my display: contents idea honestly. It does potentially pose some challenges, but it's more accurately represents what you're trying to achieve.

I'll try prototyping this next to see what it looks like

josepharhar commented 2 months ago

This is more of implementation question at this point, but I think you're better off with a div/span or a text node than my display: contents idea honestly. It does potentially pose some challenges, but it's more accurately represents what you're trying to achieve.

I'll try prototyping this next to see what it looks like

I started prototyping this and it looks appealing so far, but I need to finish it to make sure there aren't any gaps.

josepharhar commented 2 months ago

I'm still working on the prototype, but so far it seems promising. I filed an openui issue to get feedback from them on this later today: https://github.com/openui/open-ui/issues/1086

Proposed resolution: Don't create a pseudo-element for select's UA "fallback" button

css-meeting-bot commented 1 month ago

The CSS Working Group just discussed [css-ui] Pseudo-element for select's UA button, and agreed to the following:

The full IRC log of that discussion <chrishtr> jarhar: I finished implementing the removal of the buttons and replacing with div w/text in Chromium. Then fonts are inherited well. So I think it works well.
<chrishtr> jarhar: propose not to create a pseudo-element for the select button
<fantasai> +1
<ntim> +1
<chrishtr> RESOLVED: do not add a pseudo-element for the user-agent fallback select button