w3c / csswg-drafts

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

[css-values-4] inherit() function: like var() for parent value, for any property #2864

Open LeaVerou opened 6 years ago

LeaVerou commented 6 years ago

We've declined numerous author requests to extend var() to arbitrary properties, due to the potential for cycles and how expensive cycle detection would be in the general case. However, providing a way to get the parent or inherited value of any property does not cause cycles and still helps with a ton of use cases.

Use cases

Custom property value that depends on parent value

Generic --depth (1, 2)

* {
    --depth: calc(inherit(--depth, 0) + 1);
}

See also: https://github.com/w3c/csswg-drafts/issues/1962

Font metrics relative to parent

strong {
    font-weight: clamp(600, 1.2 * inherit(font-weight), 999);
}

And any other numerical typographic metric that can be specified in CSS, e.g. font-stretch, font-style (for variable fonts) etc

Many of the currentBackgroundColor use cases

https://github.com/w3c/csswg-drafts/issues/5292

While this does not address all use cases (since it would only provide an ancestor background color), it does address quite a few, and offers a workaround for others.

Matching nested radii

https://github.com/w3c/csswg-drafts/issues/7707

inherit inherit() + calc
Screen Shot 2022-09-07 at 9 15 06 AM Screen Shot 2022-09-07 at 9 15 11 AM
.child {
    padding: 1em;
    border-radius: calc(inherit(border-radius) - 1em);
}

Swapping foreground and background colors

.button {
    color: inherit(background-color);
    background: inherit(color);
}

Decorations visually extending the background

https://twitter.com/HugoGiraudel/status/1350496044681990147

blockquote::before {
    content: "❝";
    color: inherit(background-color);
    font-size: 300%;
}

Override parent margins (bleed)

.card > header {
    margin-top: calc(-1 * inherit(padding-top));
    margin-left: calc(-1 * inherit(padding-left));
    margin-right: calc(-1 * inherit(padding-right));
}

Inherit grandparent value

https://twitter.com/cjw0/status/1350499207648583683

.foo {
    --font-size: inherit(font-size);
    font-size: 0; /* for animation */
}

.foo > * {
    font-size: var(--font-size);
}

@property does provide a workaround for this particular case (just register a <length> custom property and set it on .foo’s parent), but not in the general case where no suitable registration synstax exists.

More use cases

SYwaves commented 1 year ago

A version of inherit() that only works for custom property seems syntactically confusing, given its default inheritance behavior. The ability to self-modify --foo based on the value set in the parent is useful, admittedly.

kizu commented 1 year ago

Naming-wise, I'd vote for the inherited() — this way it would be more of a description of the value it gets rather than looking like something imperative.

SYwaves commented 1 year ago

I'd go with parent() personally, since you're referencing the value set on the parent (unless the scope changes to allow querying of any arbitrary containers.)

mirisuzanne commented 1 year ago

I also think parent() might make the most sense, since it's likely that the parent value will not actually inherit. The parent value is the value that 'could inherit' if the property were not defined on the child - but in many cases we are using this to define the property on the child, so that inheritance never happens.

LeaVerou commented 1 year ago

Agreed that parent() makes more sense here.

Loirooriol commented 1 year ago

