Search code examples
csslessless-mixins

Reverse states on hover with Less


I have the following LESS code:

.favourite-action-link {
    &:after {
        color:@grey;
        content: '\e836';
    }
    &.is-favourite:after {
        color:@red;
        content: '\e811';
    }
    &:hover {
        &:after {
            color:@red;
            content: '\e811';
        }
        &.is-favourite:after {
            color:@grey;
            content: '\e836';
        }
    }
}

With the essential goal being that there is a normal state and a hover state, that are reversed when another class is present. I'll be repeating this for other actions (eg .share-action-link, .review-action-link etc) and this just seems messy the way I have it. Is there a way to create a mixin such that I could provide this like so:

.favourite-action-link {
    &:after {
        color:@grey;
        content: '\e836';
        &:hover {
            color:@red;
            content: '\e811';
        }
        .reverseOnClass(is-favourite);
    }
}

Or something like that? The only way I can think of so far would be to do:

.favourite-action-link {
    &:after {
        color:@grey;
        content: '\e836';
    }
    &.active:after {
        color:@red;
        content: '\e811';
    }
}

and then to use jQuery instead to do the hover - toggling .active on (isHovering XOR hasClass(is-favourite)) - but turning LESS into LESS + jQuery is the opposite of fixing a mess/maintainability issue.


Solution

  • I would really recommend writing it like below because it keeps the code simple and easy to read.

    .favourite-action-link {
      &:after, &.is-favourite:hover:after {
        color: @grey;
        content: '\e836';
      }
      &:hover:after, &.is-favourite:after {
        color: @red;
        content: '\e811';
      }
    }
    

    But if you really want to use a mixin to avoid repeating the selectors then you could write it like below. This mixin takes two rulesets as input and they are applied to the required selectors.

    .favourite-action-link {
      .rules-gen(
        {
          color: @grey;
          content: '\e836';
        };
        {
          color: @red;
          content: '\e811';
        }
      );
    }
    
    .rules-gen(@rule1; @rule2){
      &:after, &.is-favourite:hover:after {
        @rule1();
      }
      &:hover:after, &.is-favourite:after {
        @rule2();
      }
    }
    

    In both these methods, the selectors are also grouped and that also means reduced lines of code.

    Demo


    Or, if the extra class is not always is-favourite and it could also be something else then you could pass it also to the mixin as a parameter like below:

    .favourite-action-link {
      .rules-gen(
        {
          color: grey;
          content: '\e836';
        };
        {
          color: red;
          content: '\e811';
        };
        ~"is-favourite"
      );
    }
    
    .share-action-link {
      .rules-gen(
        {
          color: yellow;
          content: '\e836';
        };
        {
          color: gold;
          content: '\e811';
        };
        ~"active"
      );
    }
    
    .rules-gen(@rule1; @rule2; @addedClass){
      &:after, &.@{addedClass}:hover:after {
        @rule1();
      }
      &:hover:after, &.@{addedClass}:after {
        @rule2();
      }
    }
    

    Demo