Lightweight JavaScript range slider with no dependencies
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Additional UI features including tooltips, scale markers (pips), styling customization, and advanced interaction capabilities.
Scale markers that provide visual reference points along the slider track.
Adds or updates scale markers on the slider.
/**
* Add or update scale markers (pips) on the slider
* @param config - Pips configuration object
* @returns HTML element containing the pips
*/
slider.noUiSlider.pips(config: PipsConfig): HTMLElement;
interface PipsConfig {
mode: 'range' | 'steps' | 'positions' | 'count' | 'values';
values?: number[];
stepped?: boolean;
density?: number;
filter?: (value: number, type: number) => number;
format?: Formatter;
}Removes all scale markers from the slider.
/**
* Remove all scale markers from the slider
*/
slider.noUiSlider.removePips(): void;Usage Examples:
// Basic pips using range mode
slider.noUiSlider.pips({
mode: 'range',
density: 3
});
// Count mode - specific number of markers
slider.noUiSlider.pips({
mode: 'count',
values: 5, // 5 evenly spaced markers
density: 2
});
// Positions mode - markers at specific percentages
slider.noUiSlider.pips({
mode: 'positions',
values: [0, 25, 50, 75, 100],
density: 4
});
// Values mode - markers at specific values
slider.noUiSlider.pips({
mode: 'values',
values: [10, 25, 50, 75, 90],
density: 3
});
// Steps mode - markers at step intervals
slider.noUiSlider.pips({
mode: 'steps',
density: 2,
stepped: true
});
// Remove pips
slider.noUiSlider.removePips();Determines how pip positions are calculated.
'range': Distribute pips evenly across the range'steps': Place pips at step intervals'positions': Place pips at specific percentage positions'count': Place a specific number of evenly distributed pips'values': Place pips at specific valuesArray of values or positions (usage depends on mode).
// For 'positions' mode: percentage positions
values: [0, 25, 50, 75, 100]
// For 'values' mode: actual slider values
values: [100, 500, 1000, 2000]
// For 'count' mode: number of pips
values: 6 // or as single numberControls spacing and visibility of pips.
// Higher density = more pips shown
density: 1 // Show every pip
density: 2 // Show every other pip
density: 4 // Show every fourth pipWhether to align pips to step values (for 'range' and 'steps' modes).
stepped: true // Align to steps
stepped: false // Use exact positionsFunction to customize which pips are displayed.
filter?: (value: number, type: number) => number;
// Type values:
// -1: No pip
// 0: Normal pip
// 1: Large pip
// 2: Sub pip// Custom filter example
slider.noUiSlider.pips({
mode: 'range',
density: 2,
filter: function(value, type) {
// Hide pips for odd values
if (value % 2 === 1) {
return -1; // No pip
}
// Large pips for multiples of 10
if (value % 10 === 0) {
return 1; // Large pip
}
return 0; // Normal pip
}
});Custom formatting for pip labels.
slider.noUiSlider.pips({
mode: 'count',
values: 5,
format: {
to: function(value) {
return '$' + Math.round(value);
}
}
});Dynamic value display attached to slider handles.
Set during slider creation or via updateOptions.
tooltips?: boolean | Formatter | (boolean | Formatter)[];Removes all tooltips from the slider.
/**
* Remove all tooltips from the slider
*/
slider.noUiSlider.removeTooltips(): void;Returns array of tooltip elements.
/**
* Get array of tooltip DOM elements
* @returns Array of tooltip elements (null for handles without tooltips)
*/
slider.noUiSlider.getTooltips(): HTMLElement[];Usage Examples:
// Enable default tooltips
noUiSlider.create(element, {
start: [20, 80],
range: { min: 0, max: 100 },
tooltips: true
});
// Custom tooltip formatting
noUiSlider.create(element, {
start: [1000, 3000],
range: { min: 0, max: 5000 },
tooltips: {
to: function(value) {
return '$' + Math.round(value).toLocaleString();
}
}
});
// Per-handle tooltip configuration
noUiSlider.create(element, {
start: [20, 50, 80],
range: { min: 0, max: 100 },
tooltips: [
true, // Default tooltip for first handle
false, // No tooltip for second handle
{ to: value => value.toFixed(1) + '%' } // Custom for third handle
]
});
// Access and modify tooltips
const tooltips = slider.noUiSlider.getTooltips();
tooltips[0].style.backgroundColor = 'red';
// Remove tooltips
slider.noUiSlider.removeTooltips();Access to slider handle elements for advanced customization.
Returns array of handle origin elements.
/**
* Get array of handle origin DOM elements
* @returns Array of handle origin elements
*/
slider.noUiSlider.getOrigins(): HTMLElement[];Usage Examples:
// Get handle elements
const handles = slider.noUiSlider.getOrigins();
// Customize handle appearance
handles[0].style.backgroundColor = 'red';
handles[1].style.backgroundColor = 'blue';
// Add custom event listeners to handles
handles.forEach((handle, index) => {
handle.addEventListener('mouseenter', function() {
console.log('Hovering over handle', index);
});
});
// Add custom content to handles
handles[0].innerHTML = '<span class="handle-label">Min</span>';
handles[1].innerHTML = '<span class="handle-label">Max</span>';Access to default CSS classes for styling customization.
noUiSlider.cssClasses: CSSClasses;Usage Examples:
// Access default CSS classes
console.log(noUiSlider.cssClasses.handle); // "handle"
console.log(noUiSlider.cssClasses.connect); // "connect"
// Use for custom styling
const handleClass = 'noUi-' + noUiSlider.cssClasses.handle;
document.querySelectorAll('.' + handleClass).forEach(handle => {
handle.style.borderRadius = '50%';
});Override default CSS classes during creation.
noUiSlider.create(element, {
start: [20, 80],
range: { min: 0, max: 100 },
cssPrefix: 'custom-',
cssClasses: {
target: 'slider',
base: 'slider-base',
handle: 'slider-handle',
connect: 'slider-connect'
}
});Reference to the original DOM element.
/**
* Reference to the slider's target DOM element
*/
slider.noUiSlider.target: HTMLElement;Usage Examples:
// Access target element
const targetElement = slider.noUiSlider.target;
// Add custom classes
targetElement.classList.add('premium-slider');
// Custom styling
targetElement.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';
// Data attributes
targetElement.setAttribute('data-slider-id', 'price-range');// Currency pips with custom labels
slider.noUiSlider.pips({
mode: 'values',
values: [0, 1000, 2500, 5000, 10000],
format: {
to: function(value) {
if (value >= 1000) {
return '$' + (value / 1000) + 'k';
}
return '$' + value;
}
},
filter: function(value, type) {
// Large pips for major values
if ([0, 5000, 10000].includes(value)) {
return 1; // Large pip
}
return 0; // Normal pip
}
});// Create slider with tooltips
noUiSlider.create(element, {
start: [20, 80],
range: { min: 0, max: 100 },
tooltips: true,
connect: true
});
// Enhance tooltips with click handlers
const tooltips = slider.noUiSlider.getTooltips();
tooltips.forEach((tooltip, index) => {
if (tooltip) {
tooltip.addEventListener('click', function() {
const newValue = prompt('Enter new value:');
if (newValue !== null) {
slider.noUiSlider.setHandle(index, parseFloat(newValue));
}
});
tooltip.style.cursor = 'pointer';
tooltip.title = 'Click to edit value';
}
});// Update pips based on zoom level
function updatePipsForZoom(zoomLevel) {
const pipConfigs = {
1: { mode: 'count', values: 5, density: 2 },
2: { mode: 'count', values: 10, density: 3 },
3: { mode: 'range', density: 1 }
};
slider.noUiSlider.removePips();
slider.noUiSlider.pips(pipConfigs[zoomLevel]);
}
// Zoom controls
document.getElementById('zoom-in').addEventListener('click', () => {
currentZoom = Math.min(3, currentZoom + 1);
updatePipsForZoom(currentZoom);
});// Add persistent labels to handles
const handles = slider.noUiSlider.getOrigins();
const labels = ['Minimum', 'Maximum'];
handles.forEach((handle, index) => {
const label = document.createElement('div');
label.className = 'handle-label';
label.textContent = labels[index];
label.style.position = 'absolute';
label.style.top = '-30px';
label.style.left = '50%';
label.style.transform = 'translateX(-50%)';
label.style.fontSize = '12px';
label.style.fontWeight = 'bold';
handle.appendChild(label);
});// Visual progress indicator
function updateProgress() {
const values = slider.noUiSlider.get();
const progress = parseFloat(values[0]); // Use first handle
const maxValue = slider.noUiSlider.options.range.max;
const percentage = (progress / maxValue) * 100;
// Update progress bar
document.getElementById('progress-bar').style.width = percentage + '%';
// Update progress text
document.getElementById('progress-text').textContent =
Math.round(percentage) + '% Complete';
}
slider.noUiSlider.on('update', updateProgress);// Style based on values
function updateSliderStyling(values) {
const range = parseFloat(values[1]) - parseFloat(values[0]);
const target = slider.noUiSlider.target;
// Remove previous state classes
target.classList.remove('narrow-range', 'wide-range', 'full-range');
// Add appropriate class based on range
if (range < 20) {
target.classList.add('narrow-range');
} else if (range > 80) {
target.classList.add('wide-range');
} else if (range === 100) {
target.classList.add('full-range');
}
}
slider.noUiSlider.on('update', function(values) {
updateSliderStyling(values);
});// Enhance accessibility
const handles = slider.noUiSlider.getOrigins();
handles.forEach((handle, index) => {
const handleElement = handle.querySelector('.noUi-handle');
// Custom aria labels
handleElement.setAttribute('aria-label', `Handle ${index + 1}`);
// Descriptive titles
handleElement.setAttribute('title', `Drag to adjust value ${index + 1}`);
});
// Update aria-valuetext with formatted values
slider.noUiSlider.on('update', function(values, handle) {
const handleElement = handles[handle].querySelector('.noUi-handle');
handleElement.setAttribute('aria-valuetext',
`Current value: ${values[handle]}`);
});Install with Tessl CLI
npx tessl i tessl/npm-nouislider