The problem with parent() is that, when used on a pseudo-element, it should get the style from the originating element (there isn't an actual parent) or even, in the case of ::backdrop, the initial value. I don't follow @mirisuzanne logic above, prop1: inherit(prop2) would just set prop1 to the inherited value of prop2. It's explicit inheritance. So I like inherit() or inherited(), with preference for the former for consistency with the inherit keyword.

The problem that I see with the feature (custom properties seem fine) is that, if this is based on serializations, they aren't completely well-defined or interoperable. For example:

document.body.style.counterIncrement = "c";
getComputedStyle(document.body).counterIncrement; // "c" or "c 1" ??

So if I set body > * { list-style-type: inherit(counter-increment) }, it won't work if it serializes as c 1 like in Firefox and Chromium, but it would work with c which is what I would expect from the shortest serialization principle.

I'm not sure if TypedOM could be a better basis than serialization.

mirisuzanne commented 1 year ago

My point was that some use-cases involve eg --prop1: calc(parent(--prop1) + 1) – without a second property being involved. In that case, we want to access the parent value of --prop1, even though it will not inherit.

To your other point, I think that's why @LeaVerou suggested limiting this to custom properties for now.

Loirooriol commented 1 year ago

But in calc(parent(--prop1) + 1), parent(--prop1) will still be the inherited value, so that part is inheriting.

tabatkins commented 1 year ago

I think it's reasonable to use the parent() name even tho originating elements aren't necessarily parents - they usually are, and the cases they're not it's still clear enough.

And yes, limiting this to custom properties is the obvious first step. Allowing reference to arbitrary properties requires a substantially more robust definition of the data model, which we definitely don't want to have to deal with.

SYwaves commented 1 year ago

My consternation with inherit() and inherited() is that it is somewhat ambiguously typed in certain uses.

For example, border-color: inherit(background-color) appears more akin to a lateral inheritance (a la currentColor) that references the property on the element itself rather than its parent's.

(If I'm allowed to be a bit silly here, maybe the function should be called parent's() :P)

tabatkins commented 1 year ago

Well, CSS uses the term "inherit" very consistently; that sort of lateral value transfer is not referred to as inheritance by anything in CSS.

SYwaves commented 1 year ago

I was thinking more in terms of natural language and hadn't considered that. That makes it easier to accept as a sort of extension to the inherit keyword for me then.

inherited() still feels odd though, since not all properties are inherited by default.

kizu commented 1 year ago

For example, border-color: inherit(background-color) appears more akin to a lateral inheritance (a la currentColor) that references the property on the element itself rather than its parent's.

Thanks for this example, I now agree that the parent would be less ambiguous, even though my initial thoughts were similar to Tab's — inherit has its meaning that would be obvious if you know how it works, but I see value in making this more friendly to developers who did not yet fully understand what “inherit” really means.

brandonmcconnell commented 1 year ago

My vote would be for inherit() since it can inherit from more than the direct parent, for example:

.grandparent {
  --some-prop: 123;
}
.grandparent > .parent > .child {
  --some-prop: calc(inherit(--some-prop) * 10 + 4); /* 1234 */
}

parent() may confuse some people into thinking it needs to be inherited from a custom property explicitly defined on the selector's direct parent.

That, and inherit() is a common term in both back- and front-end engineering, and one we already use similarly in CSS (the inherit value for any property), and will likely need to be used eventually if not in this use-case, so I think it's the most fitting term.

tabatkins commented 1 year ago

The parent also has that value, tho.

brandonmcconnell commented 1 year ago

@tabatkins True, though the parent silently inherits that value from it's parent and so on. Either could work; I just wonder if inherit() could actually be clearer and more consistent.

How would this interact with a custom property defined using @property and inherits: false;? e.g.:

@property --property-name {
  syntax: "<color>";
  inherits: false;
  initial-value: #c0ffee;
}
WickyNilliams commented 1 year ago

I guess to me parent implies it can only get the parent's value, whereas inherit implies more (even though the regular inherit keyword gets from its parent)

Edit: is ancestral() too obscure of a term?

brandonmcconnell commented 1 year ago

We may want to save the parent() namespace for something more explicitly parent-related in the future, not that we need to keep avoiding names until we find a more fitting use case.

kizu commented 1 year ago

Had thought about it a bit more. Main issue with inherit vs parent if we'd want to use it for things that are not inherited (like using the padding to define a negative margin of the same size etc) is that with inherit we actually could not use non-inherited values.

<ul style="padding: 10px;">
  <li style="margin-left: calc(-1 * inherit(padding-left))">

In the above, on the li there is no really inherited value of the padding, as li does not inherit it from ul. But if we'd do

<ul style="padding: 10px;">
  <li style="margin-left: calc(-1 * inherit(padding-left)); padding: inherit;">

then it actually would be inherited, but this both looks weird, and also not something that someone would want to do, as almost always we would want to actually have an ability to define the padding on this element.

The fact that this would work with the custom properties would be more of a coincidence — because they're inherited by default, this would usually work (unless we'd disable the inheritance via @property).

