What to do next
GA is carrying the show and VIP is 45 points short of target. Push VIP in the final month.
A dashboard is a set of cards on one grid. These rules decide which cards to use, how big each one is and what goes in it, so every Oztix dashboard reads the same way.
A dashboard answers a few questions for one person. A promoter asks whether a show will sell out. A venue asks which nights need a push. Write those questions down first, then give each one a card. If a card answers no one’s question, cut it.
Dashboard sizes itself from its own width, not the window. It has 12 tracks from 960px, 6 from 600px and 2 below that, so it works in a side pane as well as on a phone. Cards flow in source order and the grid never moves them, so reading order always matches what people see. Cards in a row stretch to the same height. Dashboard.Section groups cards under an h2, and sections sit 32px apart.
| Size | Desktop (of 12) | Tablet (of 6) | Phone (of 2) | Use |
|---|---|---|---|---|
stat | 3 | 3 | 1 | Stat tiles |
sm | 4 | 3 | 2 | Ranked list, small chart |
md | 6 | 3 | 2 | Chart, table |
lg | 8 | 6 | 2 | Main chart |
full | 12 | 6 | 2 | Heatmap, wide table |
There are no custom spans, breakpoint overrides or dense packing. If a layout needs a size that isn’t here, it gets a new size in Roadie.
Sat 14 Nov, 30 days to go
GA is carrying the show and VIP is 45 points short of target. Push VIP in the final month.
1,464up 216, better
This week, of 2,400
61%up 9 points, better
Target 85%
112up 12%, better
Similar shows = 100
$118.4kup 9%, better
On last week
61%up 9 points, better
Ahead of similar shows. Forecast 96%
| Day | Sold | Forecast | Similar shows | Similar shows range |
|---|---|---|---|---|
| Sun 16 Aug | 14% | 14% | 7% to 22% | |
| Tue 18 Aug | 18% | 16% | 9.3% to 24% | |
| Thu 20 Aug | 21% | 19% | 12% to 27% | |
| Sat 22 Aug | 24% | 21% | 14% to 29% | |
| Mon 24 Aug | 27% | 22% | 15% to 30% | |
| Wed 26 Aug | 30% | 24% | 17% to 32% | |
| Fri 28 Aug | 32% | 26% | 19% to 34% | |
| Sun 30 Aug | 33% | 27% | 20% to 35% | |
| Tue 1 Sept | 35% | 29% | 22% to 37% | |
| Thu 3 Sept | 35% | 30% | 23% to 38% | |
| Sat 5 Sept | 36% | 32% | 25% to 40% | |
| Mon 7 Sept | 36% | 33% | 26% to 41% | |
| Wed 9 Sept | 36% | 34% | 27% to 42% | |
| Fri 11 Sept | 36% | 35% | 28% to 43% | |
| Sun 13 Sept | 36% | 36% | 29% to 44% | |
| Tue 15 Sept | 36% | 37% | 30% to 45% | |
| Thu 17 Sept | 37% | 38% | 31% to 46% | |
| Sat 19 Sept | 37% | 39% | 32% to 47% | |
| Mon 21 Sept | 38% | 40% | 33% to 48% | |
| Wed 23 Sept | 39% | 41% | 34% to 49% | |
| Fri 25 Sept | 40% | 42% | 35% to 50% | |
| Sun 27 Sept | 42% | 43% | 36% to 51% | |
| Tue 29 Sept | 43% | 44% | 37% to 52% | |
| Thu 1 Oct | 46% | 45% | 38% to 53% | |
| Sat 3 Oct | 48% | 46% | 39% to 54% | |
| Mon 5 Oct | 50% | 47% | 40% to 55% | |
| Wed 7 Oct | 53% | 48% | 41% to 56% | |
| Fri 9 Oct | 55% | 49% | 42% to 57% | |
| Sun 11 Oct | 57% | 50% | 43% to 58% | |
| Tue 13 Oct | 59% | 51% | 44% to 59% | |
| Thu 15 Oct | 61% | 53% | 46% to 61% | |
| Sat 17 Oct | 63% | 54% | 47% to 62% | |
| Mon 19 Oct | 64% | 56% | 49% to 64% | |
| Wed 21 Oct | 66% | 57% | 50% to 65% | |
| Fri 23 Oct | 68% | 59% | 52% to 67% | |
| Sun 25 Oct | 69% | 60% | 53% to 68% | |
| Tue 27 Oct | 71% | 62% | 55% to 70% | |
| Thu 29 Oct | 73% | 64% | 57% to 72% | |
| Sat 31 Oct | 76% | 67% | 60% to 75% | |
| Mon 2 Nov | 78% | 69% | 62% to 77% | |
| Wed 4 Nov | 80% | 71% | 64% to 79% | |
| Fri 6 Nov | 83% | 74% | 67% to 82% | |
| Sun 8 Nov | 86% | 77% | 70% to 85% | |
| Tue 10 Nov | 89% | 80% | 73% to 88% | |
| Thu 12 Nov | 92% | 83% | 76% to 91% | |
| Sat 14 Nov | 96% | 86% | 79% to 94% |
VIP is the one to push this month
| Ticket type | Sell-through |
|---|---|
| GA early bird300 of 300 | |
| GA1,004 of 1,700 | |
| VIP160 of 400 |
Most buyers are within 20km
| Suburb | Share |
|---|---|
| Fortitude Valley | 14% |
| West End | 11% |
| Paddington | 9% |
| Newstead | 7% |
Orders peak on Fridays and grow each week
| Day | Orders |
|---|---|
| Fri 25 Sept | 15 |
| Sat 26 Sept | 12 |
| Sun 27 Sept | 9 |
| Mon 28 Sept | 7 |
| Tue 29 Sept | 7 |
| Wed 30 Sept | 8 |
| Thu 1 Oct | 10 |
| Fri 2 Oct | 19 |
| Sat 3 Oct | 16 |
| Sun 4 Oct | 11 |
| Mon 5 Oct | 9 |
| Tue 6 Oct | 10 |
| Wed 7 Oct | 10 |
| Thu 8 Oct | 13 |
| Fri 9 Oct | 21 |
| Sat 10 Oct | 18 |
| Sun 11 Oct | 12 |
| Mon 12 Oct | 9 |
| Tue 13 Oct | 10 |
| Wed 14 Oct | 11 |
| Thu 15 Oct | 15 |
GA is carrying the show
| Month | GA | VIP | Early bird | Total |
|---|---|---|---|---|
| Aug | 414 | 60 | 300 | 774 |
| Sep | 213 | 40 | 0 | 253 |
| Oct | 377 | 60 | 0 | 437 |
Fans buy most on Friday evenings
| Weekday | 9am | 12pm | 3pm | 6pm | 9pm |
|---|---|---|---|---|---|
| Mon | 4 | 8 | 10 | 18 | 12 |
| Tue | 5 | 9 | 11 | 20 | 13 |
| Wed | 5 | 9 | 12 | 22 | 14 |
| Thu | 6 | 10 | 14 | 30 | 22 |
| Fri | 8 | 12 | 18 | 42 | 36 |
| Sat | 14 | 22 | 20 | 24 | 18 |
| Sun | 12 | 18 | 16 | 14 | 8 |
This is the show dashboard. It opens with a full note that says what to do, then four stat tiles, a full chart and two md tables side by side. Two md charts and a full heatmap close it. This column is narrower than 960px, so it shows 6 tracks or fewer. Here is the same layout at each width, and the JSX behind it.
Desktop
Tablet
Phone
Every row must fill at desktop, tablet and phone widths. These combinations do.
| Row | Desktop | Tablet | Phone |
|---|---|---|---|
Four stat | |||
Two md | |||
One full | |||
Six sm | |||
Mirrored lg + sm |
On tablet, lg takes a full row and sm takes half. So lg then sm leaves half a row empty. The mirrored sm fills it.
Desktop
Tablet
Phone
Do
Follow lg and sm with sm and lg. Both rows fill at every width.
Desktop
Tablet
Phone
Don’t
Don’t follow lg with md. It leaves gaps on desktop and tablet.
In development, Dashboard.Section logs a warning for any row that leaves a gap. It uses the same check as validateDashboard. It only sees the section’s direct children. If you wrap a card, pass size to the wrapper so the check still sees it.
[Roadie Dashboard] "Pace" has rows that don't fill: desktop row 1 leaves 4 empty; desktop row 2 leaves 6 empty; tablet row 2 leaves 3 empty. See /charts/dashboards.
DataCard is the shared card. StatTile, table cards and Chart are built on it. Its props match the JSON description field for field, so a card in JSX and a card in JSON come out the same.
61%up 16 points, better
Forecast 96% by show day
The label, headline and context line never wrap. Text that doesn’t fit ends in an ellipsis. The full text goes in the tooltip and the accessible name. The legend is the only chart text that wraps.
The headline is a value with a delta, or a takeaway sentence. Never both. Use a value when one number answers the question. Use a takeaway when there’s no single number, such as a channel mix or a table.
$118.4kdown 4%, worse
Last week, before fees
Do
Let the value and delta carry the story.
$118.4k
Revenue fell 4% on last week
Don’t
Don’t add a takeaway that repeats the value.
| Question | Card | Example |
|---|---|---|
| How much, right now | Stat tile | Tickets sold |
| Is it on track | Stat tile with a reference line | Sell-through against 85% |
| Which ones need attention | Data table with sparklines | Upcoming shows |
| What should I do | Note card | What to do next |
| Is the show selling on pace | Chart card showing LineChart with band, median, forecast, target and today | Pace index |
| How many have sold, and when | Chart card showing LineChart (cumulative), BarChart by day | Sales to date, daily orders |
| What moved sales | Annotations on a time chart | Announcements, line-up drops, email sends, ad bursts, price releases |
| How did the on-sale go | Chart card showing BarChart by hour, annotated | On-sale spike |
| Which ticket types are selling | Chart card showing StackedBars using the pair or trio sets | Ticket type mix |
| How much sold before general sale | Chart card showing StackedBars | Presale and general |
| Where did buyers come from | Chart card showing RankedBars | Channel, referrer, campaign |
| Where do buyers live | Chart card showing RankedBars with share | Postcode, region, travel distance |
| Who are the buyers | Chart card showing RankedBars or StackedBars | Age and gender brackets, new and returning |
| How far ahead do fans buy | Chart card showing Histogram | Booking lead time |
| How many tickets per order | Chart card showing Histogram | Order size |
| Where do buyers drop out | Chart card showing Funnel | Checkout conversion, waitlist to purchase |
| When do fans buy | Chart card showing Heatmap | Orders by hour and weekday, venue timezone |
| Which sections are filling | Chart card showing Heatmap, or a table of meters | Sell-through by section or zone |
| Is there unmet demand | Chart card showing BarChart with annotations, Funnel | Sell-out time by release, waitlist sign-ups, abandoned checkouts |
| How do buyers respond to price | Chart card showing RankedBars, annotated BarChart | Sales by price tier, jumps at each price release |
| What else do our buyers go to | Chart card showing RankedBars | Also bought |
| How are all my shows tracking | Chart card showing Scatter with quadrants | Portfolio pace against sell-through |
| How did entry go | Chart card showing BarChart with line, SmallMultiples per gate | Scan rate per 15 minutes, share of fans inside |
| Did people turn up | Chart card showing RankedBars with reference (similar shows) | Attendance against sold, no-show rate |
| How good was the forecast | Chart card showing LineChart | Forecast against actual |
| How much was resold | Chart card showing StackedBars | Resale share |
Copy is sized for the card’s narrowest width. Past these limits it truncates.
| Size | Label | Context |
|---|---|---|
stat | 19 characters | 20 characters |
md | 32 characters | 34 characters |
Chart cards carry the Chart/Table switch in the label’s row, so their label budget is tighter than COPY_LIMITS.
| Size | Label |
|---|---|
sm | 21 characters |
md | 21 characters |
lg | 27 characters |
full | 27 characters |
1,842up 214, better
This week, of 2,400
Do
Put the comparison on the context line.
1,842 ↑ 214 this week vs last week
Don’t
Don’t pack the comparison into the label or delta.
Every card takes a state. The app sets it. A hidden sparkline is not a state.
ready
The data arrived. This is the default.
1,842up 214, better
This week, of 2,400
loading
The app is fetching. The label shows straight away and skeletons hold the shape, so nothing jumps when the data lands.
empty
There is nothing to count yet. Say so in one line. Never draw a chart full of zeros.
No sales yet. Tickets go on sale Fri 27 Nov.
error
The request failed. The card says so in plain words and the app passes a Retry action.
We couldn't load tickets sold. Try again in a minute.
stale
The app decides when data is stale and passes the time to show in the footer.
1,842up 214, better
This week, of 2,400
Actions sit at the top right of a card, in the label’s row. They carry handlers, so the app adds them and the JSON never does. Pass actions to DataCard, StatTile or Chart. For cards described as data, pass cardActions to DashboardView. It gets each card’s spec and returns its actions, or nothing. When DashboardView renders on the server, return a client component that owns the handlers, not inline handlers.
61%
Do
Put the view switch first and More last.
VIP is the one to push
Don’t
Don’t line up icon buttons. They crowd out the label.
VIP is the one to push
Do
Show More at rest, in every state.
Don’t
Don’t hide actions until hover. Touch has no hover, and a hidden button still takes focus.
Group the menu in this order and leave out what a card can’t do. Build it with Menu: pass DataCard.MoreButton to Menu.Trigger as its render. The button keeps its name, “More actions for” the card’s label, and its ref. Name each group with Menu.GroupLabel, split groups with Menu.Separator, and open the menu with align='end' so it opens back over the card.
| Group | Items |
|---|---|
| Chart | View, Edit, Rename, Duplicate, Alerts |
| Display | Hide description, and the view options |
| Export | Download CSV, Copy as image, and Refresh data with “Last updated 4 hours ago” as helper text |
| Dashboard | Move to, Copy to, then Remove from dashboard last, in the danger intent |
cardTable(card) from @oztix/roadie-charts/tables builds it on the server, for a chart card or a table card.renderChartSvg from @oztix/roadie-charts/static.More takes room from the label, which truncates past it. Beside More, keep chart labels to 18 characters at sm and md and 23 at lg and full, and stat labels to 14. ACTIONS_LABEL_LIMITS holds them. Table and note labels keep their COPY_LIMITS. validateDashboard can’t check this, because actions aren’t part of the JSON.
priority. As the card narrows, higher numbers hide first. Columns without one always show.pin column in place.One show is behind similar shows
| Show | Daily sales, 30 days | Sell-through | Pace index | Gross |
|---|---|---|---|---|
| Ball Park MusicThe Lantern Room, Fortitude Valley | 112, up 12, better | $118.4k | ||
| Ocean AlleyParkside Amphitheatre, Geelong | 101, up 1, better | $183k | ||
| Julia JacklinThe Gasworks Room, Hobart | 78, down 22, worse | $22.9k |
At this width the sparkline column hides first. Show all columns brings it back.
Any dashboard can be written as a JSON description with its numbers inline. Card fields match the component props, and DashboardView from @oztix/roadie-charts renders it. Agents can build and check a dashboard this way before anyone sees it.
{"id": "sold", "kind": "stat", "size": "stat","label": "Tickets sold", "value": 1464,"delta": { "value": 216 }, "context": "This week, of 2,400"}
validateDashboard from @oztix/roadie-core/dashboard returns every problem with its path.
import { validateDashboard } from '@oztix/roadie-core/dashboard'const { problems } = validateDashboard(spec)
Here the second section puts a pace card at lg before a channels card at md. It returns these problems.
| Path | Message | Severity |
|---|---|---|
| sections[1] | Row 1 leaves 4 empty columns on desktop after pace | warning |
| sections[1] | Row 2 leaves 6 empty columns on desktop after channels | warning |
| sections[1] | Row 2 leaves 3 empty columns on tablet after channels | warning |
It also checks sizes per card kind, unique ids, visual columns, a source on every chart and table card, copy lengths, dashes and sentence case.
CARD_SPANS and COPY_LIMITS hold the sizes and limits above. Import them from @oztix/roadie-core/dashboard-layout when you only need the layout rules, because that entry doesn’t load Zod.