← Back toDrag and drop in React

Create a range slider

Written byPhuoc Nguyen
Created
30 Oct, 2023
Last updated
10 Nov, 2023
Tags
HTML 5 range input, React range slider
Range sliders are incredibly versatile tools that have a wide range of applications in various fields. For instance, online shopping websites use range sliders to help customers filter products based on their price range. This makes it easier for customers to find products that fit their budget and preferences.
In addition to online shopping, range sliders are also used in audio and video editing software to enable users to select specific portions of audio or video files for editing purposes.
Moreover, range sliders can also be used to adjust the sound volume in media players and music apps, making it easier for users to control the volume with a simple drag of a slider. The slider can be customized to fit the desired volume range, allowing users to fine-tune the sound output according to their preferences.
These are just a few examples of how range sliders can be used in real-world applications. In this post, we'll learn how to develop a range slider component in React. So, let's get started!

The HTML 5 range input

Did you know that HTML has a built-in range input? It's easy to use, just set the `type` attribute to `range`:
html
<input type="range" />
Although the HTML 5 range input is useful in certain situations, it has its limitations. For example, customizing the slider's appearance beyond basic styling options can be difficult. Additionally, it doesn't support multiple thumbs or vertical orientation.
To overcome these limitations and provide more flexibility, React allows us to create a custom range slider component. With this approach, we have complete control over the slider's appearance and functionality, and we can add features like support for vertical orientation.

Setting up the layout

To create a slider, you need three components: a knob, and two sides positioned on the left and right of the knob.
tsx
<div className="slider">
<div className="slider__first" />
<div className="slider__knob" />
<img className="slider__second" />
</div>
To make our slider look and function perfectly, we use a flexible box layout, also known as flexbox. First, we set the `slider` element to display as a flex container with its children aligned vertically in the center using `align-items: center`. Then, we give the `slider__first` element a fixed width of 20%, while the `slider__second` element takes up the remaining space using `flex: 1`. However, we might need to adjust the width of the first part depending on the initial value of the slider component.
This ensures that the right element stretches to fill all available space to the right of the knob. To ensure a consistent size, we give the entire slider a height of 1.5rem.
With flexbox, our slider will be perfectly organized and functional.
css
.slider {
align-items: center;
display: flex;

height: 1.5rem;
}
.slider__first {
width: 20%;
}
.slider__second {
flex: 1;
}
To make it easier to tell the first and second parts of the slider apart, we can give them different background colors. For example, we can use `background-color: rgb(99 102 241)` to give the `slider__first` element a blue color and `background-color: rgb(203 213 225)` to give the `slider__second` element a light gray color. This simple styling trick will make our slider look more polished and user-friendly.
css
.slider__first {
background-color: rgb(99 102 241);
}
.slider__second {
background-color: rgb(203 213 225);
}
To position the knob inside the slider, we use absolute positioning. First, we set the `position` property of the slider element to `relative`, making it a positioned element. Then, we add a `slider__knob` div as a child of the slider element and set its `position` property to `absolute`. This removes it from the normal flow of elements, allowing us to position it independently.
To center the knob horizontally, we use `transform: translateX(-50%)`. This moves the knob left by 50% of its own width, effectively centering it within its parent element. To position the knob vertically, we can use either `top` or `bottom`, depending on whether we want it at the top or bottom of the slider. For example, to position it at the top, we can set `top: 0`.
css
.slider {
position: relative;
}
.slider__knob {
height: 1rem;
width: 1rem;

position: absolute;
top: 0;
left: 0;
transform: translateX(-50%);
}
To give the slider knob a circular appearance, we can add `border-radius: 9999px` to the `.slider__knob` class. This will round the edges of the knob and create a circular shape. We can also set the `background-color` property of the knob to match the color of the first part of the slider. This will create a more visually appealing and cohesive slider component.
css
.slider__knob {
border-radius: 9999px;
background-color: rgb(99 102 241);
}
The layout looks great even without any interactions. Don't worry about the `translateX(8rem)` transform added to the knob element. It's just for demonstration purposes.

Adjusting the range slider

To adjust the value of a range slider, users typically drag the knob and drop it at the desired position. In our previous posts, we outlined an approach that uses the `useRef()` hook to create a reference to the knob element and attaches it to the knob via the `ref` attribute.
To support both desktop and touchscreen devices, we handled the `mousedown` and `touchstart` events, respectively. We also used internal states that contain `dx` and `dy` properties to keep track of how far the mouse has been moved. These properties determine the horizontal and vertical distance the mouse has been moved.
Here's a code snippet to refresh your memory on what we've accomplished so far:
RangeSlider.tsx
const knobRef = React.useRef();

const [{ dx, dy }, setOffset] = React.useState({
dx: 0,
dy: 0,
});

