Tokens and components for Carbon Panel and Carbon Cloud.
A single reference for the shared visual layer: CSS custom properties, control geometry, motion contracts, and the nineteen Svelte components exported from $lib/components/carbon. The examples on this page are static HTML reproductions; the Svelte files remain the implementation.
web/carbon-panel/src/lib/design-system/tokens.cssCanonical colour, spacing, control, motion and shadow values for both themes.
index.ts
web/carbon-panel/src/lib/components/carbon/index.tsPublic export surface. Nineteen components, no shared package boundary.
app.css
web/carbon-panel/src/app.cssTailwind theme mapping, utility contracts (focus-ring, motion-press, scroll-strip) and reduced-motion handling.
THEME RESOLUTION
Dark is the default. The dark block is written as :where(:root), .dark, [data-carbon-theme='dark']; :where() contributes zero specificity, so an explicit [data-carbon-theme='light'] container always wins regardless of source order. A plain :root selector would tie with the light rule and win by order, which is why it is not used.
Semantic roles, not literal values. Every row lists both themes; consumers reference the token name, never the hex.
Surfaces and text
Role
Token
Light
Dark
Usage
Page background
--background
#ffffff
#161616
Root page plane and inset wells.
Body text
--foreground
#161616
#f4f4f4
Primary reading colour on --background.
Card surface
--card
#f4f4f4
#262626
First raised plane: tiles, tables, panels.
Card text
--card-foreground
#161616
#f4f4f4
Text on --card.
Popover surface
--popover
#ffffff
#393939
Menus, popovers, floating layers.
Popover text
--popover-foreground
#161616
#f4f4f4
Text on --popover.
Muted surface
--muted
#f4f4f4
#262626
Subtle fills that recede behind content.
Secondary text
--muted-foreground
#525252
#a8a8a8
Hints, descriptions, metadata.
Accent surface
--accent
#e8e8e8
#353535
Hover layer for tertiary and ghost actions.
Accent text
--accent-foreground
#161616
#ffffff
Text on --accent.
Secondary surface
--secondary
#e0e0e0
#393939
Secondary button fill.
Secondary text
--secondary-foreground
#161616
#f4f4f4
Text on --secondary.
Structural border
--border
#e0e0e0
#393939
1 px dividers and container edges.
Control boundary
--input
#737373
#7d7d7d
Visible field boundary; stronger than --border.
Focus ring
--ring
#0f62fe
#ffffff
2 px outline, 2 px offset in product (3 px on this page).
Skeleton start
--skeleton-from
#e0e0e0
#262626
Leading stop of the shimmer gradient.
Skeleton end
--skeleton-to
#f4f4f4
#353535
Midpoint stop of the shimmer gradient.
Action and status
Role
Token
Light
Dark
Usage
Primary fill
--primary
#0f62fe
#0f62fe
Primary action and selection indicator. Pairs with white text in both themes.
Primary text
--primary-foreground
#ffffff
#ffffff
Text and icons on --primary.
Primary as text
--primary-text
#0f62fe
#78a9ff
Accent-coloured labels that must meet text contrast.
Accent text
--accent-text
#0f62fe
#78a9ff
Tertiary and ghost button labels, inline links.
Destructive fill
--destructive
#da1e28
#da1e28
Danger button and invalid field boundary.
Destructive text
--destructive-text
#a2191f
#ff8389
Error copy, never the fill.
Success fill
--success
#198038
#24a148
Status dots, progress completion.
Success text
--success-text
#0e6027
#6fdc8c
Running and healthy labels.
Warning fill
--warning
#b28600
#f1c21b
Caution marks and attention dots.
Warning text
--warning-text
#6f5200
#f1c21b
Caution copy and Caps Lock notice.
Button state overrides
Token
Light
Dark
Applied by
--button-primary-hover
#0353e9
#0353e9
CarbonButton kind="primary":hover
--button-primary-active
#002d9c
#002d9c
CarbonButton kind="primary":active
--button-secondary-hover
#c6c6c6
#4c4c4c
Secondary and tertiary active states
--button-secondary-active
#a8a8a8
#6f6f6f
Secondary, tertiary and ghost :active
--button-danger-hover
#ba1b23
#ba1b23
CarbonButton kind="danger":hover
--button-danger-active
#750e13
#750e13
CarbonButton kind="danger":active
Tag pairs
Type
Background token
Light / dark
Text token
Light / dark
green
--tag-green-bg
#defbe6 / #163b22
--tag-green-text
#0e6027 / #6fdc8c
gray
--tag-gray-bg
#e0e0e0 / #383838
--tag-gray-text
#393939 / #c6c6c6
red
--tag-red-bg
#fff1f1 / #4d2326
--tag-red-text
#a2191f / #ff8389
yellow
--tag-yellow-bg
#fcf4d6 / #423a1b
--tag-yellow-text
#6f5200 / #f1c21b
blue
--tag-blue-bg
#d0e2ff / #102a56
--tag-blue-text
#0043ce / #a6c8ff
purple
--tag-purple-bg
#e8daff / #382952
--tag-purple-text
#491d8b / #d4bbff
cyan
--tag-cyan-bg
#bae6ff / #18374e
--tag-cyan-text
#004a7e / #78c8ff
teal
--tag-teal-bg
#9ef0f0 / #173c3c
--tag-teal-text
#005d5d / #3ddbd9
magenta
--tag-magenta-bg
#ffd6e8 / #4a2636
--tag-magenta-text
#9f1853 / #ff7eb6
warm-gray
--tag-warm-gray-bg
#e7e5e4 / #3d3935
--tag-warm-gray-text
#44403c / #d6d3d1
cool-gray
--tag-cool-gray-bg
#e2e8f0 / #303a48
--tag-cool-gray-text
#334155 / #cbd5e1
Chart ramp
Charts must also carry labels, values or patterns. The ramp is categorical, not ordinal; --chart-1 is not "series one" in every chart.
--chart-1light #0f62fe / dark #4589ff
--chart-2light #198038 / dark #42be65
--chart-3light #b28600 / dark #f1c21b
--chart-4light #8a3ffc / dark #be95ff
--chart-5light #ff832b / dark #ff832b
Sidebar roles
Token
Light
Dark
--sidebar
#f4f4f4
#161616
--sidebar-foreground
#161616
#f4f4f4
--sidebar-primary
#0f62fe
#0f62fe
--sidebar-primary-foreground
#ffffff
#ffffff
--sidebar-accent
#e8e8e8
#262626
--sidebar-accent-foreground
#161616
#f4f4f4
--sidebar-border
#e0e0e0
#393939
--sidebar-ring
#0f62fe
#ffffff
Contrast is a property of the adjacent pair, not of a colour in isolation. Hover, selection and focus states must be checked on the surface they actually render over. Status colour never carries meaning alone.
02 / FOUNDATIONS
Typography
Two self-hosted variable faces, a five-step product scale, and one uppercase monospace label contract.
/fonts/jetbrains-mono-latin-wght-normal.woff2, latin subset, font-display: swap
utility
font-mono
LABELS / MONO EYEBROW
Active players
size
12 px / 1.6 line-height
tracking
0.04em
transform
uppercase
weight
400 (.mono-label), 600 (.mono-label-strong)
colour
#a8a8a8, or var(--accent-text) for .mono-label-accent
The only sanctioned use of all caps. Section labels, tile labels, table group labels. Never sentences.
Product scale
Utility
Size
Line height
Applied in
text-xs
0.75 rem / 12 px
1 rem / 16 px
Field labels, hints, errors, breadcrumbs, table body, tags
text-sm
0.875 rem / 14 px
1.25 rem / 20 px
Buttons, tabs, tables, modal body, inputs
text-base
1 rem / 16 px
1.5 rem / 24 px
CarbonButton size="xl", CarbonSearch size="lg"
text-xl
1.25 rem / 20 px
1.75 rem / 28 px
Modal title
text-2xl
1.5 rem / 24 px
2 rem / 32 px
CarbonPageHeader title
TRACKINGtracking-[0.16px]
Button labels. tracking-[0.32px] is used on field labels; tracking-tight on the page title; tracking-wider (0.05em) on tags and table titles.
NUMERALSfont-variant-numeric: tabular-nums
Required for metrics that change in place so digits do not reflow. Metric tiles, player counts, memory values.
WEIGHTSfont-light 300
Page title only. Body and controls use 400; emphasis uses 500 or 600. Values in tables stay 400.
Display type on this page uses clamp() for documentation chrome. It is not imported by either product and must not be copied into an application surface.
03 / FOUNDATIONS
Space and geometry
A four-pixel base, four control heights, one radius value and a documented focus outline.
SPACING TOKENS
481216243248
Token
rem
px
--carbon-space-1
0.25
4
--carbon-space-2
0.5
8
--carbon-space-3
0.75
12
--carbon-space-4
1
16
--carbon-space-5
1.5
24
--carbon-space-6
2
32
--carbon-space-7
3
48
CONTROL HEIGHTS
--carbon-control-sm2 rem / 32 px
--carbon-control-md2.5 rem / 40 px — default
--carbon-control-lg3 rem / 48 px
--carbon-control-xl4 rem / 64 px
RADIUS--radius: 0px
All corners are square. app.css also forces border-radius: 0 !important on every element, so a rounded utility cannot leak in.
FOCUSoutline: 2px solid var(--ring)
.focus-ring applies outline-offset: 2px on :focus-visible only. This page uses a 3 px offset for legibility against documentation borders.
BORDERS1px solid var(--border)
Structural by default. Fields gain a 2 px bottom border on focus or error; the selected tab indicator is a 2 px bar.
DENSITYdata-carbon-density
The stored compact-density preference rewrites these tokens. Layouts must remain usable in both densities; do not hard-code 40 px.
TARGET SIZE24 × 24 CSS px
Minimum pointer target, subject to the WCAG spacing exceptions. Aim for 44 × 44 on touch-heavy actions; a 32 px control must still meet the minimum.
ICON SIZE16 / 20 / 24 px
Inline icons are 16 px and inherit the text colour; standalone icons are 24 px. An icon-only control supplies an accessible name, and no icon carries state alone.
A 32 px control still needs a 24 × 24 CSS px target and adequate spacing. Prefer the 40 px default; reach for sm only in dense, pointer-driven surfaces such as table toolbars.
04 / FOUNDATIONS
Elevation
Three levels. Borders describe structure; shadows separate a floating layer from the page.
LEVEL 00Flat--carbon-shadow-none
Default surfaces, tables, tiles and dense layouts. Separation comes from a 1 px border and a surface change.
LEVEL 01Raised--carbon-shadow-raised
Transient menus, popovers, floating panels and the motion demo object.
LEVEL 02Overlay--carbon-shadow-overlay
Modal dialogs. Always combined with a backdrop and a focus trap, never with a static inline panel.
Do not stack shadows to fake a higher level, and do not add a shadow to a bordered surface that stays in flow. A layer that clips its parent is a stacking-context defect, not an elevation problem: CarbonModal portals to document.body for this reason.
05 / FOUNDATIONS
Motion
Six durations, five easings and two independent reduced-motion paths.
Colour, background and border at --motion-instant. --carbon-transition-press scales transform to --carbon-motion-press-scale (0.96).
ANIMATIONS--carbon-animation-rise-in
fade-in, rise-in (6 px via --carbon-motion-distance), pulse (1600 ms) and shimmer (1400 ms).
REDUCED MOTIONprefers-reduced-motion
A media query collapses animation and transition durations to 0.01 ms and disables smooth scrolling. The stored preference is mirrored by [data-carbon-motion='reduced'] with the same declarations, for users whose OS setting is unavailable to the application.
Motion never carries the only state cue. Animate opacity and transform; avoid transitioning layout properties. An entrance must be interruptible and must never delay an action. Spinners and skeletons stop under reduced motion.
06 / COMPONENTS
Component inventory
All nineteen exports from components/carbon/index.ts. Props are quoted from the component source; defaults are the destructured values.
These are independent Svelte components, not a shared package with a compatibility guarantee. Cloud imports a pinned token snapshot and carries its own component implementations; a prop name on this page is authoritative for Panel only. "Tokenised" means the component reads var(--…) roles; "literal" means it still contains hard-coded dark-palette hex values and will not follow a theme change.
01 / ACTION
CarbonButton
tokenised
The shared action control. Renders a native <button type="button"> or, when href is present, an <a>. One primary action per view; danger is reserved for irreversible operations.
size maps to --carbon-control-sm|md|lg|xl. sm also drops the label to text-xs; xl raises it to text-base.
iconOnly forces a square: equal width and height at the chosen control size, zero padding. The caller must supply an accessible name (aria-label).
loading renders a 16 px spinner and sets aria-busy="true". It also disables the control, so a duplicate submit is impossible while the promise is pending.
With href, a disabled or loading button keeps the anchor element but withholds href, adds role="link", aria-disabled="true" and tabindex="-1", and calls preventDefault().
The root sets motion-press (active scale 0.96) and focus-ring. Icons are marked shrink-0; labels stay on one line (whitespace-nowrap).
02 / STRUCTURE
CarbonPageHeader
literal palette
The single h1 for a route, with an optional status snippet, a short description and right-aligned actions. It does not own page padding; the shell provides the gutter.
Example
survival-01
Running
Java 21 · Paper 1.21.4 · eu-west
ConsoleStart
Prop
Type
Default
titlerequired
string
—
description
string
''
tag
Snippet
—
actions
Snippet
—
class
string
''
Title: text-2xl font-light tracking-tight. Description: text-xs in the literal #8d8d8d secondary tone.
The tag snippet sits inline with the title and wraps with it; actions move to their own row below 640 px.
The root is a flex row with border-b and pb-4. It is not sticky and does not scroll independently.
03 / SURFACE
CarbonTile
tokenised
A bordered content surface. The same component is a static panel, a clickable card or a link, decided by href and clickable.
Metric tile
Active players
12
8 onlineMax 20
Prop
Type
Default
clickable
boolean
false
href
string
—
onclick
() => void
—
class
string
''
children
Snippet
—
...restProps
unknown
—
Root selection is href first, then clickable, then a static div. In the link branch onclick is not forwarded; use the anchor's own navigation.
clickable renders <div role="button" tabindex="0"> and activates on Enter or Space. Prefer href when a real destination exists.
Interactive tiles gain a 1 px lift on hover (translateY(-1px)), hover:bg-accent and focus-ring. Static tiles gain none of these.
Padding is --carbon-space-4 (16 px). Metric tiles keep the shared hierarchy: uppercase label, one large tabular value, footer strip above a divider.
04 / STATUS
CarbonTag
tokenised
A compact status or filter label. Eleven colour pairs, two heights, an optional remove control and an optional pulse.
Heights are 20 px (sm) and 24 px (md). Label: uppercase monospace, semibold, tracking-wider.
filter renders a remove button with aria-label="Remove filter". It calls onclose, which must be provided or the button does nothing.
pulse applies motion-pulse (1600 ms, opacity 1 → 0.35 → 1). Use it for live state, not decoration.
Colour is supplemental. Every tag must contain a readable label; the eleven types are not a severity scale.
05 / DATA
CarbonDataTable
literal palette
A framed table with an optional header bar and toolbar. It supplies the chrome and the horizontal overflow container; the caller supplies thead cells and tbody rows.
Example
SERVERS3 in eu-west
Filter
Name
State
Players
survival-01
Running
12 / 20
creative-02
Stopped
—
modded-03
Starting
0 / 10
Prop
Type
Default
title
string
''
description
string
''
toolbar
Snippet
—
header
Snippet
—
children
Snippet
—
class
string
''
The header bar renders only when title or toolbar is present. header is the thead content; children renders inside tbody.
The table is wrapped in overflow-x-auto, so narrow viewports scroll the table rather than the page.
Documented gap: this component still uses literal #161616, #262626, #393939 and #f4f4f4. It will not follow the light theme until those are replaced with token roles.
Empty, loading and error states are not built in. Compose CarbonEmptyState, CarbonInlineLoading or CarbonErrorState around it rather than rendering a table with no rows.
06 / OVERLAY
CarbonModal
literal palette
A modal dialog portalled to <body>. Owns backdrop, scroll lock, focus placement, focus containment, Escape handling and focus restoration.
Example
DELETE SERVERDelete survival-01?
×
This removes the container and its volume. The world is deleted and cannot be recovered.
use:portal appends the overlay to document.body. Rendering it inside <main> left it in the page stacking context, where the fixed header and sidebar painted over it and the scroll container clipped it.
Open locks document.body.style.overflow; close restores the previous value. Backdrop clicks close; wheel events on the backdrop are prevented.
Focus moves to the first focusable descendant after 50 ms, otherwise to the dialog. Tab and Shift+Tab wrap inside the dialog. Escape closes with preventDefault() and stopPropagation(). The previously focused element regains focus on close.
size="full" is 95 vw × 90 vh. fullscreen removes the padding, uses h-screen and no maximum width.
bare suppresses header and footer chrome but keeps the title and description as sr-only elements so aria-labelledby and aria-describedby still resolve.
onprimary does not close the dialog; the caller decides. onsecondary falls back to closing when omitted. Bind open to a boolean value, not to an expression such as {value !== null}.
07 / OVERLAY
CarbonConfirm
literal palette
A small confirmation dialog built on CarbonModal. Awaiting onconfirm before closing makes it safe for slow destructive requests.
Example
▲
Delete backup?
backup-2026-11-02 · 4.2 GiB
CancelDelete
Prop
Type
Default
open
boolean (bindable)
false
title
string
'Are you sure?'
message
string
''
details
Snippet
—
confirmLabel
string
'Confirm'
cancelLabel
string
'Cancel'
danger
boolean
false
confirming
boolean
false
onconfirm
() => void | Promise<void>
—
onclose
() => void
—
Renders CarbonModal at size="sm" with hasFooter={false}; the action row lives in the body.
details renders inside a bordered monospace block. Use it for the exact resource name, size or count affected.
handleConfirm awaits onconfirm, then sets open = false. If the request throws, the dialog stays open and the error belongs in the caller's UI.
confirming disables both buttons and replaces the confirm label with "Working…". It does not stop Escape or backdrop dismissal; disable those in the caller when cancellation is unsafe.
danger adds the alert triangle and switches the confirm button to kind="danger".
08 / FORM
CarbonTextInput
tokenised
A labelled text field with hint, error and optional password reveal. Extends HTMLInputAttributes, so every native attribute is accepted.
Valid and invalid
Lowercase letters, digits and hyphens.
Must be at least 1 GiB.
Prop
Type
Default
label
string
''
helperText
string
''
error
string
''
value
string (bindable)
''
id
string
$props.id()
type
string
'text'
revealable
boolean
false
class
string
''
...restProps
input attributes
—
id falls back to the Svelte-generated $props.id(), so a label and field still connect without a caller-supplied id.
aria-describedby points at {id}-desc only when an error or hint exists. error also sets aria-invalid="true".
Message precedence is error, then the Caps Lock notice, then the hint. The Caps Lock check runs only for type="password" and reads getModifierState('CapsLock') on key down and key up.
revealable only applies to type="password". The toggle is a 24 px button with aria-pressed and a Show/Hide password label.
Field geometry: 40 px height, 1 px bottom border, 2 px border on focus or error, --carbon-control-md, 16 px horizontal padding.
09 / FORM
CarbonSelect
tokenised
A labelled native <select> with an overlaid chevron and the same label, hint and error association as CarbonTextInput.
Example
The image is pinned to this version.
Prop
Type
Default
label
string
''
helperText
string
''
error
string
''
value
string | number (bindable)
''
id
string
$props.id()
children
Snippet
—
...restProps
select attributes
—
Uses appearance-none plus a pointer-events-none chevron in the same box; the native menu, keyboard behaviour and mobile picker are preserved.
value is bound, not an onchange callback. Read it from the bound variable rather than reaching for the event.
Same {id}-desc association and aria-invalid behaviour as the text input.
10 / NAVIGATION
CarbonTabs
literal palette
A horizontal tab strip with a measured selection indicator and APG keyboard behaviour.
Strip
OverviewConsoleFilesBackups
The 2 px indicator is a 1 px element scaled with scaleX and moved with translateX; it paints above the active tab background, never under it.
Prop
Type
Default
tabsrequired
{ id: string; label: string; badge?: string | number }[]
[]
selectedTab
string (bindable)
first tab id
idPrefix
string
—
onselect
(id: string) => void
—
class
string
''
The indicator is re-measured on selection, on tab-list changes and through a ResizeObserver on the strip. The observer exists because a sidebar collapse or a scrollbar appearing changes the width without a window resize, which previously left the indicator under the wrong tab.
Keyboard: ArrowLeft and ArrowRight wrap, Home and End jump, and selection follows focus. Roving tabindex keeps one stop in the tab order.
idPrefix emits {prefix}-tab-{id} and aria-controls="{prefix}-panel-{id}". Provide the matching panels yourself.
Gotcha: onselect is an allowlist of ids in most call sites. Adding a tab without extending that handler produces a tab that selects but never loads its panel.
The strip uses scroll-strip: horizontal overflow scrolls, but no scrollbar is painted, so the strip height cannot jump.
11 / FEEDBACK
CarbonInlineLoading
literal palette
A single-line progress indicator with three outcomes: active, finished and error.
States
Loading server list✓Backup complete×Backup failed
Prop
Type
Default
description
string
'Loading...'
status
'active' | 'finished' | 'error'
'active'
class
string
''
Active renders a 16 px spinning square; finished draws a check with a 300 ms dash animation; error fades in a cross.
The component has no live region of its own. When the description changes in place, wrap it in an aria-live="polite" status region or use a status role.
Do not use it as a substitute for a skeleton when the final layout is already known.
12 / NAVIGATION
CarbonBreadcrumbs
literal palette
Ancestor navigation for a nested route. Renders a nav labelled "Breadcrumbs" with slash separators.
Trail
Prop
Type
Default
itemsrequired
{ href?: string; label: string }[]
[]
class
string
''
The final item is always rendered as a non-interactive span, even if it carries an href. That is the current location, not a destination.
Items are keyed by index, so reordering the trail reuses DOM nodes. Include the whole path in the label when that matters.
13 / STRUCTURE
CarbonShell
mixed
The application frame: top bar, collapsible side navigation, profile menu, preferences dialog and the scrolling main region. It is the only component here that depends on the SvelteKit runtime.
Frame
Header 64 px; side nav 256 px expanded, 48 px collapsed, 0 below md; main gutter 16 px, 24 px at md.
Prop
Type
Default
children
Snippet
—
<main> owns p-4 / md:p-6, scrollbar-stable, md:ml-64 or md:ml-12, and an inner mx-auto max-w-7xl space-y-6. Routes must not add another page-level padding — nesting p-6 doubles the gutter.
Imports $app/state, $app/paths, the auth and server stores, and the RPC client. It is product infrastructure, not a portable layout primitive; Cloud has its own AppShell.
Sidebar expansion persists in component state only. The profile menu, password dialog and preferences dialog are mounted here, so routes do not each re-implement them.
The header is fixed and paints above page content; the sidebar is offset by 64 px. A fixed overlay rendered inside main would sit below both — hence the modal portal.
14 / FORM
CarbonSearch
literal palette
A search input with a leading magnifier and a clear control that appears only when the field has a value.
Default / 40 px
⌕×
Small / 32 px
⌕×
Large / 48 px
⌕×
Prop
Type
Default
value
string (bindable)
''
placeholder
string
'Search...'
size
'sm' | 'md' | 'lg'
'md'
disabled
boolean
false
onclear
() => void
—
onkeydown
(e: KeyboardEvent) => void
—
class
string
''
Heights are 32, 40 and 48 px; the leading icon and horizontal padding scale with the size.
The accessible name falls back to aria-label from the rest props, then the placeholder, then "Search". A visible label is still preferred in forms.
The input is always type="text". size is removed from the forwarded attributes so it cannot collide with the native input size.
Clearing sets the bound value to '' and calls onclear; the button fades in over 100 ms.
15 / DISCLOSURE
CarbonAccordion
literal palette
The list wrapper for disclosure items. Renders a ul with a top rule; it holds no state and imposes no exclusivity.
Example
Network›
Host 0.0.0.0, port 25565. The port is allocated at creation and cannot be changed while the server exists.
Resources›
Prop
Type
Default
children
Snippet
—
class
string
''
Styling only: m-0 w-full list-none border-t p-0. Put CarbonAccordionItem children inside.
Opening one item does not close another. If the surface needs an exclusive group, own the open index in the parent.
16 / DISCLOSURE
CarbonAccordionItem
literal palette
A 48 px header row and a height-animated panel. Closed content is inert, so it cannot be focused or read by tab order.
Example
Network›
Host 0.0.0.0, port 25565.
Resources›
Prop
Type
Default
titlerequired
string
—
subtitle
string
''
open
boolean (bindable)
false
disabled
boolean
false
class
string
''
children
Snippet
—
The panel animates grid-template-rows from 0fr to 1fr over --motion-slow with an in-out easing; the chevron rotates 90° over --motion-base.
inert is applied whenever the item is closed, which removes its subtree from focus and accessibility APIs without unmounting it.
Documented gap: the trigger does not set aria-expanded or aria-controls. Add both before relying on this component where disclosure semantics are audited.
Title and subtitle both truncate. Long values lose text rather than wrapping; keep them short or put the full value in the panel.
17 / DATA
CarbonStructuredList
literal palette
A labelled list of rows with a coloured header strip and 1 px dividers. Lighter than CarbonDataTable, but not a table.
Example
PropertyValue
Imagepaper:1.21.4
Memory2.4 GiB
Created2026-11-02
Prop
Type
Default
header
Snippet
—
children
Snippet
—
class
string
''
Built from div elements, so a header cell is not associated with a row value. Use CarbonDataTable when a screen reader must associate columns with cells.
The header snippet renders inside a single grid row; the caller supplies the same column template on the header and the rows.
Rows are divided by divide-y on the row container. The outer element has a top border only.
18 / FEEDBACK
CarbonErrorState
literal palette
A full-panel failure state with a retry action. Announces itself with role="alert".
Error
▲Could not load servers
The node did not respond. Check the connection and try again.
Retry
Prop
Type
Default
title
string
'Something went wrong'
message
string
'An unexpected error occurred. Please try again.'
retryLabel
string
'Retry'
onretry
() => void | Promise<void>
—
class
string
''
The retry button renders only when onretry exists. While the returned promise is pending the button is in loading state and cannot be pressed again.
role="alert" makes the whole panel an assertive live region. Mounting it on every failed poll will announce repeatedly; keep it for terminal failures.
Say what failed and what the user can do. Do not render an error state for a request that is still in flight.
19 / FEEDBACK
CarbonEmptyState
literal palette
The zero-results and never-created state, with an optional icon snippet and one primary action.
Empty
▤No backups yet
Backups run nightly at 03:00 UTC. You can also start one now.
Create backup
Prop
Type
Default
title
string
'Nothing here yet'
description
string
''
icon
Snippet
Inbox
actionLabel
string
—
onaction
() => void
—
class
string
''
The action renders only when both actionLabel and onaction are supplied. One action; the component has no secondary slot.
There is no role and no live region. Announce a change to an empty collection in the surrounding region, not by remounting this panel.
Distinguish a truly empty collection from no search matches and from a failed request. The copy and the available action differ in each case.
07 / COMPOSITION
Composition rules
How the pieces are assembled. These are the rules that cause the most repeated defects when a route bypasses them.
01
One gutter
CarbonShell owns the main padding, maximum width and vertical rhythm. A route starts with CarbonPageHeader; it does not add p-6, max-w-* or another scroll container.
02
Scroll containers
Every vertical scroll container reserves its gutter (scrollbar-gutter: stable), applied globally to overflow-y-auto, overflow-y-scroll and overflow-auto. This prevents the reflow flicker when content grows past the viewport.
03
Horizontal strips
Tabs and other horizontal strips use scroll-strip: they scroll sideways and paint no bar, so the strip height cannot change. Never add a wrapper with overflow-x-auto that also grows a bar.
04
Nested surfaces
A tile inside a card is a bordered plane, not a shadow. space-y-6 separates page sections; space-y-2 or space-y-4 separates fields and rows. Do not mix a page gutter with a card padding of the same intent.
05
Overlays
Anything fixed goes through a portal to document.body. Rendering inside main places the layer below the fixed header and inside the scroll clip. Dialogs also lock body scroll and restore focus.
06
Widths
Content is capped at max-w-7xl (80 rem). Tables scroll inside their own region. There must be no page-level horizontal overflow at 360, 768 or 1440 px.
DENSITYdata-carbon-density
Compact mode rewrites control and spacing tokens. A layout that hard-codes heights will break. Test both densities and both themes.
Tailwind defaults. The shell's side navigation collapses at md; page headers stack below sm.
TAB ORDERDOM order
No positive tabindex. Roving tabindex is limited to composite widgets such as the tab strip.
08 / EXPERIENCE
Interaction patterns
State handling that is not expressible as a component prop.
01
Represent real state
Separate initial loading, empty, filtered-empty, stale, partial and failed. A missing metric is unavailable, not zero. Never render a placeholder value that could be mistaken for data.
02
Name the consequence
Destructive, long-running, billable and permission-sensitive actions state the resource and the outcome before the request. The confirm label repeats the verb, not "OK".
03
Preserve the work
Keep entered values on a recoverable error. Move focus to an error summary for long forms. A failed request must not clear a table or a draft.
04
Keep geometry stable
Reserve the accent-border space, the scrollbar gutter and the loading slot. Hover, selection, focus and menus must not move the layout or target a different row than the one under the pointer.
05
Report honestly
Distinguish submitted, pending, running, complete, failed and unknown. Show progress only when it is measured. Do not imply completion before the backend confirms it.
06
Keep console output intact
Monospace for logs, meaningful line breaks preserved, connection and reconnect state visible. Polling updates must not move the scroll position of someone reading older output.
09 / EXPERIENCE
Accessibility contract
Review dimensions for every component and route. These are targets, not a certification of either application.
4.5:1normal text contrast target
3:1large text and essential non-text boundary target
24 × 24minimum target size in CSS pixels, subject to WCAG exceptions
KEYBOARD
Every action is reachable and operable without a pointer. Composite widgets implement their APG pattern: tabs move selection with arrows, Home and End; dialogs trap and restore focus; disclosure content is removed from tab order when closed.
NAMES AND LABELS
Every control has an accessible name, including icon-only buttons. Visible labels are associated with their field. Hints and errors are referenced with aria-describedby, and invalid state is exposed with aria-invalid.
FOCUS
A 2 px --ring outline at 2 px offset on :focus-visible. Focus styling is never removed without an equivalent. Focus is never moved by polling or by a background refresh.
LIVE REGIONS
Async state changes that the user did not initiate are announced once through a status or alert region. Repeated polling results are not announced. CarbonInlineLoading needs a caller-provided region; CarbonErrorState is already role="alert".
MOTION AND ZOOM
Both reduced-motion paths are supported: the media query and the stored data-carbon-motion attribute. Layouts survive 200% browser zoom and text enlargement, and no content or action is lost at 320 px.
COLOUR AND MEDIA
Meaning is never carried by colour alone. Charts include labels or patterns and a text alternative. Forced-colours mode, translation, RTL and long labels are review dimensions, not assumed to work.
Automated checks catch a minority of these. Test real components in context with a keyboard and a screen reader, including error, loading and reduced-motion states.
10 / WORKFLOW
Change and propagation
The token file is a shared contract. A change is not finished when Panel renders correctly.
Propose the change against the contract.
Update tokens.css or the component source, and describe the intended effect on both themes, every state, responsive behaviour and compatibility. Do not silently change the meaning of an existing token.
Update Panel, this page and the regression checks.
The static examples here mirror the Svelte props; a new component or prop must appear on this page in the same change. Run the frontend checks and the design-system tests.
Propagate the snapshot to Cloud.
Cloud consumes a content-pinned copy with a recorded digest. Its lock records the digest, so ordinary builds need no Panel checkout or network access; a local digest check detects edits to the snapshot. A newer upstream version is only visible through an explicit comparison.
Verify the affected surfaces in both products.
Keyboard, error, loading, reduced-motion, light and dark themes, both densities, and narrow widths. Tokens do not propagate through hard-coded colours and do not prove behavioural equivalence.
SOURCE OF RECORD
The contract and the propagation procedure are documented in the design-system README. The token values on this page are copied from the canonical stylesheet at build time, not maintained by hand. Where this page and the component source disagree, the source file wins; open a change against this page.