Repository navigation
[css-cascade] Additive CSS #1594
Description
Additive CSS
The following material has been prepared for discussion at the August 2017 CSS F2F in Paris, France.
Outline
The goal of this discussion is to:
- Explain additive animation and why it was added to Web Animations
- Propose a general-purpose approach to combining CSS property values that is useful in static contexts too
- Decide if it is suitable for to ship additive animation in Web Animations and CSS Animations 2 knowing that an overlapping general-purpose feature may arrive in the near future
Part 1: Additive animation
This section describes what additive animation is, how it works, and why it is part of Web Animations.
What is additive animation?
Typically when two independent animations target the same property on the same element, one animation will clobber the other.
This can be either because of the nature of the CSS cascade, e.g.
.spin {
animation: spin 2s infinite;
}
.swell {
animation: swell 1s alternate 2;
}
@keyframes spin { to { transform: rotate(1turn) } }
@keyframes swell { to { transform: scale(2) } }Or simply because only one animation is ever applied to a given property, e.g.
div {
animation: spin 2s infinite, swell 1s alternate 2;
}
@keyframes spin {
from { transform: rotate(0turn) }
to { transform: rotate(1turn) }
}
@keyframes swell {
from { transform: scale(1) }
to { transform: scale(2) }
}Additive animation, however, allows the two independent animations to be combined together.
For example, using the Web Animations API we can express the above as:
elem.animate({ transform: 'rotate(1turn)' },
{ duration: 2000, iterations: Infinity });
elem.animate({ transform: 'scale(2)', composite: 'add' },
{ duration: 1000, iterations: 2, direction: 'alternate' });(Demo - requires Firefox Nightly or Chrome Canary)
CSS Animations 2 proposes to expose this using the animation-composite property.
e.g.
div {
animation: spin 2s infinite, swell 1s alternate 2;
}
@keyframes spin { to { transform: rotate(1turn) } }
@keyframes swell { to { transform: scale(2); animation-composite: add } }Note that this only solves the second of the two problem cases above. To solve the case where the CSS cascade results in only one animation appearing in the computed value of
animation-namewe need the more general additive approach described in part 2 of this proposal.
How does it work?
Firstly, animations that target the same property are arranged in order. This order is important because:
- for some types of properties, the addition operation might not be commutative (e.g. addition of transform lists involves concatenating lists which is equivalent to matrix multiplication which is not commutative), and
- if animation A is additive, but animation B is not, what should be the result? The answer is it depends on which is higher in the stack.
Web animations defines this as an effect stack. For animations created using the API the order is roughly equal to the order in which the animations are generated but for CSS animations, it is based on the order of the animations listed in the animation-name property.
At the bottom of the stack is the unanimated value, called the underlying value. It is possible for a single animation to be combined with the unanimated value using this same mechanism, e.g.
elem.style.transform = 'translate(100px)';
// The following animation represents an offset from the initial translation
elem.animate({ transform: 'translate(100px, 100px)', composite: 'add' },
{ duration: 1000, iterations: 2, direction: 'alternate' });(Demo - requires Firefox Nightly or Chrome Canary)
The specific procedures for adding each property needs to be defined just as we define the specific procedures for interpolating each property. The intention is that both the interpolation procedures and addition procedures for common types will be defined in CSS Values and Units and then specifications
that define new property types can defined further procedures.
One technical detail is that in some cases there are multiple notions of addition. For example, for a filter list we can define blur(2px) + blur(5px) as either blur(2px) blur(5px) or blur(7px).
As it turns out SVG uses both these modes. When two independent animations are added together it does list concatenation, i.e. blur(2px) blur(5px) from above. When an animation is defined to build on itself on each iteration it adds the function components, i.e. blur(7px) from above.
(Technically SVG only makes this distinction for transform animations and it doesn't actually do list concatenation, but matrix post-multiplication which is functionally equivalent.)
Web Animations calls these two modes add and accumulate respectively, and collectively calls these composite operations.
Why does Web Animations have additive animation?
The original brief for Web Animations was that it should represent the common base for CSS Animations, CSS Transitions, and SMIL/SVG animation. SMIL/SVG animation has additive animation so therefore Web Animations has additive animation.
Is it needed?
The most common usage of additive animation is for combining transform animations. A number of these use cases can be addressed using the individual transform properties from CSS Transforms 2. However, this does not cover all use cases such as:
- animating translateX and translateY independently (as required by libraries such as GreenSock Animation)
- animating the same transform component independently (e.g. the translate example above)
- animating other properties such as
filter(e.g. animating the blur on an element that already has a sepia effect applied), or evenopacity(e.g. animating the opacity to +50% of its current value).
The other alternative to additive animation is to simply add more and more <div> elements to represent each possible independently timed animation component. This approach does not scale well to dynamic animations where a potentially unbounded number of animations might be generated dynamically and having to adding elements for this is, in this author's opinion, one of the more frustrating parts of authoring CSS at the moment.
Part 2: Static CSS and addition
As it turns out, the notion of combining CSS property values is useful beyond just animations.
Use cases
e.g. truly generic classes
.nostalgic {
filter: sepia(60%) !add;
}
.blurry {
filter: blur(5px) !add;
}<div class="nostalgic"></div>
<div class="blurry"></div>
<div class="nostalgic blurry"></div> <!-- No problem -->e.g. modifying underlying values
.translucent {
opacity: -50% !add;
}e.g. combining animations defined by different rules that target the same property
.spin {
animation: spin 2s infinite !add;
animation-composite: add;
}
.swell {
animation: swell 1s alternate 2 !add;
animation-composite: add;
}e.g. overriding certain list items by using the cascade order
button {
filter: blur(2px);
}
button:focus {
/* This will override the blur() item but not the grayscale() due to cascade order */
filter: blur(0px);
}
button:disabled {
filter: grayscale(50%) !add;
}Proposal: !add annotation
I believe both David Baron and Tab Atkins have at some point considered the idea of an !add annotation for this.
For static CSS the CSS cascade (that is selector specificity etc.) provides the ordering for composition. The absence of an !add annotation on a declaration indicates that any lower priority declarations are ignored as they are today (even if those lower priority declarations have !add annotations).
Issues that need consideration
Some issues that would need to be considered as part of such a proposal:
- Is
!addenough? Do we need both theaddandaccumulatemodes described above? - Are there other modes we need?
- e.g. multiplication to produce 75% of the current opacity value. Or should that be achieved by extending
calc()to be able to work with the underlying value?
- e.g. multiplication to produce 75% of the current opacity value. Or should that be achieved by extending
- Can we relax the range checking used when an
!addannotation is in place so that is is possible to writeopacity: -50% !add?
(We already do this internally for SMIL and I suppose we want to do this foranimation-compositeanyway.)
Part 3: Shipping additive animations
The purpose of raising this proposal is to consider the implications of shipping additive animations now and later introducing a more generalized mechanism.
How would the additive features specified in part 1 change if we also eventually do part 2?
Example 1: Setting the composite mode in a single keyframe
// Current API (part 1)
elem.animate([ { transform: 'translate(100px)' },
{ transform: 'translate(200px)', composite: 'add' ], 2000);
// Possible future API (part 2)
elem.animate([ { transform: 'translate(100px)' },
{ transform: 'translate(200px) !add' ], 2000);
// What if both are combined? (part 1 & part 2)
elem.animate([ { transform: 'translate(100px)' },
{ transform: 'translate(200px) !add',
opacity: 0.5,
composite: 'accumulate' ],
2000);
// Presumably we would define a rule that says that if we have an !add
// annotation then that overrides any composite member such that in the above
// 'transform' uses the 'add' composite mode, and 'opacity' uses the
// 'accumulate' composite mode.
// As such, I think we can harmonize these two approaches.Example 2: Setting the composite mode across all keyframes
// Current API (part 1)
elem.animate({ transform: [ 'translate(100px)', 'translate(200px)' ] },
{ composite: 'add', duration: 2000 });
// Possible future API (part 2)
elem.animate({ transform: [ 'translate(100px) !add',
'translate(200px) !add' ] },
2000);
// What if both are combined? (part 1 & part 2)
elem.animate({ transform: [ 'translate(100px)', 'translate(200px) !add' ] },
{ composite: 'accumulate', duration: 2000 });
// Again, presumably we say that !add overrides any composite member such
// that the first value uses 'accumulate' and the second value uses 'add'.
// As such, there does not appear to be any conflict here.I think these same principles could equally apply to the CSS syntax if we choose to ship that too. For example, taking Example 1 and translating it into the proposed CSS syntax from CSS Animations 2:
/* Current API (part 1) */
div {
animation: move 2s;
}
@keyframes move {
from { transform: translate(100px); }
to { transform: translate(200px); animation-composite: add; }
}
/* Possible future API (part 2) */
div {
animation: move 2s;
}
@keyframes move {
from { transform: translate(100px) }
to { transform: translate(200px) !add }
}
/* What if both are combined? (part 1 & part 2) */
div {
animation: move 2s;
}
@keyframes move {
from { transform: translate(100px) }
to { transform: translate(200px) !add;
opacity: 0.5;
animation-composite: accumulate }
}
/* As above, the 'transform' value would use 'add' while the 'opacity' value
would use accumulate. */It seems to me that there is a relatively straightforward path for harmonizing the approach in part 1 if we also do part 2 and that, in fact, the features introduced in part 1 might be useful in combination with part 2 as a way of bulk-setting the composite operation.
Activity
FremyCompany commentedon Jul 12, 2017
Still relevant on the topic:
https://lists.w3.org/Archives/Public/www-style/2013Apr/0711.html
I have some more thoughts that are not reflected in this 4 years old thread, most notably the role custom properties could have here to improve DRY, but I would happy to discuss this further at the F2F.
jackdoyle commentedon Jul 25, 2017
I wish I had more time to dig into this, but I wanted to chime in briefly...
I fear that this feature won't really address the pain points that animators face in their day-to-day work. Here are 2 quick demos of where I think it might miss the mark:
(Read the comments in the JS code)
I'd LOVE to be able to tap into these technologies under the hood in GSAP, but from what I understand, it doesn't appear as though this feature will deliver what we really need in order to do that. Layering things on top of each other might help in some scenarios, but there are quite a few (as demonstrated above) that still seem to be impossible/cumbersome. Or perhaps I'm just missing something(?) Please forgive me if I botched something in the implementation.
FremyCompany commentedon Jul 25, 2017
@jackdoyle Thanks for the use cases, they seem relevant.
Here's a similar thing written using the solution I proposed:
Now you can add classes State1, State2 and State3 in whichever order you want and get a deterministic transition. The way this works is that the subtransforms will always be sorted by number sequence, in descending order. That means "1000 2 0" (GSAP_TRANSLATE 0) will always be first, "1000 0 1" (GSAP_ROTATE 1) will be second and "1000 0 0" (GSAP_ROTATE 0) will always be third.
You can override these values later in your stylesheet if needed by referencing their list-id:
I also have a proposal to allow you to leave out the last number as a automatically-unique value (you cannot override it later like I did with State1 and State1b, but its relative ordering will be solved automatically using css cascade order)
Last but not least, this gives a lot of flexibility when you need mixing frameworks: If there is another framework, you can (as an author) decide in which order the operations of these frameworks should take place. For instance, if this framework ("ANIMF") should take place after GSAP you can ensure it uses
and if you want to run its effects before GSAP
but you can also have its effect run during GSAP Scale phase
Since variables cascade you can even decide that element per element by redefining GSAP and ANIMF per element.
If you need some operations of ANIMF to happen before GSAP but others to happen after, you can use
birtles commentedon Jul 25, 2017
@jackdoyle I'm pretty sure you can address that use case when we add the proposed composite order control.
jackdoyle commentedon Jul 31, 2017
Sorry about the late response. For some odd reason, I wasn't notified of your replies, nor am I allowed to subscribe to the thread (even now).
@FremyCompany I can't mock up that solution in a codepen because those features aren't implemented in any browser yet, right? I read through your suggestion several times and I couldn't grasp how this would solve the problem I was pointing out. Likely because of my own cognitive deficiencies :)
A demo would be super helpful (though I realize it's probably not possible today).
@birtles I'm in a similar situation. Does the proposed composite order control allow folks to, for example, have 100 different elements flying around and randomly choosing various translateX/translateY/rotate/scale values with completely different timings without a bunch of overhead that tracks the order of previous transforms?
For example, with this additive stuff, after animating to rotate(180deg) how can I get back to 0deg without tracking previous stuff? It seems I'd have to do rotate(-180deg) in order to cancel out the previous rotate(180deg), right?
If there's a translateX(100px) and halfway through that, I need to animate translateY(200px) (without interfering with the translateX()) but previously there had already been a translateY(500px) applied, how can I ensure that it arrives at translateY(200px) instead of translateY(700px)? Would I have to getComputedStyle() and parse through the current values? What if it was part-way through a translateY(500px) animation (thus getComputedStyle() won't return the destination)?
How much work will it be for the average developer to animate to absolute translateX/translateY/scaleX/scaleY/rotate values with completely offset timings from each other (overlaps)? Got a demo of how this will solve that problem? It'd be super cool if you could fork the demos I provided and show me what you mean. Please forgive my slowness.
birtles commentedon Aug 1, 2017
Hopefully I've restored Jack's comment which I accidentally edited. Sorry about that. (And I find it quite odd I can edit someone else's comment!)
birtles commentedon Aug 1, 2017
Yes. Obviously if they are different elements, then they have separate composite stacks so there should be no conflict. Within a single element, you can call
getAnimations()and iterate over the returnedAnimations to see what animations it has running (and hence where in the stack you want to add new animations to get the desired result).You can animate the
rotateproperty torotate(0deg)without specifying afromvalue to get the desired effect.Yes, if you're looking to animate the components independently in a pseudo-additive fashion like that despite the fact that they are expressed by the same CSS property, then, for now, you'll need to go through the result of
getAnimations()to find your current target Y. You can annotate theAnimationobjects with extra metadata (e.g. the target X/Y values) when you generate them to avoid parsing the result ofgetKeyframes()later.There's some complexity but I think it's manageable and only present when all of the following are true:
rotateetc. you can use the separate propertyIf we find that this is common enough, then we can split off separate properties for the independent components that need to be animated in this way.
AmeliaBR commentedon Aug 2, 2017
(I'm reading through the proposals & comments, so I'll be making a series of comments on separate points. Overall: I really want this feature in CSS!)
This is a very misleading summary.
SVG/SMIL has two types of additive animations, yes: multiple independent animations adding together ("additive" animations) versus a single animation that increases its effect on each repeat ("accumulate" animations).
Only certain data types are additive (number, length, angle, color, x-y pairs, and transformations). However, they normally behave identically as far as how the addition is calculated, regardless whether it is addition from separate animations or from repeats.
The only time there is a difference is for
<animateTransform>. And that has less to do with the non-commutative nature of transforms, and more to do with the structure of the<animateTransform>: the transform type (e.g.,translateversusrotate) is defined separately from the values (parameters, i.e., the distance or the angle). Accumulation applies to the values, because it's a property of a single animation element, but addition applies to the complete effect.Example of accumulate and additive animations, for
<animate>and<animateTransform>Presumably, if you used
<animate>to animate a transformation by directly setting the transformation string as the value (as proposed for Level 2 SVG animations), then you would always be adding the complete animation, even for accumulations.Edited to add:
SVG/SMIL also has an additive animation option that is so super-simple I forgot about it: the
byattribute allows you to define a single-keyframe animation as a change relative to the base value on the element.AmeliaBR commentedon Aug 2, 2017
Definitely the most common, but here are some others (remembering that we're not only talking about one animation adding to another, but also about animations adding to the base value):
Unlike with transforms, creating an additive effect with nested
<div>isn't even possible. (Well, except for maybe using percentages of container width or inherited font-size.) Even for transforms, having to add extra markup for every dimension of the transform is really hacky and ugly and annoying.AmeliaBR commentedon Aug 2, 2017
General thoughts on the
!addproposal:This is one of the most straight-forward proposals I've seen for allowing partial or additive CSS declarations, which is a definite bonus. It fits nicely into CSS syntax (finally something else that uses
!qualifier). And it directly builds on CSS cascading.The main thing that would need to be worked out is the addition rules for different data types. This would be similar to how we have interpolation rules for data types -- and with the similar understanding that some things can't be added and would be replaced instead.
The simplicity could be a limitation when talking about more complex properties. For property values that consist of more than a single value, there usually wouldn't be a single universal way to define how to add two declarations.
For some cases, this issue could probably be avoided if you could build up that complex value from custom properties, and then animate the custom property values in an additive way. This could be a solution to Brian's filter example. Normally, filters could add by adding an extra filter function at the end of the chain. But you could declare a
filter: blur(var(--blur-radius));and then make the--blur-radiusdeclarations additive. (This of course assumes an easy way to declare the data types of custom properties for interpolation and addition. But that's going to happen anyway, right?)But there are other cases where you want to modify a complex value, which can't easily be deconstructed into custom properties. For example, to insert an image layer into a
background-imagestack. You need to add a comma token into your custom property value, and the syntax gets very fragile.For the specific issues Brian highlighted:
I think accumulate is useful. But it doesn't fit into the
!addsyntax. Accumulation is a property of the animation repeat cycle, not of an individual declaration. The entire animation would either need to accumulate or not. And whether it accumulates or not would be separate from whether they underlying declarations were additive or not.(I'm assuming that, for
!addin a@keyframesrule set, we are always adding on to the previous value from the cascade, and not on the previous keyframe. So it would be possible to have e.g. a 50% keyframe that was additive (so the first half of the animation is defined as a change from the base value), and then a 100% keyframe that wasn't additive (final value in the animation is the same regardless of the base value).)If you wanted to support accumulative repeats in addition to an
!addsyntax, I think the logical way to do so would be via a newanimation-*property. Accumulation would be something like alternating repeats or a forwards-fill mode, that is part of the overall timing and repeat structure of the animation, not part of the keyframes.However, there would still need to be details to figure out:
How do you define the amount of change which becomes the amount to add? If we're being consistent with SVG/SMIL (and I think Web Animations), then the additive amount for each keyframe would be measured as the value for that keyframe relative to the
fromvalue. However, SVG/SMIL only does it this way if afromis set explicitly, or if the relative change is set explicitly withby. An animation defined with implicitfromand explicittodoes not accumulate in SVG/SMIL. Which doesn't translate nicely to a CSS model. Also,valuesandaccumulatearen't well defined together in the spec; Firefox and Chrome treat them so that the values automatically become additive, then accumulative in their difference relative to the base value, instead of relative to the starting frame. Basically, it's a huge mess of logical inconsistencies and I'm not sure you'd want to automatically follow along to match.How do accumulative animations interact with reverse-direction or alternating animations? SVG/SMIL does not have these. The definitions in Web Animations do not appear to discuss it.
Now we're getting into the complicated questions that the simple syntax can't cover. Which other combination options you want would depend on the specifics of each property type. The more complex the property, the more different possible ways you might have for combining values. Think of two
text-shadowdeclarations: do you add them as a list, to create two shadows? Or do you add all the individual terms. So maybe there's a!concatenatelist option versus!add? But how does this compare with!multiplyfor other properties? It quickly becomes a tangle.A simple syntax like
!addneeds a simple rule. Just like with the SVG/SMIL additive rules, that would probably mean a very restricted set of additive data types. As mentioned above, more complicated combinations would require CSS variables.This would be very desirable. It should be similar to
calc()andvar()rules for range checking, where you only clamp when you can convert it to a meaningful final value.(This is probably it for me tonight. Sorry I didn't get a chance to review @FremyCompany's proposal. I'll be reading over the IRC minutes when I get online tomorrow.)
birtles commentedon Aug 2, 2017
How is that misleading?
Isn't that precisely what the summary says?
I don't know why that is the assumed behavior.
(For what it's worth, as the implementer of SMIL in Gecko, the distinction between these two types of addition was very surprising and was only revealed by a single test case in the SVG 1.1 test suite that failed when I used the same definition of addition across the board. The spec does not make this distinction obvious at all and arguably it only existed in the test writer's interpretation. I don't think anything can be assumed about how it ought to work from the SVG spec. One motivating reason for choosing the component-based addition for accumulation in Web Animations is to avoid creating a very long list for repeating animation.)
birtles commentedon Aug 2, 2017
Yes, I am working on adding that to CSS Values & Units since we already use this in Web Animations.
I'm optimistic we can solve this (we have most of these complex types adding together in Gecko / Servo / Blink) by doing addition on the components. There are some cases where we fall back to non-additive just like for interpolation there are some cases where we fall back to discrete animation. As with interpolation, I think we can probably fill some of these gaps in the future without breaking content.
Right.
Web Animations lets you use the accumulate behavior (i.e. adding the function parameter values rather than appending to the list) when adding independent animations.
Right. This per-keyframe additive behavior is already specified in Web Animations and implemented in Firefox and Chrome (but not shipping yet).
I assume this is referring to how to apply accumulate behavior? If so, then this proposal is simply putting forward accumulate behavior as another type of addition independent of repetition.
This is precisely the difference between
!addand!accumulatein this proposal.9 remaining items
faceless2 commentedon Dec 11, 2019
If additive behaviour was defined on a per-property basis it could apply even where lists of items aren't natural - for example:
or, slightly more hypothetically:
Various forms of text "fix up" keep being proposed[1][2][3][4][5][6], and additive behaviour seems like a good option for what will inevitably become an increasingly long list.
For font-feature-settings too. Here, I was wondering how you might offer more control over the cascade than just "add to, rather than replace, the previous matched value"
This would require each declaration has a name field, but beyond that should be no harder than the vanilla "!add" proposal.
[1] https://www.w3.org/TR/css-text-4/#text-spacing-property
[2] https://www.w3.org/Style/2013/paged-media-tasks#text-fix
[3] https://www.princexml.com/doc/css-props/#prop-prince-text-replace
[4] https://www.antennahouse.com/product/ahf66/ahf-ext.html#axf.text-replace
[5] https://specs.rivoal.net/css-custom-tt/
[6] https://books.idea.whatwg.org/#character-substitution
[7] #4246
LukeTOBrien commentedon Jan 13, 2020
Hello there,
I am just a humble web dev, nothing important, but I stumbled upon your discussion and I woul like to add my views.
It seems to me that what you want is something like (Compound Assignment in C#) Additional Assignment and subtraction assignment.
So instead of:
!addand!remove- In your eg:text-decoration: underline !addYou would have:
+:and-:- So for eg:text-decoration+: underlineFor animations I have also bee thinking about compounding a CSS var:
So if you want to slide in and animate the colour:
See my pen - I have commented out the compound var expression so that the pen will work.
Whatcha think? :-)
warengonzaga commentedon Jan 23, 2020
I'm gonna read all of this @LukeTOBrien thanks for informing the AnimateCSS community.
argyleink commentedon Mar 30, 2021
building an animation grudgingly in javascript, just for additive compositing. wish I could use CSS only. here to say I would have reached for it right now if I could 👍🏻
Kilian commentedon May 4, 2021
This would be useful for any CSS property that accepts layers, like
background,text-shadow,box-shadowor combines multiple options likefilterandfont-feature-settingslike already mentioned.A way to add to these, or even compose them, rather than fully overwriting them, would be great.
tabatkins commentedon Oct 27, 2021
I think you added this comment to the wrong issue?
GrossDesignCo commentedon Jan 27, 2022
+1, this would be really useful for design systems that need to define specific properties without overriding user styles.
dbaron commentedon Aug 11, 2022
It's not clear to me (particularly given #1594 (comment)) whether this issue should cover all additive cascading concepts in CSS, or only those intended for animation.
Also, for what it's worth, the earliest additive cascade proposal that I could find is https://lists.w3.org/Archives/Public/www-style/1999Oct/0025.html .
korenevskiy commentedon Aug 12, 2022
You can consider the syntax with a plus sign "+" at the very end .
Optionally, you can consider specifying the order of the member after the plus sign "+"
This is the equivalent of
The description should apply to all multilayer properties. Such as :
filter, background, transition, box-shadow, text-shadow and ...
This will allow you to structure styles by meaning.
SebastianZ commentedon Aug 14, 2022
I believe those for animation are already covered by the prior resolution to ship Web Animations with additive animation and by the
animation-compositionproperty @birtles mentioned in his initial post.And given the long discussion in this issue about a general solution, I think it makes sense to keep using this issue to find a general solution.
@tabatkins Could you explain why you think @Kilian's comment belongs to a different issue?
Regarding "all cascading concepts", we should first clarify what those different concepts are. As far as I can see, we have two general main concepts (which @birtles already mentioned in his initial post).
Manipulating a numeric inherited value
This means a numeric value is calculated relative to the value given by the cascade. In Brian's example this was a
blur(5px)on a givenblur(2px)resulting in ablur(7px). Another example would be adding10pxfor the top padding to a givenpadding: 20pxresulting inpadding: 30px 20px 20px 20px.I guess those use cases may already be covered by #2864,
calc()and/or custom properties.Adding a value to a list of values
This means any kind of value is appended to (or removed from) the list of a property's value, independent of whether it is inherited or not.
This is what Brian wrote as
blur(2px)+blur(5px)resulting inblur(2px) blur(5px)and generally what people mean as adding values to a property. This is obviously also what people had in mind when suggesting additive cascade as solution for #7066.There it doesn't matter if the value is space-separated like in
transformorfilteror comma-separated like inbackgroundorbox-shadowas mentioned by @Kilian.Other examples for them would be
transform: scale(1.2)+transform: translateX(100px)=transform: scale(1.2) translateX(100px),box-shadow: 2px 0 2px red+box-shadow: -2px 0 2px blue=box-shadow: 2px 0 2px red, -2px 0 2px blueorcontainer-type: style+container-type: size=container-type: style size.As mentioned above, there might also be cases in which people want to remove a value from the list of values. E.g. as we now have style containment by default (which can't be removed at the moment), people may explicitly remove it for cases they only want size containment to improve performance.
Sebastian
AdamSobieski commentedon Apr 13, 2026
Hello. I thought of another interesting theoretical use case for additive CSS beyond transitions and animations. If a CSS property,
about, could have values of typestripleand/orquad, then one would be able to form semantic graphs and datasets by means of additive CSS.Here is a more complex, intricate example with many more possibilities shown. This example illustrates how one might go about using CSS selectors to transform a simple HTML table into semantic content. The example uses a
document-aboutproperty to attach semantic content to a document-level graph, rather than storing it in per-element graphs.LeaVerou commentedon Apr 28, 2026
Something like this could also be another solution (that doesn't require parser changes, unlike some of the ideas here).