Feedback
Banners, callouts, toasts, notifications, empty states, spinners, and skeletons.
Components in this category
Full-width status banner with tone and actions.
StatusBarApp-shell footer strip for connection and process state.
CalloutHighlighted inline message box.
ToastAuto-dismissing notification card.
ToastProviderManages toast stack, timers and placement.
NotificationPersistent notification item.
InboxPanelNotification inbox dropdown panel.
NotificationBellBell icon with unread count badge.
EmptyStatePlaceholder for empty content areas.
SpinnerLoading indicator with tone support.
LoadingOverlayCovers a region while it refreshes, without collapsing the layout.
SkeletonPlaceholder loading animation.
SkeletonTextDisplays placeholder lines while text loads.
SkeletonListRow-shaped placeholder for a list or a feed.
SkeletonTablePlaceholder rows at Table's density.
SkeletonCardCard-shaped placeholder — media, header, body, actions.
Banners & status bars
Banner
Banner is a page-level notice for a condition that holds until something changes. Tone controls the tint, border, and icon. With only children, it renders as one line; passing title adds a heading above the message. flush drops the radius and side borders for a banner pinned under a TopBar.
| Prop | Type | Default | Description |
|---|---|---|---|
tone | BannerTone | "warn" | Tone name or alias. Canonical names are neutral, primary, info, ok, warn, and err. |
title | ReactNode | — | The condition in a few words. Stacks the banner. |
children | ReactNode | — | The message. |
meta | ReactNode | — | Secondary content after the message. |
actions | ReactNode | — | Buttons or links at the trailing edge. |
icon | ReactNode | null | — | Replaces the tone icon. Pass null to hide the icon. |
onDismiss | () => void | — | Shows a close button and calls this. |
dismissLabel | string | "Dismiss" | Accessible name for the close button. |
flush | boolean | — | Edge-to-edge, for a banner pinned under app chrome. |
StatusBar
The app-shell footer: a 28px band under the scrolling column. Cells are StatusBarItems, each with a dim label and a stronger value; use StatusBarItem with a tone when the value is a state.
| Prop | Type | Default | Description |
|---|---|---|---|
left | ReactNode[] | — | Leading cluster. Strings are wrapped in StatusBarItem. |
right | ReactNode[] | — | Trailing cluster, pinned to the far edge. |
connected | boolean | true | Displays a live connection cell. Set false to hide the cell. |
connectedLabel | ReactNode | "Connected" | Value of the built-in connection cell. |
import { Banner, StatusBar, StatusBarItem } from "@solarius/onset";
import type { BannerTone } from "@solarius/onset";
<Banner tone="warn" meta="3 nodes affected">
Degraded performance detected in us-east-1.
</Banner>
<Banner flush tone="err" title="Primary region unreachable" onDismiss={dismiss}>
Requests are being served from eu-west-1.
</Banner>
<StatusBar
left={[<StatusBarItem label="Region">us-east-1</StatusBarItem>]}
right={[<StatusBarItem label="p95" mono>42ms</StatusBarItem>]}
/>Callouts
Callout
Callout is an inline alert block. Unlike Banner, which frames a whole page region, it sits in the flow of the content. Use it inside forms, cards, and settings pages for tips, warnings, and confirmations.
| Prop | Type | Default | Description |
|---|---|---|---|
tone | CalloutTone | "info" | "info" | "ok" | "warn" | "err". |
title | ReactNode | — | Bold heading above the body. |
icon | ReactNode | — | Override the tone-driven default icon. |
actions | ReactNode | — | Action row under the body. |
children | ReactNode | — | Body content. |
import { Callout } from "@solarius/onset";
import type { CalloutTone } from "@solarius/onset";
<Callout tone="ok" title="Saved">
Your changes have been saved successfully.
</Callout>;Toasts
Toast
Toast is a brief, auto-dismissing notification card with a tone-driven icon, a message, an optional link, and a close button. Toast alone is only the card; pair it with ToastProvider for the managed stack.
| Prop | Type | Default | Description |
|---|---|---|---|
tone | ToastTone | "ok" | "ok" | "warn" | "err". |
children | ReactNode | — | Toast message. |
href | string | — | Link target for a "View" link. |
linkLabel | string | "View" | Link text when href is set. |
onClose | () => void | — | Shows the close button and calls this on click. |
ToastProvider
ToastProvider manages the toast stack: mounting, ordering, auto-dismiss timers, and the portaled viewport. Timers pause while the pointer is over the stack or focus is inside it.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | App content wrapped by the provider. |
placement | ToastPlacement | "bottom-right" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right". |
duration | number | 5000 | Default auto-dismiss in ms. Use 0 or Infinity for sticky. |
limit | number | 4 | Max visible toasts; oldest is dismissed to make room. |
Use the useToast() hook anywhere below the provider. It returns toast (a ToastFn that fires a toast and returns its id) and dismiss (dismiss one or all toasts). toast.ok, toast.warn, and toast.err are tone-shorthand helpers.
import { ToastProvider, useToast } from "@solarius/onset";
import type { ToastPlacement, ToastFn } from "@solarius/onset";
import { Button } from "@solarius/onset";
function App() {
return (
<ToastProvider placement="bottom-right" duration={5000}>
<Dashboard />
</ToastProvider>
);
}
function Dashboard() {
const { toast } = useToast();
return <Button onClick={() => toast.ok("Changes saved")}>Save</Button>;
}
// With options:
toast({ children: "Node provisioned", tone: "ok", duration: 3000 });
toast.warn("Rate limit approaching", { duration: 8000 });
toast.err("Deployment failed", {
href: "/deployments/42",
linkLabel: "View run",
});
// Re-fire the same id to update in place:
const id = toast({ id: "saving", children: "Saving…", tone: "ok" });
// Later:
toast({ id: "saving", children: "Saved", tone: "ok", duration: 2000 });Examples
Notifications
Notification
A persistent notification row. Unlike Toast, which is transient and disappears on its own, notifications accumulate and stay until dismissed. Each carries a read state and its own actions.
| Prop | Type | Default | Description |
|---|---|---|---|
item | NotificationItem | — | The notification data to render. |
onRead | (id: string) => void | — | Mark-as-read handler. |
onDismiss | (id: string) => void | — | Dismiss handler. |
onClick | () => void | — | Row click handler. |
NotificationItem shape: id, title, body, time (Date), read, tone (NotificationTone: "default" | "info" | "ok" | "warn" | "err"), icon, category, and actions (inline action buttons).
InboxPanel
A persistent notification inbox, typically shown behind a bell icon. Supports filtering (All / Unread) and optional grouping by category.
| Prop | Type | Default | Description |
|---|---|---|---|
items | NotificationItem[] | — | Notifications to display. |
title | ReactNode | "Notifications" | Panel heading. |
onRead | (id: string) => void | — | Mark single item as read. |
onDismiss | (id: string) => void | — | Dismiss single item. |
onReadAll | () => void | — | Mark all as read. |
onItemClick | (item: NotificationItem) => void | — | Click handler for a row. |
filterable | boolean | true | Adds All / Unread filter row. |
views | InboxView[] | — | Custom caller-filtered views. |
activeView | string | — | Controlled selected view key. |
defaultActiveView | string | — | Initial selected custom view. |
onViewChange | (key: string) => void | — | A custom view was selected. |
searchable | boolean | — | Adds notification search. |
searchValue | string | — | Controlled search text. |
onSearchChange | (value: string) => void | — | Hands search filtering to the caller. |
searchPlaceholder | string | "Search notifications" | Search field placeholder. |
unreadCount | number | — | Total unread count, including unloaded pages. |
headerActions | ReactNode | — | Right-aligned header actions. |
footer | ReactNode | — | Content pinned below the list. |
loading | boolean | — | Shows placeholder rows on an empty first load. |
grouped | boolean | — | Section rows under their category. |
emptyMessage | ReactNode | "You're all caught up." | Empty-list message. |
maxHeight | number | 380 | Scrollable list height. |
width | number | string | 320px | Panel width. |
NotificationBell
A bell icon with an unread badge. Pair it with InboxPanel inside a PopoverPanel. It forwards its ref so the popover can anchor to the bell's DOM node.
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | 0 | Unread count. |
max | number | 9 | Counts above this render as "9+". |
dot | boolean | — | Displays a dot instead of the unread count. |
import { Notification, InboxPanel, NotificationBell } from "@solarius/onset";
import type { NotificationItem, NotificationTone } from "@solarius/onset";
const items: NotificationItem[] = [
{ id: "1", title: "Deployment completed", time: new Date(), tone: "ok", read: false },
];
<Notification item={items[0]} />
<NotificationBell count={3} />Loading
Choose the loading shape by what is on screen. Nothing rendered yet, but the layout ahead is known: draw it with a Skeleton. Content already on screen being refetched: cover it with a LoadingOverlay, which keeps scroll position. A single control working after a click: a Spinner inside it.
Spinner
Spinner supports duration, thickness, tone, and an accessible label. Button, IconButton, and AuthProviderButton render it when loading is enabled. See Spinner.
| Prop | Type | Default | Description |
|---|---|---|---|
size | SpinnerSize | number | "md" | "xs" | "sm" | "md" | "lg", or a pixel diameter. |
tone | SpinnerTone | "primary" | Tone scale, plus "inherit" for currentColor. |
thickness | number | — | Ring thickness in px. |
label | string | null | "Loading" | Screen-reader label. null when visible text already says it. |
LoadingOverlay
Covers a region while its data is in flight without collapsing the layout underneath, so a re-fetch does not throw away the reader's scroll position. It waits delay milliseconds (120 by default) before appearing, so a fast response never flashes chrome. Full props on its page.
Empty states & skeletons
EmptyState
Zero-results / zero-data placeholder for tables, panels, and filtered views.
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | — | Leading illustration or icon. |
title | ReactNode | — | Headline. |
description | ReactNode | — | Dim explanatory text. |
action | ReactNode | — | Action content. |
compact | boolean | — | Tighter paddings for inside a card or table. |
Skeleton
A shimmering loading placeholder block. Set w, h, and circle for avatar placeholders.
SkeletonText
Skeleton lines with a shortened final line.
SkeletonList, SkeletonTable, SkeletonCard
SkeletonTable draws rows at Table's density, with the same cell padding and borders, so nothing shifts when data loads. SkeletonList renders a feed or list row, with an optional leading circle. SkeletonCard renders a card's contents, not its frame.
All of them take animation: "shimmer" (the default), "pulse" for a page carrying several at once, or "none". Every one goes static under prefers-reduced-motion.
import { EmptyState, Skeleton, SkeletonText, SkeletonList, SkeletonTable, SkeletonCard } from "@solarius/onset";
<EmptyState title="No data" description="Nothing to show yet." />
<Skeleton w={200} h={12} />
<SkeletonText lines={3} lastWidth="40%" />
<SkeletonTable rows={8} columns={["20%", "45%", "30%"]} />