Skip to content

ErrorPanel

<caos-error-panel>

leads with the message, says whose problem it is, and offers a way forward

May be placed on a record page, an app’s home page, a page inside an app and a step of a guided flow.

Name Attribute Description Type Default Required
heading heading, markup only What failed, as a sentence — “This surface failed to load”, not “Error”. Unset, the heading line is not drawn at all. text none declared Optional
eyebrow eyebrow, markup only The small tinted word above the heading, for the class of failure — “Timed out”, “Rejected”. Set it to an empty string to remove it. text Error Optional
fault fault, markup only user, admin, or platform (the kernel error catalog’s own Fault axis). Draws a short, plain-text line under the message saying who can act on the failure — “You can fix this.”, “Your administrator can fix this.”, or “This one’s on us — we’re already on it.” Unset, no line is drawn; the message carries the whole explanation on its own. text none declared Optional
retriable retriable, markup only Draws a default “Try again” control in the actions row that fires a retry event, so a retriable failure can offer one without the caller composing a button. Has no effect once the caller has slotted its own action content — one job, one control, never two. boolean none declared Optional
docsHref docs-href, markup only A link to the guide for this failure, drawn in the actions row as “Read the guide”. Unset, no link is drawn. text none declared Optional
code code, markup only The failure code, drawn in the mono diagnostics block as “Code ”. A real code is namespaced and stable — internal.unexpected, integration.callout_failed — never a short opaque one invented for the occasion. Unset, the row is not drawn; with neither code nor correlation id, the whole block collapses. text none declared Optional
correlationId correlation-id, markup only The id of the one request that failed, drawn as “Recorded as ” with a line underneath saying it is already on file — a RECEIPT, not a token to read aloud or relay onward. A real one is a generated uuid, never a hand-written string. text none declared Optional
Name When it fires What it carries
retry The default “Try again” control (drawn by retriable) is pressed. Retrying is the host’s job. nothing
Name What goes in it
the default slot The message — what this means for the person reading it. The headline; leads the panel.
action The way forward, hand-composed — a retry, a reload, a way back to somewhere that works. Slotting anything here retracts the default retriable control, so a page is never shown two.
Design token What it controls
--caos-color-surface The panel fill, which stays neutral rather than tinted.
--caos-color-border The 1px panel edge, and the edge of the diagnostics block.
--caos-radius-lg The panel corner radius.
--caos-color-danger-bg The disc behind the glyph, and the eyebrow fill.
--caos-color-danger The glyph itself.
--caos-color-danger-text The eyebrow lettering — the on-tint ink, so it holds up in both themes.
--caos-radius-pill The round icon disc and the eyebrow.
--caos-color-text The heading, the fault line, and the labels in the diagnostics block.
--caos-color-text-muted The message, the diagnostics values, and the receipt sentence.
--caos-color-surface-2 The fill behind the diagnostics block.
--caos-radius-sm The corners of the diagnostics block and the retry control.
--caos-color-accent The guide link.
--caos-color-accent-fill The default retry control’s fill, and –caos-color-accent-fill-hover under the pointer.
--caos-color-accent-contrast The retry control’s label. White by default; a brand with a pale accent sets a darker ink here.
--caos-font-display The typeface the heading is set in.
--caos-font-mono The typeface of the eyebrow and the diagnostics, so a code can be read character by character.
--caos-space-7 The vertical padding of the panel.
--caos-space-5 The horizontal padding of the panel.
--caos-space-3 The gap between the disc, eyebrow, heading, message and diagnostics.
--caos-space-2 The padding inside the diagnostics block and the gap between actions.

Nothing else drives this piece by calling it.

Nothing on this piece can be read back.

Part Which piece of it When it is there
::part(panel) the bordered panel Always
::part(icon) the danger glyph Always
::part(eyebrow) the small line above the heading Always
::part(title) the heading Always
::part(message) the explanatory text Always
::part(fault) the “who can act on this” line Always
::part(diagnostics) the code and correlation-id block Always
::part(actions) the row holding the retry control, the slotted action, and the guide link Always
::part(retry) the default “Try again” control drawn by retriable Always
::part(guide) the “Read the guide” link drawn by docsHref Always

A surface that could not be drawn, where there is nothing to show in its place and the failure needs to be written down rather than only announced. Reach for it when the failure is worth a record — set fault so the reader knows whose problem it is, and retriable when a second try might succeed.

  • Toast — The failure is transient and the page still works — a save that will succeed on the next try. A toast is gone before anybody can note anything down, so anything worth finding again later — anything carrying a code or a correlation id — must not be one.
  • ValidationSummary — The request was understood and REJECTED because of what was typed. Nothing failed there — every item on that list is something the same person can fix in the next thirty seconds, and a correlation id turns a working form into a support ticket nobody needed to open.
  • AccessDeniedPanel — Nothing broke — the caller is not granted it. A refusal has no code to report and will not resolve on a retry, so it needs a grant named and a person to ask, not a reference number.
  • NoticeBanner — The surface DID load and something about it is degraded — stale figures, a feed behind, one section unavailable. There is content to sit above, so the banner qualifies it rather than replacing it.
  • EmptyState — Everything worked and there is genuinely nothing to show.

