--- ## Scroller Horizontal row of equal-rank items that becomes scrollable only when it runs out of room — an overflow behaviour, not a navigation pattern. On a wide viewport it is an ordinary row: no scrolling, no arrows, no dots, and none of the accessibility duties a scroll container carries. Once it does overflow it snaps to item boundaries, takes a tab stop (`role="group"` + `label`, so the keyboard can scroll it at all — the bug in most media rows), and can show jump buttons and dots. `align="center"` turns it into a centred stage whose middle item lifts via `animation-timeline: view(inline)` — pure CSS, and where that is unsupported the row is identical minus the lift. Use it for feature cards, product rows, media strips and chip/filter bars. It never auto-rotates. For one item at a time with paging semantics use Tab; for page-number navigation use Pagination. **Import:** `import { Scroller } from '@urbicon-ui/blocks';` ### Examples ```svelte {#each features as feature (feature.id)} {/each} ``` ```svelte {#each features as feature (feature.id)} {/each} ``` ### Variants - align: center, start (default: start) - gap: lg, md, sm, xl, xs (default: md) ### Api | Prop | Type | Required | Default | Description | | --- | --- | :---: | --- | --- | | children | `Snippet` | yes | | The row's items. Each direct child becomes one snap target — width, snap alignment and (with `emphasis`) the lift are applied for you. Required. | | label | `string` | yes | | Accessible name for the row, e.g. "Main features". Required: once the row overflows it becomes a focusable `role="group"`, and an unnamed group is a nameless box to a screen reader. It is deliberately not optional-with-a- fallback — a generic default name would be worse than none. | | ...HTMLAttributes | `HTMLAttributes` | no | | HTML attributes (excluding: 'children' | 'class') | | ...ScrollerVariants | `VariantProps` | no | | Styling variants from ScrollerVariants | | align | `'start' | 'center'` | no | 'start' | Where an item comes to rest when the row snaps. `start` is the ordinary overflow row. `center` is a stage: the middle item is the subject, and the track is padded so the first and last item can reach the middle too. `center` needs items narrow enough that roughly three are visible with two peeking. Make them much wider and the centring padding takes over the row — one card adrift in empty space, which reads as a layout bug rather than a stage. The component warns about that in DEV. | | class | `string` | no | | Extra classes merged onto the root container (the column holding the row and its control bar). | | controls | `'auto' | 'always' | 'none'` | no | 'auto' | Previous/next buttons. `auto` shows them only while the row overflows — on a viewport where everything fits there is nothing to navigate, and controls for a problem that does not exist are just clutter. `always` keeps them mounted (disabled at the ends); `none` omits them. | | emphasis | `'none' | 'subtle' | 'strong'` | no | 'none' | Lift the item in the middle of the scrollport — a scale plus an elevation step, driven by scroll position via CSS. `subtle` is a light touch, `strong` is visible across a room; past roughly `strong` a row wobbles while scrolling and pulls attention away from reading, which is why this is a scale and not a free number. Retune per instance with `--blocks-scroller-emphasis-scale` / `-shadow`. **Requires `align="center"`** and is a no-op otherwise (with a DEV warning): the lift marks the item that has arrived in the middle, and a start-aligned row has no middle. Neighbours are never dimmed or blurred — the point is to mark the middle, not to hide the rest. Respects `prefers-reduced-motion`, and where `animation-timeline` is unsupported it simply does nothing. | | gap | `'xs' | 'sm' | 'md' | 'lg' | 'xl'` | no | 'md' | Space between items. | | indicator | `'none' | 'dots'` | no | 'none' | Position indicator. `dots` renders one button per **resting place** — every dot jumps to its own destination and the current one carries `aria-current`. On a centred row that is one dot per item. On a `start`-aligned row the trailing items that share the end of the scroll range share one dot, labelled with their range ("Items 4–5 of 5"): a row has only as many distinct resting places as it can scroll to, and a dot per item would light up elsewhere than the press. Leave it off for long chip bars, where a dot per position is noise either way. | | itemBasis | `string` | no | '16rem' | Width of each item, as any CSS length (`'18rem'`, `'clamp(14rem,60vw,22rem)'`). This is what decides when the row overflows, so it is a prop rather than a class: with `align="center"` the component needs it to compute the edge padding. Override per-item layout via `slotClasses.viewport` if you need responsive widths. | | nextLabel | `string` | no | 'Next' (localised) | Accessible label for the next button. | | onActiveChange | `(index: number) => void` | no | | Fires when the item at the snap anchor changes, with its zero-based index. Intentionally coarse — there is no per-frame scroll-position callback, because the CSS-native carousel primitives this component is meant to be swapped for one day could not honour one. | | preset | `string` | no | | Apply a named preset registered via ``. Prefer this over `class` overrides when the requested look falls outside the semantic intent palette — presets keep hover/active/dark-mode logic coherent and make the custom look reusable across the project. | | previousLabel | `string` | no | 'Previous' (localised) | Accessible label for the previous button. | | slotClasses | `Partial>` | no | | Per-slot class overrides merged with tv() styles. Slots: root (the column — what `class` also targets) | viewport (the scroll container; also where per-item rules like width live, via `[&>*]:…`) | controls (the bar under the row) | control (a previous/next button) | indicator (the dot group) | dot. | | snap | `'proximity' | 'mandatory' | 'none'` | no | 'proximity' — or 'mandatory' when align="center" | Snap strictness. `proximity` snaps when you release nearby and otherwise leaves scrolling alone; `mandatory` always lands on an item — right when one item at a time is the unit, but it can skip past content sitting between two snap points. `none` scrolls freely. The default follows `align`, because the two alignments mean different things: a `start` row is a list you sweep across (`proximity`), a `center` row is a stage whose middle has to land on something (`mandatory`). | | unstyled | `boolean` | no | | Remove all default tv() classes; combine with `slotClasses` to rebuild the look. Note that this also strips the layout rules that make the row scroll and snap. | Inherited from: - Omit (omit-pattern) - Omit, 'children' | 'class'> (omit-pattern) ### Slots (slotClasses keys) `root`, `viewport`, `controls`, `control`, `indicator`, `dot`