<TicketRow>
One row per ticket type: name + "from $X" price + quantity
stepper. Consumes the aggregated view from useTicketTypes() so
the displayed price is the cheapest active tier and the stepper
clamps against the rolled-up totalAvailable and
maxPerOrder. Rendered by <AvailabilityList> for each
ticket type the event exposes; mount directly when building a
custom picker layout.
Import
import { TicketRow } from "@feelrift/react";
Basic usage
import { RiftEvent, RiftProvider, TicketRow } from "@feelrift/react";
<RiftProvider>
<RiftEvent eventId="evt_summerfest_2026">
<TicketRow ticketTypeId="tt_ga" />
<TicketRow ticketTypeId="tt_vip" showRemaining />
</RiftEvent>
</RiftProvider>;
Renders null when the ticket type has no active wave (sold out
across all tiers, ID stale, etc.). The picker speaks ticket types
only — the wave concept doesn't surface here; the breakdown across
waves shows up in <EstimateBreakdown> once a quantity is
selected.
Props
Accepts all HTMLAttributes<HTMLDivElement> (except children —
see below) plus:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
ticketTypeId | string | Yes | — | Ticket-type ID this row represents (tt_…). |
asChild | boolean | No | false | Swap the wrapping <div> for a consumer-supplied element via Radix Slot. |
children | ReactNode | ((args: { ticketType, quantity, setQuantity }) => ReactNode) | No | — | Replace the default inner contents. Function form receives the aggregated ticket-type data + quantity + setter. |
showRemaining | boolean | No | false | Show a "N left" indicator next to the price when availability is positive. Scarcity-pressure is opt-in. |
now | () => Date | No | — | Wall-clock source forwarded to useTicketTypes for active-wave filtering. Tests / Storybook use this to freeze. |
className | string | No | — | Appended to the SDK's rift-ticket-row class. |
…rest | HTMLAttributes<HTMLDivElement> | No | — | Forwarded to the root element. |
children
Two forms:
- JSX child — replaces the default inner row contents (name,
price, stepper). The wrapping
<div>still applies the SDK's class composition. - Function child — receives
{ ticketType, quantity, setQuantity }and returns whatever you want. Useful for an entirely custom stepper while keeping the SDK's clamping and selection wiring.
<TicketRow ticketTypeId="tt_ga">
{({ ticketType, quantity, setQuantity }) => (
<MyCard
title={ticketType.name}
price={ticketType.fromPrice}
count={quantity}
onChange={setQuantity}
/>
)}
</TicketRow>
showRemaining
Default false. When true, the row appends a " · N left"
indicator to the price when totalAvailable > 0. Scarcity
messaging affects conversion both ways — leave it off unless the
design explicitly wants it.
asChild
When true, the row delegates rendering to its immediate child
via Radix Slot, preserving the className composition and the
default inner contents:
<TicketRow ticketTypeId="tt_ga" asChild>
<article className="my-card" />
</TicketRow>
Render output
<div class="rift-ticket-row">
<div class="rift-ticket-row__info">
<div class="rift-ticket-row__name">{ticketType.name}</div>
<div class="rift-ticket-row__price">
{From $40.00 / Sold out}
<span class="rift-ticket-row__remaining"> · {N} left</span>
</div>
</div>
<div class="rift-ticket-row__stepper">
<button aria-label="Remove one {name} ticket">−</button>
<span class="rift-ticket-row__quantity">{quantity}</span>
<button aria-label="Add one {name} ticket">+</button>
</div>
</div>
Stepper aria-labels carry the ticket-type name and are
localized via useLocale() — multiple rows on the same page
produce distinct, AT-readable labels.
Behavior
- Cheapest-tier price. The displayed
fromPricecomes from the aggregated view's first active wave, sorted cheapest-first. Selecting more than the current tier'savailablewalks the selection into the next-cheapest tier — the per-row stepper doesn't surface that;<EstimateBreakdown>does. - Stepper clamping. Upper bound is
min(maxPerOrder, totalAvailable).maxPerOrderis the most permissive active cap with available inventory;totalAvailableis the sum. The buttons disable at the bounds. - Sold-out state. When
totalAvailable === 0, the price slot shows "Sold out" and the stepper buttons disable. - Selection context. Reads from / writes to
useAvailabilitySelection()— the same context<AvailabilityList>and<ReserveButton>consume. Multiple rows for the same event share a selection.
Examples
Custom stepper via function children
<TicketRow ticketTypeId="tt_ga">
{({ ticketType, quantity, setQuantity }) => (
<div className="my-row">
<strong>{ticketType.name}</strong>
<input
type="number"
value={quantity}
min={0}
max={Math.min(ticketType.maxPerOrder, ticketType.totalAvailable)}
onChange={(e) => setQuantity(Number(e.target.value))}
/>
</div>
)}
</TicketRow>
Scarcity indicator on
<TicketRow ticketTypeId="tt_vip" showRemaining />
Storybook with a frozen clock
<TicketRow ticketTypeId="tt_ga" now={() => new Date("2026-07-01T12:00:00Z")} />
Related
<AvailabilityList>— renders one<TicketRow>per ticket type by default.<EstimateBreakdown>— surfaces the wave-tier breakdown that this row deliberately hides.useTicketTypes— the aggregated view this row consumes. Public types exported from@feelrift/react.- Pricing and estimates — the wave-tier model and the cheapest-first aggregation.