# ScrollArea

> A bounded scroll container with a thin scrollbar on the inline-end edge, keyboard-scrollable and RTL-aware.

Source: https://docs.nasaqui.com/components/scroll-area

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/scroll-area.json
```

A container that scrolls when its content is larger than the box you give it, with a slim scrollbar that
fades in on hover and while scrolling. It replaces the platform scrollbar with one that looks the same in every
browser, and it keeps the vertical bar on the inline-end edge, so in RTL it sits on the left.

## When to use

- A fixed-height panel, a long list inside a card, a code block, a sidebar section.
- A row that must scroll sideways without a native scrollbar.

## When not to use

- The page itself: let the document scroll.
- Content that should simply grow: don't bound it.
- Tables that need sticky headers: use `Table` / `DataTable`.

## Import

```tsx
import { ScrollArea } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { ScrollArea } from "@fadymondy/nasaq/web";

export function Notes({ items }: { items: string[] }) {
  return (
    <ScrollArea aria-label="Notes" className="h-64 w-72 rounded-card border border-border">
      <ul className="flex flex-col gap-2 p-3 text-body-sm">
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </ScrollArea>
  );
}
```

## Anatomy

```
<ScrollArea>            data-slot="scroll-area"            bound its size with className
├─ viewport             data-slot="scroll-area-viewport"   role="region", focusable
├─ <ScrollBar>          data-slot="scroll-area-scrollbar"  vertical and/or horizontal
│  └─ thumb             data-slot="scroll-area-thumb"
└─ corner               data-slot="scroll-area-corner"     only with orientation="both"
```

## API

### ScrollArea (`ScrollAreaProps`)

All Base UI `ScrollArea.Root` props, plus:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"vertical" \| "horizontal" \| "both"` | `"vertical"` | Which axes get a scrollbar. |
| `viewportClassName` | `string` | none | Classes for the viewport. |
| `aria-label` | `string` | none | Name of the scroll region. Provide it and localise it. |

### ScrollBar

All Base UI `ScrollArea.Scrollbar` props. `orientation` defaults to `"vertical"`. Only needed when composing manually.

## Examples

Horizontal strip in Arabic:

```tsx
import { ScrollArea } from "@fadymondy/nasaq/web";

export function Tags() {
  return (
    <ScrollArea orientation="horizontal" aria-label="الوسوم" className="w-64">
      <div className="flex w-max gap-2 pb-3">
        {["تصميم", "برمجة", "تسويق", "مبيعات", "دعم", "مالية"].map((t) => (
          <span key={t} className="rounded-full bg-secondary px-3 py-1 text-caption">{t}</span>
        ))}
      </div>
    </ScrollArea>
  );
}
```

## Accessibility

| Key | Action |
| --- | --- |
| Tab | Focuses the scroll region. |
| Arrow keys, Page Up/Down, Home/End | Scroll the focused region. |

- The viewport is `role="region"` and focusable, so keyboard users can reach overflowing content that has no
  focusable child. Give it an `aria-label` and translate it.
- The scrollbar is a visual aid; it is not focusable.

## RTL & i18n

The vertical scrollbar uses `end-0`, so it is on the right in LTR and the left in RTL. Horizontal content starts at the
inline start and scrolls toward the inline end.

## Styling & tokens

Thumb `bg-nq-line-strong`, focus ring `nq-focus`. State attributes on the scrollbar: `data-hovering`, `data-scrolling`.
On the root and viewport: `data-has-overflow-x`, `data-has-overflow-y`, `data-overflow-y-start`, `data-overflow-y-end`
(useful for edge fades). `className` merges onto the root.

## Do / Don't

- Do set a height or width on the root, otherwise nothing overflows.
- Don't nest scroll areas on the same axis.

## Related

[table](https://docs.nasaqui.com/components/table), [sheet](https://docs.nasaqui.com/components/sheet), [dropdown-menu](https://docs.nasaqui.com/components/dropdown-menu).

## Lab

https://docs.nasaqui.com/?path=/docs/components-layout-scroll-area--docs

## Code

### React

```tsx
import { ScrollArea } from "@fadymondy/nasaq/web";

export function Notes({ items }: { items: string[] }) {
  return (
    <ScrollArea aria-label="Notes" className="h-64 w-72 rounded-card border border-border">
      <ul className="flex flex-col gap-2 p-3 text-body-sm">
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </ScrollArea>
  );
}
```

### shadcn

```tsx
import { ScrollArea } from "@/components/ui/scroll-area";

export function Notes({ items }: { items: string[] }) {
  return (
    <ScrollArea aria-label="Notes" className="h-64 w-72 rounded-card border border-border">
      <ul className="flex flex-col gap-2 p-3 text-body-sm">
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </ScrollArea>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqScrollArea } from "@fadymondy/nasaq/vue";

const items = Array.from({ length: 24 }, (_, i) => `Note ${i + 1}`);
</script>

<template>
  <NqScrollArea aria-label="Notes" class="h-64 w-72 rounded-card border border-border">
    <ul class="flex flex-col gap-2 p-3 text-body-sm">
      <li v-for="item in items" :key="item">{{ item }}</li>
    </ul>
  </NqScrollArea>
</template>
```

### Blade

```blade
<x-nq::scroll-area label="Notes" class="h-64 w-72 rounded-card border border-border">
    <ul class="flex flex-col gap-2 p-3 text-body-sm">
        @foreach (range(1, 24) as $i)
            <li>Note {{ $i }}</li>
        @endforeach
    </ul>
</x-nq::scroll-area>
```

### HTML + Alpine

```html
<div data-slot="scroll-area" x-data="nqScrollArea()" x-on:pointerenter="hovering = true" x-on:pointerleave="hovering = false"
    class="relative min-h-0 min-w-0 overflow-hidden h-64 w-72 rounded-card border border-border">
    <div data-slot="scroll-area-viewport" x-ref="viewport" role="region" tabindex="0" aria-label="Notes"
        class="size-full overflow-auto rounded-[inherit] outline-none [scrollbar-width:none] [&amp;::-webkit-scrollbar]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus"><ul class="flex flex-col gap-2 p-3 text-body-sm">
                    <li>Note 1</li>
                    <li>Note 2</li>
                    <li>Note 3</li>
                    <li>Note 4</li>
                    <li>Note 5</li>
                    <li>Note 6</li>
                    <li>Note 7</li>
                    <li>Note 8</li>
                    <li>Note 9</li>
                    <li>Note 10</li>
                    <li>Note 11</li>
                    <li>Note 12</li>
                    <li>Note 13</li>
                    <li>Note 14</li>
                    <li>Note 15</li>
                    <li>Note 16</li>
                    <li>Note 17</li>
                    <li>Note 18</li>
                    <li>Note 19</li>
                    <li>Note 20</li>
                    <li>Note 21</li>
                    <li>Note 22</li>
                    <li>Note 23</li>
                    <li>Note 24</li>
            </ul></div>
            <div data-slot="scroll-area-scrollbar" data-orientation="vertical" x-ref="barY" x-bind="bar" hidden
            class="absolute flex touch-none select-none p-0.5 opacity-0 transition-opacity duration-150 ease-nq data-hovering:opacity-100 data-scrolling:opacity-100 inset-y-0 end-0 w-2.5">
            <div data-slot="scroll-area-thumb" x-ref="thumbY" x-bind="thumb" class="relative w-full rounded-full bg-nq-line-strong"></div>
        </div>
            </div>
```
