Feedback Components
Feedback components communicate system status to users — success messages, loading indicators, error states, and empty placeholders.
Toast Notifications
Section titled “Toast Notifications”Package: svelte-sonner ^1.1.0
UI wrapper: $lib/components/ui/sonner
The Sonner component (the toast container) is mounted once at the root layout. It automatically adapts to the current color mode via mode-watcher:
<!-- Already configured in the root layout --><script lang="ts"> import { Sonner } from "$lib/components/ui/sonner";</script>
<Sonner />The wrapper provides custom icons for each toast type using Lucide icons:
| Type | Icon |
|---|---|
| Loading | Loader2Icon (animated spin) |
| Success | CircleCheckIcon |
| Error | OctagonXIcon |
| Info | InfoIcon |
| Warning | TriangleAlertIcon |
Import the toast function directly from svelte-sonner anywhere in your code:
<script lang="ts"> import { toast } from "svelte-sonner";
async function handleSave() { toast.loading("Saving changes...");
try { await saveData(); toast.success("Changes saved successfully"); } catch (error) { toast.error("Failed to save changes"); } }</script>Common patterns
Section titled “Common patterns”// Simple messagestoast.success("Profile updated");toast.error("Something went wrong");toast.info("New feature available");toast.warning("Your session will expire soon");toast.success("Post published", { description: "Your post is now visible to followers.",});toast.promise(saveChanges(), { loading: "Saving...", success: "Saved!", error: "Could not save.",});toast.success("Message sent", { action: { label: "Undo", onClick: () => undoSend(), },});Loading Skeleton
Section titled “Loading Skeleton”Import: $lib/modules/shared
The LoadingSkeleton component renders animated placeholder shapes that match common content layouts. It uses the Skeleton primitive from $lib/components/ui/skeleton.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "card" | "post" | "chat" | "profile" | "card" | Layout pattern to render |
count | number | 3 | Number of skeleton items to show |
Variants
Section titled “Variants”Renders rounded bordered containers with three lines of varying width. Best for generic list items, settings panels, or card grids.
<LoadingSkeleton variant="card" count={3} />Mimics a social media post: circular avatar, username/timestamp row, content lines, and action buttons.
<LoadingSkeleton variant="post" count={5} />Simulates a message list: circular avatar, name, preview text, and timestamp.
<LoadingSkeleton variant="chat" count={8} />Full profile page placeholder: cover image, circular avatar with ring, name, bio, and stats row. Renders a single profile (ignores count).
<LoadingSkeleton variant="profile" />Usage in pages
Section titled “Usage in pages”<script lang="ts"> import { LoadingSkeleton } from "$lib/modules/shared";
let { data } = $props();</script>
{#if data.loading} <LoadingSkeleton variant="post" count={5} />{:else} <!-- Render actual content -->{/if}Empty State
Section titled “Empty State”Import: $lib/modules/shared
The EmptyState component displays a centered placeholder when a list or page has no content to show.
| Prop | Type | Required | Description |
|---|---|---|---|
icon | Component<{ class?: string }> | No | Svelte component to render as the icon |
title | string | Yes | Primary message |
description | string | No | Secondary explanatory text |
class | string | No | Additional CSS classes |
<script lang="ts"> import { EmptyState } from "$lib/modules/shared"; import InboxIcon from "@tabler/icons-svelte/icons/inbox";</script>
{#if messages.length === 0} <EmptyState icon={InboxIcon} title="No messages yet" description="Start a conversation to see messages here." />{/if}The component renders with py-16 vertical padding and centers all content. The icon (if provided) renders at size-10 in text-muted-foreground, with the title in text-lg font-semibold and description in text-sm text-muted-foreground constrained to max-w-xs.
Confirmation Modal
Section titled “Confirmation Modal”Import: $lib/modules/shared
The ConfirmationModal provides a pre-built dialog for destructive or important actions that require explicit user consent.
<script lang="ts"> import { ConfirmationModal } from "$lib/modules/shared";
let showDeleteConfirm = $state(false);
function handleDelete() { // perform deletion showDeleteConfirm = false; }</script>
<ConfirmationModal bind:open={showDeleteConfirm} title="Delete Post" description="This action cannot be undone. The post and all its comments will be permanently deleted." onConfirm={handleDelete}/>Error Handling Patterns
Section titled “Error Handling Patterns”Honeycomb does not have a single global error display component. Instead, errors are handled through a combination of strategies:
Toast-based errors
Section titled “Toast-based errors”For form submissions and API calls, use toast.error():
try { await api.updateProfile(formData); toast.success("Profile updated");} catch (e) { toast.error("Failed to update profile");}SvelteKit error pages
Section titled “SvelteKit error pages”For route-level errors, SvelteKit’s built-in +error.svelte pages handle 404s and server errors.
Inline validation
Section titled “Inline validation”Form fields use aria-invalid attributes which the UI primitives style automatically with a destructive ring:
<Input type="email" aria-invalid={!!errors.email} />{#if errors.email} <p class="text-sm text-destructive">{errors.email}</p>{/if}Summary
Section titled “Summary”| Need | Component / Function | Import |
|---|---|---|
| Success/error notifications | toast | svelte-sonner |
| Loading placeholders | LoadingSkeleton | $lib/modules/shared |
| Basic skeleton shapes | Skeleton | $lib/components/ui/skeleton |
| No-content placeholder | EmptyState | $lib/modules/shared |
| Destructive action confirmation | ConfirmationModal | $lib/modules/shared |
| Form field errors | aria-invalid + inline text | Built-in to UI primitives |