Open LeaVerou opened 6 years 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.
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.
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.)
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.
Agreed that parent()
makes more sense here.
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.
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.
But in calc(parent(--prop1) + 1)
, parent(--prop1)
will still be the inherited value, so that part is inheriting.
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.
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)
Well, CSS uses the term "inherit" very consistently; that sort of lateral value transfer is not referred to as inheritance by anything in CSS.
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.
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.
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.
The parent also has that value, tho.
@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;
}
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?
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.
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:
padding
, or custom properties with inherits: false
.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).
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.
We have previously resolved that this only gets parent values, not ancestor values, so parent()
seems fitting.
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.
@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.
To pile on to the use cases here, I have two that this would uniquely solve:
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);
}
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:
var()
fallbacks. This makes the defaults cumbersome to specify. They have to be copied into ever var()
expression - you can't just adopt a common stylesheet that defines the defaults.var(--foo, --default-foo)
. This lets you import a set of defaults into every component, and lets the properties be overridden by users, but has a high overhead cost.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.
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.
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)) */
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:
parent()
parent-var()
inherit()
inherited()
ancestor()
closest()
(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.
The CSS Working Group just discussed [css-values-4] inherit() function: like var() for parent value, for any property
, and agreed to the following:
RESOLVED: WG considered parent() and decided to stay with inherit()
RESOLVED: define inhert() on custom properties only, as a step towards full thing
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.
I really want this for
width
andheight
as a way to finally escape the confusing aspects of using percentages which are locked to one or the other (for example withpadding
). Please consider addingwidth
andheight
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!
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.
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
?
@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 thatcqw
andcqh
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 wayinherits
does. I initially came to this bug trying to find if there was anything likepw
andph
(to matchvw
andvh
andcqw
andcqh
), and eventually was led to this bug saying "We don't need that since you can just doinherit(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 usinginherit()
, except when a CSS custom property is set toinherits: 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).
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:
~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!
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.
@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:
inherit()
function would give us the computed value of the height
/width
properties, regardless of layout.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.
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 likepadding: 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.
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.
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.
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 likepadding: 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.
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?
@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.
@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?
@brandonmcconnell The computed value of border-*-width is an absolute length, so assuming this is based on the serialization of the specified property:
inherit(border-width)
will be a <length>
and calc()
will work.calc()
will not work. Presumably it will become invalid at computed-value time, and behave as medium
(3px).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 |
@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!
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.
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.
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:
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)See also: https://github.com/w3c/csswg-drafts/issues/1962
Font metrics relative to parent
2690
2764
And any other numerical typographic metric that can be specified in CSS, e.g.
font-stretch
,font-style
(for variable fonts) etcMany of the
currentBackgroundColor
use caseshttps://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()
+ calcSwapping foreground and background colors
Decorations visually extending the background
https://twitter.com/HugoGiraudel/status/1350496044681990147
Override parent margins (bleed)
Inherit grandparent value
https://twitter.com/cjw0/status/1350499207648583683
@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