Skip to content

Components / Sections

Product Rail

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.

Live demo

On the shelf

This firing, piece by piece

All pieces

Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.

Variants · each row writes the attribute the prop writes; the section's own CSS answers

tone
density

Measured

JavaScript of its own

0 B

CSS 18.5 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.

Browser support

Limited availability

Chrome 116 · not Firefox · Safari 26; needs scroll-timeline, timeline-scope. Elsewhere: the row scrolls and snaps; the bar under it is not drawn.

Install Pro

The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:

terminal
npx shadcn@latest add @moonarc-pro/product-rail
src/pages/index.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>

Without a key the registry answers 401 with an upgrade message.

Props

PropTypeDefaultDescription
products{ href, name, spec?, price?, art: { name, colour?, accent?, block? }, transition? }[]noneThe 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.
eyebrowstringnoneA short line above the heading.
headlinestringnoneThe heading. Slot "headline" wins and may hold an <em>, set in the emphasis face and colour.
more{ href: string; label: string }noneA link beside the heading, e.g. to the whole collection.
labelstring'Products'Accessible name of the scrolling row.
priceLabelstring'Price'The words a screen reader hears before each price.
level2 | 32Heading 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.

Theming and overrides

The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.

Classes

.ma-prail.ma-prail__eyebrow.ma-prail__head.ma-prail__headline.ma-prail__item.ma-prail__more.ma-prail__progress.ma-prail__titles.ma-prail__track.ma-prail__viewport

Slots

slot="eyebrow"slot="headline"

Reduced motion

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

With ClientRouter

CSS only; nothing to rebind.

Why it is built this way

Replaces: Product sliders (Swiper, Splide) · Featured product carousels (Shopify themes). See the migration table.

Product Art

Backgrounds

A product drawn flat, for a shop slot that has no photograph yet: 29 drawings in six sets (clothes, ceramics, coffee, skincare, furniture and objects, bakery).

every browserhover

Firing 08 is on the shelf

Clay for the table, thrown slowly

Cups, bowls and vases thrown by hand and fired a few dozen at a time.

Fired
Every six weeks
Batch
About 120 pieces
Made by
Two pairs of hands
  • Tall vase32 cm · StonewarePrice: $86

  • Morning cup300 ml · Two glazesPrice: $24

  • Breakfast bowl16 cm · Speckled clayPrice: $32

Show a piece

The first screen of a maker's site: the heading, a line, two actions and three facts on one side; on the other, the products themselves, each drawn on its plate (ProductArt) with its name, a spec line in the mono face and its price.

newly · Chrome 105 · Firefox 121 · Safari 15.4loadclick
Glaze

Tall vase

Price: $86

A tall vase for branches and long stems, thrown in two parts and joined at the shoulder.

9 of 24 left in this batch

Size
32 cm tall, 14 cm across
Clay
Speckled stoneware
Care
Wash by hand
Delivery

Wrapped in paper and card, and sent the week after it comes out of the kiln.

The top of a product's page: the product drawn large on its plate (ProductArt), with its colourways under it as small swatches (radio buttons) that recolour the drawing in place; beside it the trail back to the shop, the name, the price, a line, how many of the batch are left, two actions, the specifications in the mono face and a few notes that open (care, delivery).

newly · Chrome 105 · Firefox 121 · Safari 15.4click

The shop

Pieces from the current firing

A whole collection on one page, filtered by kind: the products as cards (each drawn on its plate with ProductArt, a hang tag for the price, the name as the card's link and a spec line in the mono face) in two, three or four columns by the box's width, and above them a row of filters with their counts (core FilterGrid: the cards that stay slide into place, and the URL keeps the filter).

newly · Chrome 137 · Firefox 144 · Safari 18.4hoverclick

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown