Carbon / Design system
Contents

REFERENCE / FOUNDATIONS WORKING SYSTEM

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.

tokens.css
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.

01 / FOUNDATIONS02 / COMPONENTS03 / COMPOSITION04 / WORKFLOW

01 / FOUNDATIONS

Colour tokens

Semantic roles, not literal values. Every row lists both themes; consumers reference the token name, never the hex.

Surfaces and text

RoleTokenLightDarkUsage
Page background--background#ffffff#161616Root page plane and inset wells.
Body text--foreground#161616#f4f4f4Primary reading colour on --background.
Card surface--card#f4f4f4#262626First raised plane: tiles, tables, panels.
Card text--card-foreground#161616#f4f4f4Text on --card.
Popover surface--popover#ffffff#393939Menus, popovers, floating layers.
Popover text--popover-foreground#161616#f4f4f4Text on --popover.
Muted surface--muted#f4f4f4#262626Subtle fills that recede behind content.
Secondary text--muted-foreground#525252#a8a8a8Hints, descriptions, metadata.
Accent surface--accent#e8e8e8#353535Hover layer for tertiary and ghost actions.
Accent text--accent-foreground#161616#ffffffText on --accent.
Secondary surface--secondary#e0e0e0#393939Secondary button fill.
Secondary text--secondary-foreground#161616#f4f4f4Text on --secondary.
Structural border--border#e0e0e0#3939391 px dividers and container edges.
Control boundary--input#737373#7d7d7dVisible field boundary; stronger than --border.
Focus ring--ring#0f62fe#ffffff2 px outline, 2 px offset in product (3 px on this page).
Skeleton start--skeleton-from#e0e0e0#262626Leading stop of the shimmer gradient.
Skeleton end--skeleton-to#f4f4f4#353535Midpoint stop of the shimmer gradient.

Action and status

RoleTokenLightDarkUsage
Primary fill--primary#0f62fe#0f62fePrimary action and selection indicator. Pairs with white text in both themes.
Primary text--primary-foreground#ffffff#ffffffText and icons on --primary.
Primary as text--primary-text#0f62fe#78a9ffAccent-coloured labels that must meet text contrast.
Accent text--accent-text#0f62fe#78a9ffTertiary and ghost button labels, inline links.
Destructive fill--destructive#da1e28#da1e28Danger button and invalid field boundary.
Destructive text--destructive-text#a2191f#ff8389Error copy, never the fill.
Success fill--success#198038#24a148Status dots, progress completion.
Success text--success-text#0e6027#6fdc8cRunning and healthy labels.
Warning fill--warning#b28600#f1c21bCaution marks and attention dots.
Warning text--warning-text#6f5200#f1c21bCaution copy and Caps Lock notice.

Button state overrides

TokenLightDarkApplied by
--button-primary-hover#0353e9#0353e9CarbonButton kind="primary" :hover
--button-primary-active#002d9c#002d9cCarbonButton kind="primary" :active
--button-secondary-hover#c6c6c6#4c4c4cSecondary and tertiary active states
--button-secondary-active#a8a8a8#6f6f6fSecondary, tertiary and ghost :active
--button-danger-hover#ba1b23#ba1b23CarbonButton kind="danger" :hover
--button-danger-active#750e13#750e13CarbonButton kind="danger" :active

Tag pairs

TypeBackground tokenLight / darkText tokenLight / 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

TokenLightDark
--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.

INTERFACE / INTER VARIABLE

survival-01

stack
'Inter Variable', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif
weights
100–900 variable
source
/fonts/inter-latin-wght-normal.woff2, latin subset, font-display: swap
utility
font-sans
TECHNICAL / JETBRAINS MONO VARIABLE

play.example.net:25565
12 / 20 players
2.4 GiB memory

stack
'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
weights
100–900 variable
source
/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

UtilitySizeLine heightApplied in
text-xs0.75 rem / 12 px1 rem / 16 pxField labels, hints, errors, breadcrumbs, table body, tags
text-sm0.875 rem / 14 px1.25 rem / 20 pxButtons, tabs, tables, modal body, inputs
text-base1 rem / 16 px1.5 rem / 24 pxCarbonButton size="xl", CarbonSearch size="lg"
text-xl1.25 rem / 20 px1.75 rem / 28 pxModal title
text-2xl1.5 rem / 24 px2 rem / 32 pxCarbonPageHeader 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
Tokenrempx
--carbon-space-10.254
--carbon-space-20.58
--carbon-space-30.7512
--carbon-space-4116
--carbon-space-51.524
--carbon-space-6232
--carbon-space-7348
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.

TokenLightDark
--carbon-shadow-raised0 1px 2px rgb(0 0 0 / 0.12), 0 4px 12px rgb(0 0 0 / 0.08)0 1px 2px rgb(0 0 0 / 0.32), 0 4px 12px rgb(0 0 0 / 0.24)
--carbon-shadow-overlay0 12px 32px rgb(0 0 0 / 0.2), 0 2px 8px rgb(0 0 0 / 0.12)0 12px 32px rgb(0 0 0 / 0.5), 0 2px 8px rgb(0 0 0 / 0.3)

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.

