# StickyScroll

> Two-column scroll narrative with a scrolling item list and a sticky panel that swaps its content to the item centred in view, stacking to a single top-sticky panel over the items on narrow containers.

- Package: `fancy-ui-vue` (Vue 3.5+ + Tailwind CSS v4)
- Category: Layout
- Tags: scroll, layout, sticky, narrative, scrollytelling, micro-interaction
- Types: `StickyScrollProps`
- Docs: https://fancy-ui.rama.app/docs/components/sticky-scroll
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/vue/src/components/sticky-scroll

## Import

```vue
<script setup lang="ts">
import { StickyScroll } from 'fancy-ui-vue';
</script>
```

Setup (once per project): `npm install fancy-ui-vue`, then put these lines in your main CSS file (the stylesheet imports go right after `@import "tailwindcss";`):

```css
@import "tailwindcss";
@import "fancy-ui-vue/tailwind.css";
@import "fancy-ui-vue/styles.css";
```

Full guide: https://fancy-ui.rama.app/llms.txt

The names below are the ones to write in a Vue template; when one differs from the Svelte name, the Svelte name follows in parentheses. Snippets become named slots (`#default` is the default snippet), a prop the Vue port declares as a model is a `v-model:<name>` (except `ref`, exposed via `defineExpose({ ref })`: put a template ref on the component and read `.ref`) and callbacks are listeners (`onclick` is `@click`). Every component sets `inheritAttrs: false`: `class` is a declared prop, merged where the Svelte component applies it (not always the root element), and only when the props table below lists it; other attributes do not fall through unless the package README lists the component as forwarding native attributes. Divergences: https://github.com/RamaHerbin/fancy-ui/tree/main/vue#divergences-from-the-svelte-api

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items` (required) | `T[]` | — | Required. The items rendered down the scrolling column. |
| `v-model:activeIndex` (Svelte `activeIndex`) | `number` | `0` | Bindable. The active item's index. Holds its last value when nothing intersects the centre line. |
| `panelSide` | `"start" \| "end"` | `"end"` | Which logical side the panel sits on. Flips physically under dir="rtl". |
| `crossfade` | `boolean` | `true` | Whether the panel crossfades between items. Effective value is always false under reduced motion. |
| `panelClass` | `string` | — | Additional CSS classes for the sticky panel wrapper. |
| `panelHidden` | `boolean` | `true` | Whether the panel is aria-hidden. Set false when the panel holds content found nowhere else. |
| `@change` (Svelte `onChange`) | `(index: number, item: T) => void` | — | Called only when the active index actually changes. |
| `class` | `string` | — | Additional CSS classes for the root |
| `ref` | `HTMLDivElement \| null` | `null` | Bindable reference to the root element |

## Slots

| Slot | Description |
| --- | --- |
| `#item` (Svelte `item`) | Renders one row, given the item, its index, and whether it's the active one: (item, index, active). (Snippet<[T, number, boolean]>) |
| `#panel` (Svelte `panel`) | Renders the sticky panel's content for the active item: (item, index). (Snippet<[T, number]>) |

## Example

No Vue example is published for this component yet: start from the import above and the props table. The full examples are Svelte-only, on https://fancy-ui.rama.app/docs/components/sticky-scroll.md.
