Design Systems Hot Takes

Mixinimation

4 min read

In speaking with a colleague, we were discussing solutions for people who were unable to use a framework’s custom component that still expected to have the styles for that component. Think of how you might have a React <Button/> that would need to be visually similar to some static HTML page <button/>.

Folks writing CSS normally should quickly identify that placing all of the necessary styles under a single identifiable selector that can be applied to the element is the normal way of getting the job done.

.my-button {
    /** Reusable styles for the button here */
}
<button class="my-button">Click me</button>

This assumes that the styles for .my-button are found on the same page as the HTML <button class="my-button"/>. This is a reasonable method for having reusable styles.

But maybe you don’t have the ability to update the HTML, how would you change the CSS such that the buttons would receive the styles? What you should first land on is updating the selector to be more generic:

button {
    /** Reusable styles for the button here */
}

However, it’s possible that we don’t want to affect all buttons this way. Specifically, we want to have some reusable styles that are written for the common <Button/> component, but we also want to take those same styles and create a new stylesheet specific for when we can’t access the HTML. In other words, we don’t want to write the same styles twice, but we do want to change what those styles target. How could we do this?

If you’re familiar with the CSS tooling ecosystem, you might suggest using a @mixin syntax made popular by Sass. That will allow the styles to be written once, and be reused underneath different selectors.

@mixin my-button {
    /** Reusable styles for the button here */
}

/** In the <Button/> component styles */
.my-button {
    @include my-button;
}

/** In the restricted HTML styles */
button {
    @include my-button;
}

Unfortunately, this is not native CSS syntax yet. So, this assumes that you are using a preprocessor to transform the @mixin syntax into native CSS that the browser can parse. Though, I was curious; is there a way we can get mixin behavior in CSS today? And I think there is…

Keyframes

The @keyframes syntax is a method to add a collection of styles to an element. Typically, we’d define blocks of declarations for each point in the keyframe timeline. We could define all of these styles as the from and to points, causing these styles to be applied for the entire duration of the animation.

@keyframes mixin-button {
    from, to { /** Reusable styles */ }
}

Next, we can write the animation value as the following:

.my-button {
    animation: mixin-button 0.001ms forwards;
}

The animation shorthand has three values. The first is the animation-name value which refers to the @keyframes name set earlier. The next is the animation-duration which cannot be 0, the default. Setting to a very small value causes the animation to be nearly immediate. The animation-fill-mode is set to forwards to ensure that the styles provided by the animation continue past the end of the animation so they persist after the very short duration.

Now remembering to write those three values in the shorthand can be a bit cumbersome, so instead we can hide all of this in a CSS custom property.

@property --mixin-button {
  syntax: "*";
  inherits: false;
  initial-value: mixin-button 0.001ms forwards;
}

This allows us to use the following syntax to apply all of the styles using a single property-value pair under a selector.

/** In the <Button/> component styles */
.my-button {
    animation: var(--mixin-button);
}

/** In the restricted HTML styles */
button {
    animation: var(--mixin-button);
}

Here’s the whole implementation working in Codepen:

<Laughs maniacally into his computer>

Drawbacks

Ignoring the hacky nature of applying all the styles by hiding them underneath the animation property, one drawback to this will also be where actual animations would be expected for these elements. While there is a syntax to apply multiple animations to a single element, I’m not sure how these might conflict. Additionally, if you are using a node detection script, the animation would fire for all of these elements if listening for animations.

My recommendation: try it out, put it in the dark corner of your toolbox but, avoid reaching for this unless you really have challenging constraints. We’re all waiting patiently for more native solutions and with CSS moving fast, it’ll be here sooner than we realize.

Relearning line height Interoperable tokens