Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <!-- most basic, used for Knobs demo -->
<div class="range-slider" style='--min:0; --max:100; --step:5; --value:75; --text-value:"75";'>
  <input type="range" min="0" max="100" step="5" value="75" oninput="this.parentNode.style.setProperty('--value',this.value); this.parentNode.style.setProperty('--text-value', JSON.stringify(this.value))">
  <output></output>
  <div class='range-slider__progress'></div>
</div>

<!-- Single slider (with shadows) -->
<div class="range-slider grad" style='--min:0; --max:10000; --step:100; --value:200; --text-value:"200"; --prefix:"$"'>
  <input type="range" min="0" max="10000" step="100" value="200" oninput="this.parentNode.style.setProperty('--value',this.value); this.parentNode.style.setProperty('--text-value', JSON.stringify((+this.value).toLocaleString()))">
  <output></output>
  <div class='range-slider__progress'></div>
</div>

<!-- Double range slider (flat design)  -->
<div class="range-slider flat" data-ticks-position='top' style='--min:-500; --max:500; --value-a:-220; --value-b:400; --suffix:"%"; --text-value-a:"-220"; --text-value-b:"400";'>
  <input type="range" min="-500" max="500" value="-220" oninput="this.parentNode.style.setProperty('--value-a',this.value); this.parentNode.style.setProperty('--text-value-a', JSON.stringify(this.value))">
  <output></output>
  <input type="range" min="-500" max="500" value="400" oninput="this.parentNode.style.setProperty('--value-b',this.value); this.parentNode.style.setProperty('--text-value-b', JSON.stringify(this.value))">
  <output></output>
  <div class='range-slider__progress'></div>
</div>

<!-- Double range slider with different CSS variables & all possible values printed across -->
<div class="range-slider overlay" style='--min:0; --max:10; --value-a:2; --value-b:5; --text-value-a:"2"; --text-value-b:"5";'>
  <input type="range" min="0" max="10" value="2" oninput="this.parentNode.style.setProperty('--value-a',this.value); this.parentNode.style.setProperty('--text-value-a', JSON.stringify(this.value))">
  <input type="range" min="0" max="10" value="5" oninput="this.parentNode.style.setProperty('--value-b',this.value); this.parentNode.style.setProperty('--text-value-b', JSON.stringify(this.value))">
  <div class='range-slider__values'>0 1 2 3 4 5 6 7 8 9 10</div>
  <div class='range-slider__progress'></div>
</div>
              
            
!

CSS

              
                ////////////////////////////////////////////////
// Gradient slider demo
.range-slider.grad {
  --progress-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2) inset;
  --progress-flll-shadow: var(--progress-shadow);
  --fill-color: linear-gradient(to right, LightCyan, var(--primary-color));
  --thumb-shadow: 0 0 4px rgba(0, 0, 0, 0.3),
    -3px 9px 9px rgba(255, 255, 255, 0.33) inset,
    -1px 3px 2px rgba(255, 255, 255, 0.33) inset,
    0 0 0 99px var(--primary-color) inset;

  input {
    &:hover {
      --thumb-transform: scale(1.2);
    }

    &:active {
      --thumb-shadow: inherit;
      --thumb-transform: scale(1);
    }
  }
}

////////////////////////////////////////////////
// Flat double-range slider demo

.range-slider.flat {
  --thumb-size: 25px;
  --track-height: calc(var(--thumb-size) / 3);
  --progress-shadow: none;
  --progress-flll-shadow: none;
  --thumb-shadow: 0 0 0 7px var(--primary-color) inset, 0 0 0 99px white inset;
  --thumb-shadow-hover: 0 0 0 9px var(--primary-color) inset,
    0 0 0 99px white inset;
  --thumb-shadow-active: 0 0 0 13px var(--primary-color) inset;
}

////////////////////////////////////////////////
// Double-range slider demo

.range-slider.overlay {
  --primary-color: #d6e9ff;
  --track-height: 50px;
  --thumb-size: var(--track-height);
  --thumb-color: var(--primary-color);
  --thumb-shadow: none;
  --progress-flll-shadow: none;
  --progress-shadow: none;
  --progress-background: none;
  --progress-radius: 0px;
  --ticks-color: var(--primary-color);
  --ticks-height: 0;
  --ticks-thickness: 0;
  --ticks-gap: 0px; // must specify units
  --min-max-font: 700 18px Arial;
  --min-max-opacity: 1;
  --show-min-max: none;

  color: #0366d6;

  input {
    &:hover {
      // cannot use "--thumb-shadow-hover" because the "--is-left-most" variable is needed (which is declared per-input)
      // "--is-left-most" is either "0" or "1"
      --thumb-shadow: calc(25px - (50px * var(--is-left-most))) 0 0 -15px #0366d6
        inset;
    }

    &:active {
      --thumb-color: inherit;
    }
  }

  .range-slider {
    &__values {
      width: calc(100% - 50% / (var(--max) - var(--min)));
    }
  }
}

////////////////////////////////////////////////
// The main styles

.range-slider {
  --primary-color: #0366d6;

  --value-offset-y: var(--ticks-gap);
  --value-active-color: white;
  --value-background: transparent;
  --value-background-hover: var(--primary-color);
  --value-font: 700 12px/1 Arial;

  --fill-color: var(--primary-color);
  --progress-background: #eee;
  --progress-radius: 20px;
  --track-height: calc(var(--thumb-size) / 2);

  --min-max-font: 12px Arial;
  --min-max-opacity: 0.5;
  --min-max-x-offset: 10%; // 50% to center

  --thumb-size: 22px;
  --thumb-color: white;
  --thumb-shadow: 0 0 3px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.5) inset,
    0 0 0 99px var(--thumb-color) inset;

  --thumb-shadow-active: 0 0 0 calc(var(--thumb-size) / 4) inset
      var(--thumb-color),
    0 0 0 99px var(--primary-color) inset, 0 0 3px rgba(0, 0, 0, 0.4);

  --thumb-shadow-hover: var(--thumb-shadow);

  --ticks-thickness: 1px;
  --ticks-height: 5px;
  --ticks-gap: var(
    --ticks-height,
    0
  ); // vertical space between the ticks and the progress bar
  --ticks-color: silver;

  // ⚠️ BELOW VARIABLES SHOULD NOT BE CHANGED
  --step: 1;
  --ticks-count: Calc(var(--max) - var(--min)) / var(--step);
  --maxTicksAllowed: 30;
  --too-many-ticks: Min(1, Max(var(--ticks-count) - var(--maxTicksAllowed), 0));
  --x-step: Max(
    var(--step),
    var(--too-many-ticks) * (var(--max) - var(--min))
  ); // manipulate the number of steps if too many ticks exist, so there would only be 2
  --tickInterval: 100/ ((var(--max) - var(--min)) / var(--step)) * var(--tickEvery, 1);
  --tickIntervalPerc: calc(
    (100% - var(--thumb-size)) / ((var(--max) - var(--min)) / var(--x-step)) *
      var(--tickEvery, 1)
  );

  --value-a: Clamp(
    var(--min),
    var(--value, 0),
    var(--max)
  ); // default value ("--value" is used in single-range markup)
  --value-b: var(--value, 0); // default value
  --text-value-a: var(--text-value, "");

  --completed-a: calc(
    (var(--value-a) - var(--min)) / (var(--max) - var(--min)) * 100
  );
  --completed-b: calc(
    (var(--value-b) - var(--min)) / (var(--max) - var(--min)) * 100
  );
  --ca: Min(var(--completed-a), var(--completed-b));
  --cb: Max(var(--completed-a), var(--completed-b));

  // breakdown of the below super-complex brain-breaking CSS math:
  // "clamp" is used to ensure either "-1" or "1"
  // "calc" is used to inflat the outcome into a huge number, to get rid of any value between -1 & 1
  // if absolute diff of both completed % is above "5" (%)
  // ".001" bumps the value just a bit, to avoid a scenario where calc resulted in "0" (then clamp will also be "0")
  --thumbs-too-close: Clamp(
    -1,
    1000 * (Min(1, Max(var(--cb) - var(--ca) - 5, -1)) + 0.001),
    1
  );
  --thumb-close-to-min: Min(1, Max(var(--ca) - 2, 0)); // 2% threshold
  --thumb-close-to-max: Min(1, Max(98 - var(--cb), 0)); // 2% threshold

  @mixin thumb {
    appearance: none;
    height: var(--thumb-size);
    width: var(--thumb-size);
    transform: var(--thumb-transform);
    border-radius: var(--thumb-radius, 50%);
    background: var(--thumb-color);
    box-shadow: var(--thumb-shadow);
    border: none;
    pointer-events: auto;
    transition: 0.1s;
  }

  display: inline-block;
  height: Max(var(--track-height), var(--thumb-size));
  // margin: calc((var(--thumb-size) - var(--track-height)) * -.25) var(--thumb-size) 0;
  background: linear-gradient(
      to right,
      var(--ticks-color) var(--ticks-thickness),
      transparent 1px
    )
    repeat-x;
  background-size: var(--tickIntervalPerc) var(--ticks-height);
  background-position-x: calc(
    var(--thumb-size) / 2 - var(--ticks-thickness) / 2
  );
  background-position-y: var(--flip-y, bottom);

  padding-bottom: var(--flip-y, var(--ticks-gap));
  padding-top: calc(var(--flip-y) * var(--ticks-gap));

  position: relative;
  z-index: 1;

  &[data-ticks-position="top"] {
    --flip-y: 1;
  }

  // mix/max texts
  &::before,
  &::after {
    --offset: calc(var(--thumb-size) / 2);
    content: counter(x);
    display: var(--show-min-max, block);
    font: var(--min-max-font);
    position: absolute;
    bottom: var(--flip-y, -2.5ch);
    top: calc(-2.5ch * var(--flip-y));
    opacity: Clamp(0, var(--at-edge), var(--min-max-opacity));
    transform: translateX(
        calc(var(--min-max-x-offset) * var(--before, -1) * -1)
      )
      scale(var(--at-edge));
    pointer-events: none;
  }

  &::before {
    --before: 1;
    --at-edge: var(--thumb-close-to-min);
    counter-reset: x var(--min);
    left: var(--offset);
  }

  &::after {
    --at-edge: var(--thumb-close-to-max);
    counter-reset: x var(--max);
    right: var(--offset);
  }

  &__values {
    position: relative;
    top: 50%;
    line-height: 0;
    text-align: justify;
    width: 100%;
    pointer-events: none;
    margin: 0 auto;
    z-index: 5;

    // trick so "justify" will work
    &::after {
      content: "";
      width: 100%;
      display: inline-block;
      height: 0;
      background: red;
    }
  }

  &__progress {
    --start-end: calc(var(--thumb-size) / 2);
    --clip-end: calc(100% - (var(--cb)) * 1%);
    --clip-start: calc(var(--ca) * 1%);
    --clip: inset(-20px var(--clip-end) -20px var(--clip-start));
    position: absolute;
    left: var(--start-end);
    right: var(--start-end);
    top: calc(
      var(--ticks-gap) * var(--flip-y, 0) + var(--thumb-size) / 2 -
        var(--track-height) / 2
    );
    //  transform: var(--flip-y, translateY(-50%) translateZ(0));
    height: calc(var(--track-height));
    background: var(--progress-background, #eee);
    pointer-events: none;
    z-index: -1;
    border-radius: var(--progress-radius);

    // fill area
    &::before {
      content: "";
      position: absolute;
      // left: Clamp(0%, calc(var(--ca) * 1%), 100%); // confine to 0 or above
      // width: Min(100%, calc((var(--cb) - var(--ca)) * 1%)); // confine to maximum 100%
      left: 0;
      right: 0;
      clip-path: var(--clip);
      top: 0;
      bottom: 0;
      background: var(--fill-color, black);
      box-shadow: var(--progress-flll-shadow);
      z-index: 1;
      border-radius: inherit;
    }

    // shadow-effect
    &::after {
      content: "";
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      box-shadow: var(--progress-shadow);
      pointer-events: none;
      border-radius: inherit;
    }
  }

  & > input {
    -webkit-appearance: none;
    width: 100%;
    height: var(--thumb-size);
    margin: 0;
    position: absolute;
    left: 0;
    top: calc(
      50% - Max(var(--track-height), var(--thumb-size)) / 2 +
        calc(var(--ticks-gap) / 2 * var(--flip-y, -1))
    );
    cursor: -webkit-grab;
    cursor: grab;
    outline: none;
    background: none;

    &:not(:only-of-type) {
      pointer-events: none;
    }

    &::-webkit-slider-thumb {
      @include thumb;
    }
    &::-moz-range-thumb {
      @include thumb;
    }
    &::-ms-thumb {
      @include thumb;
    }

    &:hover {
      --thumb-shadow: var(--thumb-shadow-hover);
      & + output {
        --value-background: var(--value-background-hover);
        --y-offset: -5px;
        color: var(--value-active-color);
        box-shadow: 0 0 0 3px var(--value-background);
      }
    }

    &:active {
      --thumb-shadow: var(--thumb-shadow-active);
      cursor: grabbing;
      z-index: 2; // when sliding left thumb over the right or vice-versa, make sure the moved thumb is on top
      + output {
        transition: 0s;
      }
    }

    &:nth-of-type(1) {
      --is-left-most: Clamp(0, (var(--value-a) - var(--value-b)) * 99999, 1);
      & + output {
        &:not(:only-of-type) {
          --flip: calc(var(--thumbs-too-close) * -1);
        }

        --value: var(--value-a);
        --x-offset: calc(var(--completed-a) * -1%);
        &::after {
          content: var(--prefix, "") var(--text-value-a) var(--suffix, "");
        }
      }
    }

    &:nth-of-type(2) {
      --is-left-most: Clamp(0, (var(--value-b) - var(--value-a)) * 99999, 1);
      & + output {
        --value: var(--value-b);
      }
    }

    // non-multiple range should not clip start of progress bar
    &:only-of-type {
      ~ .range-slider__progress {
        --clip-start: 0;
      }
    }

    & + output {
      --flip: -1;
      --x-offset: calc(var(--completed-b) * -1%);
      --pos: calc(
        ((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%
      );

      pointer-events: none;
      position: absolute;
      z-index: 5;
      background: var(--value-background);
      border-radius: 10px;
      padding: 2px 4px;
      left: var(--pos);
      transform: translate(
        var(--x-offset),
        calc(
          150% * var(--flip) - (var(--y-offset, 0px) + var(--value-offset-y)) *
            var(--flip)
        )
      );
      transition: all 0.12s ease-out, left 0s;

      &::after {
        content: var(--prefix, "") var(--text-value-b) var(--suffix, "");
        font: var(--value-font);
      }
    }
  }
}

////////////////////////////////////////////////
// PAGE SETUP

body {
  height: 100vh;
  display: grid;
  place-content: space-evenly;
  place-items: center;
  gap: 10%;
  padding: 0;

  @media screen and (max-width: 500px) {
    padding-top: 2em;
    gap: 8%;
  }
}

a {
  position: fixed;
  top: 1em;
  left: 1em;
  display: inline-block;
  height: 2em;

  @media screen and (max-width: 500px) {
    position: static;
    order: -1;
  }

  > img {
    display: inherit;
    height: 100%;
  }
}

body > .range-slider,
label[dir="rtl"] .range-slider {
  width: Clamp(300px, 50vw, 800px);
  min-width: 200px;
}

              
            
!

JS

              
                document.body.insertAdjacentHTML('afterbegin', `<a href="https://github.com/yairEO/ui-range" target="_blank"><img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Logo.png"/></a>`)

// These slider range component was used in my other component:
// https://github.com/yairEO/color-picker

var settings = {
  visible: 0, 
  theme: {
    backgroud: "rgba(0,0,0,.9)",
  },
  CSSVarTarget: document.querySelector('.range-slider'),
  knobs: [
    "Thumb",
    {
      cssVar: ['thumb-size', 'px'],
      label: 'thumb-size',
      type: 'range',
      min: 6, max: 33 //  value: 16,
    },
    "Value",
    {
      cssVar: ['value-active-color'], // alias for the CSS variable
      label: 'value active color',
      type: 'color',
      value: 'white'
    },
    {
      cssVar: ['value-background'], // alias for the CSS variable
      label: 'value-background',
      type: 'color',
    },
    {
      cssVar: ['value-background-hover'], // alias for the CSS variable
      label: 'value-background-hover',
      type: 'color',
    },
    {
      cssVar: ['primary-color'], // alias for the CSS variable
      label: 'primary-color',
      type: 'color',
    },
    {
      cssVar: ['value-offset-y', 'px'],
      label: 'value-offset-y',
      type: 'range', value: 5, min: -10, max: 20
    },
    "Track",
    {
      cssVar: ['track-height', 'px'],
      label: 'track-height',
      type: 'range', value: 8, min: 6, max: 33
    },
    {
      cssVar: ['progress-radius', 'px'],
      label: 'progress-radius',
      type: 'range', value: 20, min: 0, max: 33
    },
    {
      cssVar: ['progress-color'], // alias for the CSS variable
      label: 'progress-color',
      type: 'color',
      value: '#EEEEEE'
    },
    {
      cssVar: ['fill-color'], // alias for the CSS variable
      label: 'fill-color',
      type: 'color',
      value: '#0366D6'
    },
    "Ticks",
    {
      cssVar: ['show-min-max'],
      label: 'hide min/max',
      type: 'checkbox',
      value: 'none'
    },
    {
      cssVar: ['ticks-thickness', 'px'],
      label: 'ticks-thickness',
      type: 'range',
      value: 1, min: 0, max: 10
    },
    {
      cssVar: ['ticks-height', 'px'],
      label: 'ticks-height',
      type: 'range',
      value: 5, min: 0, max: 15
    },
    {
      cssVar: ['ticks-gap', 'px'],
      label: 'ticks-gap',
      type: 'range',
      value: 5, min: 0, max: 15
    },
    {
      cssVar: ['min-max-x-offset', '%'],
      label: 'min-max-x-offset',
      type: 'range',
      value: 10, step: 1, min: 0, max: 100
    },
    {
      cssVar: ['min-max-opacity'],
      label: 'min-max-opacity',
      type: 'range', value: .5, step: .1, min: 0, max: 1
    },
    {
      cssVar: ['ticks-color'], // alias for the CSS variable
      label: 'ticks-color',
      type: 'color',
      value: '#AAAAAA'
    },
  ]
}

new Knobs(settings)


              
            
!
999px

Console