0

Changelog

New features, improvements, and fixes.

Timeline, Frame, and the meter family

Five components for reporting history and bounded values — plus a shared color scale across every progress and meter indicator.

v1.1.0

All five come from the same gap. ondo had good primitives for inputs and lists, but nothing purpose-built for reporting a history or a bounded value, so products kept hand-rolling the same shapes on top of Card and raw SVG.

Timeline shows events in chronological order, vertically or horizontally. Each item declares its step, and the timeline's value marks how far the sequence has advanced — items at or below it render as completed, and the rail fills to match.

<Timeline>
  <TimelineItem step={1}>
    <TimelineHeader>
      <TimelineDate>March 2024</TimelineDate>
      <TimelineTitle>Project Initialized</TimelineTitle>
    </TimelineHeader>
    <TimelineIndicator />
    <TimelineSeparator />
    <TimelineContent>
      Successfully set up the project repository and initial architecture.
    </TimelineContent>
  </TimelineItem>
</Timeline>

The indicator and the separator are separate parts, which is what makes the alternating, horizontal, and custom-indicator layouts in the docs possible without fighting the component.

The indicator itself takes variant for the color, fill for whether it draws as a ring or a disc, and animate for ping or pulse on the step that is currently running; that last one respects prefers-reduced-motion. size on the root scales the dot, its icon, and the whole rail together, because the item's margin and the separator's length are derived from the indicator size rather than hard-coded.

<Timeline value={2} size="lg">
  <TimelineItem step={2}>
    <TimelineIndicator variant="warning" fill="solid" animate="ping" />
    <TimelineSeparator />
  </TimelineItem>
</Timeline>

MeterRing is a radial reading of a bounded value, built on Base UI Meter, so it is announced correctly. The arc is a conic gradient masked into a ring — two elements, no SVG — and it takes currentColor, so variant recolors it.

<MeterRing value={0.68} max={1} format={{ style: "percent" }} variant="success">
  <MeterRingLabel>Coverage</MeterRingLabel>
</MeterRing>

formatValue replaces the centered text when a number format cannot say what is needed — an em-dash for "no reading yet" beats a ring confidently displaying 0.00.

Meter and ProgressRing fill in the rest of the grid. There are two questions to answer — is this a task finishing, or a value that simply is what it is? and bar or ring? — and now every combination has a component, so the name you reach for decides the ARIA role.

LinearRadial
Task completionProgressProgressRing
Bounded readingMeterMeterRing

ProgressRing is the genuinely new one here: because it is built on Base UI Progress, value={null} marks it indeterminate, and the arc becomes a spinner instead of claiming a number it does not have.

<ProgressRing value={null}>
  <ProgressRingLabel>Connecting</ProgressRingLabel>
</ProgressRing>

All four now share one color scale. variantdefault, info, success, warning, destructive — sets a text color on the root and the fill paints with currentColor, so a className="text-…" override works everywhere too. Progress gained this prop in the process; it previously hardcoded its fill.

Frame is the surface the Timeline's horizontal example sits on: a bordered container that nests panels inside it. The panel radius derives from the frame's, so the corners stay concentric instead of doubling up, and --frame-radius is the single knob for both.

<Frame stacked>
  <FramePanel>
    <FrameHeader>
      <FrameTitle>Deployment</FrameTitle>
    </FrameHeader>
  </FramePanel>
  <FramePanel>Second panel, joined to the first.</FramePanel>
</Frame>

Toast replaces Sonner

A Base UI toast component takes over from the Sonner wrapper.

v1.0.0

Toast is a new component built on Base UI Toast, and it replaces Sonner. Upstream shadcn made the same move on its Base UI variant: sonner is gone from the docs and toast took its place.

import { toast } from "@/components/ui/toast"
 
toast.add({
  title: "Event created",
  description: "Sunday, December 3 at 9:00 AM",
})

