- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Desktop Window
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Frame
- Heading
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Live Waveform
- Marker
- Menubar
- Message
- Message Scroller
- Meter
- Meter Ring
- Native Select
- Navigation Menu
- NumberCount
- Pagination
- Popover
- Progress
- Progress Ring
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Timeline
- Toast
- Toggle
- Toggle Group
- Tooltip
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.
| Linear | Radial | |
|---|---|---|
| Task completion | Progress | ProgressRing |
| Bounded reading | Meter | MeterRing |
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. variant — default, 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.
| size | height | text | icon | radius |
|---|---|---|---|---|
xl | 44px | text-base | 20px | rounded-lg |
2xl | 52px | text-lg | 24px | rounded-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", andos="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
DesktopWindowContentfollows your theme. - Decorative by default. Controls render as
aria-hiddenspans and stay out of the tab order. PassonClose,onMinimize, oronMaximizeand 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.
staticpaints symmetric bars across the frequency bands;scrollingshows historical average volume moving right to left like a timeline. - Processing state.
processinganimates a wave while you wait for input — and settles to a calm static frame underprefers-reduced-motion. - Bring your own stream. Pass a
streamto 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
primarycolor —className'stext-*orbarColoroverride 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 h1–h6 for application UI,
keeping the document outline and the visual scale apart:
levelsets the rendered tag and, on its own, the matching size.sizeoverrides that scale independently, so anh2can carryh1weight when the design asks for it while the outline stays correct. Sizes1–6are a single bold ramp from2.25remdown to1.125rem, sized inremwith unitless line-height ratios andemtracking, composed from utilities with no theme token behind them.wrapmaps to the Tailwindtext-wraputilities (normal,nowrap,balance,pretty) to control how a heading breaks across lines.renderswaps the element while keeping the styles, for cases like alegendinside afieldset.
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, andAlertgaininfo,success, andwarningvariants — the same soft-tint recipe asdestructive.Alertvariants now fill their background and border with the semantic tint, plus a newprimaryvariant.Toastmessages (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 itschildrenand overlays an animated count with99+overflow, hide-on-zero, andplacement.
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.