A full menu of illustrated photo cards with a sticky category rail that follows the section in view, scrolls smoothly on click and keeps the active chip centered.
Corner Kitchen · Order for pickup or delivery
Smashed to order, fries cut every morning, shakes spun with real ice cream.
Potato bun, griddled to order · 3 items
Two 3 oz patties, American cheese, pickles, shaved onion, house sauce.
Buttermilk thigh, hot honey glaze, cabbage slaw, dill pickles.
Black bean and beet patty, avocado, tomato, chipotle mayo.
Cut fresh every morning · 3 items
Twice fried, sea salt and a side of garlic aioli.
Cheddar sauce, crispy shallots, scallions, pickled jalapeño.
Lacinato kale, parmesan, sourdough crumbs, lemon dressing.
12 inch, naturally leavened · 3 items
San Marzano tomato, fior di latte, basil, olive oil.
Cup and char pepperoni, mozzarella, chili honey.
Jasmine rice, glazed chicken, soft egg, cucumber, sesame.
Made at the counter · 3 items
Fresh lemon, hibiscus syrup, sparkling water over ice.
Vanilla custard, burnt caramel, whipped cream, cherry.
Double ristretto, steamed oat milk, 8 oz.
2 items
Buttermilk biscuit, macerated berries, vanilla cream.
Dark chocolate layers, fudge frosting, flaky salt.
npx shadcn@latest add https://hyperblur.dev/r/menu-photo-rail.jsonWorks in any React project set up with the shadcn CLI. The file lands in components/hyperblur/menu-photo-rail.tsx.
import { MenuPhotoRail } from "@/components/hyperblur/menu-photo-rail"
<MenuPhotoRail stickyTop={64} onAdd={(item) => console.log(item.id)} />| Prop | Type | Default | Description |
|---|---|---|---|
| sections | { id: string; label: string; note?: string; items: MenuPhotoRailItem[] }[] | Burgers, Sides, Pizza & bowls, Drinks, Desserts | Menu sections. An item is { id, name, description, price: number, art, tint?, tag? }; art is one of burger, chicken-burger, veggie-burger, fries, loaded-fries, salad, pizza, pepperoni, bowl, soda, shake, coffee, cake, chocolate-cake. |
| currency | string | "USD" | ISO currency code passed to Intl.NumberFormat. Prices are plain numbers. |
| locale | string | "en-US" | Locale passed to Intl.NumberFormat for every price. |
| onAdd | (item, section) => void | Called when an Add button is pressed. The button also keeps a local count badge. | |
| onItemOpen | (item, section) => void | When set, the whole card becomes a button that calls it, for example to open an item drawer. | |
| stickyTop | number | 0 | Offset in px where the rail sticks, for pages with a fixed header. |
| labels | Partial<{ eyebrow; title; subtitle; rail; add; added; count; countOne; open }> | English copy | Every visible string. count uses {count}, for example "{count} items". |
| accent | string | "#ff6a3d" | Color of the active count, Add hover and focus rings. |
| appearance | "dark" | "light" | "dark" | Dark or light surfaces. |
| className | string | Extra classes on the section root. | |
| style | React.CSSProperties | Inline styles on the section root. |