The ScrollContainer component moves a track of items horizontally, with navigation buttons, pointer drag, optional wrap-around, optional proximity scaling, optional markers, and an index you can drive from outside.
It moves a track with a transform rather than scrolling a box. That is what lets tiles paint past the container's edges — the items either side of the ones in front stay visible, so the run reads as something continuous rather than a window with two hard cuts in it. Pass clip to keep everything inside the component's own box instead.
Because the browser is no longer doing the scrolling, a trackpad's horizontal gesture does not move the track. draggable is on by default and the controls are always shown, so there is never an invisible affordance.
Past the last item the track continues into the first, in the same direction, with no rewind. Both controls stay enabled.
Markers show how many items there are and which one is in front, and jump to one when pressed. markers takes true to put them opposite the controls, or "top" / "bottom" to name the side.
The track follows a pointer — mouse, pen or finger. A gesture is claimed for one axis on its first movement and never reassigned, so a vertical swipe still scrolls the page. Let go past a fifth of a tile, or flick, and the track advances; anything shorter settles back. Without infinite the track resists being pulled past either end and springs back.
A drag that ends on a tile does not also open it — the click is swallowed before it reaches the item.
proximity scales each tile by how far it is from the one in front, so the run has a focus that moves with it. It is continuous rather than stepped: halfway through a drag the outgoing and incoming tiles are the same size, so the emphasis crosses between them as the pointer moves instead of snapping when the track settles.
Pass true for a gentle default, or a number for how much smaller each whole step away is, as a fraction — 0.08 makes the immediate neighbour 92%. Tiles stop shrinking at 72%, so a long run does not trail off into nothing.
active brings an item to the front by its index, and onActiveChange reports the item that arrived there, however it got there — a control, a marker or a drag. Together they let something outside the component stand in for the markers: a row of names, a set of thumbnails, a table of contents.
active is a command, not a lock. The run still moves on its own, and it is onActiveChange that keeps the caller's own state level with it. An active that never changes does not pin the run in place.
Position navigation controls at the top or bottom with different horizontal alignments.
Every prop other than the ones listed below is applied to each tile, so a tile is styled from out here.
Tiles carry no appearance of their own: no border, no radius, no aspect ratio. The only sizing they arrive with is a minWidth floor, so a carousel of anything that fills its parent has something to fill. Give them whatever shape the content needs.