So while we want to make margin-left: calc(-1 * inherit(padding-left)); work as is — getting the padding value from the parent even though it is not inherited, we would in some way break the existing meaning of CSS inheritance.

Thus, if one of the use-cases would be to use the actual parent's value without inheritance, the parent now seems like a better choice, and it would have a different, not yet existing definition of something like

parent(property) - take the value of this property as if it was inherited to this element, regardless of it is actually inherited

This way it:

  1. Could be used to get values of non-inherited values, like padding, or custom properties with inherits: false.
  2. Would still logically work for inherited properties.

Because of this “as if” for inheritance, and not actually using the inheritance, having the inherit in the name could be incorrect.

Adding just inherit for the first iteration with only CSS variables — I still think using parent for this is better, as in the future the inherit would be useless on its own (I can't really see any uses for it if we'd have both it and parent at the same time).

mirisuzanne commented 1 year ago

Yeah, I would make @brandonmcconnell's argument in reverse: we are explicitly talking here about the value on the parent. In some cases, that value may have been inherited from farther up the DOM, and in some cases it might inherit on the element being styled -- but that inheritance is beside the point. All we know for sure is that we want the value from the parent element.

That also matches the value we would get from a style container query. And it side-steps any further complexity around inherited properties working different from non-inherited.

LeaVerou commented 1 year ago

We have previously resolved that this only gets parent values, not ancestor values, so parent() seems fitting.

tabatkins commented 1 year ago

How would this interact with a custom property defined using @property and inherits: false;? e.g.:

All properties have inherited values, regardless of whether they inherit by default or not. You can specify inherit for any property, after all. ^_^

So there's no special interaction there. It grabs the inherited value from the parent, identically to using inherit. (This just lets you refer to other properties.)


While I do like parent(), I'd like to acknowledge that "this acts identically to inherit, just from a different property" is a pretty reasonable argument for inherit(). Probably makes the behavior easier to explain/understand, and we do this "make a keyword into a function to allow customization" trick in several other places in CSS.

Loirooriol commented 1 year ago

@kizu margin-left: calc(-1 * inherit(padding-left)) just makes margin-left inherit from the padding-left of the parent/originating element/whatever and mix that inherited value with something else. The value of padding-left on the current element doesn't matter, you don't need padding: inherit.

I think some people may be misunderstanding inherit(prop) as in using the value of prop on the same element, like var(--prop), but this would lead to circularities. And tracking dependency cycles like for custom properties would have problems: we already have dependencies between properties, and if authors can add more, compat could prevent us from adding more dependencies in the future.

justinfagnani commented 1 year ago

To pile on to the use cases here, I have two that this would uniquely solve:

Recursive calculations

I've come across recursive DOM structures like trees where you want some measurement to be defined in terms of the parent value. A tree item might need to be 100% of the tree width to paint leading and trailing decorations, but you want the label indentation to increase with each nesting. A style like this would be a great way to do it:

:host {
  --level: calc(inherit(--level, 0) + 1);
}
.label {
  padding-left: calc(var(--level) * 16px);
}

Defaults for custom properties

Right now it's very difficult for a component to define properties that are overridable by users but have default values. var() allows this of course, but the property definition itself doesn't. This means component authors have a few tough options:

What devs I work with often ask for is a way to define a custom property with a very low importance, sort of the opposite of !important:

:host {
  --secondary-color: coral !default;
}

Which would let --secondary-color still be set from the outside.

It seems like inherit() could fill this role nicely with a fallback:

:host {
  --secondary-color: inherit(--secondary-color, coral);
}

This style of default could be adopted into every component, before component user styles in the cascade, and the default will only take effect if the user doesn't otherwise define the property.

fantasai commented 1 year ago

I would like to point out that the CSSWG has already resolved to add inherit() to css-values-5. I think the Agenda+ is just for @LeaVerou’s suggestion that we start by limiting it to custom properties because their computed value serialization is already fully defined.

rthrejheytjyrtj545 commented 1 year ago

Is there any reason to consider adding a type to a function similar to attr() and instead of giving the declaration an initial or inherited value, if there is no fallback value, when IACVT, output that same value as the result?

E.g.

