<div class="demo">
  <div>
    <button class="js-classily" type="button" data-target=".my-class.red, .my-class.gold, .my-class:not(.blue), .js-classily.active, this" data-class="red, gold, blue, active, active">Toggle Blue</button>
    <button class="js-classily" type="button" data-target=".my-class.blue, .my-class.gold, .my-class:not(.red), .js-classily.active, this" data-class="blue, gold, red, active, active">Toggle Red</button>
    <button class="js-classily" type="button" data-target=".my-class.blue, .my-class.red, .my-class:not(.gold), .js-classily.active, this" data-class="blue, red, gold, active, active">Toggle Gold</button>
  </div>
  <h1 class="my-class">I'm a content that could change color.</h1>
</div>
$mq-mobile: 320px;
$mq-desktop: 960px;

@function strip-unit($value) {
  @return $value / ($value * 0 + 1);
}

@mixin css-locks($properties, $min-vw, $max-vw, $min-value, $max-value) {
  @each $property in $properties {
    #{$property}: $min-value;
  }

  @media screen and (min-width: $min-vw) {
    @each $property in $properties {
      #{$property}: calc(#{$min-value} + #{strip-unit($max-value - $min-value)} * (100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)});
    }
  }

  @media screen and (min-width: $max-vw) {
    @each $property in $properties {
      #{$property}: $max-value;
    }
  }
}

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

html {
  font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans",
"Droid Sans", "Helvetica Neue", sans-serif;
  @include css-locks(font-size, $mq-mobile, $mq-desktop, 15px, 20px);
}

body {
  min-height: 100vh;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.demo {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 1rem;
  
  h1 {
    color: transparent;
    background-color: DodgerBlue;
    background-image: linear-gradient(to right, transparentize(#ff6347, .45), transparentize(#ff6347, .9));
    -webkit-background-clip: text;
    background-clip: text;
    margin-top: 1rem;
    padding: 1rem;
    text-align: center;
    font-weight: 900;
    transition: all 0.3s ease-out;
  }

  button,
  a {
    display: inline-flex;
    color: darken(#fff, 5%);
    background-color: transparentize(#fff, .8);
    background-clip: padding-box;
    font-size: .875rem;
    font-weight: 600;
    letter-spacing: .06em;
    border: 0.25rem solid transparent;
    border-radius: 0.5rem;
    padding: 0.5rem;
    margin-bottom: 0.25rem;
    position: relative;
    text-shadow: 0 1px 0 Black;
    text-transform: uppercase;
    text-decoration: none;
    text-align: center;
    box-shadow: 0 1px 3px -1px Black;
    outline: 0;
    cursor: pointer;
    transition: all 0.15s ease-out;

    &:before {
      content: '';
      background-color: Blue;
      background-image: linear-gradient(to bottom right, transparentize(#ff6347, .45), transparentize(#ff6347, .9));
      border-radius: 0.5rem;
      position: absolute;
      top: -0.25rem;
      right: -0.25rem;
      bottom: -0.25rem;
      left: -0.25rem;
      z-index: -1;
      transition: background-color 0.15s linear;
    }
  
    &:hover {
      color: White;

      &:before {
        background-color: Purple;
      }
    }
  
    &.active {
      color: White;

      &:before {
        background-color: RoyalBlue;
      }
    }

    &:active {
      box-shadow: none;
    }
  }

  .blue {
    background-color: Aqua;
  }

  .red {
    background-color: Tomato;
  }

  .gold {
    background-color: Gold;
  }
}

footer {
  width: 100%;
  background-color: Purple;
  background-image: linear-gradient(to bottom right, transparentize(#ff6347, .45), transparentize(#ff6347, .9));
  padding: 1rem;
  text-align: center;
  
  a {
    color: White;
  }
}
View Compiled
(function (root, factory) {
  const pluginName = 'Classily'

  if (typeof define === 'function' && define.amd) {
    define([], factory(pluginName))
  } else if (typeof exports === 'object') {
    module.exports = factory(pluginName)
  } else {
    window[pluginName] = factory(pluginName)
  }
}(this, (pluginName) => {
  const defaults = {
    selector: '.js-classily'
  }

  /**
   * Merge defaults with user options
   * @param {Object} defaults Default settings
   * @param {Object} options User options
   */
  const extend = function (target, options) {
    const extended = {}

    Object.keys(defaults).forEach((prop) => {
      if (Object.prototype.hasOwnProperty.call(defaults, prop)) {
        extended[prop] = defaults[prop]
      }
    })

    Object.keys(options).forEach((prop) => {
      if (Object.prototype.hasOwnProperty.call(options, prop)) {
        extended[prop] = options[prop]
      }
    })

    return extended
  }

  /**
   @private
   * Find target elements and toggle classes
   * @param {Object} cur Current element (that users clicks on)
   * @param {String} sel Selectors for finding target elements
   * @param {String} cl Classes ti toggle on target elements
   */
  const toggleFunction = (cur, sel, cl) => {
    const $tar = Array.prototype.slice.call(document.querySelectorAll(sel))

    if (sel.indexOf('this') !== -1) {
      $tar.push(cur)
    }

    if ($tar) {
      const cls = cl.split(' ');
      
      for (let i = 0; i < $tar.length; i += 1) {
        for (let j = 0; j < cls.length; j += 1) {
          $tar[i].classList.toggle(cls[j])
        }
      }
    }
  }

  /**
   @private
   * Get config parameters from data attributes and pass them to toggle function
   * @param {Object} event Event object (click)
   */
  const toggleEvent = (event) => {
    if (event.currentTarget.getAttribute('data-prevent') === 'default') {
      event.preventDefault()
    }

    const selectors = event.currentTarget.getAttribute('data-target').split(',')
    const classes = event.currentTarget.getAttribute('data-class').split(',')

    if (selectors.length === classes.length) {
      selectors.forEach((currentSelector, j) => {
        toggleFunction(event.currentTarget, currentSelector.trim(), classes[j].trim())
      })
    } else {
      const targetSelector = selectors.map(selectorItem => selectorItem.trim()).join(',')
      const targetClass = classes.map(classItem => classItem.trim()).join(' ')
      toggleFunction(event.currentTarget, targetSelector, targetClass)
    }
  }

  /**
   * Plugin Object
   * @param {Object} options User options
   * @constructor
   */
  function Classily (options) {
    this.options = extend(defaults, options)
    this.init()
  }

  /**
   * Classily prototype
   * @public
   * @constructor
   */
  Classily.prototype = {
    init () {
      // Find all matching DOM elements
      this.selectors = document.querySelectorAll(this.options.selector)

      for (let i = 0; i < this.selectors.length; i += 1) {
        const selector = this.selectors[i]
        // Attach click event on matching DOM element and call toggle event
        selector.addEventListener('click', toggleEvent)
      }
    },
    destroy () {
      // Find all matching DOM elements
      this.selectors = document.querySelectorAll(this.options.selector)

      for (let i = 0; i < this.selectors.length; i += 1) {
        const selector = this.selectors[i]
        // Dettach click event on matching DOM element
        selector.removeEventListener('click', toggleEvent)
      }
    }
  }
  return Classily
}))

new Classily({
  selector: ".js-classily"
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.