# Scroll Area

> Scroll areas scroll their content inside a fixed box and draw a slim scrollbar over it.

Source: /docs/components/scroll-area

```tsx
import { ScrollArea } from '@aksornproject/aksara-ui'
```

## Vertical scrolling

`ScrollArea` is a namespace of six parts. `Root` is the box, and its size sets how much content shows. `Viewport` fills the Root and scrolls. `Content` wraps what scrolls. `Scrollbar` holds a `Thumb`.

The Scrollbar shows only while the content is taller than the Root. The thumb length is the share of the content in view, so a long list gets a short thumb. The track turns light grey while the pointer is over it or drags the thumb.

```tsx
import { ScrollArea } from '@aksornproject/aksara-ui'

const LESSONS = Array.from(
  { length: 20 },
  (_, index) => `Lesson ${index + 1}: Reading and comprehension`,
)

export default function ScrollAreaVertical() {
  return (
    <ScrollArea.Root className="h-[240px] w-full max-w-[320px] rounded-radius-radius-8 border border-color-border">
      <ScrollArea.Viewport role="region" aria-label="Lessons">
        <ScrollArea.Content>
          <ul className="flex flex-col gap-spacing-8 p-spacing-16">
            {LESSONS.map((lesson) => (
              <li key={lesson}>{lesson}</li>
            ))}
          </ul>
        </ScrollArea.Content>
      </ScrollArea.Viewport>
      <ScrollArea.Scrollbar>
        <ScrollArea.Thumb />
      </ScrollArea.Scrollbar>
    </ScrollArea.Root>
  )
}
```

## Horizontal scrolling

`orientation="horizontal"` on the Scrollbar draws it along the bottom edge. A wheel over the track scrolls sideways.

```tsx
import { ScrollArea } from '@aksornproject/aksara-ui'

const UNITS = Array.from({ length: 10 }, (_, index) => `Unit ${index + 1}`)

export default function ScrollAreaHorizontal() {
  return (
    <ScrollArea.Root className="w-full max-w-[320px] rounded-radius-radius-8 border border-color-border">
      <ScrollArea.Viewport role="region" aria-label="Units">
        <ScrollArea.Content>
          <ul className="flex w-max gap-spacing-12 p-spacing-16 pb-spacing-20">
            {UNITS.map((unit) => (
              <li
                key={unit}
                className="flex size-[96px] items-center justify-center rounded-radius-radius-8 bg-color-background-muted"
              >
                {unit}
              </li>
            ))}
          </ul>
        </ScrollArea.Content>
      </ScrollArea.Viewport>
      <ScrollArea.Scrollbar orientation="horizontal">
        <ScrollArea.Thumb />
      </ScrollArea.Scrollbar>
    </ScrollArea.Root>
  )
}
```

## Both directions

Add a Scrollbar for each direction and a `Corner`. The Corner fills the square where the two tracks meet, so they never overlap.

```tsx
import { ScrollArea } from '@aksornproject/aksara-ui'

const CLASSES = Array.from({ length: 16 }, (_, index) => `Class ${index + 1}`)
const PERIODS = Array.from({ length: 8 }, (_, index) => `Period ${index + 1}`)

export default function ScrollAreaBoth() {
  return (
    <ScrollArea.Root className="h-[240px] w-full max-w-[320px] rounded-radius-radius-8 border border-color-border">
      <ScrollArea.Viewport role="region" aria-label="Timetable">
        <ScrollArea.Content>
          <table>
            <tbody>
              {CLASSES.map((name) => (
                <tr key={name}>
                  {PERIODS.map((period) => (
                    <td
                      key={period}
                      className="px-spacing-12 py-spacing-4 whitespace-nowrap"
                    >
                      {`${name}, ${period}`}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </ScrollArea.Content>
      </ScrollArea.Viewport>
      <ScrollArea.Scrollbar>
        <ScrollArea.Thumb />
      </ScrollArea.Scrollbar>
      <ScrollArea.Scrollbar orientation="horizontal">
        <ScrollArea.Thumb />
      </ScrollArea.Scrollbar>
      <ScrollArea.Corner />
    </ScrollArea.Root>
  )
}
```

## Styling

Each part merges `className` last, so your classes win. Give the Root a height or a width, because the Viewport only scrolls when the Root is smaller than the content. The scrollbars float over the content, so leave room at the edge when text runs under the track.

## Accessibility

- The scrollbars are hidden from assistive technology and never take focus. Keyboard and screen reader users scroll the Viewport.
- The Viewport becomes a tab stop while its content overflows. Give it `role="region"` and an `aria-label` that names the content, such as "Lessons", so a screen reader announces what the focused box holds.
- After a Tab, the Viewport scrolls with the browser scrolling keys, such as Page Down and End.
- The focus ring draws around the Viewport while it has keyboard focus.

## API

### ScrollArea.Root

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ScrollAreaRootState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: ScrollAreaRootState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `overflowEdgeThreshold` | `number \| Partial<{ xStart: number; xEnd: number; yStart: number; yEnd: number; }>` | `0` | The threshold in pixels that must be passed before the overflow edge attributes are applied. Accepts a single number for all edges or an object to configure them individually. |
| `className` | `string` |  | Merged last with `cn()`, so these classes win. |

### ScrollArea.Viewport

| Prop | Type | Description |
| --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ScrollAreaViewportState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: ScrollAreaViewportState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `className` | `string` | Merged last with `cn()`, so these classes win. |

### ScrollArea.Content

| Prop | Type | Description |
| --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ScrollAreaContentState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: ScrollAreaContentState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `className` | `string` | Merged last with `cn()`, so these classes win. |

### ScrollArea.Scrollbar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ScrollAreaScrollbarState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: ScrollAreaScrollbarState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `orientation` | `"horizontal" \| "vertical"` | `'vertical'` | Whether the scrollbar controls vertical or horizontal scroll. |
| `keepMounted` | `boolean` | `false` | Whether to keep the HTML element in the DOM when the viewport isn't scrollable. |
| `className` | `string` |  | Merged last with `cn()`, so these classes win. |

### ScrollArea.Thumb

| Prop | Type | Description |
| --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ScrollAreaThumbState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: ScrollAreaThumbState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `className` | `string` | Merged last with `cn()`, so these classes win. |

### ScrollArea.Corner

| Prop | Type | Description |
| --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ScrollAreaCornerState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: ScrollAreaCornerState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `className` | `string` | Merged last with `cn()`, so these classes win. |