LIVE EXAMPLE / RISE IN
Server ready

200 ms · 6 px · ease out

Instant / controls--motion-instant 100 ms
Route change--motion-page 140 ms
Fast / state--motion-fast 150 ms
Base / entrance--motion-base 200 ms
Slow / deliberate--motion-slow 300 ms
Surface / infrequent--motion-surface 500 ms
Stagger step--motion-stagger-step 40 ms
TokenValueUsed for
--ease-out-quartcubic-bezier(0.25, 1, 0.5, 1)Default exit/entrance easing, colour transitions
--ease-out-quintcubic-bezier(0.22, 1, 0.36, 1)Larger travel where quart feels abrupt
--ease-out-expocubic-bezier(0.16, 1, 0.3, 1)Sheets and long-distance entrances
--ease-in-out-standardcubic-bezier(0.65, 0, 0.35, 1)Looping indicators, tab indicator, accordion height
--ease-sheetcubic-bezier(0.32, 0.72, 0, 1)Bottom and side sheets
TRANSITIONS--carbon-transition-color

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.

KINDS
SIZES
STATES
PropTypeDefault
kind'primary' | 'secondary' | 'tertiary' | 'ghost' | 'danger''primary'
size'sm' | 'md' | 'lg' | 'xl''md'
iconOnlybooleanfalse
disabledbooleanfalse
loadingbooleanfalse
hrefstring—
onclick(event) => void—
classstring''
...restPropsbutton or anchor attributes—
  • 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
PropTypeDefault
title requiredstring—
descriptionstring''
tagSnippet—
actionsSnippet—
classstring''
  • 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
PropTypeDefault
clickablebooleanfalse
hrefstring—
onclick() => void—
classstring''
childrenSnippet—
...restPropsunknown—
  • 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.

TYPES
greengrayredyellowbluepurplecyantealmagentawarm-graycool-gray
SIZES AND STATES
sm 20md 24pulsefilter
PropTypeDefault
type'blue' | 'green' | 'red' | 'purple' | 'cyan' | 'teal' | 'magenta' | 'yellow' | 'gray' | 'warm-gray' | 'cool-gray''gray'
size'sm' | 'md''md'
filterbooleanfalse
pulsebooleanfalse
onclose() => void—
classstring''
...restPropsunknown—
  • 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
NameStatePlayers
survival-01Running12 / 20
creative-02Stopped—
modded-03Starting0 / 10
PropTypeDefault
titlestring''
descriptionstring''
toolbarSnippet—
headerSnippet—
childrenSnippet—
classstring''
  • 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
PropTypeDefault
openboolean (bindable)false
titlestring''
descriptionstring''
primaryButtonTextstring'Confirm'
secondaryButtonTextstring'Cancel'
dangerbooleanfalse
size'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'full''2xl'
hasFooterbooleantrue
barebooleanfalse
fullscreenbooleanfalse
onprimary() => void—
onsecondary() => void—
onclose() => void—
footerSnippet—
  • 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
PropTypeDefault
openboolean (bindable)false
titlestring'Are you sure?'
messagestring''
detailsSnippet—
confirmLabelstring'Confirm'
cancelLabelstring'Cancel'
dangerbooleanfalse
confirmingbooleanfalse
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.
PropTypeDefault
labelstring''
helperTextstring''
errorstring''
valuestring (bindable)''
idstring$props.id()
typestring'text'
revealablebooleanfalse
classstring''
...restPropsinput 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.
PropTypeDefault
labelstring''
helperTextstring''
errorstring''
valuestring | number (bindable)''
idstring$props.id()
childrenSnippet—
...restPropsselect 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.

PropTypeDefault
tabs required{ id: string; label: string; badge?: string | number }[][]
selectedTabstring (bindable)first tab id
idPrefixstring—
onselect(id: string) => void—
classstring''
  • 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 listBackup completeBackup failed
PropTypeDefault
descriptionstring'Loading...'
status'active' | 'finished' | 'error''active'
classstring''
  • 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
PropTypeDefault
items required{ href?: string; label: string }[][]
classstring''
  • 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.

PropTypeDefault
childrenSnippet—
  • <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.
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
PropTypeDefault
childrenSnippet—
classstring''
  • 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
PropTypeDefault
title requiredstring—
subtitlestring''
openboolean (bindable)false
disabledbooleanfalse
classstring''
childrenSnippet—
  • 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
PropTypeDefault
headerSnippet—
childrenSnippet—
classstring''
  • 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
PropTypeDefault
titlestring'Something went wrong'
messagestring'An unexpected error occurred. Please try again.'
retryLabelstring'Retry'
onretry() => void | Promise<void>—
classstring''
  • 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
PropTypeDefault
titlestring'Nothing here yet'
descriptionstring''
iconSnippetInbox
actionLabelstring—
onaction() => void—
classstring''
  • 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.

RESPONSIVE BREAKPOINTSsm 640 · md 768 · lg 1024 · xl 1280

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.