TerraUI v0.1.1

Shelf

A titled horizontal strip of pockets, such as recent items or favourites. Only a heading and spacing frame it, so the items stay the subject.

iPhone 17 Pro · iOS 27
A heading with trailing content, typically a Counter, over a strip that scrolls sideways.A heading with trailing content, typically a Counter, over a strip that scrolls sideways.
import { Cell, Counter, Shelf } from "@terratcg/ui";
import { View } from "react-native";

export default function ShelfRecent() {
  return (
    <View style={{ width: 320 }}>
      <Shelf title="Your other copies" trailing={<Counter value={2} total={5} size="sm" tone="secondary" />}>
        <Cell style={{ width: 72, height: 100 }} />
        <Cell style={{ width: 72, height: 100 }} />
        <Cell frame="well" style={{ width: 72, height: 100 }} />
        <Cell frame="well" style={{ width: 72, height: 100 }} />
        <Cell frame="well" style={{ width: 72, height: 100 }} />
      </Shelf>
    </View>
  );
}

Import

import { Shelf } from "@terratcg/ui";

Usage

<Shelf title="Recent" trailing={<Counter value={2} total={5} size="sm" tone="secondary" />}>
  {recent.map((card) => (
    <Cell key={card.id} onPress={() => open(card)} accessibilityLabel={card.name} style={{ width: 72, height: 100 }}>
      <CardArt card={card} />
    </Cell>
  ))}
</Shelf>

The title is a secondary SectionHeader, with trailing at its end. The children sit in a horizontal ScrollView, spaced by the theme.

Examples

Without a title, bleeding to the edge

Without title there is no header row; inset pads the strip and bleeds it by the same amount, so items scroll to the edge of the screen.

iPhone 17 Pro · iOS 27
Without title there is no header row; inset pads the strip and bleeds it by the same amount, so items scroll to the edge of the screen.Without title there is no header row; inset pads the strip and bleeds it by the same amount, so items scroll to the edge of the screen.
import { Cell, Shelf } from "@terratcg/ui";
import { View } from "react-native";

export default function ShelfUntitled() {
  return (
    <View style={{ width: 320 }}>
      <Shelf inset={16}>
        <Cell frame="dashed" style={{ width: 72, height: 100 }} />
        <Cell frame="dashed" style={{ width: 72, height: 100 }} />
        <Cell frame="dashed" style={{ width: 72, height: 100 }} />
        <Cell frame="dashed" style={{ width: 72, height: 100 }} />
        <Cell frame="dashed" style={{ width: 72, height: 100 }} />
      </Shelf>
    </View>
  );
}

Props

Prop

Type

Shelf also accepts React Native's View props. A testID also names the strip, as <testID>-strip.

Accessibility

  • Heading: the title is announced as a header, so screen reader users can jump between shelves.
  • Strip: the items are read in order; give each its own label, as a pressable Cell needs.
  • Scrolling: the strip scrolls with the platform's own horizontal scrolling, without a scroll indicator. Leave part of the next item visible so it reads as scrollable.

Platform differences

  • iOS and Android: the same strip, scrolling natively on each platform.
  • Web: Shelf renders through react-native-web for these docs, but the web is not a supported target.

Rules

  • Give the shelf a title: trailing shows only beside one.
  • Set inset to your page's horizontal padding, so items scroll to the screen edge but rest aligned with the page.
  • Keep items one height; a Shelf is a strip, not a masonry grid.

On this page