---
title: Scrollbar
description: A custom horizontal scrollbar component that syncs with scroll position via CSS scroll-timeline animations and supports pointer-driven interaction.
---

```tsx
import { Scrollbar } from '../ui/Scrollbar';

export function ScrollbarDemo() {
  return (
    <div>
      <Scrollbar
        aria-label="Demo horizontal scroller"
        timelineName="--scroller-outer"
        scrollbarWidth="60%"
      >
        <div className="w-600 p-16">
          <div className="flex gap-8">
            {Array.from({ length: 20 }).map((_, i) => (
              <div
                key={i}
                className="flex h-100 w-100 flex-shrink-0 items-center justify-center rounded-8 bg-ultramarine text-white"
              >
                {i + 1}
              </div>
            ))}
          </div>
        </div>
      </Scrollbar>
      <div className="bg-ultramarine p-8 -m-8 mt-8">
        <Scrollbar
          aria-label="Dark horizontal scroller"
          variant="dark"
          timelineName="--scrollerDark-outer"
          scrollbarWidth="60%"
        >
          <div className="w-600 p-16">
            <div className="flex gap-8">
              {Array.from({ length: 20 }).map((_, i) => (
                <div
                  key={i}
                  className="flex h-100 w-100 flex-shrink-0 items-center justify-center rounded-8 bg-white text-black"
                >
                  {i + 1}
                </div>
              ))}
            </div>
          </div>
        </Scrollbar>
      </div>
    </div>
  );
}
```

## Usage

The `Scrollbar` component provides a custom horizontal scrollbar with automatic scroll synchronization and full pointer interaction:

- **Drag the nub** to scroll to any position
- **Click the track** outside the nub to jump to that position, then continue dragging
- Touch targets are extended vertically (~16 px) for easier interaction on touch devices

### Browser Support

**Note:** `scroll-timeline` is an experimental CSS feature. For older browsers, you'll need to include a [scroll-timeline polyfill](https://github.com/flackr/scroll-timeline).

### Basic Example

```tsx
import { Scrollbar } from '@clubmed/trident-ui/ui/Scrollbar';

<Scrollbar>
  <div className="w-[900px]">
    {/* Scrollable content */}
  </div>
</Scrollbar>
```

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | - | The scrollable content |
| `timelineName` | `string` | unique per instance | CSS timeline name for the scroll animation |
| `scrollbarWidth` | `string` | `'60%'` | Width of the scrollbar thumb as a percentage |
| `variant` | `'light' \| 'dark'` | `'light'` | Color theme variant |
| `className` | `string` | - | Additional classes for the container |
| `scrollerClassName` | `string` | - | Additional classes for the scrollable area |
| `scrollbarClassName` | `string` | - | Additional classes for the scrollbar track |
| `scrollerStyle` | `CSSProperties` | - | Additional style of the scrollable area |
| `autoHide` | `boolean` | `false` | Hides the track, and keeps the scrollable area out of the tab order and of the landmarks, while the content fits |
| `aria-label` | `string` | `'Scrollable content'` | Accessible label for the scrollable region |
| `aria-labelledby` | `string` | - | Id of the element naming the scrollable region; wins over `aria-label` |

### Custom Timeline Name

Each instance gets a unique timeline name by default, so several scrollbars can live on the same page. Set one yourself to reference it from your own CSS:

```tsx
<Scrollbar timelineName="--header-scroller">
  <div className="w-[900px]">Header content</div>
</Scrollbar>

<Scrollbar timelineName="--body-scroller">
  <div className="w-[1200px]">Body content</div>
</Scrollbar>
```

### Scrollbar Thumb Width

Control the scrollbar thumb width with the `scrollbarWidth` prop:

```tsx
<Scrollbar scrollbarWidth="40%">
  <div className="w-[900px]">Content</div>
</Scrollbar>
```

### Dark Variant

Use the `variant` prop for different color themes:

```tsx
<Scrollbar variant="dark">
  <div className="w-[900px]">Content</div>
</Scrollbar>
```

### Accessibility

The component includes proper ARIA roles and attributes:
- `role="region"` on the scrollable area with customizable `aria-label`
- `role="scrollbar"` on the scrollbar track with orientation and value attributes
- Keyboard navigation support with `tabIndex={0}` on the scrollable area
- The track points to the scrollable area (`aria-controls`) and announces the scroll position in `aria-valuenow`, from 0 to 100
- With `autoHide`, content that fits is neither a tab stop nor a landmark, and the track is hidden

### Auto hide

`autoHide` measures the content with a `ResizeObserver` and only displays the track while it overflows:

```tsx
<Scrollbar autoHide aria-labelledby="prices-title">
  <table>{/* rows */}</table>
</Scrollbar>
```

## CSS-Only Implementation

If you prefer not to use the React component, you can implement the scrollbar manually using CSS scroll-timeline properties. This approach provides automatic scroll synchronization but without the interactive drag functionality.

### Manual Setup

The scrollbar requires three elements with specific timeline properties:

1. **Container** - Defines the timeline scope
2. **Scroller** (`data-scroller`) - The scrollable container that creates the scroll timeline
3. **Scrollbar** (`data-scrollbar`) - The visual scrollbar with animated thumb

```tsx
<div style={{ timelineScope: '--my-scroller' }}>
  <div 
    data-scroller
    style={{ scrollTimeline: '--my-scroller x' }}
  >
    <div className="w-[900px]">
      {/* Scrollable content */}
    </div>
  </div>
  <div 
    data-scrollbar
    style={{ '--scrollbar-width': '60%' } as React.CSSProperties}
  >
    <div 
      data-scrollprogress 
      style={{ animationTimeline: '--my-scroller' }}
    />
  </div>
</div>
```

### Timeline Name Requirements

Each scroller needs a unique timeline name (e.g., `--my-scroller`) that must be:
- Set on the container with `timelineScope: '--my-scroller'`
- Used in the scroller's `scrollTimeline` property with the `x` axis: `'--my-scroller x'`
- Referenced in the scrollprogress element's `animationTimeline: '--my-scroller'`

This allows multiple scrollbars to coexist on the same page, including nested scrollers.

### Using Tailwind Classes

You can also use Tailwind's arbitrary value syntax:

```tsx
<div className="[timeline-scope:--my-scroller]">
  <div 
    data-scroller
    className="[scroll-timeline:--my-scroller_x]"
  >
    <div className="w-[900px]">Content</div>
  </div>
  <div data-scrollbar>
    <div 
      data-scrollprogress 
      className="[animation-timeline:--my-scroller]"
    />
  </div>
</div>
```

### Dark Variant (CSS-Only)

For a dark themed scrollbar, add `data-scroller="dark"`:

```tsx
<div style={{ timelineScope: '--my-scroller' }}>
  <div 
    data-scroller="dark"
    style={{ scrollTimeline: '--my-scroller x' }}
  >
    {/* content */}
  </div>
  <div data-scrollbar>
    <div 
      data-scrollprogress 
      style={{ animationTimeline: '--my-scroller' }}
    />
  </div>
</div>
```

### Component vs CSS-Only

The `Scrollbar` component provides the same visual result as the CSS-only approach but with additional benefits:
- Proper ARIA attributes for accessibility
- Cleaner, more maintainable API
- Encapsulated timeline name management
- Pointer interaction: drag nub, click track, extended touch targets

Use the component for better accessibility and developer experience, or use the CSS-only approach for maximum flexibility and minimal JavaScript. Note that the CSS-only approach does not include pointer-driven scrolling.

