Feedback
Components

Feedback

Banners, callouts, toasts, notifications, empty states, spinners, and skeletons.

Components in this category

Banners & status bars

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.

BannerProps props
PropTypeDefaultDescription
toneBannerTone"warn"Tone name or alias. Canonical names are neutral, primary, info, ok, warn, and err.
titleReactNode—The condition in a few words. Stacks the banner.
childrenReactNode—The message.
metaReactNode—Secondary content after the message.
actionsReactNode—Buttons or links at the trailing edge.
iconReactNode | null—Replaces the tone icon. Pass null to hide the icon.
onDismiss() => void—Shows a close button and calls this.
dismissLabelstring"Dismiss"Accessible name for the close button.
flushboolean—Edge-to-edge, for a banner pinned under app chrome.
Banner

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.

StatusBarProps props
PropTypeDefaultDescription
leftReactNode[]—Leading cluster. Strings are wrapped in StatusBarItem.
rightReactNode[]—Trailing cluster, pinned to the far edge.
connectedbooleantrueDisplays a live connection cell. Set false to hide the cell.
connectedLabelReactNode"Connected"Value of the built-in connection cell.
StatusBar
ImportTSX
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.

CalloutProps props
PropTypeDefaultDescription
toneCalloutTone"info""info" | "ok" | "warn" | "err".
titleReactNode—Bold heading above the body.
iconReactNode—Override the tone-driven default icon.
actionsReactNode—Action row under the body.
childrenReactNode—Body content.
Callout
ImportTSX
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.

ToastProps props
PropTypeDefaultDescription
toneToastTone"ok""ok" | "warn" | "err".
childrenReactNode—Toast message.
hrefstring—Link target for a "View" link.
linkLabelstring"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.

ToastProviderProps props
PropTypeDefaultDescription
childrenReactNode—App content wrapped by the provider.
placementToastPlacement"bottom-right""top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right".
durationnumber5000Default auto-dismiss in ms. Use 0 or Infinity for sticky.
limitnumber4Max 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.

ImportTSX
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

Toast tones

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.

NotificationProps props
PropTypeDefaultDescription
itemNotificationItem—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).

Notification

InboxPanel

A persistent notification inbox, typically shown behind a bell icon. Supports filtering (All / Unread) and optional grouping by category.

InboxPanelProps props
PropTypeDefaultDescription
itemsNotificationItem[]—Notifications to display.
titleReactNode"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.
filterablebooleantrueAdds All / Unread filter row.
viewsInboxView[]—Custom caller-filtered views.
activeViewstring—Controlled selected view key.
defaultActiveViewstring—Initial selected custom view.
onViewChange(key: string) => void—A custom view was selected.
searchableboolean—Adds notification search.
searchValuestring—Controlled search text.
onSearchChange(value: string) => void—Hands search filtering to the caller.
searchPlaceholderstring"Search notifications"Search field placeholder.
unreadCountnumber—Total unread count, including unloaded pages.
headerActionsReactNode—Right-aligned header actions.
footerReactNode—Content pinned below the list.
loadingboolean—Shows placeholder rows on an empty first load.
groupedboolean—Section rows under their category.
emptyMessageReactNode"You're all caught up."Empty-list message.
maxHeightnumber380Scrollable list height.
widthnumber | string320pxPanel width.
InboxPanel

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.

NotificationBellProps props
PropTypeDefaultDescription
countnumber0Unread count.
maxnumber9Counts above this render as "9+".
dotboolean—Displays a dot instead of the unread count.
NotificationBell
ImportTSX
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.

SpinnerProps props
PropTypeDefaultDescription
sizeSpinnerSize | number"md""xs" | "sm" | "md" | "lg", or a pixel diameter.
toneSpinnerTone"primary"Tone scale, plus "inherit" for currentColor.
thicknessnumber—Ring thickness in px.
labelstring | null"Loading"Screen-reader label. null when visible text already says it.
Spinner

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.

LoadingOverlay

Empty states & skeletons

EmptyState

Zero-results / zero-data placeholder for tables, panels, and filtered views.

EmptyStateProps props
PropTypeDefaultDescription
iconReactNode—Leading illustration or icon.
titleReactNode—Headline.
descriptionReactNode—Dim explanatory text.
actionReactNode—Action content.
compactboolean—Tighter paddings for inside a card or table.
EmptyState

Skeleton

A shimmering loading placeholder block. Set w, h, and circle for avatar placeholders.

Skeleton

SkeletonText

Skeleton lines with a shortened final line.

SkeletonText

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.

Skeleton variants

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.

ImportTSX
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%"]} />