# Product Rail (Moonarc)

A row of products that runs off the page's edge: each drawn on its plate (ProductArt) with a hang tag for the price, the name as the card's link and a spec line in the mono face. The row scrolls sideways and snaps to a card, and a thin bar under it follows the scroll. A hover or the keyboard focus on a card picks its product up and swings its tag. Each picture can carry a view-transition-name, so a product page that names its picture the same way takes it over from the card. Zero JavaScript.

- Pro block. Install: `npx shadcn@latest add @moonarc-pro/product-rail` (license key required)
- Tier B · category section · trigger hover, scroll
- Readout: `<ProductRail products={[{ href, name, spec, price, art }]} />`
- Browser support: limited (Chrome 116 · not Firefox · Safari 26); elsewhere: the row scrolls and snaps; the bar under it is not drawn
- Measured cost: 0 B JS (CSS 18.5 kB raw)
- Page: https://moonarc.dev/components/product-rail/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `products` | `{ href, name, spec?, price?, art: { name, colour?, accent?, block? }, transition? }[]` | none | The products, in order. art.name is a ProductArt drawing; block the plate (the first four in turn when unset); transition a view-transition-name for the picture, unique on the page. |
| `eyebrow` | `string` | none | A short line above the heading. |
| `headline` | `string` | none | The heading. Slot "headline" wins and may hold an <em>, set in the emphasis face and colour. |
| `more` | `{ href: string; label: string }` | none | A link beside the heading, e.g. to the whole collection. |
| `label` | `string` | `'Products'` | Accessible name of the scrolling row. |
| `priceLabel` | `string` | `'Price'` | The words a screen reader hears before each price. |
| `level` | `2 | 3` | `2` | Heading level of the section's heading; the products' names are one below. |
| `tone` | `'surface' | 'muted' | 'ink' | 'brand'` | `unset (paints nothing)` | The section's ground; the plates keep their blocks on every tone. |
| `density` | `'compact' | 'cozy' | 'roomy'` | `'cozy'` | Block padding ×0.8 / ×1 / ×1.25 through --ma-density. |

## Usage

```astro
---
import ProductRail from '@/components/moonarc/pro/ProductRail.astro';
---
<ProductRail
  eyebrow="On the shelf"
  more={{ href: '/pieces/', label: 'All pieces' }}
  products={[
    { href: '/pieces/morning-cup/', name: 'Morning cup', spec: '300 ml', price: '$24', art: { name: 'cup', colour: 'var(--ma-block-1)', block: 2 } },
    { href: '/pieces/low-vase/', name: 'Low vase', spec: '18 cm', price: '$64', art: { name: 'vase', colour: 'var(--ma-block-4)', block: 2 } },
    { href: '/pieces/dinner-plate/', name: 'Dinner plate', spec: '27 cm', price: '$38', art: { name: 'plate', colour: 'var(--ma-block-3)', block: 4 } },
  ]}
>
  <span slot="headline">This firing, <em>piece by piece</em></span>
</ProductRail>
```

## Reduced motion

The products and the tags stay where they are drawn; the row still scrolls.

## With ClientRouter

CSS only; nothing to rebind.

## Craft

- The row is a list in a region that takes the focus, so the arrow keys scroll it; axe asks this of any scrolling region.
- The name is the card's link and its ::after covers the card: a reader hears the name once, then the spec and the price.
- The tag hangs by its hole and swings past upright on hover: the price moves a little more than the product does, the way a tag on a string would.
- The bar under the row is a scroll-driven animation on a named timeline shared through timeline-scope, so it follows the scroll with no listener.

## Replaces

- Product sliders (Swiper, Splide)
- Featured product carousels (Shopify themes)

## Source

Pro block. The source is served by the license-gated registry: `npx shadcn@latest add @moonarc-pro/product-rail` with a key in components.json (https://moonarc.dev/account/).
