The first release of Roadie Charts, with nine chart types and dashboards, plus Avatar, Menu, Toast, and more new components.
Nine chart types for event and ticketing insights, each with a table view, in the new @oztix/roadie-charts.
Dashboard cards in named sizes that reflow by container width, with stat tiles, deltas, and sparklines.
A person's photo with initials or an icon behind it, built on Base UI's avatar primitive.
A dropdown list of actions that opens from a button.
A brief message that confirms an action or reports its result.
An inline message that sits in the flow of the page.
A single panel that a trigger shows and hides.
A number input with decrement and increment buttons, built on Base UI's number field primitive.
A one-time code input with one slot per character.
A bar for a task underway.
For picking a number or a range by dragging.
An on or off control for settings that apply straight away.
A button that stays pressed or unpressed, such as favourite or bold
Avatar, a person's photo with initials or an icon behind it, built on Base UI's avatar primitive.<Avatar src name /> covers most uses: it draws initials from name, falls back to a user icon without one, and shows whichever is underneath if the photo fails. Avatar.Image and Avatar.Fallback compose it by hand, and the image stays mounted so it lazy loads and can render through Roadie Image. Sizes run xs to xl, shape is circle or square, and the fallback takes the surrounding intent. Avatar.Group overlaps a row of avatars with a ring in the page colour, and Avatar.GroupCount ends it with +N. getInitials is exported too.
SeeAvatar
Callout, an inline message that sits in the flow of the page.The short form takes intent, title and the body as children, and shows a status icon for info, success, warning and danger. The compound form (Callout.Icon, Callout.Title, Callout.Description, Callout.Actions) covers actions and rich bodies; actions sit below the text in a narrow callout and beside it in a wide one. emphasis takes Badge's values and defaults to subtle, onDismiss adds a dismiss button, and Callout.Title becomes a heading through render.
SeeCallout
Checkbox and CheckboxGroup, styled as siblings of RadioGroup.Checkbox takes a label and description, shows a tick or, when indeterminate, a dash, and picks up invalid, required and disabled from a surrounding Field. CheckboxGroup tracks an array of ticked values, lays out vertically or horizontally in subtler or normal emphasis, and supports a parent "select all" item through allValues and parent.
Collapsible, a single panel that a trigger shows and hides.Collapsible.Trigger shows a trailing caret that turns when open (showCaret={false} hides it) and renders onto a Roadie Button through render. Collapsible.Panel passes keepMounted and hiddenUntilFound through. Collapsible.Text, placed inside a Collapsible, clamps a paragraph to lines (3 by default) and ends the last line with an inline "…more" that fades the text behind it. The trigger shows only when the text overflows; lessLabel={null} makes it expand only. The Collapsible root now owns the open state and shares it with its parts.
is-disclosure-animated now also animates Base UI panels that aren't <details>,
by height from --collapsible-panel-height.
SeeCollapsible
Dashboard with named card sizes that reflow by container width, DataCard, StatTile, Delta, Sparkline, Meter, DataTable with inline sparkline and meter columns that hide by priority on narrow cards, and general Table primitives.Every one renders completely on the server.
LineChart (with band, median, forecast, target, today and annotations), BarChart, RankedBars, StackedBars, Histogram, Funnel, Heatmap, Scatter and SmallMultiples.Each renders live with Roadie tokens, on the server, and as standalone SVG through renderChartSvg in @oztix/roadie-charts/static, which also exports each chart's definition. Each chart's Table view rows come from @oztix/roadie-charts/tables, which a server can call without the chart engine. A chart inside a Chart card now supplies the card's table and summary, so table is optional, and a chart that can't draw puts its card in the error state.
The Chart card's Chart and Table switch is now an icon-only ToggleGroup,
because both are views of the same data. Each item is a button with
aria-pressed and aria-controls, and the hidden view is inert but keeps its
box, so the card height holds across the switch. There are no tab or
tabpanel roles any more. ToggleGroup keeps focus on the item you left when
the window regains focus, instead of moving it to the pressed item.
Cards take actions at the top right. Chart has an actions prop that sits
after the Chart and Table switch, and DashboardView has a cardActions render
prop that adds actions to every card kind described as data.
DataCard.MoreButton is the More button that ends a card's actions. It passes
its props and ref through, so it is the trigger of the card's Menu, passed to
Menu.Trigger as its render. @oztix/roadie-charts/tables adds plotTable,
the Table view rows for any chart plot in a dashboard description, and
cardTable, the table behind any card, for actions such as Download CSV.
DataCard now shows its actions in every state, so a refresh works on a card
that failed to load; Chart hides only its view switch without data. Repeated
BarChart x values add up their bars, and the line keeps the first value it
has.
In core, a chart card's plot accepts every chart kind, each with its own
schema exported from @oztix/roadie-core/dashboard, and validateDashboard
checks plot fields, annotations outside the data and repeated names. A legend
item takes median: true to draw a dashed median through a band key, in
ChartLegend and in a dashboard description alike, and validateDashboard
warns when a median sits on any other shape. parseWallTime in
@oztix/roadie-core/dataviz reads an ISO string as venue wall time, and
isWallTime says whether a value is one. Both accept a space in place of the
T, as in 2026-11-14 19:30, read the whole string and refuse a date that
doesn't exist, such as 31 February. --chart-highlight now follows nested
accent, dark and intent sections. Inline code no longer splits a short name
across lines; it breaks mid-token only when the token can't fit a line.
Menu, a dropdown list of actions that opens from a button.Menu.Content wraps the portal, positioner and popup and takes side, align, sideOffset and alignOffset directly. Items take a leading icon, a trailing shortcut and an intent (use danger for destructive actions). Menu.Item takes an href, routed through RoadieLinkProvider. Checkbox and radio items, groups with labels, separators and submenus are included, and Navigator menus now share the same surface and row styles. On a touch screen a tapped row that keeps the menu open no longer stays highlighted; the keyboard highlight still shows.
SeeMenu
NumberField, a number input with decrement and increment buttons, built on Base UI's number field primitive.<NumberField min={0} max={10} /> renders the whole stepper; compose NumberField.Group, Input, Decrement, Increment and ScrubArea when you need a different layout. The buttons turn off at min and max. format takes Intl.NumberFormat options for currency, percentages and units. It inherits invalid, required and disabled from Field.
size and emphasis work like Input. emphasis='subtler' drops the field
box for round buttons, sized like IconButton, either side of the value.Decrement and Increment take emphasis and intent, so
<NumberField.Increment emphasis='strong' intent='accent' /> gives an accent
add button.removable turns the decrease button into a Remove button with a trash icon
one step above min.editable={false} stops typing while the buttons and arrow keys still step,
unlike readOnly, which stops every change.min, max and
format. A typeable value keeps a tap target of at least 2.75rem, 3.5rem on
touch screens, and at subtler sits in a chip that behaves like a subtle
Input. Pass className='w-full' to stretch the field.format
and locale, and stays still for people who prefer reduced motion.SeeNumber field
OTPField, a one-time code input with one slot per character.<OTPField length={6} /> renders every slot, and groupSize={3} splits them 3-3 with a separator. Compose OTPField.Input and OTPField.Separator for other layouts. Slots look like Input and match its 32, 40 and 48px heights. Pasting a full code fills every slot, the first slot offers autoComplete='one-time-code', numeric codes bring up the number pad, and onValueComplete fires once the last digit lands. It inherits invalid, required and disabled from Field.
SeeOTP field
Progress, a bar for a task underway.<Progress value={40} label='Uploading' /> renders the label, the value and the track; valueText swaps the percentage for text such as "120 of 400" and reads it to screen readers. Pass value={null} while the length is unknown and the bar sweeps across the track. The parts (Label, Value, Track, Indicator) compose for custom layouts. The fill is the accent by default, like Meter, and takes the intent when you pass intent, such as success or danger once a task has ended. Core gains the animate-indeterminate utility behind the sweep, which holds a still, soft bar under reduced motion.
SeeProgress
RoadieProvider, one root provider for a Roadie app: <RoadieProvider link={NextLink}>{children}</RoadieProvider>.It mounts RoadieLinkProvider, ThemeProvider, Toast.Provider with a Toast.Viewport, Tooltip.Provider and Base UI's DirectionProvider. Each part takes its usual options through theme, toast (plus the viewport's position and container), tooltip and direction, and false leaves it out. Every individual provider stays exported, and a nested one still overrides its part. In development, Roadie warns when a RoadieProvider sits inside another one, or when a ThemeProvider or Toast.Provider it already mounts wraps it or sits straight inside it.
Top toasts now clear Pane.Header. A Navigator frame tracks the bottom of
the tallest pane header along the top of the window, and keeps it in step as
the header collapses, so top-end and top-center toasts sit just below it.
--toast-viewport-offset-top stays yours and adds to that. Bottom positions
are unchanged.
SeeInstallation
Slider, for picking a number or a range by dragging.<Slider label='Price' defaultValue={[20, 80]} /> renders the label, the value, the track and one thumb per value. format takes Intl.NumberFormat options, such as AUD currency, and formats in en-AU unless you pass a locale. Inside Field it takes its name, helper or error text, and invalid and disabled from there. The fill is accent, like Switch and Checkbox, and turns danger when invalid. size (sm, md, lg) scales the thumb and track, each keeping a 44px touch target, and direction='vertical' runs it up the page. The parts (Label, Value, Control, Track, Indicator, Thumb) compose for custom layouts such as tick marks.
SeeSlider
Switch, an on or off control for settings that apply straight away.Pass label and description for a labelled settings row, or drop it into Field and it takes its name, helper or error text, and invalid, required and disabled from there. The checked track is accent with a tick, the thumb slides with a transition that stops under reduced motion, and size is sm or md.
SeeSwitch
Toast, a brief message that confirms an action or reports its result.Mount Toast.Provider with a Toast.Viewport once at the app root, then call useToastManager().add({ title, description, intent, actionProps }) from any component, or createToastManager() from outside React. intent (success, danger, warning or info) colours the toast and leads it with a matching icon, actionProps adds a small button such as Undo or Retry, and promise shows a spinner until the work settles. A thin Toast.Progress bar along the bottom of timed toasts shows how long they have left, pausing whenever their timer pauses (hover, focus, a background window). Toast.Viewport takes a position (bottom-end by default, bottom-center, top-end or top-center); small screens always span the chosen edge. Set --toast-viewport-offset-bottom (or -top) to clear fixed UI such as a checkout bar. Toasts fan out on hover or focus and swipe away towards their edge. Core adds the motion-toast utility that stacks and animates them from either edge: toasts enter, restack and snap back on a spring and leave quickly. Core also adds --ease-spring-lively, a spring with a visible bounce (about 9% overshoot) for transforms that should catch the eye.
The @base-ui/react peer range rises to ^1.8.0. Toast relies on 1.8 for
update(id, previous => …) and for timers that keep their remaining time
across repeated pauses, which the progress bar follows.
SeeToast
Toggle and ToggleGroup.Toggle is a pressed or unpressed button that takes Button's sizes and steps up one emphasis when pressed; with only an icon inside it renders square. ToggleGroup is a segmented control with a pill that slides to the pressed item. emphasis follows Toggle: normal (the default) has a bordered track and subtle a tinted one, each with a solid pill, and subtler has no track and a tinted pill. It is single select by default and never ends up empty, takes multiple for independent toggles, and has size (matching Button heights), direction and disabled.
subtler Toggle or ToggleGroup easier to see.A pressed subtler toggle or group item now takes a soft fill with no border and a strong icon or label. The fill alone stays under 3:1 against the surface, so the fill and the icon carry the state together: use subtler for quiet controls, and subtle or normal when the state must stand out. On the unpressed side, a subtler Toggle now rests in subtle text rather than normal text, like the items of a group. Core adds the is-selected utility that carries this look, with a system highlight edge under forced colours. The subtler Tabs underline keeps its look and now takes the highlight colour under forced colours, where it used to disappear. The chart card's Chart and Table switch moves up to md, so each item is 32px, the size of the card's More button.
@oztix/roadie-core/dashboard describes a dashboard as JSON, with a Zod schema, a JSON Schema export for tool inputs, and validateDashboard, which reports every problem with its path, including rows that leave gaps, copy that will truncate, and unknown keys. A zod-free @oztix/roadie-core/dashboard-layout subpath exposes the card sizes, spans, tracks, findRowGaps and COPY_LIMITS for UI code that must not load Zod. @oztix/roadie-core/dataviz gains formatValue and delta helpers in the house number formats, and chartHex now returns chrome colours for static renderers. Chart ink now follows nested .dark and intent-* sections, the highlight's hex fallback uses the default accent hue, and chart marks get texture patterns under forced colours and print.
SeeDashboard
Charts get --chart-1 to --chart-8 for series, --chart-heat-* for amounts, --chart-diverge-* for ahead of or behind a benchmark, --chart-status-* for meaning, and --chart-highlight, which follows --accent-hue. Each comes with bg-, fill-, stroke- and text-chart-* utilities, in light and dark. Canvas, SVG and PDF renderers can read the same values as hex from @oztix/roadie-core/dataviz, with the greys tinted by the accent hue as the CSS ones are, and chartColorVar(i) gives a CSS variable when the slot is dynamic. The palette is checked for colour-blind separation in CI.
text-on-strong (--intent-text-on-strong), the label colour for strong fills.Each intent takes whichever text colour reads better on its fill, and reaches APCA Lc 60 in both modes, at rest, on hover and when pressed, in browsers that support color-mix(). emphasis-strong, CalendarTile's strong band, and the strong Tabs and ToggleGroup pills use it.
text-inverted is unchanged: it's still the page's text colour flipped, and
pairs with bg-inverted. To migrate, use text-on-strong for labels on
bg-strong or emphasis-strong fills where you used text-inverted.
A custom accent keeps its white text too. The new getAccentChromaSync(hex)
caps an accent's chroma to what sRGB can show at step 9, because browsers clip
a more saturated fill and the clip makes it lighter. getBootstrapScript and
generateAccentScale, which writes the hex fallback scale, now use it, so a
saturated green or cyan accent no longer renders too light.
Browsers that draw oklch() but not color-mix() (Safari 15.4 to 16.1) get
step 9 for the lifted strong fills and steps 7 and 6 for hover and press, so
the fills stay solid there. Success and brand-secondary read at about Lc 57 to
58 at rest in those browsers.
generateAccentScale now reports fgOnStrong by APCA, so it matches
text-on-strong: white for Oztix blue, where it used to say black. It also
no longer lifts a grey accent to chroma 0.1, so the hex fallback matches the
CSS.
White labels on brand, accent, info and danger are 3.2 to 3.5:1 by WCAG 2. That meets 3:1, not the 4.5:1 WCAG 2 asks of normal-size text. The accessibility page now names strong-fill labels as the APCA exception.
Hover and press use the new --intent-bg-strong-hover and
--intent-bg-strong-active tokens. Neutral is unchanged.
This is a minor bump because it adds a utility and tokens, and some strong fills and their label colours change.
@oztix/roadie-charts: the Chart card frame with a Chart and Table switch, ChartLegend, ChartTooltip, texture patterns for forced colours and print, and DashboardView, which renders a dashboard description from @oztix/roadie-core/dashboard.Two reference dashboards ship as data from @oztix/roadie-charts/examples. Plots on TanStack Charts follow in a later release.
RadioGroup and Select inherit disabled from Field. Their own
disabled prop still wins.RadioGroup.Item uses description to describe the radio, not to name it.
label and description accept any React node.RadioGroup.Label names the group when you use it.Popover.Content takes side, align, sideOffset and alignOffset
directly, like Tooltip.Content. They win over the same keys in
positionerProps, which are now deprecated. Tooltip.Content gains
alignOffset, so Tooltip, Menu and Popover take the same four.intent prop on Input, Textarea and Select.Trigger is deprecated.
Form controls take their colour from state, and is-interactive-field
handles it. It still works and will be removed in v3.Tabs.Tab that holds only an icon is now a circle at every size and emphasis, with the icon centred and the indicator matching it.It warns in development when it has no aria-label or aria-labelledby.
SeeTabs
@base-ui/react peer range from ^1.0.0 to ^1.6.0.Roadie already needed 1.6: Drawer, the InputGroup parts of Combobox and Autocomplete, and Select.Label don't exist before 1.3, and Navigator menus and tooltips fail their tests before 1.6. If you pin @base-ui/react below 1.6, upgrade it along with this release.
ThemeProvider, getAccentStyleSync and the cart drawer's accent theming cap the accent's chroma to what sRGB can show at step 9, through getAccentChromaSync from @oztix/roadie-core/colors.A saturated green or cyan accent no longer renders its strong fill too light for white text.
A highlight the pointer made only fills where the pointer can hover, and a keyboard highlight still shows everywhere.
Switch, Checkbox, CheckboxGroup, RadioGroup, Select, NumberField and OTPField
now point aria-describedby at the text Field actually renders. A control
marked invalid inside a valid Field pointed at an error text that was never
rendered, so screen readers lost the helper text.
Select.Value now shows the selected item's label instead of its raw value, on
the server and first render too. Select reads each Select.Item's text (its
string children or its Select.ItemText), and an items or
itemToStringLabel you pass still wins. Values of a multiple select show as
their labels.
multiple.The roots are generic over the value and multiple, like Base UI's, so <Select multiple> takes an array for value and defaultValue and hands one to onValueChange. A single select infers its value type from value or defaultValue instead of unknown. SelectProps and ComboboxProps take the same optional type parameters for wrappers.
A Select trigger no longer grows past its container. A long label truncates with the icon kept in view, and a multiple select shows the labels that fit then counts the rest, such as "Bee Gees, Custard +2". Screen readers still hear every label.
SeeSelect
On a touch screen the last button tapped kept its hover colour and lift, so it looked stuck or half disabled until something else was tapped. A tapped raised field also keeps its rim light, and a translucent field stays see-through. Focus and press states are unchanged.