// Render
return (
<div
className="slider__knob"
ref={knobRef}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
);
We update the `dx` and `dy` states whenever users move the mouse. Whenever these values change, we adjust the position of the knob element accordingly.
However, when it comes to our slider, there are a couple of differences compared to creating a draggable element. First, we must limit the `dx` to the range of 0 and the width of the container, as users cannot drop the knob outside of it. Second, users can only drag the knob horizontally, so we do not need to track the `dy` state.
Here's a code snippet to update the knob element. The `containerRef` variable is the React reference that represents the entire slider container.
RangeSlider.tsx
React.useEffect(() => {
const container = containerRef.current;
const knobEle = knobRef.current;

const containerWidth = container.getBoundingClientRect().width;
const delta = Math.min(Math.max(0, dx), containerWidth);
knobEle.style.transform = `translate3d(${delta}px, 0, 0)`;
}, [dx]);
To prevent the knob from being dragged outside of the slider container, we can use the `Math.min` and `Math.max` functions. We use 0 as the lower bound and the width of the container as the upper bound. This ensures that the knob stays within the container's width.
Once we've calculated the delta value, which determines how far we want to move the knob horizontally, we update its position using a CSS transform. Specifically, we set its `transform` property to `translate3d(${delta}px, 0, 0)`. This moves the knob by delta pixels along the x-axis while keeping its y- and z-coordinates unchanged.
By updating this position whenever `dx` changes, we ensure that our slider component responds smoothly and accurately to user input.
Finally, we need to update the width of the first half element based on the delta and container width. To get this done, we use a `useRef()` hook to create a `firstHalfRef` reference. This reference helps us retrieve the current value and set its `width` property.
We calculate the percentage value by dividing the delta by container width, multiplying by 100, and appending '%' to create a valid CSS value. This ensures that the first half element resizes dynamically as users drag the knob back and forth along the slider.
RangeSlider.tsx
const firstHalfEle = firstHalfRef.current;
firstHalfEle.style.width = `${(delta * 100) / containerWidth}%`;
To see how it works, simply drag and drop the knob in the demo below. It's that easy!

Resolving the flickering issue

We have discovered that to show users that they can move the knob, we can set the `cursor` style to `move`.
css
.slider__knob {
cursor: move;
}
When the user moves their mouse around, the default cursor shows up since the mouse isn't hovering over the resizer. This causes the screen to flicker as the cursor keeps changing.
To solve this issue, we can set the cursor for the entire page when the user moves their mouse.
RangeSlider.tsx
const knobEle = knobRef.current;
knobEle.style.cursor = "move";
document.body.style.cursor = "move";
To make it clear that users can drag the slider with their mouse or touch input, we can set the cursor style of both the resizer and the `body` element to `move`. This will give users a clear indication that they can interact with the slider.
When designing a range slider component, it's crucial to prioritize the user experience. We want users to interact with the slider smoothly and without any hiccups. One common issue is accidentally selecting content in the first and second parts of the slider while dragging the knob.
To tackle this problem, we can use CSS styles like `userSelect` and `pointerEvents`. By setting these styles to `none`, we disable user selection and pointer events on the slider's elements.
Disabling user selection prevents users from accidentally selecting text or other content within the slider while dragging the knob. Disabling pointer events ensures that any click or hover events are ignored on those elements when they are dragged, making it easier for users to interact with the slider without interruption.
By using these simple styles, we can create a more seamless and intuitive user experience for our range slider component.
RangeSlider.tsx
const firstHalfEle = firstHalfRef.current;
const secondHalfEle = secondHalfRef.current;

firstHalfEle.style.userSelect = "none";
firstHalfEle.style.pointerEvents = "none";
secondHalfEle.style.userSelect = "none";
secondHalfEle.style.pointerEvents = "none";
Similarly, when the user releases the mouse, we must reset the cursor.
RangeSlider.tsx
const knobEle = knobRef.current;
knobEle.style.removeProperty('cursor');
document.body.style.removeProperty('cursor');
To see the complete implementation, check out the `updateCursor` and `resetCursor` functions in the demo below.
Check out the final demo below:

Conclusion

In this post, we explored how to create a custom range slider component using React and CSS. First, we broke down the HTML structure of the slider and styled it to make it look polished.
Next, we learned how to update the slider value by tracking user input and adjusting the knob element's position accordingly. We also discovered how to limit the knob's movement within the slider container and dynamically update the width of the first half element.
Finally, we tackled some common issues that can arise when designing a range slider, such as flickering cursors and accidental selection of content while dragging the knob. By using CSS styles like `userSelect` and `pointerEvents`, we created a more seamless user experience.
With these techniques in mind, you can now design your own custom sliders that are both functional and visually appealing. Keep experimenting with different designs and functionality to create sliders that work best for your users!

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn