ComponentsFeedback States
Feedback States
Show loading, progress, errors, empty states, and notifications.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport the component into your page or component file.
import {
Alert,
Announcer,
Callout,
EmptyState,
LiveRegionProvider,
Progress,
ProgressCircle,
Skeleton,
SkeletonAvatar,
SkeletonButton,
SkeletonText,
Spinner,
ToastProvider,
ToastViewport,
useAnnouncer,
useToast,
} from "@dethink/components";Feedback primitives compose across loading, async status, empty data, and mutation confirmation flows.
Loading and progress
Spinner is decorative unless labelled. Progress exposes determinate or indeterminate progressbar semantics, while Skeleton reserves layout space.
Show sourceexamples/feedback-states/loading.tsx
"use client";
import {
Progress,
ProgressCircle,
SkeletonAvatar,
SkeletonButton,
SkeletonText,
Spinner,
} from "@dethink/components";
export function FeedbackLoading() {
return (
<div className="grid gap-6">
<div className="flex flex-wrap items-center gap-4">
<Spinner label="Loading reports" tone="primary" />
<Spinner aria-label="Refreshing" variant="dots" tone="muted" />
<Progress label="Import progress" value={68} showValue tone="info" />
</div>
<div className="border-border grid gap-4 rounded-lg border p-4">
<div className="flex items-center gap-4">
<ProgressCircle label="Upload" value={72} showValue tone="success" />
<div className="grid flex-1 gap-2">
<SkeletonText lines={3} animation="shimmer" />
<div className="flex items-center gap-3">
<SkeletonAvatar />
<SkeletonButton />
</div>
</div>
</div>
</div>
</div>
);
}Messaging and empty states
Alert carries status urgency, Callout stays contextual, and EmptyState keeps action slots predictable.
Show sourceexamples/feedback-states/messaging.tsx
"use client";
import { AlertTriangle, CheckCircle2, Inbox, UploadCloud } from "lucide-react";
import { Alert, Button, Callout, EmptyState } from "@dethink/components";
export function FeedbackMessaging() {
return (
<div className="grid gap-4">
<Alert
icon={<CheckCircle2 />}
title="Deployment queued"
description="The release will continue in the background."
tone="success"
/>
<Alert
icon={<AlertTriangle />}
title="Payment retry failed"
description="Update the payment method before retrying the invoice."
tone="destructive"
urgency="assertive"
/>
<Callout
icon={<UploadCloud />}
title="Large imports"
description="Files over 100 MB continue processing after this panel closes."
/>
<EmptyState
visual={<Inbox />}
title="No failed jobs"
description="Background jobs that need attention will appear here."
primaryAction={<Button variant="outline">Refresh</Button>}
variant="table"
/>
</div>
);
}Shared announcements
LiveRegionProvider centralizes polite and assertive announcements and supports debounced result-count updates.
- Polite
- No result announcement sent.
- Assertive
- No failure announcement sent.
Toast workflow
Toast uses the shared announcer, explicit dismiss controls, auto-dismiss timers, pause-on-hover/focus, and Motion-powered stack presence.
Show sourceexamples/feedback-states/toast-workflow.tsx
"use client";
import { AlertTriangle, CheckCircle2, ListChecks } from "lucide-react";
import {
Button,
ToastProvider,
ToastViewport,
useToast,
} from "@dethink/components";
function ToastControls() {
const { toast } = useToast();
return (
<div className="flex flex-wrap gap-2">
<Button
leftIcon={<CheckCircle2 />}
onClick={() =>
toast({
action: {
label: "Undo",
onClick: () => undefined,
},
description: "Workspace settings were saved.",
title: "Saved",
tone: "success",
})
}
>
Save
</Button>
<Button
leftIcon={<AlertTriangle />}
variant="outline"
onClick={() =>
toast({
description: "The import worker stopped before finishing.",
persistent: true,
title: "Import failed",
tone: "destructive",
})
}
>
Fail import
</Button>
<Button
leftIcon={<ListChecks />}
variant="outline"
onClick={() =>
toast({
announcement: "CSV import is ready for review.",
render: ({ dismiss }) => (
<div className="grid gap-3">
<div className="grid gap-1">
<div className="text-foreground font-medium">
CSV import ready
</div>
<div className="text-muted-foreground">
42 rows matched. 3 rows need review before publishing.
</div>
</div>
<Button size="sm" variant="outline" onClick={dismiss}>
Review later
</Button>
</div>
),
tone: "info",
})
}
>
Custom toast
</Button>
</div>
);
}
export function FeedbackToastWorkflow() {
return (
<div className="min-h-72">
<ToastProvider motion="standard">
<ToastControls />
<ToastViewport />
</ToastProvider>
</div>
);
}Feedback state primitives share token-backed styling, stable slots, and reduced-motion-safe behavior.
| Prop | What it does | Default |
|---|---|---|
LiveRegionProvider{ renderRegions, politeClassName, assertiveClassName } | Provides one polite and one assertive live-region channel for announcements. | { renderRegions: true } |
useAnnouncer() => { announce, announcePolite, announceAssertive, clear } | Imperative announcement API with debounce and coalescing options. | Not set |
Spinner{ size, tone, variant, label } | Decorative loading indicator by default; labelled instances expose role=status. | { size: "md", tone: "current", variant: "ring" } |
Progress{ value, min, max, indeterminate, label, status, showValue, tone } | Linear progressbar with determinate, indeterminate, value label, and status text support. | { min: 0, max: 100 } |
ProgressCircleProgressProps & { thickness } | Circular progress visual with the same value semantics as Progress. | { thickness: 8 } |
Skeleton{ animation: "pulse" | "shimmer" | "none", radius } | Decorative layout-preserving placeholder with reduced-motion-safe animation. | { animation: "pulse", radius: "md" } |
Alert{ tone, variant, urgency, title, description, icon, actions, onDismiss } | Inline status surface with optional urgent announcement semantics and action slots. | { tone: "info", variant: "soft", urgency: "polite" } |
CalloutAlertProps | Contextual guidance surface that stays non-urgent unless explicitly configured. | { tone: "neutral", variant: "outline", urgency: "none" } |
EmptyState{ variant, tone, visual, title, description, primaryAction, secondaryAction } | Composable empty, no-results, no-access, onboarding, and recovery state layout. | { variant: "card", tone: "neutral" } |
ToastProvider{ toasts, defaultToasts, placement, motion, maxToasts, defaultDuration } | Owns toast state, announcements, timers, and the imperative useToast API. | { placement: "bottom-end", motion: "standard" } |
ToastRecord.renderReactNode | ({ toast, dismiss }) => ReactNode | Overrides default title, description, and action layout; pair with announcement for live-region copy. | Not set |
ToastViewport{ placement, motion } | Fixed, safe-area-aware stack surface for actionable and dismissible toasts. | Not set |