ui-smth-container:has(> ui-smth) {
   border-color: stripes(red, tan);
   > ui-smth:only-child {
      text-decoration-color:
         color-mix(in oklab,
            inherit(border-color color),
            #0000)}} /* oklab(from currentColor l a b / calc(alpha * .5)) */
LeaVerou commented 1 year ago

I would like to point out that the CSSWG has already resolved to add inherit() to css-values-5. I think the Agenda+ is just for @LeaVerou’s suggestion that we start by limiting it to custom properties because their computed value serialization is already fully defined.

Indeed.

There is also the option of limiting it to custom properties PLUS an allowlist of properties with fully defined computed value serialization, such as background-color, font-size, or font-weight (these are not random, they come from actual use cases mentioned here), with the intention of eventually expanding it to all serializable properties.

It may also be nice to settle on a name. The ones proposed are:

(feel free to edit the list if I missed some) I think we largely have consensus for parent(), so it would be nice to have a resolution.

css-meeting-bot commented 1 year ago

The CSS Working Group just discussed [css-values-4] inherit() function: like var() for parent value, for any property, and agreed to the following:

The full IRC log of that discussion <TabAtkins> scribe+
<fantasai> lea: as pointed out, we already drsolved to do it
<fantasai> lea: but might be a good strategy to split out into parts
<TabAtkins> lea: There was a lot of use-cases for cusotm property inheritance, it has defined serialization and a lot of uses
<fantasai> lea: and precedent in container queries of starting with cascading properties, even though plan is to expand later
<fantasai> Laptop: another possibility could be to have custom properties plus a regstricted set of regular properties in L1
<astearns> s/Laptop/lea/
<TabAtkins> lea: The advantag ei sit covers mor euse-cases, the disadvantage is it's harder for authors to udnerstand which is which
<TabAtkins> lea: but tha'ts a problem with any allowlist, and there's several in cSS
<TabAtkins> lea: So I lean toward the "allowlist of properties"
<TabAtkins> lea: It would also be nice to resolve on a name, the issue still doesn't reflect consensus
<TabAtkins> lea: Seems consensus leans toward parent
<bramus> q+
<TabAtkins> lea: If that's the case, would be good to record a resolution
<fantasai> bramus: wanted to ask about naming towards parent()
<fantasai> bramus: that might imply from some non-native speakers to select the direct parent?
<fantasai> dbaron: that's exactly what it means
<lea> s/So I lean toward the "allowlist of properties"/So I lean toward custom properties + restricted allowlist of regular properties/
<fantasai> bramus: won't walk up to ancestors?
<fantasai> TabAtkins: it will inherit, so get the value from the parent
<fremy_> q+
<astearns> ack bramus
<TabAtkins> (I still like inherit(), but I'm happy with parent() )
<astearns> ack fremy_
<fantasai> fremy_: I think parent() is fine
<lea> fyi list of proposed names here: https://github.com/w3c/csswg-drafts/issues/2864#issuecomment-1645708854
<fantasai> fremy_: only thing I wonder if instead we can do var() with parent as first keyword
<fantasai> fremy_: then easy to add new sources later
<fantasai> fremy_: it's a bit longer though
<TabAtkins> var(parent --foo)
<fantasai> fremy_: just want to propose that but don't have a strong opinion
<lea> q?
<lea> q+
<fantasai> fremy_: but parent() doesn't express that it's a variable
<Rossen_> ack lea
<fantasai> lea: relating it to var() makes it sound like it's only for custom properties
<astearns> +1 lea
<fantasai> lea: but the goal is to extend to all properties
<fantasai> lea: highly unlikely that var() wil extend to all properties
<TabAtkins> right, impossible
<fantasai> fremy_: fine by me, if no objection to parent()
<oriol> q+
<fantasai> astearns: other opinions?
<astearns> ack oriol
<fantasai> oriol: about naming issue, I do like inherit() as a function
<fantasai> oriol: we already have the 'inherit' keyword
<fantasai> oriol: so this is an obvious extension to it
<fantasai> oriol: why add a completely different keyword
<fantasai> oriol: inherit also looks at the parent
<fantasai> +1 to oriol's argument
<Rossen_> +1 to inherit
<lea> inherit was a poor name, but I can see the argument for consistency.
<fantasai> astearns: any other opinions about inherit() , parent() etc?
<fantasai> dbaron: I think I might have a slight preference for parent() because inherit() is about taking the value from the same property and copying from parent to child
<fantasai> dbaron: but this is more general, says take the value of that property to use in this parent
<lea> q?
<fantasai> astearns: so argument for consistency is less because slightly different interpretation?
<fantasai> dbaron: that said not a strong preference
<fantasai> lea: the way I see inherit is, by default the default argument is the property itself, therefore can omit parens
<fremy_> if there is no strong preference, maybe we can strawpoll and just take the most popular one?
<fantasai> lea: whereas if you use a different property, you need to add parens and put as argument
<fantasai> POLL: 1. inherit() 2. parent()
<bramus> 2
<oriol> 1
<fantasai> 1
<Rossen_> 1
<dbaron> 2
<fremy_> 1
<SebastianZ> 1
<astearns> 1
<ydaniv> 1
<bts> 1
<TabAtkins> 1 or 2
<lea> abstain
<changseok> 1
<florian> 0
<miriam> either
<dholbert> abstain
<fantasai> astearns: not seeing a reason to change
<fantasai> PROPOSED: WG considered parent() and decided to stay with inherit()
<fantasai> RESOLVED: WG considered parent() and decided to stay with inherit()
<fantasai> astearns: you were arguing for doing just custom properties, or just custom properties and small subset, or doing all at once
<fantasai> dbaron: to be clear, talking about source property, not the property you're using the function in
<fantasai> [correct]
<fantasai> lea: want to hear from implementers
<fantasai> astearns: option to do custom properties now and rest later makes some sense to me
<fantasai> astearns: I'm less convinced about small subset
<fantasai> +1 to astearns
<emilio> +1
<fantasai> astearns: why should we do that?
<fantasai> lea: primary issue with arbitrary properties is primarly shorthands, and a lot of use cases (if not most) that don't require them
<emilio> q+
<fantasai> lea: so I suspect we can cover a fairly large percentage of use cases by having a well-selected allow-list
<fantasai> lea: while not needing to solve serialization
<fantasai> lea: but I'm not an implementer, great to hear from someone who is
<astearns> ack emilio
<fantasai> emilio: not particularly about shorthands, but let's consider a single example
<fantasai> emilio: you have font-family, and browser disagree whether to serialization as ident or string
<fantasai> emilio: and how you do that changes where it's valid to use
<fantasai> emilio: I think some properties first is a lot more controversial
<fantasai> emilio: for variables, you just have the tokens the author wrote, so no disagreements between browsers
<fantasai> fantasai: that's a large QA project
<fantasai> fantasai: [explains]
<fantasai> dbaron: we could get a bunch of the work done to detect differences by writing a fuzzer
<fantasai> dbaron: then we could look at how much work it is
<fantasai> fremy_: we tried when we did TypedOM, and ended up not finishing because so big
<fantasai> TabAtkins: big, but also I had other things to work on
<dbaron> s/a fuzzer/a fuzzer, with WPT as input/
<fantasai> TabAtkins: wasn't like it was unsolveable
<fantasai> iank_: it's solveable, but non-trivial amount of work
<fantasai> astearns: just so I'm clear on the inherit question
<fantasai> astearns: if we don't allow non-custom properties at the outset, ppl can work around it by setting a value on a var, and using inherit on that var
<fantasai> TabAtkins: yes
<fantasai> astearns: proposed to resolve on custom properties only at the outset
<TabAtkins> (finding a good allowlist is nearly the same work as finding all the differences and working toward fixing them)
<fantasai> RESOLVED: define inhert() on custom properties only, as a step towards full thing
tolmasky commented 9 months ago

I really want this for width and height as a way to finally escape the confusing aspects of using percentages which are locked to one or the other (for example with padding). Please consider adding width and height to the allowed list.

LeaVerou commented 9 months ago

I really want this for width and height as a way to finally escape the confusing aspects of using percentages which are locked to one or the other (for example with padding). Please consider adding width and height to the allowed list.

For width and height, I think container query units will probably work much better for most use cases, and they're already supported eveyrwhere!

tolmasky commented 9 months ago

For width and height, I think container query units will probably work much better for most use cases, and they're already supported eveyrwhere!

Unless I'm misunderstanding, that first requires me to make a container query though, right? I can't just say padding: calc(10 * cqh) calc(10 * cqw), right? I have to first create an arbitrary container query so that cqw and cqh actually refer to the right thing? This seems way more cumbersome than being able to just refer to your parent's width and height, the way percentages implicitly (but confusingly do), and the way inherits does. I initially came to this bug trying to find if there was anything like pw and ph (to match vw and vh and cqw and cqh), and eventually was led to this bug saying "We don't need that since you can just do inherit(width), only to then scroll to the end and discover those were dropped. So back to square one.

brandonmcconnell commented 9 months ago

That's my understanding as well. Moreover, in some of my container query testing, I've noticed that setting up a container produces some wonky and unexpected results to the element that is declared as a container, causing its height to essentially zero out, if not explicitly set.

Being able to inherit those computed values would be a huge win in this case. Most of the times I've set up a container, I've done so specifically to use its width and height, so this seems like it would be easier to reach for in simpler circumstances like this.

Using container queries

/* Parent Element */
.parent {
  width: 300px;
  height: 200px;
  container-type: size;
}

/* Child Element */
.child {
  width: 50cqw;
  height: 50cqh;
}

Using inherit()

/* Parent Element */
.parent {
  width: 300px;
  height: 200px;
}

/* Child Element */
.child {
  width: calc(inherit(width) / 2);
  height: calc(inherit(height) / 2);
}

Also, I understand it would be easier on the first pass of this feature to only support CSS custom properties with the inherit() function, but in 99% of cases, can't CSS custom properties be naturally inherited without using inherit(), except when a CSS custom property is set to inherits: false using @property?

LeaVerou commented 9 months ago

@tolmasky

For width and height, I think container query units will probably work much better for most use cases, and they're already supported eveyrwhere!

Unless I'm misunderstanding, that first requires me to make a container query though, right? I can't just say padding: calc(10 * cqh) calc(10 * cqw), right? I have to first create an arbitrary container query so that cqw and cqh actually refer to the right thing? This seems way more cumbersome than being able to just refer to your parent's width and height, the way percentages implicitly (but confusingly do), and the way inherits does. I initially came to this bug trying to find if there was anything like pw and ph (to match vw and vh and cqw and cqh), and eventually was led to this bug saying "We don't need that since you can just do inherit(width), only to then scroll to the end and discover those were dropped. So back to square one.

You just need to set appropriate container-* properties, not a container query. And soon (much sooner than inherit() may ship) you'll be able to refer to containers higher than the closest one. inherit() will not do what you want most of the time, because it would only give you a parent width/height, and only if it's explicitly set, which it usually is not. Whereas cq units will actually be computed based on the used dimensions.

@brandonmcconnell

That's my understanding as well. Moreover, in some of my container query testing, I've noticed that setting up a container produces some wonky and unexpected results to the element that is declared as a container, causing its height to essentially zero out, if not explicitly set.

This sounds like a potential browser bug?

Being able to inherit those computed values would be a huge win in this case. Most of the times I've set up a container, I've done so specifically to use its width and height, so this seems like it would be easier to reach for in simpler circumstances like this.

See above, you wouldn't be inheriting used values with inherit().

Also, I understand it would be easier on the first pass of this feature to only support CSS custom properties with the inherit() function, but in 99% of cases, can't CSS custom properties be naturally inherited without using inherit(), except when a CSS custom property is set to inherits: false using @property?

a) Most use cases for inherit() are around using the inherited value of property A in property B, which is not something you get with regular inheritance. b) Regular inheritance doesn't allow you to do math. --foo: calc(var(--foo) + 1) is IACVT, whether --foo inherits or not. --foo: inherit is valid, but you can’t do anything with it (though we should probably enable that).

tolmasky commented 9 months ago

You just need to set appropriate container-* properties, not a container query. And soon (much sooner than inherit() may ship) you'll be able to refer to containers higher than the closest one. inherit() will not do what you want most of the time, because it would only give you a parent width/height, and only if it's explicitly set, which it usually is not. Whereas cq units will actually be computed based on the used dimensions.

I guess there are actually two issues here:

  1. ~In the specific padding case that I was originally trying to solve, it occurs to me that both this inheritance stuff and the container query approach don't really do what I need, since padding percentages are based off of the current element's size, and not the parent. So what I really want is just new units like %w and %h, to differentiate between the width and height when referring to percentages. That way I could just do something like padding: 10%h 10%w. Should I open a new issue to discuss that specifically?~ As helpfully pointed out by @Loirooriol, I got this mixed up. Thank you!

  2. When wanting to refer to a parent's size, it still feels like it would be useful to have something like %h and %w, since it seems that specifying the container- properties may sign you up for more behavior than you bargained for, including, potentially, performance implications? I ask because percentages seem to get away with providing access to parent size information (with limitations), without having to formally denote something as a container (in other words, they'll work with "any" parent, when it makes sense), and theoretically without any additional performance implications. In other words, height: 10%h would work in all circumstances that height: 10% works, including when it is effectively ignored.

    EDIT: Perhaps it is useful to clarify that the more interesting case is that height: 10%w would work in all circumstances that width: 10% works, including when it is effectively ignored.

mirisuzanne commented 9 months ago

@brandonmcconnell @LeaVerou

That's my understanding as well. Moreover, in some of my container query testing, I've noticed that setting up a container produces some wonky and unexpected results to the element that is declared as a container, causing its height to essentially zero out, if not explicitly set.

This sounds like a potential browser bug?

That's not a browser bug, it's the result of size containment. In order to create a container for height queries, the height has to be 'contained' and therefor extrinsic. Querying an intrinsic height would result self-referential behavior: the height is based on the contents, which are styled based on the height, etc. So applying a container-type: size will result in zero intrinsic height.

Which gets at a distinction between what these two features would do, which Lea also covered above:

If we want the former, we have to live with the size containment limitation. There's no way to get the actual used dimensions without that limitation. If we want the latter, we can avoid containment… But the examples so far rely on explicit heights anyway.

That's roughly what Lea said. But then:

See above, you wouldn't be inheriting computed values with inherit().

Looking back through the thread, I can't find this stated anywhere. In fact, most of the discussion is about the fact that you would inherit the computed value – which makes sense, because that's how inheritance works.

kizu commented 9 months ago

So what I really want is just new units like %w and %h, to differentiate between the width and height when referring to percentages. That way I could just do something like padding: 10%h 10%w. Should I open a new issue to discuss that specifically?

I really think those are just container query length units use cases. Exposing them without the containment could lead to circularity.

Some other cases could also be handled by aspect-ratio, like height: 10%w might be expressed as aspect-ratio: 10; (if we know that the width will be 100%).

Can you provide your use cases to look at, so it would be easier to gauge if they can be done with what we have, or if inherit() could help with them? What are you trying to achieve? Not as a reduced solution without a problem, but as a problem itself.

Loirooriol commented 9 months ago

since padding percentages are based off of the current element's size, and not the parent

No. Percentages refer to the inline size of the containing block (which is typically established by the parent, but not necessarily). See https://drafts.csswg.org/css-box-4/#propdef-padding

Should I open a new issue to discuss that specifically?

I think you first need to figure out what you are actually trying to do, and then, if if your usecase would benefit from new functionality, open a new issue.

tolmasky commented 9 months ago

I really think those are just container query length units use cases. Exposing them without the containment could lead to > > since padding percentages are based off of the current element's size, and not the parent

No. Percentages refer to the inline size of the containing block (which is typically established by the parent, but not necessarily). See https://drafts.csswg.org/css-box-4/#propdef-padding

You're absolutely right on this -- don't know why I mixed these up!

I think you first need to figure out what you are actually trying to do, and then, if if your use case would benefit from new functionality, open a new issue.

Not sure if you are asking for a specific reduction here or what, but in my experience this is generally difficult to do for cases like this (especially since there's always some workaround). I think just referring to the large number of questions around how to get padding to be a percentage of height should demonstrate a need for wanting to have the same existing functionality as we currently get with percentage, but for height as well as width. In a world where padding percentages were based on height instead of width, I'd now be asking for some way to base it off of width. I don't believe there's some "proof" that width-based padding is important enough to bake in, but height-based padding isn't.

tolmasky commented 9 months ago

So what I really want is just new units like %w and %h, to differentiate between the width and height when referring to percentages. That way I could just do something like padding: 10%h 10%w. Should I open a new issue to discuss that specifically?

I really think those are just container query length units use cases. Exposing them without the containment could lead to circularity.

I certainly haven't thought through all the ramifications, but I would be surprised if this addition is any more circular than the existing percentages we already have access to. One way to look at it is that all geometry-based % units today are already either implicitly %w or %h. In the padding case, % is identical to my proposed %w. In the width case as well. In the height case, % is identical to my proposed %h. It doesn't seem obvious to me that allowing you to be explicit about it would change that, especially if it followed the same rules as % today to avoid any potential circularity.

kizu commented 9 months ago

I don't believe there's some "proof" that width-based padding is important enough to bake in, but height-based padding isn't.

The key to thinking about this is to think about them not as “width” and “height”, but as “inline” and “block”.

These are very different: predominantly, the inline dimension stays the same, while the block one grows. Adding a padding in the inline direction, in a normal flow, reduces the space available for content and does not change the inline dimension of the parent. Adding a padding in the block direction is not constrained, and makes the element and its container grow in the block direction (thus, can lead to circularity).

And, because block and inline dimensions are so different, the use-cases for the percentage in them are also different! Percentage in the inline dimension usually reflects the available space: we can make the padding bigger when we have a wider screen (or wider container). What will the padding in a block dimension reflect?

Loirooriol commented 9 months ago

@tolmasky Then you should file a new issue, since you want to resolve percentages with the actual size of the containing block, while inherit(height) will be a computed value, and may not be the containing block.

I would be surprised if this addition is any more circular

Yeah it should be fine, in the block axis it's easier for percentages to be cyclic but that can also happen in the inline axis, and the behavior is defined in https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution. In fact, for a while the flexbox spec allowed resolving padding percentages like you want (see #2085), so it's not intrinsically problematic.

brandonmcconnell commented 9 months ago

@LeaVerou @mirisuzanne Thanks for clarifying the behavior of inheritance here. It sounds like we might still need to discuss whether those values are computed or not.

How would the below example work?

.bordered {
  border: 1px solid #000;
  > .bordered {
    border: inherit;
    border-width: calc(inherit(border-width) + 1px);
  }
}

At first glance, I would expect each .bordered element to have the same border as its parent .bordered element, but increase in width by 1px with each nesting level. Is this accurate?

Loirooriol commented 9 months ago

@brandonmcconnell The computed value of border-*-width is an absolute length, so assuming this is based on the serialization of the specified property:

If you want an example that varies depending on whether this uses the computed vs the used value:

#foo { height: 100px }
#foo > #bar { height: 50% }
#foo > #bar > #baz { height: inherit(height) }

Then I would say that this inherits the computed value:

Element Computed Used
foo 100px 100px
bar 50% 50px
baz 50% 25px
LeaVerou commented 9 months ago

@mirisuzanne

See above, you wouldn't be inheriting computed values with inherit().

Looking back through the thread, I can't find this stated anywhere. In fact, most of the discussion is about the fact that you would inherit the computed value – which makes sense, because that's how inheritance works.

Typo, I meant used values. Have edited now!

kizu commented 5 months ago

This week I posted two articles: “Alternating Style Queries” and ”Self-Modifying Variables: the inherit() Workaround” that show how we could use the style queries in the future (working in Chrome and in Safari TP for now) to work around some use cases (those that involve custom properties) for inherit(). They could be of use for anyone who would like to prototype how inherit() could look in their code.

EisenbergEffect commented 5 months ago

I've been using the same technique that @kizu describes to work around the lack of inherit(), particularly in design system scenarios where color layer adaptations are needed for accessibility. I'd love to see inherit() make it into the spec as it's a much cleaner solution than the container query hacks for this type of thing.

una commented 3 months ago

Another usecase would be essentially enabling #5292 (currentBackgroundColor) and other similar values. Would be highly useful!

i.e. Leveraging background-color to apply border styles: