Skip to content

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

Open

Description

@LeaVerou

[Edit: We've now defined 'inherit()', but only for custom properties. The issue is left open to track the fuller feature.]


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: #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

#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

#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

Activity

  1. upsuper commented on Jul 3, 2018

    @upsuper
    Member

    We probably don't want it to be a function, or at least not a function accepting arbitrary property. Different properties may have different underlying storage / handling, so it could be hard if it uses arbitrary property in arbitrary property.

    It might be fine to restrict it to properties with only a single numeric value, but I believe in the past we occasionally extend single-value properties into multi-values, so a general mechanism like this may not be future-proofing.

  2. upsuper commented on Jul 3, 2018

    @upsuper
    Member

    Also it sounds like a dupe of #2764.

  3. jonjohnjohnson commented on Jul 3, 2018

    @jonjohnjohnson

    @FremyCompany mentioned the cascade proposal and I imagine this playing into that? #1594 (comment)

  4. LeaVerou commented on Jul 3, 2018

    @LeaVerou
    MemberAuthor

    @upsuper Not a dupe of #2764, which is about a special case of this.

    Could you elaborate a bit on the underlying storage/handling issue? How could it get in the way? Is it theoretical or are there existing examples of properties that would have issues?

    Also, by future-proofing, are you referring to implementation issues or author code?

  5. upsuper commented on Jul 3, 2018

    @upsuper
    Member

    Could you elaborate a bit on the underlying storage/handling issue? How could it get in the way? Is it theoretical or are there existing examples of properties that would have issues?

    I wrote that mostly concerning the implementation difficulties in mind, since we (in Stylo) use lots of different types to handle subtle differences between properties. But I realized that it is actually not a problem of types, but the differences themselves. For example, word-spacing and letter-spacing is almost the same except that the former accepts <percentage> in addition to normal | <length>. If any property can be referenced to any property, we would need a mechanism to extract only certain type of value, with either a fallback or just treating as invalid at computed-value time.

    Such mechanism has its own issue, that supported type at a value position may depend on the context, and can change as CSS evolves. For example, there is plan to allow division between two dimensions, which may enable <percentage> being used in a property accepting only <dimension> as far as you divide it with another percentage. Also there is some idea about allowing auto in calc for some properties. Those kind of changes may affect above-mentioned mechanism.

    Also, by future-proofing, are you referring to implementation issues or author code?

    For spec about how to handle it. Actually the above has something related to future-proofing already.

  6. LeaVerou commented on Jul 3, 2018

    @LeaVerou
    MemberAuthor

    It doesn't need to retain anything about the type, just serialize the value and use it the same as when var() is used.

  7. upsuper commented on Jul 3, 2018

    @upsuper
    Member

    OK, so I misunderstood the approach you are proposing. This approach is probably better to some extent, although I'm a bit concerned that serialization of computed value of properties may not be as well-defined and interoperable as token stream used by custom properties.

  8. tabatkins commented on Jul 3, 2018

    @tabatkins
    Member

    Discussion with dbaron suggested that the effect would be that you basically just sub in the serialization of the computed value (probably per the well-specified serialization in Typed OM).

    If the property you're subbing in doesn't work for the property you're subbing into, it would follow the same rules as var(), and make it invalid at computed-value time.

  9. CyberAP commented on Oct 29, 2018

    @CyberAP

    Shouldn't it be called computed() then, instead of inherit()? Makes more sense to me and it is more declarative, than an imperative inherit.
    Or just simply prop() as it's a direct CSS property reference here.

  10. LeaVerou commented on Jan 17, 2021

    @LeaVerou
    MemberAuthor

    I just came across this issue again looking for something else and was reminded of how useful this would be.

    @tabatkins wrote

    Discussion with @dbaron suggested that the effect would be that you basically just sub in the serialization of the computed value (probably per the well-specified serialization in Typed OM).

    If the property you're subbing in doesn't work for the property you're subbing into, it would follow the same rules as var(), and make it invalid at computed-value time.

    It sounds like there is a clear implementation path, but the discussion just stalled 2 years ago.
    Can we move it forwards?

    I promised in the first post that if this is feasible, I could try and collect a list of use cases. Here are a few:

    Edit: Added these to OP, for convenience.

    In terms of how it should behave, a few more thoughts:

    • Does it return the parent value regardless of what it is, or walk up ancestors until it finds a value or hit the root? This is especially relevant for non inherited properties. It seems like it could be more useful if it returned the closest non-initial value, rather than the direct parent, but then there are no guarantees about what element you're actually getting a value from. E.g. consider the "swap background and text colors in children" above. What if the parent ends up not having a color set at all, and inherit(background-color) ends up walking up the ancestors and returning e.g. the page's background? OTOH, that might actually be the background you're seeing...
    • It should probably have a fallback, just like var(). The fallback would apply when the returned value is initial.
    • As @upsuper and @tabatkins pointed out above, if after subbing the value, the resulting declaration doesn't make sense, the whole thing just becomes IACVT.
    • Can shorthands be referenced? If so, what do they return? If they cannot be referenced, this means that when we turn a property into a shorthand, code starts breaking (this was already a risk, it was just limited to JS). Also this would mean that even fairly "obvious" use cases would become complicated (e.g. inherit(border-color) cannot be used).
    • Some potential names: inherit(), inherited(), parent(), ancestor(), closest()
  11. Loirooriol commented on Jan 18, 2021

    @Loirooriol
    Contributor

    Does it return the parent value regardless of what it is, or walk up ancestors until it finds a value or hit the root?

    In general I don't think walking ancestors makes much sense for non-inherited properties. Even for inherited ones it may be undesirable. So I would just return the parent value.

    It should probably have a fallback, just like var(). The fallback would apply when the returned value is initial.

    But using the fallback for initial values would be inconsistent with var() and env(). I think the fallback should be used when the serialization of the computed value is the empty string (like a guaranteed-invalid value, or a shorthand that can't represent the values of its longhands). IMO custom behaviors for specific values should be achieved with conditionals (#5624).

    Can shorthands be referenced? If so, what do they return?

    If this feature is based on serializations, then it doesn't seem problematic to me. Just try to serialize the shorthand, and return that in case of success, or empty string otherwise. When reparsed, empty string can be treated as a guaranteed-invalid value, and use the fallback value if provided, or become IACVT otherwise.

  12. LeaVerou commented on Jan 18, 2021

    @LeaVerou
    MemberAuthor

    @Loirooriol

    Does it return the parent value regardless of what it is, or walk up ancestors until it finds a value or hit the root?

    In general I don't think walking ancestors makes much sense for non-inherited properties. Even for inherited ones it may be undesirable. So I would just return the parent value.

    I suppose that's more predictable (and probably more performant and easier to implement). After all, if one wants the value from a specific ancestor, there's an easy workaround:

    /* Swap background & foreground in descendants use case: */
    .container > * {
    	--container-background: inherit(background-color);
    }
    
    .container * {
    	color: var(--container-background);
    	background: inherit(color);
    }

    It should probably have a fallback, just like var(). The fallback would apply when the returned value is initial.

    But using the fallback for initial values would be inconsistent with var() and env(). I think the fallback should be used when the serialization of the computed value is the empty string (like a guaranteed-invalid value, or a shorthand that can't represent the values of its longhands). IMO custom behaviors for specific values should be achieved with conditionals (#5624).

    The fallback for var(--foo) is applied when --foo is initial ([demo],(https://dabblet.com/gist/b49604259b2765fec0966ba649dad8c8) spec). I agree with your reasoning however, especially since this allows shorthands to be used, which is really important.

    env() has an entirely different fallback mechanism (falls back when the name is not recognized, not depending on the value).

    Can shorthands be referenced? If so, what do they return?

    If this feature is based on serializations, then it doesn't seem problematic to me. Just try to serialize the shorthand, and return that in case of success, or empty string otherwise. When reparsed, empty string can be treated as a guaranteed-invalid value, and use the fallback value if provided, or become IACVT otherwise.

    I really like this. 💯

  13. Loirooriol commented on Jan 18, 2021

    @Loirooriol
    Contributor

    The fallback for var(--foo) is applied when --foo is initial

    Ah, yes, that's because the initial value for unregistered custom properties is the guaranteed-invalid value, which is what triggers the fallback. But if you register the property with some other initial value, then initial doesn't trigger the fallback in var(), so I guess it shouldn't in inherit() either.

  14. andruud commented on Mar 25, 2021

    @andruud
    Member

    walk up ancestors until it finds a value

    Sounds like it's already settled, but I'll emphasize anyway that this is something we should definitely not do.

    sub in the serialization of the computed value

    We recently (-ish) invented the equivalent token sequence, so we can just re-use that for this.


    I've been trying to think about whether or not the new "cross property inheritance" proposed here creates any significant new problems (performance, complexity, etc) for style recalcs. I think it should be fine. It's similar to situations that must anyway be dealt with via var() and explicit inheritance of non-inherited properties.

    This looks like a relatively "easy win", so I'm supportive, FWIW. 👍

  15. 95 remaining items

  16. EisenbergEffect commented on May 7, 2024

    @EisenbergEffect

    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.

  17. una commented on Jun 12, 2024

    @una
    Contributor

    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:

  18. fantasai commented on Oct 28, 2024

    @fantasai
    Collaborator

    This is now edited in for custom properties as resolved. Leaving the issue open for the more general discussion.

  19. beratbayram commented on Jan 9, 2025

    @beratbayram

    Maybe we could re-use var() ? Since custom properties have to start with two dashes, it will be easy to distinguish them.

    .parent {
      border-color  : red;
      --border-color: blue;
    }
    
    .child {
      border-color: var(border-color);   /* red */
      border-color: var(--border-color); /* blue */
    }
  20. Loirooriol commented on Jan 9, 2025

    @Loirooriol
    Contributor

    @beratbayram No, var() gets the value from the current element, not from the parent, so it would be extremely confusing and inconsistent.

  21. LeaVerou commented on Jan 11, 2025

    @LeaVerou
    MemberAuthor

    Also, inherit() works for custom properties too (and will be implement ONLY for custom properties first!) and has a different meaning than var().

  22. brandonmcconnell commented on Apr 22, 2025

    @brandonmcconnell

    @LeaVerou You may want to update the title to use css-values-5 instead of css-values-4, as this feature to be included in the values-5 spec, not values-4.

  23. changed the title [-][css-values-4] inherit() function: like var() for parent value, for any property[/-] [+][css-values-5] inherit() function: like var() for parent value, for any property[/+] on Apr 24, 2025
  24. Crissov commented on Oct 15, 2025

    @Crissov
    Contributor

    Since this is still open and Blink intents to prototype, I’m not opening a new issue: Is there any chance the inherit() function could behave more like inherit global keywords in that it always refers to the (custom) property it is used within? That means, drop the first parameter, thus only expect an optional fallback value.

    For accessing ancestral values from arbitrary properties, there could either be a separate function like parent() / parental() or the first parameter could just be made optional although I don’t see how that would work reliably.

  25. Crissov commented on Jan 9, 2026

    @Crissov
    Contributor

    Just for the record, MathML introduces an add() function to CSS which takes the parent (numeric) value and adds the parameter to it, but it is only specified for its math-depth property.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions