Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-ui] Changing UA styles based on the computed value of the appearance property #10028

Open
josepharhar opened this issue Mar 4, 2024 · 20 comments
Labels
css-ui-4 Current Work

Comments

@josepharhar
Copy link
Contributor

I'm working on improvements to the <select> element (whatwg thread here), and I came across an issue while implementing a prototype of the Apple-supported behavior of switching to a new rendering mode based on the value of the appearance property.

The select element has several UA style rules which we don't want in the new rendering mode, such as white-space:pre, which makes all of the options in the popup have a bunch of line breaks rendered in my examples.

I tried adding an internal pseudo-selector which matches when the element has the new appearance value, but I found that it took two style updates to get the final style, which @andruud described as a "non-starter".

@annevk suggested that I should ask here to see if there is a way that we can make this work.

Also, if there are any previous discussions about the appearance property that are related to this, I would appreciate links to them.

cc @nt1m

@annevk
Copy link
Member

annevk commented Mar 5, 2024

#5998 is the other thread.

In particular what we're looking for here is a way whereby a new value of the appearance property impacts the backing style sheet of the element. It's acceptable if this mechanism only works for HTML form controls.

@nt1m conceptualized this idea as a user agent at-rule, e.g.,

select {
  @appearance-base {
    border: thin solid ButtonBorder;
    ...
  }
}

(Conceptually this needs to be something like an at-rule as you might need to style various states as well.)

@josepharhar
Copy link
Contributor Author

I’ve tried implementing a few solutions to this and here are my thoughts:

solution 1: internal pseudo-class for child content

We could have an internal pseudo-class which matches the select when it has a child in the new content model, which would be a button or datalist. This way we can adjust the UA styles based on the new DOM to get the new UA styles. I currently have this implemented in the chromium prototype here: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/resources/html.css;l=918-929;drc=df34b11bbe7a46c53c782a6d4a24145a4af05e10

Issues:

  • In the case that you put appearance:bikeshed on a select which doesn’t have the new <button> or <datalist> content, you won’t get updated UA styles.

solution 2: adjust style after style calculation to remove old appearance:auto UA styles

After style recalc, chromium and webkit have a style adjuster which changes the computed style. We could look for appearance:bikeshed on a select element here and remove a few styles we don’t want, like border and background-color. I started a patch to implement this in chromium here: https://chromium-review.googlesource.com/c/chromium/src/+/5348242/2/third_party/blink/renderer/core/css/resolver/style_adjuster.cc

Issues:

  • If the author uses appearance:bikeshed and then wants to apply the exact same border, background-color, etc that we removed from the UA stylesheet, it will be removed by the style adjuster and the author won’t even know why.

solution 3: leave the UA styles in there

We could just leave the problematic UA styles in there and make the developer revert all of them when they apply appearance:bikeshed.

Issues:

  • This has poor ergonomics, would get negative feedback from developers, and may hurt adoption of this feature.

solution 4: put an attribute on select to opt-in

We could use an HTML attribute instead of appearance:bikeshed to opt in to the new behavior and content model (similar to to the proposed switch attribute?). This attribute could be used in the UA stylesheet to change the styles as desired.

solution 5: add an internal pseudo-class or @ rule which matches when style is computed to appearance:bikeshed

We could add an internal @ rule or internal pseudo-class to gate the UA styles based on appearance as desired.

Issues:

  • As I explained in the issue description, this would require two style recalcs to get a final style.

@emilio do you have any thoughts?

@annevk
Copy link
Member

annevk commented Mar 7, 2024

I'm not sure solution 5 would require two style passes. I think that depends on how it's implemented. If it's implemented in terms of solution 2 for instance, maybe not? Also, for solution 2 you can read out author styles and account for them. It's just a lot of work which is why some kind of novel mechanism is needed that abstract that so you don't have to implement "base style sheets" from scratch for each control.

@emilio
Copy link
Collaborator

emilio commented Mar 7, 2024

I agree with @andruud that requiring two styling passes is a no-go. It's also cyclic, even though if you restrict the pseudo-class / at-rule to the UA sheet you can kinda cheat and assume that there's no cycle...

What styles do you plan to apply to <select> on this case? <select> already has default styles that are somewhat reasonable (borders etc) for appearance: none.

We discussed this with @annevk and @mfreed7 in the context of <input type=checkbox switch> too. My preference would be to not do this. Have the default styles be sane and either:

  • Make appearance: none be the "stylable, uses a non-replaced box and thus has pseudo-elements and other goodies" behavior (we can do this for switch, but likely not for <select>).
  • Make appearance: base create a non-replaced box, and thus render child elements, have pseudo-elements etc. appearance: none would still create a replaced box that wouldn't create renderers / layout objects for the pseudo-elements and thus would remain behaving as it is.

This has no cycles and is trivialish to implement, but whether this is possible depends on the kind of styling differences that you want to have between base and none / auto.

@emilio emilio added the css-ui-4 Current Work label Mar 7, 2024
@emilio
Copy link
Collaborator

emilio commented Mar 7, 2024

cc @chrishtr and @zcorpan who were also in that conversation.

@emilio
Copy link
Collaborator

emilio commented Mar 7, 2024

For the white-space: pre issue that can probably be removed? Gecko at least doesn't have it unless I'm missing something.

@emilio
Copy link
Collaborator

emilio commented Mar 7, 2024

Ah, I guess gecko has white-space: nowrap !important. But we could make that part of the internal box styles for the none / auto case.

@nt1m
Copy link
Member

nt1m commented Mar 7, 2024

@emilio While your suggestion does remove magic from style system, it does introduce a burden to the web developer to specify input::decoration (or however the anonymous box will be targeted by the web dev), which I think is undesirable.

@emilio
Copy link
Collaborator

emilio commented Mar 7, 2024

@nt1m you mean select::decoration (or something)? And how so? You can use styles for the internal anonymous box when appearance isn't base, and make base give you a regular box.

@nt1m
Copy link
Member

nt1m commented Mar 7, 2024

Would the base styles for the input move to the anonymous box? Or would the anonymous box have a pseudo-element to target them (e.g. ::decoration) with the UA sheet styling that?

@emilio
Copy link
Collaborator

emilio commented Mar 7, 2024

What kinds of differences do we want between none and base? Most controls with none look reasonable (checkbox / radio being the exception).

My preference would be to keep the styles the same, and the difference would be which kind of box none creates vs. base (so that e.g. display: flex works with appearance: base and so on).

@josepharhar
Copy link
Contributor Author

I reviewed all of the UA styles for <select> as well as the ones desired for a stylable <select>. It turns out that we don't need to change style on the <select> element; we only need to style its child elements like the options, button or datalist. We can do that with new interoperable UA styles that always apply, even for existing usages of <select>, since existing <select> usage has no children that render via CSS.

Therefore "solution 3: leave the UA styles in there" will work fine and turns out to have no significant problem of poor ergonomics. And we can go with what Emilio suggested and all is well.

When the time comes to add stylability to other form controls, we can follow this pattern and just leave existing interoperable UA styles in place, and expect developers to add in their own customizations.

@annevk
Copy link
Member

annevk commented Mar 11, 2024

But that means we cannot for instance have consistent sizing and theming for appearance:base controls, can we? Because whenever any of that would clash with none it would not be okay? I thought the idea with base was that we'd have a "web native" consistent look & feel across form controls.

@josepharhar
Copy link
Contributor Author

But that means we cannot for instance have consistent sizing and theming for appearance:base controls, can we?

That’s correct. The user agent style sheet rules will have to be the same for all values of appearance (though we do have some flexibility to choose the rules for children of the select element without compat risk).

Because whenever any of that would clash with none it would not be okay?

Right.

I thought the idea with base was that we'd have a "web native" consistent look & feel across form controls.

