Shelf

Spotify-style horizontal row of cards with overlay navigation chevrons and edge-fade gradients, built on Carousel.

Clients ready for orchestration

New households or unfinished configuration.

S
Smith Household
Held-away pension found — connect for orchestration.
V
Vasquez Household
Investment mandate not yet signed by client.
L
Larsen Household
New household — no flows configured yet.
C
Crowley Household
Held-away pension found — connect for orchestration.
P
Patel Household
Annual review due — pull latest balances.
O
Okafor Household
ISA top-up pending advisor approval.
<Shelf>  <ShelfHeader>    <ShelfTitle>Clients ready for orchestration</ShelfTitle>    <ShelfDescription>New households or unfinished configuration.</ShelfDescription>  </ShelfHeader>  <ShelfContent itemsPerView={{ default: 1, sm: 2, md: 3, lg: 4 }}>    {households.map((h) => (      <ShelfItem key={h.id}>        <Card size="sm">          <CardHeader>            <Avatar size="sm">              <AvatarFallback>{h.initial}</AvatarFallback>            </Avatar>            <CardTitle>{h.name}</CardTitle>            <CardDescription>{h.note}</CardDescription>          </CardHeader>          <CardFooter>            <Button variant="link" size="sm">              Set up <ArrowRight />            </Button>          </CardFooter>        </Card>      </ShelfItem>    ))}  </ShelfContent></Shelf>

Usage

import {
  Shelf,
  ShelfAction,
  ShelfContent,
  ShelfDescription,
  ShelfHeader,
  ShelfItem,
  ShelfTitle,
} from "@reva/ui";

Shelf is a thin opinionated composition over Carousel optimised for the most common Reva product pattern: a horizontally-scrollable row of cards with a section header, overlay navigation chevrons, and edge-fade gradients. Chevrons are hidden on small screens (touch-swipe only) and auto-hide on md+ when there is nothing more to scroll in that direction. The fade gradient blends from the surrounding surface to transparent, sourced from the --shelf-fade-from CSS variable (defaults to var(--background)); shelves placed inside a Card or muted section override per instance, e.g. <Shelf style={{ "--shelf-fade-from": "var(--card)" }}>.

Composition

Use the following composition to build a Shelf:

Shelf
├── ShelfHeader
│   ├── ShelfTitle
│   ├── ShelfDescription   (optional)
│   └── ShelfAction        (optional, right-aligned slot)
└── ShelfContent
    ├── ShelfItem
    └── ShelfItem

Examples

With action

Pair the header with ShelfAction to surface a "View all" link or any other right-aligned control. The action bottom-aligns with the description's last line.

Recently active households

Updated in the last 24 hours.

Smith Household
Last activity: 2h ago
Vasquez Household
Last activity: 5h ago
Larsen Household
Last activity: 12h ago
Crowley Household
Last activity: 18h ago
Patel Household
Last activity: 22h ago
<Shelf>  <ShelfHeader>    <ShelfTitle>Recently active households</ShelfTitle>    <ShelfDescription>Updated in the last 24 hours.</ShelfDescription>    <ShelfAction>      <Button variant="link" size="sm">View all</Button>    </ShelfAction>  </ShelfHeader>  <ShelfContent itemsPerView={{ default: 1, sm: 2, lg: 4 }}>    {/* …items… */}  </ShelfContent></Shelf>

Inside a Card

Override the fade source via --shelf-fade-from so the gradient blends with the parent surface. Here the parent is a Card (var(--card)).

Recent activity

The last six things that happened.

Plan approved
Smith Household · 1h ago
Document signed
Vasquez Household · 3h ago
ISA contribution
Larsen Household · 5h ago
Pension review
Crowley Household · 8h ago
Onboarding complete
Patel Household · 12h ago
Compliance check
Okafor Household · 18h ago
<Card>  <CardContent>    <Shelf style={{ "--shelf-fade-from": "var(--card)" }}>      <ShelfHeader>        <ShelfTitle>Recent activity</ShelfTitle>        <ShelfDescription>The last six things that happened.</ShelfDescription>      </ShelfHeader>      <ShelfContent itemsPerView={3}>        {/* …items… */}      </ShelfContent>    </Shelf>  </CardContent></Card>

Responsive itemsPerView

Pass a responsive object to adapt the visible count per breakpoint. Resize the viewport — count steps from 1 (mobile) → 4 (large desktop). Below md, chevrons hide and the row is touch-scrollable.

Strategies

Growth
Long-horizon equities
Balanced
Mixed growth + income
Income
Dividend-focused
Preservation
Capital protection
ESG
Sustainability-led
Global core
Diversified worldwide
<Shelf>  <ShelfHeader>    <ShelfTitle>Strategies</ShelfTitle>  </ShelfHeader>  <ShelfContent itemsPerView={{ default: 1, sm: 2, md: 3, lg: 4 }}>    {/* …items… */}  </ShelfContent></Shelf>

Edge state — fewer items than itemsPerView

When the number of items is less than or equal to itemsPerView, both chevrons and edge fades hide automatically. The row reads as a static grid.

Today's tasks

Review Smith plan
Due 10:00
Sign Vasquez mandate
Due 12:00
Send Larsen welcome
Due 15:00
<Shelf>  <ShelfHeader>    <ShelfTitle>Today's tasks</ShelfTitle>  </ShelfHeader>  <ShelfContent itemsPerView={4}>    {/* three items only */}  </ShelfContent></Shelf>

Props

Shelf

PropTypeDefaultDescription
classNamestringMerged onto the root <section>.
...restReact.ComponentProps<"section">Standard section attributes; use style to set CSS variables like --shelf-fade-from.

ShelfHeader / ShelfTitle / ShelfDescription / ShelfAction

ComponentNotes
ShelfHeaderCSS grid; auto-rows-min items-end gap-x-4 gap-y-1. Becomes grid-cols-[1fr_auto] when a ShelfAction is present.
ShelfTitleRenders Heading as="h3" size="lg" weight="semibold". Accepts all Heading props.
ShelfDescriptionRenders Text size="sm" color="muted-foreground". Accepts all Text props.
ShelfActionRight-aligned slot, bottom-aligned to the header (col-start-2 row-span-full self-end). Holds any element — typically Button variant="link" or IconButton.

ShelfContent

PropTypeDefaultDescription
itemsPerViewnumber | { default, sm, md, lg, xl, 2xl }{ default: 1, sm: 2, md: 3, lg: 4 }Fully-visible items per breakpoint.
optsEmblaOptionsTypeForwarded to the underlying Carousel. Defaults: align: "start", containScroll: "trimSnaps".
pluginsEmblaPluginType[]Forwarded to the underlying Carousel.
setApi(api: CarouselApi) => voidForwarded to the underlying Carousel.
classNamestringMerged onto the relative-positioned wrapper.

ShelfItem

Pass-through <div>. Inherits the responsive basis-* classes from ShelfContent's itemsPerView. Per-item override: <ShelfItem className="basis-1/2">…</ShelfItem>.

On this page