<AvailabilityList>
Flat per-ticket-type picker. Defaults render one <TicketRow> per
unique ticket-type ID with the cheapest available price as the
"From $X" entry display. Wave concept (pricing tiers) is collapsed
internally via useTicketTypes(); attendees never see wave names
in the picker.
Selection state lives one level up on <RiftEvent> so the picker
and its reserve action can be siblings in any layout (sticky CTA,
two-column with persistent summary, separate panels).
Import
import { AvailabilityList } from "@feelrift/react";
Basic usage
import { AvailabilityList, RiftEvent, RiftProvider } from "@feelrift/react";
<RiftProvider>
<RiftEvent eventId="evt_summerfest_2026">
<AvailabilityList />
</RiftEvent>
</RiftProvider>;
Props
Accepts all HTMLAttributes<HTMLDivElement> plus:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
asChild | boolean | No | false | Swap the root element via Radix Slot. |
children | ReactNode | ((ticketTypes: readonly AggregatedTicketType[]) => ReactNode) | No | — | Replace the default rendering. Function form receives the aggregated, currently-active ticket-type list. |
now | () => Date | No | () => new Date() | Wall-clock source for active-wave filtering. Forwarded to useTicketTypes. Use to freeze time in tests / Storybook. |
className | string | No | — | Appended to the SDK's rift-availability-list class. |
…rest | HTMLAttributes<HTMLDivElement> | No | — | Forwarded to the root element. |
children
Three precedence rules apply, highest first:
- Function child —
(ticketTypes: readonly AggregatedTicketType[]) => ReactNode. Receives the aggregated per-ticket-type list (waves already collapsed, currently-active waves only). Use for full layout control without re-implementing the aggregation. - JSX child — composed inside the root wrapper. Use for static header/footer additions around the default list.
- No child — the SDK's default flat list of
<TicketRow>renders.
asChild composes orthogonally — it swaps the outer wrapper
element, not the inner contents.
now
Active-wave filtering depends on the current wall clock. The
default (() => new Date()) ticks with real time. Override for
test scenarios or Storybook stories where determinism matters:
<AvailabilityList now={() => new Date("2026-07-01T12:00:00Z")} />
Render output
Wraps content in <div class="rift-availability-list"> (or the
slot root). Inside:
- Loading state (
isLoading && !data): single<div class="rift-availability-list__loading">…</div>. - Error / no data state: single
<div class="rift-availability-list__error">carrying the generic error string from the locale table. - Default data state: one
<TicketRow>per ticket type inside<div class="rift-availability-list__tickets">. No wave headers, no wave names — the picker speaks ticket types only.
Behavior
- Aggregated view. Built on
useTicketTypes(), which collapses waves to one entry per ticket-type ID and filters bygetNow()against each wave'sstartsAt/endsAt. Cheapest active wave's price becomes the row's "From $X". Stepper clamps atmin(maxPerOrder, totalAvailable), wheremaxPerOrderis the most permissive active cap with available inventory. - Selection lives on
<RiftEvent>. Reads and writes selection viauseAvailabilitySelection(); doesn't provide the context. Any sibling (sticky reserve button,<EstimateBreakdown>, custom cart) reads the same scope. See<RiftEvent>— Event-scoped selection. - Selection is ephemeral. Lives in React state on
<RiftEvent>, not the persisted store — reservations supersede it. - No fetch trigger. Mounting
<AvailabilityList>does NOT trigger an explicit refetch;useAvailability()(whichuseTicketTypescomposes) decides when to refetch based on its options (default: mount-time fetch + cache TTL).
Note The wave-tier breakdown surfaces in
<EstimateBreakdown>once a quantity is selected. The picker stays flat; the breakdown explains the math.
Examples
Custom rendering via function children
import {
AvailabilityList,
useAvailabilitySelection,
type AggregatedTicketType,
} from "@feelrift/react";
<AvailabilityList>
{(ticketTypes) =>
ticketTypes.map((tt) => <MyTicketRow key={tt.id} ticketType={tt} />)
}
</AvailabilityList>;
function MyTicketRow({ ticketType }: { ticketType: AggregatedTicketType }) {
const { selection, setQuantity } = useAvailabilitySelection();
const quantity = selection.get(ticketType.id) ?? 0;
return (
<div>
<span>{ticketType.name}</span>
<span>From ${ticketType.fromPrice / 100}</span>
<button onClick={() => setQuantity(ticketType.id, quantity + 1)}>
+
</button>
<span>{quantity}</span>
<button
onClick={() => setQuantity(ticketType.id, Math.max(0, quantity - 1))}
>
−
</button>
</div>
);
}
Slot the root element
<AvailabilityList asChild>
<article className="card" />
</AvailabilityList>
Programmatic selection reset
function ClearButton() {
const { clearSelection } = useAvailabilitySelection();
return <button onClick={clearSelection}>Clear selection</button>;
}
Related
<EstimateBreakdown>— pricing breakdown that appears as soon as a quantity is selected.<TicketRow>— the per-ticket-type row the default layout renders.<ReserveButton>— reads the selection context to build the reservation request.useTicketTypes— the aggregated view this component consumes. Public types exported from@feelrift/react.useAvailabilitySelection— hook for reading/writing the selection from custom components. Public types exported from@feelrift/react.- Pricing and estimates — the wave-tier model and the algorithm behind the aggregation.