The panel is a role="alert" region labelled by its own heading, so a screen reader is told about the failure when the panel arrives rather than when somebody happens to reach it, and the glyph is hidden because the heading already says it. The diagnostics block is ordinary selectable text — the code and the reference can be copied, which a graphic could not offer. Left to the author: the heading has to say what failed (an alert reading “Error” tells a screen-reader user nothing they can act on), and a hand-composed action slot needs a real focusable control, because a person who has just been interrupted by an alert wants somewhere for the caret to go.

The kernel’s own catch-all (internal.unexpected) rendered as this component actually renders it: the message leads, the fault line says it is on us, the retry is drawn by retriable rather than hand-composed, and the code + reference sit underneath as a receipt — a real uuid, framed as already recorded rather than as something to relay.

The page around it: The page around it owns retrying: it listens for the retry event this control raises.

{
"id": "example",
"section": "A platform failure, with a retry",
"columns": 1,
"items": [
{
"id": "error_panel_1",
"type": "component",
"key": "error_panel",
"inputs": {
"heading": "This surface failed to load",
"fault": "platform",
"retriable": true,
"code": "internal.unexpected",
"correlation_id": "5c3f8a2e-9b41-4d7a-8e2c-1a6f0d9b3c47"
},
"children": [
"Something went wrong on our end. Reference 5c3f8a2e-9b41-4d7a-8e2c-1a6f0d9b3c47 — we're looking into it."
]
}
]
}
<caos-error-panel heading="This surface failed to load" fault="platform" retriable code="internal.unexpected" correlation-id="5c3f8a2e-9b41-4d7a-8e2c-1a6f0d9b3c47">Something went wrong on our end. Reference 5c3f8a2e-9b41-4d7a-8e2c-1a6f0d9b3c47 — we're looking into it.</caos-error-panel>

A real, currently-emittable integration failure (integration.callout_failed, fault platform, retriable in the catalog) with its own two controls slotted in — which is why no default retry is drawn: the caller composed its own, and one job gets one control.

{
"id": "example",
"section": "With a hand-composed way forward",
"columns": 1,
"items": [
{
"id": "error_panel_1",
"type": "component",
"key": "error_panel",
"inputs": {
"eyebrow": "Timed out",
"heading": "The reporting service did not answer",
"fault": "platform",
"code": "integration.callout_failed",
"correlation_id": "d18e4f6b-2a75-4c90-b3e1-7f8a0c5d9e21"
},
"children": [
"Couldn't reach the reporting service. We'll retry; no action needed yet.",
{
"id": "button_2",
"type": "component",
"key": "button",
"inputs": {
"variant": "primary"
},
"slot": "action",
"children": [
"Try again"
]
},
{
"id": "button_3",
"type": "component",
"key": "button",
"inputs": {
"variant": "ghost"
},
"slot": "action",
"children": [
"Back to the record"
]
}
]
}
]
}
<caos-error-panel eyebrow="Timed out" heading="The reporting service did not answer" fault="platform" code="integration.callout_failed" correlation-id="d18e4f6b-2a75-4c90-b3e1-7f8a0c5d9e21">
Couldn't reach the reporting service. We'll retry; no action needed yet.
<caos-button slot="action" variant="primary">Try again</caos-button>
<caos-button slot="action" variant="ghost">Back to the record</caos-button>
</caos-error-panel>

A conflicting write (conflict.stale_version, fault user) — the reader’s own next action is reload and retry, so fault="user" and a link to the guide are shown; nothing here is retriable in place, because the fix is reloading, not pressing the same button again.

{
"id": "example",
"section": "A user-fixable failure, with a guide",
"columns": 1,
"items": [
{
"id": "error_panel_1",
"type": "component",
"key": "error_panel",
"inputs": {
"eyebrow": "Conflict",
"heading": "This record changed while you were working",
"fault": "user",
"code": "conflict.stale_version",
"correlation_id": "a9024b7e-6f13-4e88-9c5a-3d2b1f0e8a76",
"docs_href": "/guidance/conflicts"
},
"children": [
"Someone else updated this record while you were working. Reload and try again."
]
}
]
}
<caos-error-panel eyebrow="Conflict" heading="This record changed while you were working" fault="user" code="conflict.stale_version" correlation-id="a9024b7e-6f13-4e88-9c5a-3d2b1f0e8a76" docs-href="/guidance/conflicts">Someone else updated this record while you were working. Reload and try again.</caos-error-panel>