That was the original plan, but I concluded that it’s not implementable due to the circularity / double-style recalc / needing bespoke style engine hacks problems mentioned earlier.

I think that’s acceptable, because developers likely want to add their own customizations anyway in this mode, and it’s simple because there are only four CSS properties to override (border, border-radius, display and background-color).

@chrishtr
Copy link
Contributor

Hi, I worked with @argyleink to explore how developers might style a checkbox, switch or range control on top of appearance:base . The prototype assumes that the UA stylesheet for input doesn’t change, but that rules for pseudo-element box tree children can be added as we define them (in particular, look at the @layer demo.base cascade layer). It also assumes a rendering model where pseudo-element contents like checkbox glyph and range are replaceable slot-style and are descendants in the layout tree.

Note: <select> should be fine also, based on another analysis @josepharhar and I did of the UA style sheet for that element that he referred to earlier. There are also various <selectlist> examples (from a previous design) that demonstrate the concept.

Again, these are not API proposals, but just data to show styling can work, to validate that the current design path regarding CSS is feasible for additional form controls.

In the prototypes you'll find markup like:

<base-checkbox tabindex=0>
      <base-mark class="base">
            <svg viewBox="0 0 512 512"><path d="M416 128L192 384l-96-96"/></svg>
      </base-mark>
</base-checkbox>

The intent of the custom elements is to help articulate an end result where in the box tree the marker is under the input:

Input
  Marker
    <developer content>

The range input needs more parts than just a custom checkmark; the shape of it is like this (modeled very much after what has already been shipped):

<base-range tabindex=0>
      <base-range-container>
          <base-track></base-track>
          <base-progress></base-progress>
          <base-thumb></base-thumb>
      </base-range-container>
</base-range>

When appearance: base is used on a range, the resulting box tree looks something like this:

Input
  Group
    Track
      <developer content>
    Progress
      <developer content>
    Thumb
      <developer content>

@annevk
Copy link
Member

annevk commented Mar 25, 2024

I don't really understand what I'm looking at. Are you suggesting that appearance:base would change the contents of the box/shadow tree? I thought @emilio was opposed to that?

How does it end up being different from appearance:none?

This still seems like a far cry from the appearance:base we envisioned, that would give web developers a cross-browser pre-styled control that would satisfy all relevant UX, a11y, and i18n criteria.

I would find it useful if we could find another hour to discuss this in person again. Happy to organize that if that's agreeable.

@emilio
Copy link
Collaborator

emilio commented Mar 25, 2024

@annevk not necessarily. The box tree might change without the shadow tree having to change.

E.g. the shadow tree could be the same, but appearance: auto might create a replaced box (which won't render the children). It's the same as <iframe> fallback content or so.

@annevk
Copy link
Member

annevk commented Mar 25, 2024

@emilio fair, but that doesn't really address how it's different from appearance:none? Is that idea that sometimes we expose pseudo-elements in appearance:none and sometimes not?

@emilio
Copy link
Collaborator

emilio commented Mar 25, 2024

appearance: none has some compat requirements for e.g., <select> that might prevent it from just creating boxes for its children regularly. I'd expect appearance: none for select to keep creating a replaced box, but maybe we can get away without that? Not sure.

@josepharhar
Copy link
Contributor Author

It hadn't occurred to me before, but we could do something like light-dark() to support this (thanks @lilles!). We could give the UA properties that we care about an internal CSS function which evaluates to one thing for appearance:auto/none, and a different one for appearance:base/base-select.

As I've implemented in chromium, it looks like this for background-color specifically:

select {
  background-color: -internal-appearance-auto-base-select(Field, transparent);
}

This required the appearance property to be computed before background-color, but I was able to enforce this in chromium by setting the priority of appearance to a higher value than background-color.

I don't know if the per-property priority is something that exists in csswg specs, and I'm not sure if the particular form of this function is what we would want to spec either, but from my point of view this solves the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-ui-4 Current Work
Projects
None yet
Development

No branches or pull requests

5 participants