Skip to main content

<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:

NameTypeRequiredDefaultDescription
ticketTypeIdstringYesTicket-type ID this row represents (tt_…).
asChildbooleanNofalseSwap the wrapping <div> for a consumer-supplied element via Radix Slot.
childrenReactNode | ((args: { ticketType, quantity, setQuantity }) => ReactNode)NoReplace the default inner contents. Function form receives the aggregated ticket-type data + quantity + setter.
showRemainingbooleanNofalseShow a "N left" indicator next to the price when availability is positive. Scarcity-pressure is opt-in.
now() => DateNoWall-clock source forwarded to useTicketTypes for active-wave filtering. Tests / Storybook use this to freeze.
classNamestringNoAppended to the SDK's rift-ticket-row class.
…restHTMLAttributes<HTMLDivElement>NoForwarded 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 fromPrice comes from the aggregated view's first active wave, sorted cheapest-first. Selecting more than the current tier's available walks 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). maxPerOrder is the most permissive active cap with available inventory; totalAvailable is 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")} />
  • <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.