Related specifications & authors
Introduction
I've received valuable feedback regarding the complexity of an --open switch I used to demonstrate @starting-style (shown below), which I employed to avoid redundant styles.
dialog {
--open: 0;
--closed: calc(1 - var(--open));
transform: translateY(calc(var(--closed) * -50%));
&, &::backdrop {
--duration: calc((var(--open) * 0.5s) + (var(--closed) * 0.25s));
transition: all var(--duration) ease-out allow-discrete;
opacity: var(--open);
}
&[open] {
--open: 1;
}
@starting-style {
&[open] {
--open: 0;
}
}
}
This way, I could set up my styles in one place and control the open/closed state via a single variable. However, as I added more styles to my state, I found myself having to duplicate them into @starting-style each time.
Problem Statement
While @starting-style is undeniably powerful and allows for granular control, it often requires redundancy even for simpler use cases. Consider the following example, the same as above but without the variable trick:
dialog {
transform: translateY(-50%);
&, &::backdrop {
transition: all 0.25s ease-out allow-discrete;
opacity: 0;
}
&[open] {
transform: translateY(0);
&, &::backdrop {
transition-duration: 0.5s;
opacity: 1;
}
}
@starting-style {
&[open] {
transform: translateY(-50%);
&, &::backdrop {
opacity: 0;
}
}
}
}
In this example, the closed-state styles are duplicated inside @starting-style. This duplication becomes more apparent when animating from a custom set of closed-state styles to the default open-state styles:
dialog {
transition: all 0.25s ease-out allow-discrete;
&:not([open]) {
transform: translateY(-50%);
&, &::backdrop {
opacity: 0;
}
}
@starting-style {
&[open] {
transform: translateY(-50%);
&, &::backdrop {
opacity: 0;
}
}
}
}
The styles inside @starting-style are often the same as the closed state but must still be duplicated for the engine to recognize them. This is where I believe there is an opportunity to simplify the process.
Proposed Solution
I propose introducing a pseudo-class counterpart to @starting-style to eliminate the need for duplicating styles:
dialog {
transition: all 0.25s ease-out allow-discrete;
&:not([open]), &[open]:starting-style {
transform: translateY(-50%);
&, &::backdrop {
opacity: 0;
}
}
}
This syntax informs the engine about the state it is animating from without requiring the developer to duplicate styles. If we want to set styles for the open state as well, we can do so like this:
dialog {
transition: all 0.25s ease-out allow-discrete;
&, &[open]:starting-style {
transform: translateY(-50%);
&, &::backdrop {
opacity: 0;
}
}
&[open] {
transform: translateY(0);
&, &::backdrop {
transition-duration: 0.5s;
opacity: 1;
}
}
}
Conclusion
By introducing a pseudo-class :starting-style counterpart to @starting-style, we can avoid requiring developers to redeclare styles "before-state" styles inside @starting-style. This proposal aims to improve the developer experience and make @starting-style more approachable for simpler use cases while retaining its power and flexibility for more complex scenarios.
Some related notes re CSS Mixins (TL;DR: they don't solve this problem)
While CSS Mixins will simplify the process of managing styles for different states, I believe this enhancement would be most valuable as an addition to @starting-style itself, included in the selectors spec. It provides a more straightforward solution for common use cases, reducing redundancy in CSS code and making it easier for developers to define and manage animations between states.
The below examples all assume these mixins are present:
@mixin --dialog-closed {
transform: translateY(-50%);
&, &::backdrop { opacity: 0; }
}
@mixin --dialog-open {
transform: translateY(0);
&, &::backdrop { opacity: 1; }
}
Even using CSS Mixins, the mixins would still need to be invoked again within @starting-style:
dialog {
@apply --dialog-closed;
&[open] {
@apply --dialog-open;
}
@starting-style {
&[open] {
@apply --dialog-closed;
}
}
}
Now, with :starting-style and mixins:
dialog {
&, &[open]:starting-style {
@apply --dialog-closed;
}
&[open] {
@apply --dialog-open;
}
}
Related specifications & authors
Introduction
I've received valuable feedback regarding the complexity of an
--openswitch I used to demonstrate@starting-style(shown below), which I employed to avoid redundant styles.This way, I could set up my styles in one place and control the open/closed state via a single variable. However, as I added more styles to my state, I found myself having to duplicate them into
@starting-styleeach time.Problem Statement
While
@starting-styleis undeniably powerful and allows for granular control, it often requires redundancy even for simpler use cases. Consider the following example, the same as above but without the variable trick:In this example, the closed-state styles are duplicated inside
@starting-style. This duplication becomes more apparent when animating from a custom set of closed-state styles to the default open-state styles:The styles inside
@starting-styleare often the same as the closed state but must still be duplicated for the engine to recognize them. This is where I believe there is an opportunity to simplify the process.Proposed Solution
I propose introducing a pseudo-class counterpart to
@starting-styleto eliminate the need for duplicating styles:This syntax informs the engine about the state it is animating from without requiring the developer to duplicate styles. If we want to set styles for the open state as well, we can do so like this:
Conclusion
By introducing a pseudo-class
:starting-stylecounterpart to@starting-style, we can avoid requiring developers to redeclare styles "before-state" styles inside@starting-style. This proposal aims to improve the developer experience and make@starting-stylemore approachable for simpler use cases while retaining its power and flexibility for more complex scenarios.Some related notes re CSS Mixins (TL;DR: they don't solve this problem)
While CSS Mixins will simplify the process of managing styles for different states, I believe this enhancement would be most valuable as an addition to
@starting-styleitself, included in theselectorsspec. It provides a more straightforward solution for common use cases, reducing redundancy in CSS code and making it easier for developers to define and manage animations between states.The below examples all assume these mixins are present:
Even using CSS Mixins, the mixins would still need to be invoked again within
@starting-style:Now, with
:starting-styleand mixins: