# useScrollableArea

## Usage

Adds canonical axis-aware scroll behavior to structure owned by the caller. An axis is effective only when its computed overflow is scroll-capable and geometry exceeds the shared 1px tolerance. Both viewport and content boxes are observed.

Bind `viewport` to the scrolling box as a function ref, and `content` to the box your content sits in. `viewportAttrs` carries `role`, `aria-label`, `tabindex`, and the `data-scroll-*` attributes written from the current measurement. `viewportStyle` carries `overflow` and `overscroll-behavior`. Name and role come from `label` and `role`, or from `keyboardAccess`, which also says where the keyboard enters. The measured state is registered as a scroll owner, so nested areas and focus delegation can tell which level scrolls. A hidden or zero-size viewport cannot be measured and keeps its last state. Options are read reactively, so a component's `props` object can be passed straight in and later changes are picked up.

```ts
import { useScrollableArea } from '@astryx-vue/core'
```

```vue
<script setup lang="ts">
import { useScrollableArea } from '@astryx-vue/core'

const { content, viewport, viewportAttrs, viewportStyle } = useScrollableArea({
  axis: 'block',
  label: 'Release notes',
})
</script>

<template>
  <div :ref="viewport" v-bind="viewportAttrs" :style="viewportStyle">
    <div :ref="content">
      Long content
    </div>
  </div>
</template>
```

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Bind both `viewport` and `content`. A real observed content box is required for live overflow changes. |
| Do | Give the area a concise `label`. The viewport enters the tab order only while it actually scrolls. |
| Do | Use `axis` for the direction you intend to scroll, and `both` only when the content really scrolls two ways. |
| Do | Keep `overscroll` at `allow` unless a finished gesture should stop at this box. |
| Don't | Write your own `overflow` on the viewport; `viewportStyle` picks `clip` or `auto` from the measurement. |
| Don't | Wrap it in another overflow container. One viewport should own the scroll. |

## Parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `UseScrollableAreaOptions` | — | Logical scroll intent, overscroll policy, and Sticky containment. |
| `options.axis` | `'inline' \| 'block' \| 'both'` | `'block'` | Which logical axis may scroll. |
| `options.keyboardAccess` | `ScrollKeyboardAccess` | `{ owner: 'viewport', label, role }` | Where the keyboard enters a scrolling area. `{ owner: 'content' }`: the content has its own focusable cells, so the viewport stays out of the Tab order and unnamed. `{ owner: 'viewport', label, role? }`: the viewport becomes a Tab stop while it scrolls. `{ owner: 'contentOrViewport', label, role? }`: the same, but a Tab into it hands focus straight to a plain leading button or link inside; arrow-key scrolling stays with the browser. |
| `options.label` | `string` | — | Accessible name for the viewport. Used when `keyboardAccess` is not given. |
| `options.role` | `'group' \| 'region'` | `'group'` | Role of the viewport. |
| `options.overscroll` | `'allow' \| 'contain'` | `'allow'` | Overscroll policy for an axis that is actually scrollable. |
| `options.stickyContainment` | `'whenScrollable' \| 'always'` | `'whenScrollable'` | Whether the viewport is a Sticky containing block only while it scrolls, or in every state. |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `viewport` | `(element: unknown) => void` | Function ref for the viewport element. It attaches the scroll listener and the ResizeObserver. |
| `content` | `Ref` | Template ref for the content box. Its size changes are observed too. |
| `viewportAttrs` | `ComputedRef<Record<string, string \| number \| undefined>>` | `role`, `aria-label`, `tabindex`, and the `data-scroll-*` attributes for the current measurement. |
| `viewportStyle` | `ComputedRef` | `overflow` for both axes, plus `overscroll-behavior` for the requested axes. |
| `state` | `ComputedRef` | `inline` and `block`, each with `isScrollable`, `atStart`, and `atEnd`. |
