Skip to content

css expression inheritance #2749

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

Open
Wes0617 opened this issue Jun 8, 2018 · 15 comments
Open

css expression inheritance #2749

Wes0617 opened this issue Jun 8, 2018 · 15 comments

Comments

@Wes0617
Copy link
Contributor

Wes0617 commented Jun 8, 2018

Hi w3c people! I have a proposal about calc(), var() and similar functions.

I'd like to be able to inherit the expression itself rather than the computed-once value they contain.

For example if I write calc(1em + 20px) I expect to get the current font-size added of 20px, rather than the font-size computed at the time of the very first evaluation of the calc() expression.
A simple example could be the following:

div{
    --foo: 10px;
    --border: calc(var(--foo) - 5px); /* --border must always be --foo - 5px */
}


div > p{
    --foo: 100px;
    /* --border: inherit; /* no effect */
    /* --border: var(--border); /* no effect */
    border: var(--border) red solid; /* expecting 95px red solid - but got 5px red solid */
}

http://jsfiddle.net/pd3zeg9v/

What do you think? Or maybe this is already possible and I don't know how?
Thank you in advance.

@Wes0617
Copy link
Contributor Author

Wes0617 commented Jun 8, 2018

Also related https://bugs.chromium.org/p/chromium/issues/detail?id=850932
(thought it was just a bug, but can see why doesn't work)

@Loirooriol
Copy link
Contributor

I think it's a bit confusing that some things like var() and inherit are resolved in the element which has the declaration, but other things like em units are resolved on the element that uses the variable.

Maybe add a token or raw functional notation that accepts a sequence of tokens and represents them literally, without allowing them to be substituted, and without adding variables into the dependency graph. Like --prop: token(inherit) or --prop: token(var(--foo)). Then var(--prop) would remove the token() and resolve what's inside it.

Would this make sense, syntactically?

@jonjohnjohnson
Copy link

letting var() refer to the inherited value of the variable, rather than the current value, is planned for L2.

#1594 (comment)

@tabatkins
Copy link
Member

This is not currently possible with unregistered custom properties (the default).

If you register the property with syntax: "<length>" or similar, then --foo: 1em will resolve based on the font-size of the declaring element, not the element that finally uses the variable.

One of the plans for Variables 2 is to add a !-syntax for asking the browser to immediately evaluate a custom property as a given type, based on information from the declaring element, like --foo: 1em !type <length>;, which would let you do this without having to register it.

@jonjohnjohnson That's a different issue entirely, related to letting you "build up" a variable value as you nest things.

@Wes0617
Copy link
Contributor Author

Wes0617 commented Jun 13, 2018

I'm not sure I follow, but I really would like to do:

--foo: calc(parent-var(--foo) + 20px);

would work like oop programming languages that support inheritance:

this.foo ~ var(--foo)           ie the last available definition
super.foo ~ parent-var(--foo)   ie the inherited definition

@tabatkins
Copy link
Member

Yes, that's the "different" thing I was talking about. Plan is to address that in Variables 2, with a syntax like var(--foo parent) or something.

@jonjohnjohnson
Copy link

@tabatkins though I understand this "building up" a variable as you nest is a separate feature, I don't s'pose iteration like...

.el       { --index: 1; }
.el ~ .el { --index: calc(var(--index currentValue) + 1); }

...would ever be viable? In line with #1026?

@Wes0617
Copy link
Contributor Author

Wes0617 commented Jun 14, 2018

this is amazing! thank you tabatkins!

@Loirooriol
Copy link
Contributor

It seems the discussion got derailed to reading the value of a variable from the parent element (#1962) or to force the specified value to be resolved immediately. That's useful for sure, but it doesn't seem to provide a way to avoid resolving var() and let it be inherited as specified. Unless I'm missing something this is what the first post asked, and I think it can also have its uses.

@jonjohnjohnson I would prefer .el { --index: sibling-index(.el); }

@jonjohnjohnson
Copy link

@Loirooriol I know this is off op topic, but my example --index custom property wasn't solely asking about a literal index, but about building up of any custom property in any degree based upon cascade/selector/sequence.

@tabatkins
Copy link
Member

tabatkins commented Dec 13, 2019

Ah, I did indeed misread the issue.

The mechanics of "allow a var() to resolve to a CSS-wide keyword" should be the same as the current "var() becomes inherit/initial if it's the guaranteed-invalid value". In other words, seems like it would be fine implementation-wise, it would just need a special syntax to trigger it.

Late-resolving a var() (fetching the custom property value at time of var() resolution) has no mechanical problems either, but it does has some design issues. tokens() would be a "use once" thing - if you build up a variable from other variables, they'll be resolved at the middle stage, rather than at the ends. I guess you could wrap tokens() around itself multiple times, so each substitution stripped one layer off, but that requires a fragile prediction of how much you'll do, and might not even be possible if the build-up is based on depth.

Maybe it's ok that it'll only survive a single substitution? Or maybe it survives all substitutions, and you have to explicitly un-raw it with another function when you want the substitution to happen.

@benface
Copy link

benface commented Oct 14, 2024

Could this potentially be addressed with an extension of the solution proposed in #2864? (cc @LeaVerou)

Here's what I'm thinking, using the same example as OP:

div {
    --foo: 10px;
    --border: calc(var(--foo) - 5px); /* --border must always be `--foo - 5px` */
}


div > p {
    --foo: 100px;
    border: inherit(--border) red solid; /* --border is inherited as `calc(var(--foo) - 5px)`, so it ends up being 95px */
}

Or, maybe I'm misunderstanding how inherit() is expected to work and it would actually inherit the parent's computed value of --border, so 5px. If that's the case, we could add a keyword or argument to inherit() to make it work the desired way, or even a different function (e.g. inherit-raw(--border)). This would also solve #9612.

@Loirooriol
Copy link
Contributor

@benface inherit(--border) should inherit the computed value, i.e. with variables substituted.

And I don't like your alternatives, they would force browsers to keep the raw expressions of the parent element just in case some children references it. Seems better to just say that you want some expression to inherit as-is when you set it on the parent.

@benface
Copy link

benface commented Oct 14, 2024

they would force browsers to keep the raw expressions of the parent element just in case some children references it

I hadn't considered this. Alright, nevermind. 🫠

@penx
Copy link

penx commented Nov 26, 2024

The design tokens spec, which is being used by tools such as Figma, require that references are preserved:

Tools SHOULD preserve references and therefore only resolve them whenever the actual value needs to be retrieved

https://tr.designtokens.org/format/#aliases-references

This means that, in Figma, if you change the value of a variable in a layer, all other variables that point to this also update.

As above, this isn't the case with CSS variables. In the below example, repointing primary-light does not update button-bg-color:

button {
  background-color: var(--button-bg-color);
}

:root {
  --primary-light: #0000FF;
  --primary-dark: #6666FF;
  --button-bg-color: var(--primary-light);
}

[data-mode="dark"] {
  --button-bg-color: var(--primary-dark);
}

[data-color="red"] {
  --primary-light: #FF0000;
  --primary-dark: #FF6666; 
}
<body data-mode="dark">
  <div data-color="red">
    <button>Button</button>
  </div>
</body>

Unless I am missing something in the inherit() function spec, it seems that in order to accurately implement design tokens references using CSS variables, the ability to preserve references (e.g. with expression inheritance) will be needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants