How do you 'unset' changes that a mixin has introduced to an element?

TLDR: use a reset parameter in the mixin.

In a recent project I had the need to reverse the effects a mixin has introduced before.

For example, the design wants all heading elements displayed in uppercase letters, and uppercase often needs some tiny adjustment of letter-spacing for better readability and looks, so I created a small mixin, "versal":

  /* in tools/_mixins.scss */
  @mixin versal($spacing:0.03em) {
    text-transform: uppercase;
    letter-spacing: $spacing;
  }

This can be used where uppercase text should be displayed, for example in the '_basics.scss' rules, as a default for heading elements and heading classes:

  /* in _basics.scss */
  ...
  h1,h2,h3,h4,h5,h6,[class$="heading"] {
    @include versal();
  }

Now somewhere in the layout of the site, a normal, mixed-case heading element should be displayed, lets say a heading in an address block in the footer. So in the '_footer.scss' maybe this rule is used:

  /* in components/_footer.scss */
  ...
  .footer-block-heading {
    padding: 0.5em;
    text-transform: none;
    letter-spacing: 0;
    ...
  }
  ...

With this approach I have to remember what properties are set in my versal mixin and overwrite them accordingly.

No big deal, as long as the mixin uses only a few properties and won't be extended or changed later on, in which case I may have to overwrite the new properies afterwards, too. This can be a maintenance nightmare in larger code bases.

It never really bothered me until I started to use a 'visually hidden' mixin for elements that should not be visible, but still be exposed to non-visual interpreters (think keyboard- or screenreader-usage).

Currently this mixin sets quite a lot of properties:

  /* in tools/_mixins.scss */
  @mixin vh(){
    /* visually hidden, not van halen */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    border: 0;
  }

In the current project I had a case where I needed to modify an element that got hidden via this mixin before, 'unhiding' it in a later break point.

Since the best practice behind "visually hiding" content while remaining them accesible for keyboard or screenreader use had changed over the years, the vh() mixin already had changed over the projects and may be changed again in the future. Keeping track of all the properties affected by this mixin and reverting them accordingly when needed is not so easy.

The single source of truth in that case is the mixin itself - here the properties are set. So duplicating this mixin, renaming it to something like 'un-vh()' perhaps would be an option. In fact I think that is how Foundation handles it with their element-invisible / element-invisible-off mixins.

I decided to explore another option, to keep things nicely inside that one mixin, by creating a 'reset' parameter, setting it to empty as a default, and checking against it inside the mixin:

  /* in tools/_mixins.scss */
  @mixin vh($mode:''){
    @if $mode=='reset' {
      position: initial;
      width: initial;
      height: initial;
      padding: initial;
      margin: initial;
      overflow: intial;
      clip: initial;
      border: initial;
    } @else {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0,0,0,0);
      border: 0;
    }
  }

The nice thing about this is, should I need to alter the properties in the mixin, I can also directly alter their "reset" part without the need to check through my code base where theses changes have to be changed in my rules.

So now, on rules where I need to undo the mixin's work, I can use the same mixin again, but with 'reset':

  /* in components/_foo.scss */
  .hide-from-eyes {
    @include vh();
    ...
  }
  ...
  @media(...){
    .hide-from-eyes {
      @include vh('reset');
      ...
    }
  }

This seems to work pretty well. :-)

The "versal" mixin works equally well:

  /* in tools/_mixins.scss */
  @mixin versal($mode:'',$spacing:0.03em){
    @if $mode=='reset' {
      text-transform: initial;
      letter-spacing: initial;
    } @else {
      text-transform: uppercase;
      letter-spacing: $spacing;
    }
  }
 
  /* in components/_footer.scss */
  .footer-block-heading {
    @include versal('reset');
    padding: 0.5em;
    ...
  }

A potential downside is the usage with mixins that already take a lot of parameters, I am not sure how the order of parameters and which parameter is given when calling the mixin, may present a problem.

Maybe as a convention the 'reset' parameter should always be the first one, but I don't know currently how this'll behave when you leave out the 'reset' and use the second parameter as the first and so on.

There is an old article by Hugo Giraudel regarding parameters, lists, and parameterlists in scss on Site-Point, I still need to digest the differences. :-)

But for my simple use case the pattern works fine, currently. So maybe it works for you, as well.

Let me know what you think.

Photo by Maik Jonietz on Unsplash