# Grouped tables, totals, and stacked areas

Released 24 Oct 2026. [Components 2.17.0](https://github.com/TicketSolutionsPtyLtd/roadie/blob/main/packages/components/CHANGELOG.md#2170), [Core 2.12.0](https://github.com/TicketSolutionsPtyLtd/roadie/blob/main/packages/core/CHANGELOG.md#2120), [Charts 0.3.0](https://github.com/TicketSolutionsPtyLtd/roadie/blob/main/packages/charts/CHANGELOG.md#030), [Widgets 3.7.1](https://github.com/TicketSolutionsPtyLtd/roadie/blob/main/packages/widgets/CHANGELOG.md#371), and [Lint plugin 0.2.0](https://github.com/TicketSolutionsPtyLtd/roadie/blob/main/packages/lint-plugin/CHANGELOG.md#020)

Insight pages can group and total their tables, chart part of a whole over time, compare events by days out or on the event clock, and set a dashboard's period by dragging across a chart.

## Highlights

- **StackedArea**: A chart for part of a whole over time, such as scans by gate, with a share mode, a today marker, and annotations. See [StackedArea](https://ticketsolutionsptyltd.github.io/roadie/charts/stacked-area.md).
- **Grouped tables**: DataTable groups rows by one or more columns, with a total for each group, and RecordTable gains a totals row. See [Grouped tables](https://ticketsolutionsptyltd.github.io/roadie/foundations/tables.md).
- **Range selection**: Drag across a line, bar, or stacked area chart to select dates, or set a dashboard's period from a card. See [Range selection](https://ticketsolutionsptyltd.github.io/roadie/charts/dashboards.md).
- **Event comparisons**: Line charts line events up by days out or on the event's own clock, where a night runs to 6am. See [Event comparisons](https://ticketsolutionsptyltd.github.io/roadie/charts/line-chart.md).
- **@oztix/roadie-lint-plugin** (breaking): The ESLint plugin is now @oztix/roadie-lint-plugin and runs in oxlint too, with a preset that catches tests that can't fail. See [@oztix/roadie-lint-plugin](https://ticketsolutionsptyltd.github.io/roadie/overview/linting.md).

## New

- Add `size` (`sm`, `md`, `lg`) to `Checkbox`, `CheckboxGroup`, and `RadioGroup`, and `trailing` to `Checkbox` and `RadioGroup.Item` for a count or value at the end of the row.

  See [Checkbox](https://ticketsolutionsptyltd.github.io/roadie/components/checkbox.md), [Radio group](https://ticketsolutionsptyltd.github.io/roadie/components/radio-group.md).

  The default `md` box is now 20px, down from 24px, which also makes RecordTable's and Records' selection checkboxes smaller; pass `size='lg'` to keep the old size. A ticked or indeterminate checkbox and a chosen radio now fill with the accent's strong colour, with the icon or dot in the text colour for strong, and drop the sunken inset of an empty box. Stacked items in a `CheckboxGroup` or `RadioGroup` sit 2px apart at `sm` and 4px at `md`, down from 8px, and stay 8px apart at `lg`. A radio's dot is now half its circle at every size.
- `DataCard` and `Chart` take `controls`: switches for what the card's body shows, such as a chart's Cumulative, Incremental and Percentage modes.

  See [DataCard](https://ticketsolutionsptyltd.github.io/roadie/charts/data-card.md), [Chart](https://ticketsolutionsptyltd.github.io/roadie/charts/chart.md).

  They sit in a full-width row under the headline, wrap on a narrow card, and stay in place in every state and in a chart's Table view. `StatTile` doesn't take them.
- Add `groupBy` and `defaultExpanded` to `DataTable`.

  See [DataTable](https://ticketsolutionsptyltd.github.io/roadie/charts/data-table.md).

  Rows group by one or more text or status columns, outermost first. Each group gets a row that sums its number columns the way `totals` does, which opens and closes like an Accordion when anywhere on it is pressed, and every level's label sits indented in the title column, or the first column without a sum when there's no text column. Groups start closed: `defaultExpanded` opens them all, or the groups at the paths of values you give. Sorting orders groups by their sums, or by label, at every level. Dashboard `table` cards take the same two fields, `DashboardView` passes them on, and `validateDashboard` checks them.
- Add `totals` to `RecordTable` and `useRecords`: a row of totals under the headings, over every record the search and filters match.

  See [RecordTable](https://ticketsolutionsptyltd.github.io/roadie/components/record-table.md).

  `totals` sums the number and money fields in the browser; with server data, pass the server's sums as `{ values }`, or resolve them from `loadRange` beside `rowCount`. It labels itself "Totals for 1,234 shows" from `recordName`, and reads as one line above the rows on a phone. A column's `total: false` leaves it blank. Tables without `totals` don't change.
- Show the total of a number column that comes first in `DataTable`.

  See [DataTable](https://ticketsolutionsptyltd.github.io/roadie/charts/data-table.md).

  The totals label, each group's label, and the grouped header now go in the title column, the pinned text column or else the first text column, as in `RecordTable`. A table with no text column puts them in the first column without a sum, so the label never covers a total. `validateDashboard` follows the same rule: it warns about a totals row only when every column has a sum, or when `values` gives one for the label's column, and about grouped rows only when there's no text column and the label's column isn't status. In core, `tableLabelIndex(columns)` finds the label's column, `tableTitleColumn(columns)` the title, and `labelColumnIndex` applies the rule to columns of another shape; `totalsLabel(count, recordName, exact)` writes "Totals for 1,234 shows", with a + after a count that's a floor.
- Add `selectsPeriod` to the `line`, `bar`, and `stacked-area` plots of a dashboard description, so a drag across that card, or Shift with an arrow key and then Enter, sets the dashboard's period.

  See [Dashboard design](https://ticketsolutionsptyltd.github.io/roadie/charts/dashboards.md), [Dashboard](https://ticketsolutionsptyltd.github.io/roadie/charts/dashboard.md).

  `DashboardView` passes the range to `onPeriodChange` with the current comparison, and `validateDashboard` warns when x isn't dated or there's no period. Dashboards without it don't change.
- Add `xFormat` to LineChart and the dashboard `line` plot, with a `daysOut` format for comparing events by days to the event.

  See [Dashboard](https://ticketsolutionsptyltd.github.io/roadie/charts/dashboard.md).

  Ticks read "30d", and tooltips, tables and screen readers say "30 days before" and "Event day". Core adds `XFormat` and `formatX` to `@oztix/roadie-core/dataviz`.
- Add an `eventTime` x format to LineChart and the dashboard `line` plot, for comparing events on their own clock.

  `x` is hours from midnight starting the first event day, and a day runs to 6am. Ticks read "Day 2" and "19:00", and tooltips, tables and screen readers say "Day 2, 7:15pm". Core exports `END_OF_NIGHT_HOUR` from `@oztix/roadie-core/datetime`, the 6am cutoff that both a late show's night and an `eventTime` day share.
- Add `toElasticsearch` on `@oztix/roadie-core/records/elasticsearch`, which turns a records request into Elasticsearch query DSL (`query`, `sort`, `from`, and `size`) that means the same as `matchesRecordQuery`.

  See [Records model](https://ticketsolutionsptyltd.github.io/roadie/foundations/records.md).

  `paths` maps a field to the path filters and sort read, such as a `keyword` sub-field, and `searchFields` sets what search reads. `except` leaves out one field's chips, so a page can count that field's values against every other filter. It needs Elasticsearch 7.10 or later, or OpenSearch.
- Add `mode`, `today` and `annotations` to StackedArea and the dashboard `stacked-area` plot.

  `mode='share'` stacks every time to 100%, `today` marks the top of the stack with a dot that reads the total, and annotations work as on LineChart.
- Add `StackedArea` (`@oztix/roadie-charts/stacked-area`), a chart for part of a whole over time, such as scans by gate.

  See [StackedArea](https://ticketsolutionsptyltd.github.io/roadie/charts/stacked-area.md).

  The largest layer stacks at the bottom, past 6 the smallest roll into Other on top, and the tooltip opens with the total. Dashboards take it as plot kind `stacked-area`, with `/static` and `/tables` support. `ChartTooltip` rows take `shape: 'none'` for a row with no key, such as a total.
- Add `onRangeSelect` to `LineChart`, `BarChart`, and `StackedArea`, so a drag across dated x, or Shift with an arrow key and then Enter, selects a range.

  See [LineChart](https://ticketsolutionsptyltd.github.io/roadie/charts/line-chart.md), [BarChart](https://ticketsolutionsptyltd.github.io/roadie/charts/bar-chart.md), [StackedArea](https://ticketsolutionsptyltd.github.io/roadie/charts/stacked-area.md).

  It reports an inclusive `AbsoluteRange` in the venue's wall time, such as `{ start: '2026-10-03', end: '2026-10-10' }`, which fits straight into `DateRangePicker` or `onPeriodChange`. Charts without it don't change.
- Add `canonical-class`, which fails a Tailwind class not in its canonical form, such as `p-[16px]`, and fixes it to its canonical form, such as `p-4`.

  It reads Roadie's theme through `tailwindcss`, a new optional peer dependency at 4.1.15 or later. The `recommended` preset turns it on as an error in ESLint and oxlint, so an app can report new errors after upgrading: run the linter with `--fix` to rewrite them.
- Add the testing preset, which catches tests that can't be trusted: `roadie/no-css-class-in-jsdom` fails a `calc()`, container query, or breakpoint class in `toHaveClass`, `roadie/no-css-source-in-jsdom` fails a test that reads CSS source, `roadie/no-layout-fake-in-jsdom` fails a faked layout read, such as `getBoundingClientRect`, or a faked viewport media query, `roadie/no-cva-output-assertion` fails an assertion on the class string a variants function returns, and `roadie/no-fixed-sleep` fails a fixed wait, such as `setTimeout` inside a promise or `page.waitForTimeout`.

  In oxlint, extend `oxlint/testing.json`. In ESLint, spread `configs.testing` into your config array. The rules apply to `*.test.*` files. Tests that run in a real browser skip the jsdom rules: `*.browser.test.*` files, `*.spec.*` files, which Playwright runs, and files in an `e2e` folder. Those keep `roadie/no-fixed-sleep`, and a `*.test.*` file among them keeps `roadie/no-cva-output-assertion` too. The [linting guide](https://ticketsolutionsptyltd.github.io/roadie/overview/linting/#testing) shows both setups.
- Add `meta.docs.url` to each rule, linking it to its section of the [linting guide](https://ticketsolutionsptyltd.github.io/roadie/overview/linting/), so an editor can open the guide from a reported rule ID.

## Changed

- Update chart time ticks, tooltips, tables and spoken points to the date and time foundations.

  See [DateTime](https://ticketsolutionsptyltd.github.io/roadie/components/date-time.md).

  Ticks read "19:30" and "27 Nov", and tooltips read "Fri 27 Nov 2026, 7:30pm", the same strings `<DateTime>` gives.
- Update `CartContents` to stable, so a breaking change to it only comes in a major version.

  See [CartContents](https://ticketsolutionsptyltd.github.io/roadie/roadie-widgets/cart-contents.md).
- **Breaking.** Breaking: `@oztix/roadie-lint-plugin` is the new name of `@oztix/roadie-eslint-plugin`, and it now runs in oxlint as well as ESLint.

  To upgrade, swap the package in `package.json` and change your config's import to the new name. The rules, their `roadie/` IDs, and `configs.recommended` are the same.

  For oxlint 1.88 or later, extend `./node_modules/@oztix/roadie-lint-plugin/oxlint/recommended.json` from `.oxlintrc.json`, or import `@oztix/roadie-lint-plugin/oxlint/recommended.json` in `oxlint.config.ts`. The preset loads the plugin as `roadie`, so rule IDs match ESLint's. `eslint` and `oxlint` are both optional peer dependencies, so an app that uses one doesn't get a missing-peer warning for the other. The [linting guide](https://ticketsolutionsptyltd.github.io/roadie/overview/linting/) shows both setups.
- Update the README to lead with oxlint.

  ESLint 9 and 10 still run the same rules, for an app that can't move to oxlint yet.

## Fixed

- Fix an open `Accordion` item growing past its container when it holds wide content, such as a code block, so it no longer pushes a phone page sideways.

  See [Accordion](https://ticketsolutionsptyltd.github.io/roadie/components/accordion.md).
- RecordTable and RecordGrid in a `fill` box centre their empty and error states in the space under the header, instead of showing them at the top.

  Rows, cards, and loading placeholders still start at the top.
- Fix a ticked or indeterminate `Checkbox` that's `invalid`, which now fills with danger instead of the accent, so the error still shows.

  See [Checkbox](https://ticketsolutionsptyltd.github.io/roadie/components/checkbox.md).
- Fix pressing a later pinned `DataTable` cell in a linked or grouped row, which now follows the row's link or toggles its group like any other cell.

  See [DataTable](https://ticketsolutionsptyltd.github.io/roadie/charts/data-table.md).

  A number column that `groupBy` leaves first also keeps its figures end-aligned, and a linked row's title is a subtle `Link`, underlined in the accent colour while the row is hovered or focused.
- A `List` row with long text no longer widens the list past its container on a narrow screen; the title truncates and the trailing slot stays in view.

  See [List](https://ticketsolutionsptyltd.github.io/roadie/components/list.md).
- Fix an `is-interactive-within` surface taking the hover, press and focus ring of a nested surface's target.

  See [Card](https://ticketsolutionsptyltd.github.io/roadie/components/card.md), [DataTable](https://ticketsolutionsptyltd.github.io/roadie/charts/data-table.md), [RecordTable](https://ticketsolutionsptyltd.github.io/roadie/components/record-table.md).

  A `Card` around a grouped `DataTable`, linked `DataTable` rows or `RecordTable` list rows now stays still when one of those rows is hovered, pressed or focused, and a subtler card keeps its tint. A target now belongs to the nearest `is-interactive-within` around it.
- Fix the rows in a `QueryField` or `Records.Search` list jumping as the highlight moves.

  See [Query field](https://ticketsolutionsptyltd.github.io/roadie/components/query-field.md), [Records model](https://ticketsolutionsptyltd.github.io/roadie/foundations/records.md).

  The row with the Enter hint was 1px taller than the rest, so every row below it shifted. Each row is now tall enough for the hint.
- `RadioGroup` now dims a disabled group or item and ignores presses on its row, like `Checkbox`.

  See [Radio group](https://ticketsolutionsptyltd.github.io/roadie/components/radio-group.md), [Checkbox](https://ticketsolutionsptyltd.github.io/roadie/components/checkbox.md).

  An invalid group, from `invalid` or its `Field`, draws its empty circles with a danger border and tint, fills the chosen one with danger like an invalid ticked `Checkbox`, and sets `aria-invalid` on the `radiogroup`.
- RecordTable puts the totals label in the title column, or with no title in the first column without a sum that isn't an image, as `DataTable` does, so the label no longer covers a summed column's total, and in the title column it no longer hides with a column's `priority`.

  See [DataTable](https://ticketsolutionsptyltd.github.io/roadie/charts/data-table.md).

  With `totals` and records selected, the bulk actions bar's focus rings are no longer clipped at the top.
- Fix a scrolling `ToggleGroup` jumping back to its pressed item when it resized while keyboard focus was on another item, which hid the focused item.

  See [Toggle group](https://ticketsolutionsptyltd.github.io/roadie/components/toggle-group.md).

  It now keeps the focused item in view, and the pressed one when focus is elsewhere.
- `BarChart` with `onRangeSelect` now shades the selected bars whole, from the first bar's left edge to the last bar's right edge, instead of from centre to centre.

  See [BarChart](https://ticketsolutionsptyltd.github.io/roadie/charts/bar-chart.md).
- Clicking a chart no longer draws a focus ring around it in Chrome.

  Tabbing to a chart still shows the focus ring.
- LineChart's `today` dot now sits on the highlighted series, as the forecast cone does, instead of always on the first series.

  A numeric x axis no longer borrows the y `format`, so days stop rendering as percentages.
- LineChart and StackedArea keep an annotation label clear of the today label.

  A label that would cross it reads on the other side of its rule, or drops a line when neither side is clear.