Mount Toaster once near the root of your app. It bundles the provider, portal, viewport, and default toast list, and toast is a standalone manager — the caller does not need to be inside the provider.

The whole surface is exported for custom layouts: Toast, ToastAction, ToastClose, ToastContent, ToastDescription, ToastPortal, ToastProvider, ToastTitle, ToastViewport, plus createToastManager and useToastManager.

This is a breaking change. Sonner is removed from the registry, and the sonner package is no longer a dependency. The APIs are not compatible:

- toast("Event created", { description: "…" })
+ toast.add({ title: "Event created", description: "…" })
 
- toast.success("Saved")
+ toast.add({ type: "success", title: "Saved" })

Status icons still cover success, info, warning, error, and loading, using the same icon set and semantic colors Sonner used, so the look carries over.

xl and 2xl sizes across the scale

Button, Input, Toggle, Select, and NativeSelect gain xl (44px) and 2xl (52px) steps.

v0.6.0

Button, Input, Toggle, Select, and NativeSelect now accept size="xl" and size="2xl". Button also gains icon-xl and icon-2xl.

sizeheighttexticonradius
xl44pxtext-base20pxrounded-lg
2xl52pxtext-lg24pxrounded-xl
<Button size="xl">Extra large</Button>
<Button size="2xl">2X large</Button>
<Button size="icon-2xl" aria-label="Add">
  <IconPlus />
</Button>
<Input size="2xl" placeholder="2X large" />

Select and NativeSelect accepted only sm and default, so they gain lg in the same release rather than skipping a step. Textarea and InputGroup gain the full scale too — a textarea matches an input of the same size on horizontal padding, text, and radius, and an input group passes its size down to the control inside.

Unlike the smaller sizes, xl and 2xl keep their text size at every breakpoint instead of dropping to text-sm on desktop. xl sits only 4px above lg, and the text size is what tells them apart.

These are the first sizes with a corner radius above rounded-md. ButtonGroup and ToggleGroup round their end caps to md, which would have squared off one side of an xl or 2xl child; those rules now key off the child's own data-size, so each child keeps its radius and a group mixing sizes still resolves per child.

InputGroup, Combobox, Select, and NativeSelect also gain xs, so the scale now runs xs through 2xl everywhere Input does. An xs input group shrinks its addon padding to leave room for a control.

InputGroupButton now takes every Button size, with each name meaning exactly what it means there. Omit size and the button fits itself to the group instead.

InputGroupButton's four named sizes are unchanged and still match upstream shadcn exactly. Omit size and the button now fits itself to the group instead; in a default group that is identical to xs, so nothing moves.

The default size is unchanged, so this is a non-breaking update.

Desktop Window

Static macOS, Windows 11, and Ubuntu window chrome for screenshots, heroes, and product mockups.

v0.5.0

DesktopWindow wraps any content in desktop window chrome:

  • Three platforms, one markup. os="macos", os="windows", and os="ubuntu" share the same children. The prop flips CSS rather than the DOM, so switching platforms is a one-word change — the controls and the title move on their own. "ubuntu" renders the Yaru/GNOME headerbar Ubuntu ships by default; other Linux desktops draw windows differently and are out of scope.
  • Chrome is fixed, content is yours. Titlebar backgrounds, traffic light colors, corner radii, and the Windows close-button red are pinned to the real platform values in oklch, so the frame reads as that OS in any project. Only DesktopWindowContent follows your theme.
  • Decorative by default. Controls render as aria-hidden spans and stay out of the tab order. Pass onClose, onMinimize, or onMaximize and that one control becomes a labeled button with a focus ring.
  • Platform-accurate detail. macOS reveals its glyphs on titlebar hover or focus; Windows 11 and Ubuntu show them always. Ubuntu's close button is a gray circle like its neighbours — the red close button is a Windows trait. No new CSS variables, and the only dependency is class-variance-authority.

It is chrome, not a window manager: no dragging, resizing, or minimise and maximise state. Toolbars, app icons, and sidebars go in the titlebar as children.

