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

Charts

  • Guidelines

    • Data visualisation
    • Dashboard design
    • Tables
  • Layout

    • DashboardBeta
    • Dashboard periodBeta
    • DataCardBeta
  • Data pieces

    • StatTileBeta
    • MeterBeta
    • SparklineBeta
    • DataTableBeta
    • DeltaBeta
  • Chart types

    • LineChartBeta
    • BarChartBeta
    • RankedBarsBeta
    • StackedBarsBeta
    • StackedAreaBeta
    • HistogramBeta
    • FunnelBeta
    • HeatmapBeta
    • ScatterBeta
    • SmallMultiplesBeta
  • Chart parts

    • ChartBeta
    • ChartLegendBeta
    • ChartPatternsBeta
    • ChartTooltipBeta
  • Examples

    • Show dashboard
    • Portfolio dashboard
    • Audience dashboard

Dashboard period

Status
Beta
Added
Components 2.16.020 Oct 2026
Docs updated
10 Oct 2026

A dashboard’s period and what it compares with, in one picker.

Import

import { DashboardPeriod } from '@oztix/roadie-components/dashboard-period'

Examples

Default

One button shows the period and, when there is one, the dates it compares with: “This month 1 to 31 Oct 2026 vs 1 to 30 Sept 2026”. It opens a DateRangePicker that waits for Apply, because each change refetches every card. Its presets look ahead 30 and 90 days, back 30 days and 12 months, and over this financial year. Under the range, the Compare switch turns the comparison on, and Previous period or Previous year picks it, with the dates it covers below. Apply sends the period and its comparison together. The value is { range, compare }, and no comparison leaves compare out. compareOptions changes the choices: see below.

function Default() {
const [period, setPeriod] = useState({ range: 'this-month', compare: 'previous-period' })
return (
<div className='grid gap-4'>
<DashboardPeriod value={period} onValueChange={setPeriod} />
<p className='text-sm text-subtle'>Value: {JSON.stringify(period)}</p>

States

readOnly shows the period and comparison with a lock, for a dashboard whose period the page sets, such as one show’s on-sale window. disabled turns it off.

{/* Default */}
<DashboardPeriod defaultValue={{ range: 'last-month', compare: 'previous-period' }} />
{/* Read only */}
<DashboardPeriod readOnly defaultValue={{ range: { start: '2026-11-02', end: '2026-11-29' }, compare: 'previous-year' }} />
{/* Disabled */}
<DashboardPeriod disabled defaultValue={{ range: 'last-month', compare: 'previous-period' }} />

With custom dates

Add 'custom' to compareOptions, and Custom dates opens a range of its own, starting from the previous period’s dates. The comparison is then { start, end }. Leave it out, as by default, and custom dates the app sets still show as Custom dates, without a picker, so switching away and back keeps them.

function Custom() {
const [period, setPeriod] = useState({
range: { start: '2026-11-02', end: '2026-11-08' },
compare: { start: '2025-11-03', end: '2025-11-09' }
})
return (

With your own comparisons

compareOptions lists the choices in order. Roadie’s are 'previous-period', 'previous-year' and 'custom'. The app’s own are { value, label, description? }, such as similar venues. Choosing one sets compare to its value, which comes back as it is: Roadie works out no dates for it, shows its description where the dates go, and the button reads “vs similar venues”. 'none' adds the Compare switch. Leave it out, as here, and nothing turns the comparison off once one is chosen. The default is ['none', 'previous-period', 'previous-year']. An empty list, or ['none'] alone, leaves the comparison out of the picker. A comparison the value holds that the list leaves out still shows among the choices. If you hoist the list, add as const, so TypeScript keeps your values.

function Benchmark() {
const [period, setPeriod] = useState({ range: 'last-month', compare: 'similar' })
return (
<div className='grid gap-4'>
<DashboardPeriod
today='2026-10-07'

With presets

presets replaces the period’s list, as on DateRangePicker. A sales dashboard reports on periods to date, so it takes the picker’s own dateRangePresets.

<DashboardPeriod
defaultValue={{ range: { period: 'month', offset: 0, toDate: true }, compare: 'previous-period' }}
presets={dateRangePresets}
/>

With fixed presets

A preset can be fixed dates, such as a last 30 days that ends yesterday, worked out by the app. When the period is one of them, the button shows its label with its dates, as it does for relative presets, and the phone’s list shows its dates beside it.

// Wed 7 Oct 2026. The app's data ends yesterday.
const PRESETS = [
{ value: { direction: 'next', amount: 30, unit: 'day' } },
{ value: { start: '2026-09-07', end: '2026-10-06' }, label: 'Last 30 days' },
{ value: { start: '2025-10-07', end: '2026-10-06' }, label: 'Last 12 months' },
{ value: { start: '2026-07-01', end: '2027-06-30' }, label: 'This financial year' }

With the data’s dates

Pass the same dataStart, dataEnd and alignWeekday you give resolveComparison, and the comparison shows the dates the app will fetch, or “Not enough history” or “Nothing to compare” when the data can’t cover them. Here sales data runs to today, so this month compares with the same days of last month, and the previous year lines up weekdays.

<DashboardPeriod
today='2026-10-07'
dataEnd='2026-10-07'
alignWeekday
defaultValue={{ range: 'this-month', compare: 'previous-period' }}
/>

With your own controls

Pass the app’s other controls, such as a channel filter, as children, and they sit after the period. On a narrow container they stack.

<DashboardPeriod defaultValue={{ range: 'last-month', compare: 'previous-period' }}>
<Select defaultValue='all'>
<Select.Trigger size='lg' aria-label='Channel' className='w-full @sm:w-fit gap-2'>
<Select.Value />
<Select.Icon />
</Select.Trigger>

In a dashboard

Give a dashboard description a period, and DashboardView shows this toolbar above its sections. Its onPeriodChange gets the new period: resolve it with resolveDateRange and resolveComparison, fetch, and pass back a description with the new period and its history. A delta marked comparison: true follows the comparison. Here sales data starts on 1 March 2026, so month to date has nothing to compare with a year back. Choose the previous period to see the deltas.

// Wed 7 Oct 2026 in Melbourne. Sales data runs from 1 March to today.
const TODAY = '2026-10-07'
const SALES_DATA = {
now: new Date('2026-10-07T02:00:00Z'),
timeZone: 'Australia/Melbourne',
dataStart: '2026-03-01',

Guidelines

  • Previous period follows the calendar. This month compares with last month, this quarter with last quarter, and month to date with last month to the same day. Rolling ranges and fixed dates compare with the same number of days, ending the day before. Custom comparison dates are used as given.
  • A period to date compares with the same days a period back. On 7 Oct 2026, this financial year to date, or this financial year with dataEnd set to today, compares with 1 Jul to 7 Oct 2025, last financial year to the same day. The same year as fixed dates, { start: '2026-07-01', end: '2027-06-30' } with dataEnd set to yesterday, compares with the same number of days just before it instead: 25 Mar to 30 Jun 2026. Pick the form that matches how your data is compared.
  • Give your own comparisons values of their own. Not 'none', 'custom', 'previous-period' or 'previous-year': an option with one of those values is left out, with a warning in development. Roadie treats any other compare as no comparison of its own: resolveComparison refuses it, DashboardView hides deltas marked comparison: true, and validateDashboard warns about them, and about one that reads like Roadie’s, such as 'previous_period'.
  • Say when there’s nothing to compare. Pass dataStart to resolveComparison and to this toolbar. When the comparison starts before the data, the cards say “Not enough history”; when it ends before, “Nothing to compare”. Neither shows a delta.
  • One size, paired with large controls. The button is a large control: 48px tall, with the comparison's dates on a second line. On the same row, use size='lg' Buttons, IconButtons and Selects.
  • Keep the period in the description. The toolbar shows the description’s period, so cards and controls never disagree.

Compare like with like

resolveComparison(range, compare, { now, timeZone, dataEnd: today })

Do

Pass dataEnd for data recorded as it happens, such as sales. A period still in progress then compares with the same days of the period before. Leave it out for dates known ahead, such as shows coming up.

resolveComparison('this-month', 'previous-period', { now, timeZone })

Don’t

Don’t compare a month that’s two days old with a whole month. The delta looks like a fall.

Pair it with large controls

Choose dates,PeriodChoose dates, Last month vs previous year,
<DashboardPeriod value={period} onValueChange={setPeriod}>
<Button size='lg'>Export</Button>
</DashboardPeriod>

Do

Put large Buttons, IconButtons and Selects beside it, so the row shares one height.

Choose dates,PeriodChoose dates, Last month vs previous year,
<DashboardPeriod value={period} onValueChange={setPeriod}>
<Button size='sm'>Export</Button>
</DashboardPeriod>

Don’t

Don't set small or medium controls beside it. They sit short of its edge.

Benchmarks are comparisons

compareOptions={['previous-period', 'previous-year', { value: 'similar', label: 'Similar venues' }]}

Do

Add a benchmark such as similar venues to compareOptions, so it sits with the other comparisons and the button names it.

Don’t

Don’t put a benchmark in its own toggle beside the period. Two controls then both say what the numbers compare with, and can disagree.

Accessibility

  • The toolbar is a group named “Dashboard period”, or by your aria-label.
  • The button is named after its period and comparison, such as “Choose dates, This month 1 to 31 Oct 2026 vs 1 to 30 Sept 2026, Period”, which holds the button’s text as shown. It works as DateRangePicker does with commit='apply': Cancel, Escape or the drawer’s Close drops changes to both the period and the comparison.
  • After the range comes the Compare Switch, when compareOptions has 'none', then a ToggleGroup named “Compare with”, then the comparison’s dates or your option’s description, which are announced as they change. On a phone the Compare row sits above Apply at the drawer’s foot, so it stays in view on both tabs, and the choices scroll sideways when they don’t fit.
  • With Custom dates, a DateRangePicker named “Comparison dates” sits under the choices. It opens over the picker, a drawer over the drawer on a phone, and its dates wait for Apply with the rest.
  • Read only, the button shows a lock and doesn’t open.

API reference

DashboardPeriod

A dashboard's period, with what it compares with, in one picker.

valueDashboardPeriodValue<App>

The period and its comparison. Pair with `onValueChange`.

defaultValueDashboardPeriodValue<App>

The period to start from when uncontrolled. Defaults to the past 30 days, compared with the first of `compareOptions` other than `'custom'`.

onValueChange((value: DashboardPeriodValue<App>) => void)

Called when Apply is pressed with the period and its comparison. Each change refetches the dashboard, so both wait for Apply.

presetsreadonly DateRangePreset[]

The period's presets, as on `DateRangePicker`.

Defaults to [ { value: { direction: 'next', amount: 30, unit: 'day' } }, { value: { direction: 'next', amount: 90, unit: 'day' } }, { value: { direction: 'past', amount: 30, unit: 'day' } }, { value: { direction: 'past', amount: 12, unit: 'month' } }, { value: { period: 'year', offset: 0, fiscal: true } } ].

compareOptionsreadonly ("none" | "previous-period" | "previous-year" | "custom" | DashboardPeriodCompareOption<App>)[]

The Compare choices, in order. `'previous-period'` and `'previous-year'` are Roadie's; `'custom'` picks dates; `{ value, label, description? }` is the app's own, passed through as `compare: value` with no dates, and read on the button as "vs " and the label with its first letter lowercased. `'none'` adds the Compare switch; without it nothing turns the comparison off. A comparison the value holds that the list leaves out still shows. Uncontrolled, the first choice other than `'custom'` starts on. With nothing listed but `'none'`, there is no Compare row. Hoist the list with `as const` so the app's values are kept.

Defaults to [ 'none', 'previous-period', 'previous-year' ] as const.

dataStartstring

The first day the data holds, as given to `resolveComparison`, so each comparison lists the dates the app will fetch.

dataEndstring

The last day the data holds, as given to `resolveComparison`. Only for data recorded as it happens, such as sales.

alignWeekdayboolean

Previous year goes back 52 weeks, as given to `resolveComparison`.

readOnlyboolean

Shows the period with a lock, without letting it change.

disabledboolean
emphasis"normal" | "subtle" | "subtler"

Defaults to 'normal'.

timeZonestring

IANA zone whose calendar decides today. Defaults to the viewer's.

todaystring

Today as an ISO date. Defaults to today in `timeZone`.

weekStartnumber

Defaults to 1.

fiscalYearStartnumber

Defaults to 7.

localestring

Defaults to 'en-AU'.

Inherited from DOMAttributes

childrenReactNode

The app's own controls, placed after the period.

Previous page← DashboardNext pageDataCard →