RoadieRoadie
  • Home
  • Brand
  • Foundations
  • Content
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeBrandFoundations
Appearance

Widgets

  • CartContents
  • CartDrawerBeta

CartContents

Status
Stable
Added
Widgets 3.3.022 Jun 2026
Updated
Widgets 3.7.124 Oct 2026
Docs updated
24 Oct 2026

The day-grouped event and ticket list that powers the cart drawer's body. It's exported on its own so you can render it on a full /cart page.

  • Two layouts via container: the drawer body, or a full page with a Cart header (ticket count + live expiry) and a sticky footer
  • Groups events by day with sticky day headers
  • Reserved seats render as range badges via the shared formatSeatRange
  • Per-event remove with an inline confirm, and removals animate out
  • Presentational. You pass it a CartDetails and it renders. It has no overlay and does no fetching

It's the same component CartDrawer renders internally, so a /cart page and the docked drawer stay visually identical over one shared core.

Live demo

This is how a /cart page would use it: in a normal page column, with no overlay. The frame below is a fixed-height "page area". Use the React / Vue tabs to compare the skins, and the controls to test:

  • container switches the layout. page fills the frame with a Cart header (ticket count + live expiry), a sticky edge-to-edge footer, and a centred empty state. drawer is the body-only list (no header/footer) for embedding in your own chrome.
  • Add event / Empty cart / Reset change the cart length. Empty it to see the last event animate out and the empty state animate in. Per-event removal (the trash icon) animates too. Runs on a canned cart.
import { CartContents } from '@oztix/roadie-widgets/cart-contents/react'
// container="page" adds the header + footer and fills its container's height.
<div className="min-h-full">
<CartContents cart={cart} container="page" onNavigate={go} browseHref="/events"
checkoutUrl={url} locale="en-AU" currency="AUD" onRemoveEvent={remove} />

Installation

pnpm add @oztix/roadie-widgets

CartContents is presentational, so it needs lighter peers than the full drawer (no react-query). Install the peers for the skin you use.

React

pnpm add react react-dom @oztix/roadie-components motion @number-flow/react \
@phosphor-icons/react

Vue

pnpm add vue @number-flow/vue @phosphor-icons/vue focus-trap

Tailwind setup

CartContents' classes live in the package's compiled output, so Tailwind needs to scan it. Otherwise the styles never make it into your CSS. Add the CSS imports for the skin you use to your global stylesheet.

React. The React skin renders @oztix/roadie-components, so include its CSS as well:

/* app/globals.css */
@import '@oztix/roadie-core/css';
@import '@oztix/roadie-components/css';
@import '@oztix/roadie-widgets/css';

Vue. The Vue skin uses core classes only, so it needs no components CSS:

/* app/globals.css */
@import '@oztix/roadie-core/css';
@import '@oztix/roadie-widgets/css';

Each package's CSS ships its own @source, so Tailwind scans the compiled classes without you ever writing a node_modules path.

Import

// React
import { CartContents } from '@oztix/roadie-widgets/cart-contents/react'
import type { CartDetails } from '@oztix/roadie-widgets/cart-contents/react'
// Vue
import { CartContents } from '@oztix/roadie-widgets/cart-contents/vue'

Either entry pulls in only the list. It brings none of the drawer's overlay, drag, focus-trap, or expiry code. The cart drawer composes this same component, so the standalone page and the docked drawer stay identical.

Usage

Pass a CartDetails and the presentational props; the host owns data fetching and routing. Build a CartDetails however you like, or reuse the shared cart client:

import { createCartClient } from '@oztix/roadie-widgets/cart'
import { CartContents } from '@oztix/roadie-widgets/cart-contents/react'
const cart = createCartClient({ host: '' }) // same-origin
const details = await cart.getDetails(collectionId)
  • container is the layout preset. drawer (default) is the body-only list (no header or footer, natural height) for embedding in your own chrome. It's what CartDrawer renders. page is the full /cart experience: a fill-height column with a Cart header (ticket count + live expiry), a sticky edge-to-edge footer pinned to the bottom, and a centred empty state. Give the parent a height (e.g. <div className="min-h-full">). You don't need an external flex-1, mt-auto, or justify-center.

  • onRemoveEvent / busy wire per-event removal. busy disables the trash triggers while a remove is in flight.

  • removingEventId takes the event you're deleting for the duration of your async remove. CartContents dims the whole list and shows a danger Removing… overlay on that event until it leaves cart. Pair it with your remove handler:

    const [removingId, setRemovingId] = useState<string | null>(null)
    async function remove(eventId: string) {
    setRemovingId(eventId)
    try {
    await cart.removeItem(details.cartId, eventId)

    Vue is identical. Bind :removing-event-id="removingId" and handle @remove-event.

When cart has no tickets, CartContents renders the empty state with the browseHref call to action. Under container="page" it's centred and uses the larger (md) empty-state size; the drawer preset uses the compact (sm) size. Removing the last event animates the list out and the empty state in.

Props

API reference

CartContents

cartCartDetails
Required
onNavigate(href: string) => void
Required
browseHrefstring
Required

App-specific browse target for the empty state.

checkoutUrlstring | null
Required

Pre-validated checkout URL. Null when the safety check failed — CTA disabled.

localestring
Required
currencystring
Required
classNamestring
container"drawer" | "page"

Layout preset. `drawer` (default) is body-only; `page` adds the header, sticky footer and fill-height.

Defaults to drawer.

onRemoveEvent((eventId: string) => void)

Optional per-event remove handler. Receives the `eventId`.

busyboolean

True while a remove is in flight — disables the trash triggers.

Defaults to false.

removingEventIdstring | null

Event currently being removed. Dims the list and shows a "Removing…" overlay on that event until it leaves `cart`. Set it for the duration of your async remove.

Defaults to null.

Previous page← OverviewNext pageCartDrawer →