For decades, scrollbars have been a critical component in the creation of user interfaces. They are designed to give users a visual cue as to how much content they can view at once, and their use dates back to the earliest days of graphical user interfaces.

Implementing a scrollbar for the first time can be more complicated than immediately apparent, as it has many moving parts that need to work together.

The goal of this article is to provide a technical and instructional guide on how to implement scrollbars in software applications, including their mechanics, design considerations, and implementation steps. By learning the fundamentals of how scrollbars work, designers can create user interfaces that are visually accessible and intuitive for all users.

This is a revised version of my original article you can find here. You can download a markdown copy of this article here.



This section lists the definitions of each component of a scrollbar. Please see the included diagram for reference.

Code Walkthrough

In this section, let's take a look at a basic implementation of a scrollbar. I will be providing code using basic JavaScript for the sake of clarity.

First, we define many of the general constants or variables we will be using for the rest of this tutorial. But for the purposes of this example, these are mostly constants. However, these will likely be variables instead of constants in a proper implementation.

	// Size of the content or document
	const contentSize = 400
	// Size of the window
	const windowSize = 100
	// How many units we scroll when we click a button
	const scrollUnit = 10
	// Determine how large our track is
	const trackSize = 80
	// Divide the window size by the content size to get a ratio
	// This ratio represents the proportion of content currently visible in the window
	const windowContentRatio = windowSize / contentSize
	// The position of our window in accordance to its top on the content.
	// The top of the window over the content.
	let windowPosition = 100

Next, we will write a simple function to get the actual size of the grip. This function needs to take into consideration the size of the track and the size of the content compared to the window to provide proper visual feedback, as the grip size visually indicates how much of the total content is currently visible in the window.

In addition, we also provide a minimum size for ease of use and a maximum size to prevent issues when displaying the grip.

	// The minimal size of our grip
	const minimalGripSize = 20
	// The maximum size of our grip
	const maximumGripSize = trackSize
	function getGripSize() {
		// Multiply the trackSize by the ratio to determine how large our grip will be
		let gripSize = trackSize * windowContentRatio
		// Prevent the grip from becoming too small
		if (gripSize < minimalGripSize) {
			gripSize = minimalGripSize
		// Prevent the grip from becoming too large
		if (gripSize > maximumGripSize) {
			gripSize = maximumGripSize
		return gripSize

The position of the grip is as vital to the usefulness of a scrollbar as the size. On raster displays, it is most common for the origin of the display to be at the top left. As such, the origin of most components is also on the top left. When drawing a component manually, this location is used to position it. At this location, the actual body of the component is drawn. For the purposes of this example, this is assumed to be the case.

With this in mind, we will create a function to calculate the total distance the grip can travel within the track. As we need to draw the body of the grip, this cannot simply be the track size. The simplest solution is to simply subtract the size of the grip from the total track length, as the following function shows.

	// Calculate the size of the scrollable track area
	function getScrollableTrackSize() {
		return trackSize - getGripSize()

The most comfortable experience when scrolling content is that it comes to an end at the bottom of the page. After all, it would be unusual to keep scrolling when there is no more content.

The simplest way to implement this behavior is to subtract the size of the window from the size of the content.

	// Determine the distance that the window can scroll over
	const scrollableWindowAreaSize = contentSize - windowSize

Now that we have determined the area where the grip could reside and how much of the content for the window to scroll, it is time to calculate where the grip actually is.

When determining the position of the grip on the track, we need to consider the proportional position of the window over the content. To prevent the grip from going beyond the maximum bounds of the track, we use the scrollable track size from earlier.

	// Determine the location of the grip on the track
	function getGripPosition() {
		// The ratio of the window to the scrollable area.
		const windowPositionRatio = windowPosition / scrollableWindowAreaSize
		// Determine the location by multiplying the ratio
		let gripPositionOnTrack = getScrollableTrackSize() * windowPositionRatio
		return gripPositionOnTrack

To set the location of the grip, we have to do much of the same. The following function allows directly setting the absolute position of the grip. However, as a supplied value may be outside of the track, we also force it within the bounds of the scrollable track area.

	// Sets the location of the grip on the track.
	function setGripPosition(position) {
		// we do this to keep the grip from flying off from the end of the track.
		const scrollableTrackSize = getScrollableTrackSize()
		// Limit the grip so that it is not flying off the track
		if (position < 0) {
			position = 0
		if (position > scrollableTrackSize) {
			position = scrollableTrackSize
		// Much like the windowPositionRatio, but for the grip and track.
		const newGripPositionRatio = position / scrollableTrackSize
		// We apply this ratio in the same way as we did to determine the grips
		// location, but to the window instead.
		windowPosition = newGripPositionRatio * scrollableWindowAreaSize

Directly manipulating the location of the track can be useful but is relatively uncommon. More likely, the user will drag the grip to move the window over the content.

As we can already set the grip position, dragging it becomes trivial. By calculating the delta of movement of an input, we can directly add this to the previous position. The following function does exactly this.

	// Moves the grip by the given delta. A positive delta moves forward.
	function dragGripOnTrack(gripPositionDelta) {
		// Determine the new location of the grip
		let newGripPosition = getGripPosition() + gripPositionDelta
		// Set the new position

Finally, there are alternative methods of using a scrollbar other than simply dragging the grip. Most commonly, this is done by moving the window position instead of using the slider. The following function allows us to do so. This can be useful for particularly long content where the smallest movement of the slider may cause the window to skip over content.

	// Scrolls the window position by the given amount
	function scroll(distance) {
		windowPosition = windowPosition + distance
		if (windowPosition < 0) {
			windowPosition = 0
		if (windowPosition > scrollableWindowAreaSize) {
			windowPosition = scrollableWindowAreaSize
		// Update the scrollbar here

Here are a few examples of how you could use this function.

	// Click on buttons to go back or forward
	// Click on the track above or below the grip to skip pages

Other possible uses for this function might be a scroll wheel or dial.

Considerations and Pitfalls


Scrollbars are more than a simple navigational tool on modern user interfaces. Instead, they are an essential part of a good user experience. Regardless of your platform or target audience, a well-implemented scrollbar will enhance the user's experience. After all, the best scrollbars are the ones that no user ever thinks about.