Range Group

@use 'sprucecss/scss/spruce' as *;

.range-group {
  display: flex;
  flex-direction: column;
  gap: spacer('xs');

  .form-label {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: spacer('xs');
    justify-content: space-between;

    &__value {
      font-weight: 400;
    }
  }

  &__inner {
    align-items: center;
    display: flex;
    gap: spacer('xs');

    .form-range {
      flex-grow: 1;
      margin-block-start: 0;
    }

    .form-range-control {
      flex-shrink: 0;
    }
  }
}
<div class="range-group">
    <label class="form-label" for="quantity">
        <span class="form-label__title">Quantity</span>
        <span class="form-label__value" data-suffix="piece">1 piece</span>
    </label>
    <div class="range-group__inner">
        <button class="btn btn--primary btn--sm btn--icon" data-action="decrement" aria-label="Decrement quantity">
            <svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
                <line x1="5" y1="12" x2="19" y2="12"></line>
            </svg>
        </button>
        <input class="form-range range-group__control" id="quantity" type="range" min="1" max="99" value="1" step="1"/>
        <button class="btn btn--primary btn--sm btn--icon" data-action="increment" aria-label="Increment quantity">
            <svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
                <line class="vertical-line" x1="12" y1="5" x2="12" y2="19"></line>
                <line x1="5" y1="12" x2="19" y2="12"></line>
            </svg>
        </button>
    </div>
</div>
(() => {
  const initializeRangeGroup = (rangeGroup) => {
    const decrement = rangeGroup.querySelector('[data-action="decrement"]');
    const increment = rangeGroup.querySelector('[data-action="increment"]');
    const label = rangeGroup.querySelector('.form-label__value');
    const suffix = label.getAttribute('data-suffix') || '';
    const control = rangeGroup.querySelector('.range-group__control');
    const step = parseInt(control.getAttribute('step'), 10) || 1;
    const min = parseInt(control.getAttribute('min'), 10) || 0;
    const max = parseInt(control.getAttribute('max'), 10) || 100;

    const checkDisabledState = (value) => {
      decrement.disabled = min === value;
      increment.disabled = max === value;
    };

    const updateValues = (value) => {
      control.value = value;
      label.textContent = `${value} ${suffix}`;
      checkDisabledState(value);
    };

    control.addEventListener('input', (e) => {
      updateValues(parseInt(e.target.value, 10));
    });

    decrement.addEventListener('click', () => {
      if (control.value > min) {
        updateValues(parseInt(control.value, 10) - step);
      }
    });

    increment.addEventListener('click', () => {
      if (control.value < max) {
        updateValues(parseInt(control.value, 10) + step);
      }
    });

    updateValues(parseInt(control.value, 10));
  };

  const rangeGroups = document.querySelectorAll('.range-group');

  if (rangeGroups.length) {
    rangeGroups.forEach(initializeRangeGroup);
  }
})();

Range Group

The range-group component helps us to create a more complete range field with a value display and button control.

Dependencies

  • Alpine.js - Some or all of the components require Alpine.js for functionality.

Technical Details

  • It uses native Spruce elements and styling, so the component is mainly about a small JS.
  • On the .form-label__value element, you can set a data-suffix value to add a unit at the end of the value display.
  • It automatically gets the step, min, and max values.
  • You should always use a proper aria-label value.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our extensive UI library built with Spruce CSS.

Blog

Read about front-end development and concepts of Spruce CSS.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.