scrollbar
A graphical object that controls the scrolling of content within a viewing area. Scrollbars allow users to navigate through content that exceeds the visible area of a container.
Overview
The scrollbar role identifies an element as a graphical object that controls scrolling within a content area. It represents the current scroll position as a value within a defined range (typically 0-100 representing percentage scrolled).
Scrollbars use the aria-controls attribute to reference the scrollable content they control. The aria-orientation attribute specifies whether the scrollbar is vertical or horizontal.
Native Scrollbars vs role="scrollbar"
Native browser scrollbars are automatically accessible and require no additional ARIA. Use role="scrollbar" only when creating fully custom scrollbar implementations. Consider using CSS scrollbar styling properties when possible, as they maintain native accessibility.
Live Demo: Scrollbar Interactions
Vertical Scrollbar
Content line 1 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 2 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 3 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 4 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 5 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 6 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 7 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 8 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 9 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 10 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 11 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 12 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 13 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 14 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 15 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 16 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 17 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 18 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 19 - This is sample scrollable content that demonstrates the scrollbar functionality.
Content line 20 - This is sample scrollable content that demonstrates the scrollbar functionality.
Position: 0%
Horizontal Scrollbar
Position: 0%
Try with keyboard: Focus the scrollbar, then use Arrow Keys to scroll incrementally, Page Up/Down for larger jumps, or Home/End to jump to start/end.
Code Examples
Basic Vertical Scrollbar
<!-- Basic Vertical Scrollbar -->
<div
role="scrollbar"
aria-controls="content-area"
aria-orientation="vertical"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Scroll content"
tabindex="0"
>
<div class="scrollbar-thumb"></div>
</div>
<div id="content-area" tabindex="0">
<!-- Scrollable content here -->
</div>
<!-- Note: The scrollbar controls the content via aria-controls -->Horizontal Scrollbar
<!-- Horizontal Scrollbar -->
<div
role="scrollbar"
aria-controls="gallery"
aria-orientation="horizontal"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Scroll gallery horizontally"
tabindex="0"
>
<div class="scrollbar-track">
<div class="scrollbar-thumb" style="left: 50%"></div>
</div>
</div>
<div id="gallery" class="horizontal-scroll-content">
<!-- Gallery items -->
</div>Keyboard Navigation
<!-- Scrollbar with Full Keyboard Support -->
<div
role="scrollbar"
aria-controls="scrollable-content"
aria-orientation="vertical"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="0 percent scrolled"
tabindex="0"
id="custom-scrollbar"
>
<div class="scrollbar-track">
<div class="scrollbar-thumb"></div>
</div>
</div>
<script>
const scrollbar = document.getElementById('custom-scrollbar');
const content = document.getElementById('scrollable-content');
let currentValue = 0;
const step = 10;
const pageStep = 25;
scrollbar.addEventListener('keydown', (e) => {
let newValue = currentValue;
switch (e.key) {
case 'ArrowUp':
case 'ArrowLeft':
e.preventDefault();
newValue = Math.max(0, currentValue - step);
break;
case 'ArrowDown':
case 'ArrowRight':
e.preventDefault();
newValue = Math.min(100, currentValue + step);
break;
case 'PageUp':
e.preventDefault();
newValue = Math.max(0, currentValue - pageStep);
break;
case 'PageDown':
e.preventDefault();
newValue = Math.min(100, currentValue + pageStep);
break;
case 'Home':
e.preventDefault();
newValue = 0;
break;
case 'End':
e.preventDefault();
newValue = 100;
break;
}
if (newValue !== currentValue) {
currentValue = newValue;
updateScrollbar();
}
});
function updateScrollbar() {
scrollbar.setAttribute('aria-valuenow', currentValue);
scrollbar.setAttribute('aria-valuetext', currentValue + ' percent scrolled');
// Update visual thumb position
const thumb = scrollbar.querySelector('.scrollbar-thumb');
thumb.style.top = currentValue + '%';
// Scroll the content
const maxScroll = content.scrollHeight - content.clientHeight;
content.scrollTop = (currentValue / 100) * maxScroll;
}
</script>Native Scrollbar Styling (Preferred)
<!-- Using Native Scrollbar (Preferred) -->
<div class="scroll-container" tabindex="0">
<div class="scroll-content">
<!-- Content that overflows -->
<p>Long content here...</p>
</div>
</div>
<style>
.scroll-container {
height: 300px;
overflow-y: auto;
/* Custom scrollbar styling (limited cross-browser) */
}
/* Webkit browsers */
.scroll-container::-webkit-scrollbar {
width: 12px;
}
.scroll-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 6px;
}
.scroll-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 6px;
}
.scroll-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Firefox */
.scroll-container {
scrollbar-width: thin;
scrollbar-color: #888 #f1f1f1;
}
</style>
<!-- Note: Native scrollbars are automatically accessible -->React Component
// React Custom Scrollbar Component
import { useState, useRef, useCallback, useEffect } from 'react';
interface CustomScrollbarProps {
orientation?: 'vertical' | 'horizontal';
label: string;
children: React.ReactNode;
}
function CustomScrollbar({
orientation = 'vertical',
label,
children,
}: CustomScrollbarProps) {
const [scrollPercent, setScrollPercent] = useState(0);
const contentRef = useRef<HTMLDivElement>(null);
const isVertical = orientation === 'vertical';
const updateScrollFromContent = useCallback(() => {
const content = contentRef.current;
if (!content) return;
const scrollable = isVertical
? content.scrollHeight - content.clientHeight
: content.scrollWidth - content.clientWidth;
if (scrollable > 0) {
const scrolled = isVertical ? content.scrollTop : content.scrollLeft;
setScrollPercent(Math.round((scrolled / scrollable) * 100));
}
}, [isVertical]);
useEffect(() => {
const content = contentRef.current;
if (!content) return;
content.addEventListener('scroll', updateScrollFromContent);
return () => content.removeEventListener('scroll', updateScrollFromContent);
}, [updateScrollFromContent]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
const content = contentRef.current;
if (!content) return;
const step = 50;
const pageStep = 200;
switch (e.key) {
case 'ArrowUp':
case 'ArrowLeft':
e.preventDefault();
if (isVertical) content.scrollTop -= step;
else content.scrollLeft -= step;
break;
case 'ArrowDown':
case 'ArrowRight':
e.preventDefault();
if (isVertical) content.scrollTop += step;
else content.scrollLeft += step;
break;
case 'PageUp':
e.preventDefault();
if (isVertical) content.scrollTop -= pageStep;
else content.scrollLeft -= pageStep;
break;
case 'PageDown':
e.preventDefault();
if (isVertical) content.scrollTop += pageStep;
else content.scrollLeft += pageStep;
break;
case 'Home':
e.preventDefault();
if (isVertical) content.scrollTop = 0;
else content.scrollLeft = 0;
break;
case 'End':
e.preventDefault();
if (isVertical) {
content.scrollTop = content.scrollHeight;
} else {
content.scrollLeft = content.scrollWidth;
}
break;
}
}, [isVertical]);
return (
<div className="scrollbar-container">
<div
ref={contentRef}
className={`scroll-content ${isVertical ? 'vertical' : 'horizontal'}`}
tabIndex={0}
>
{children}
</div>
<div
role="scrollbar"
aria-controls="scroll-content"
aria-orientation={orientation}
aria-valuenow={scrollPercent}
aria-valuemin={0}
aria-valuemax={100}
aria-valuetext={`${scrollPercent} percent scrolled`}
aria-label={label}
tabIndex={0}
onKeyDown={handleKeyDown}
className={`custom-scrollbar ${isVertical ? 'vertical' : 'horizontal'}`}
>
<div
className="scrollbar-thumb"
style={{
[isVertical ? 'top' : 'left']: `${scrollPercent}%`,
}}
/>
</div>
</div>
);
}
// Usage
<CustomScrollbar orientation="vertical" label="Scroll article">
<article>
<h2>Long Article Content</h2>
<p>Content here...</p>
</article>
</CustomScrollbar>Using aria-valuetext
<!-- Using aria-valuetext for Better Descriptions -->
<div
role="scrollbar"
aria-controls="document"
aria-orientation="vertical"
aria-valuenow="25"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="Page 1 of 4, 25% scrolled"
aria-label="Document scroll position"
tabindex="0"
>
<div class="scrollbar-thumb"></div>
</div>
<!-- For documents with known sections -->
<div
role="scrollbar"
aria-controls="document"
aria-orientation="vertical"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="Currently viewing: Chapter 3 - Getting Started"
aria-label="Document navigation"
tabindex="0"
>
<div class="scrollbar-thumb"></div>
</div>Keyboard Support
Direction depends on orientation
Direction depends on orientation
Typically one viewport height
Typically one viewport height
Sets aria-valuenow to aria-valuemin
Sets aria-valuenow to aria-valuemax
Best Practices
Use native scrollbars when possible - they are automatically accessible
Always specify aria-controls to reference the scrollable content
Set aria-orientation to indicate vertical or horizontal scrolling
Provide aria-valuetext for more descriptive position announcements
Implement all expected keyboard interactions (arrows, Page Up/Down, Home/End)
Ensure the scrollbar thumb size indicates the viewport-to-content ratio
Don't create custom scrollbars unless absolutely necessary for design
Don't forget to update aria-valuenow when scroll position changes
Don't make scrollbars too thin to grab with a mouse or touch
Don't hide scrollbars completely - users need to know content is scrollable
Supported ARIA Attributes
aria-controlsRequiredReferences the ID of the scrollable content element
aria-valuenowRequiredCurrent scroll position value
aria-valueminRequiredMinimum scroll value (typically 0)
aria-valuemaxRequiredMaximum scroll value (typically 100)
aria-orientationSpecifies vertical or horizontal scrolling
aria-valuetextHuman-readable description of scroll position
aria-labelAccessible name for the scrollbar
aria-labelledbyReferences element(s) that label the scrollbar
Common Use Cases
Accessibility Notes
Focus Management
The scrollbar should be focusable with tabindex="0". When focused, it receives keyboard events for scrolling. The scrollable content itself should also be focusable so users can scroll with arrow keys while reading.
Visual Indicators
Make the scrollbar thumb clearly visible against the track. Indicate when more content is available by showing the scrollbar. Consider showing scroll shadows at content edges to indicate scrollable areas.
Screen Reader Announcements
Screen readers announce scrollbars with their label, orientation, and current value. Use aria-valuetext to provide context like "Page 3 of 10" instead of just "30%".

