ChatError
Quiet inline failure banner for a chat turn: the error, an optional detail line, and a retry button that disables itself while the retry is in flight
Preview
<script lang="ts">
import { onMount } from "svelte";
import { ChatError } from "fancy-ui-svelte";
/** How long the pretend request takes before it comes back green. */
const RETRY_MS = 1500;
let phase = $state<"failed" | "retrying" | "recovered">("failed");
let timer: ReturnType<typeof setTimeout> | undefined;
function retry() {
phase = "retrying";
timer = setTimeout(() => {
timer = undefined;
phase = "recovered";
}, RETRY_MS);
}
function replay() {
clearTimeout(timer);
timer = undefined;
phase = "failed";
}
// The pending retry outlives nothing: clear it if the reader navigates away
// mid-flight.
onMount(() => () => clearTimeout(timer));
</script>
<div class="flex w-full max-w-md flex-col gap-3 p-6">
<div class="bg-primary text-primary-foreground ml-auto rounded-2xl rounded-br-sm px-4 py-2.5">
<p class="text-sm">What changed in yesterday's deploy?</p>
</div>
{#if phase === "recovered"}
<div class="bg-muted mr-auto rounded-2xl rounded-bl-sm px-4 py-2.5">
<p class="text-sm leading-relaxed">
Three commits landed: a cache fix, the new export endpoint, and a dependency bump.
</p>
</div>
<button
type="button"
class="text-muted-foreground hover:text-foreground self-start text-xs underline underline-offset-4"
onclick={replay}
>
Replay the failure
</button>
{:else}
<ChatError
message="The assistant couldn't finish that reply"
detail="network_error · request 8f21c0"
onRetry={retry}
retrying={phase === "retrying"}
/>
{/if}
</div>Installation
Usage
<script lang="ts">
import { ChatError } from 'fancy-ui-svelte';
</script>
<ChatError />Examples
Basic Usage
<script lang="ts">
import { onMount } from "svelte";
import { ChatError } from "$lib/fancy-ui/chat-error";
/** How long the pretend request takes before it comes back green. */
const RETRY_MS = 1500;
let phase = $state<"failed" | "retrying" | "recovered">("failed");
let timer: ReturnType<typeof setTimeout> | undefined;
function retry() {
phase = "retrying";
timer = setTimeout(() => {
timer = undefined;
phase = "recovered";
}, RETRY_MS);
}
function replay() {
clearTimeout(timer);
timer = undefined;
phase = "failed";
}
// The pending retry outlives nothing: clear it if the reader navigates away
// mid-flight.
onMount(() => () => clearTimeout(timer));
</script>
<div class="flex w-full max-w-md flex-col gap-3 p-6">
<div class="bg-primary text-primary-foreground ml-auto rounded-2xl rounded-br-sm px-4 py-2.5">
<p class="text-sm">What changed in yesterday's deploy?</p>
</div>
{#if phase === "recovered"}
<div class="bg-muted mr-auto rounded-2xl rounded-bl-sm px-4 py-2.5">
<p class="text-sm leading-relaxed">
Three commits landed: a cache fix, the new export endpoint, and a dependency bump.
</p>
</div>
<button
type="button"
class="text-muted-foreground hover:text-foreground self-start text-xs underline underline-offset-4"
onclick={replay}
>
Replay the failure
</button>
{:else}
<ChatError
message="The assistant couldn't finish that reply"
detail="network_error · request 8f21c0"
onRetry={retry}
retrying={phase === "retrying"}
/>
{/if}
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
message | string | "Something went wrong" | The failure line |
detail | string | - | Secondary muted line under the message, e.g. the error code |
onRetry | () => void | - | Pressing retry calls this; the retry button only exists when it is set |
retryLabel | string | "Retry" | Label for the retry button |
retrying | boolean | false | Whether a retry is in flight: disables the button and marks the row busy |
Slots
| Slot | Description |
|---|---|
icon | Leading icon, replacing the default warning triangle |
children | Rendered instead of the message and detail block |
Links
Related components
ChatMessage
One conversation turn, aligned and dressed by its role, streaming its body while the answer arrives, with an action rail that fades in on hover and a version navigator underneath
ChatPanel
The shell a conversation lives in: a sticky header, a transcript that pins itself to the bottom while an answer arrives and offers the way back once you scroll up, and a sticky composer row
Composer
The input at the bottom of a chat, taken apart: a root that owns the draft and eight parts that read it — a growing textarea, a send button that becomes a stop button, a toolbar, a model picker, an attachment row with its chips, and a completion menu you can mount twice on the same draft for / commands and @ mentions