Live Waveform

A canvas-based real-time audio waveform visualizer with microphone input, static and scrolling modes, and a processing state.

v0.4.0

LiveWaveform visualizes audio in real time on a canvas, for voice and multimodal UIs:

  • Two modes. static paints symmetric bars across the frequency bands; scrolling shows historical average volume moving right to left like a timeline.
  • Processing state. processing animates a wave while you wait for input — and settles to a calm static frame under prefers-reduced-motion.
  • Bring your own stream. Pass a stream to visualize audio you already have (a shared mic track, or an agent's response audio); otherwise it requests the microphone itself and cleans the stream up on unmount.
  • The render loop parks itself when idle, callbacks are read from refs so inline handlers don't re-acquire the microphone, and the bars default to the primary color — className's text-* or barColor override it.

Adapted from ElevenLabs UI's live-waveform to Base UI-era ondo conventions.

Heading

A new Heading component that keeps a heading's semantic level and its visual size independent.

v0.3.0

Heading renders an h1h6 for application UI, keeping the document outline and the visual scale apart:

  • level sets the rendered tag and, on its own, the matching size.
  • size overrides that scale independently, so an h2 can carry h1 weight when the design asks for it while the outline stays correct. Sizes 16 are a single bold ramp from 2.25rem down to 1.125rem, sized in rem with unitless line-height ratios and em tracking, composed from utilities with no theme token behind them.
  • wrap maps to the Tailwind text-wrap utilities (normal, nowrap, balance, pretty) to control how a heading breaks across lines.
  • render swaps the element while keeping the styles, for cases like a legend inside a fieldset.

Rendered markdown and MDX already get their heading styles from the typeset layer, so this component is meant for application UI rather than prose.

Semantic color variants

info, success, and warning join destructive as first-class variants across Button, Badge, Alert, and Sonner.

v0.2.1

Semantic colors beyond destructive. The info, success, and warning tokens now surface as first-class variants across the registry:

  • Button, Badge, and Alert gain info, success, and warning variants — the same soft-tint recipe as destructive.
  • Alert variants now fill their background and border with the semantic tint, plus a new primary variant.
  • Toast messages (toast.success / .info / .warning / .error) are themed with the semantic tokens.

Animated number counters

NumberCount and NumberBadge bring rolling-digit counts to avatars, icons, and stats.

v0.2.0

Two new items built on @number-flow/react:

  • NumberCount — a new component: an animated number whose digits roll between values. A thin wrapper over NumberFlow with ondo defaults; every NumberFlow prop (format, prefix, suffix, spinTiming, trend, …) passes straight through.
  • NumberBadge — a new composition: a self-anchoring count badge for avatars and icons. Wraps its children and overlays an animated count with 99+ overflow, hide-on-zero, and placement.

Both install their @number-flow/react dependency automatically.

Input gets size variants

Input now matches Button's xs/sm/default/lg size scale.

v0.1.1

Input now accepts a size prop — xs, sm, default, and lg — matching the height, padding, and radius scale already used by Button. Pair a compact Input with a Button size="sm" (or any other size) and they'll line up.

<Input size="xs" placeholder="Extra small" />
<Input size="sm" placeholder="Small" />
<Input size="default" placeholder="Default" />
<Input size="lg" placeholder="Large" />

The default size is unchanged, so this is a non-breaking update.

Introducing Compositions

A new layer of ready-made components composed from ondo-ui primitives.

v0.1.0

We've added a new Compositions layer to ondo-ui — the space between components and blocks.

  • Components are the low-level primitives you compose yourself.
  • Compositions are ready-made components that combine primitives behind a focused props API.
  • Blocks are full page sections (coming soon).

EmptyView

The first composition is EmptyView — a ready-made empty state composed from Empty and Button. Pass your content and go:

bunx --bun shadcn@latest add @ondo-ui/empty-view

Browse compositions on the new Compositions page, now in the top nav and the docs sidebar.