This is the canonical visual reference for every reusable primitive in Tela Design Language (TDL). The definitive reference may be found at https://github.com/paulmooreparks/tela/blob/main/TELA-DESIGN-LANGUAGE.md. Toggle the theme in the topbar to preview each component in light and dark modes. Every class in this document is part of the design language; every design language primitive appears here. If you need a pattern that is not shown, open a discussion before inventing a new class.
All colors, spacing, and typography are defined as CSS custom properties on
:root. The theme (light or dark) is applied by setting
data-theme on <html>. Accent, warn, and danger
colors are theme-invariant; backgrounds, text, and borders flip.
One family (system UI stack) for body text, one family (monospace) for code, version strings, and terminal output. Font weights are restricted to 400, 500, 600, and 700. Never use italic or underline for emphasis in UI chrome.
h3.sub--text-muted--text-muted, never reduce
opacity.Every content-area button shares an elevation invariant: a 1px border, a non-zero drop shadow, and a fill that contrasts with the card it sits on. The elevation is what signals "raised = pressable" without depending on hover. Touch devices cannot hover; colorblind users cannot rely on color cues alone. Elevation is the universal signal.
.btn.btn-primary.btn.btn.btn-danger::before) for
colorblind-safe identification. Hover commits to filled red..btn-destructive..btn.btn-destructive.btn labeled "Cancel"..btn.btn-icontitle attribute for the action name..chrome-btn (topbar-only circular button).
.btn-smLinks are the one place in TDL where underline carries meaning. Any clickable text must be underlined by default — not only on hover. The underline is the universal affordance for text links. The brand link is the one exception.
.link--accent-hover..link-muted--text-muted so it sits quietly beside surrounding body content. Hover brightens to
--text.
The file share is configured in Client Settings. See the file sharing guide for details.
.btn, not a
.link.
.brand-link is the one exception — no underline, ever.Status labels are flat, inline, non-interactive, and always prefixed by a glyph (colored dot, checkmark, or arrow). The glyph is the primary signal so meaning survives red-green colorblindness. Status elements never have a border, fill, or elevation — that would invite clicks.
.status.status-online (accent),
.status-degraded (warn), .status-error (danger),
.status-offline (muted).
.status-current /
.status-outdated
::before. The glyph is the primary signal for colorblind-safe identification.
.chip--surface-alt, no border. Explicitly not a state indicator — use
.status for state.
.dot.status. Use alone
when space is tight or when the dot is paired with a name instead of a state label..dot-online, .dot-degraded,
.dot-error, .dot-offline.
title)
so the meaning is reachable.All inputs share a common visual grammar: a 1px border, subtle surface fill, accent-colored focus ring (3px low-opacity), and a consistent padding scale. Labels always appear above the control (except for checkboxes and radios, where the label is to the right of the control).
.form-input.form-group to include the
label and hint..form-input.mono.form-input.invalid +
.form-error
.form-select.form-input.
.form-textarea.form-check.form-check wrapper; the radios are visually grouped
in a row or stacked column.
Cards are the primary content container. White (or dark) surface, 1px border,
8px radius, subtle shadow. Never nest a card inside another card. For visual
grouping inside a card, use h3.sub labels with spacing.
.card.card-title → .card-desc →
.card-body → .card-footer.
.card.card-dangerModals center on a semi-transparent overlay. The dialog has a header with title and close button, a body, and an action footer on a subtle tinted background. A confirmation modal always has exactly two buttons: Cancel (left) and the commit action (right, Primary or Destructive).
tk_01K8ABYZ and disconnect any session using
it. This cannot be undone.
Tables use uppercase-muted headers, muted row separators, and a hover highlight on selectable rows. Numeric or identifier columns should use the monospace font.
| Tool | Installed | Available | |
|---|---|---|---|
| TelaVisor | v0.7.0-dev.20 | v0.7.0-dev.20 | |
| tela | v0.7.0-dev.20 | v0.7.0-dev.20 | |
| telad service (running) | v0.7.0-dev.18 | v0.7.0-dev.20 | |
| telahubd | not installed | v0.7.0-dev.20 |
Tab bars use text buttons with an accent bottom border on the active tab.
Toolbars are containers for content-area command buttons; the buttons
themselves are .btn.btn-sm — the same elevation invariant
as every other content button, just smaller. There is no separate
toolbar-button class. The toolbar's tinted background is what makes it
visually distinct, not the buttons inside it.
Sidebar items are selectable rows with a left accent border on the active item. They pair a name (and optional dot, chip, or version status) with a tight hit area.
The topbar has a true light variant and a true dark variant. Chrome buttons
and the brand mark use topbar-scoped custom properties (--tb-*)
so the same markup adapts to either context. The topbar is the only place
where .chrome-btn and .brand-link appear.
.brand-link
The clickable brand mark. Deliberate exception to the "visible non-hover
affordance" rule: web convention has trained users that a top-left logo goes
home, so the click must work. "Tela" follows --tb-brand
(dark text on light topbar, white on dark). "Visor" / "Saya" stays accent
green in both themes. Hover: subtle brightness bump
(filter: brightness(1.15)). No underline, ever. Focus:
2px accent outline with 4px offset.
.chrome-btn
Circular icon button with a persistent outline and a meaning-carrying glyph.
Topbar only — never in content. Three color variants: neutral,
.chrome-accent (active/primary), and .chrome-danger
(quit). Inline SVG is preferred over Unicode glyphs for stroke consistency
across platforms.
.mode-barThe mode bar switches between top-level application modes (TelaVisor's Clients / Infrastructure; Awan Saya's user / admin). It lives in the topbar only — content-area navigation uses the main tab bar.
The mode bar uses an outlined container (signaling "these are
interactive options") with a 3px accent bar flush along the bottom
edge of the active segment (the existing TDL vocabulary for "you are
here", matching the main tab bar's active indicator). The active
segment is bold, has cursor: default, and carries no
button chrome. Inactive segments show a subtle hover fill on mouseover
to confirm they are interactive.
Inline, non-blocking messages. Toasts appear briefly after an action. Empty states explain what the user sees (or doesn't see) without burying a useful action.
.toast.empty-state.progresswidth on .progress-bar) or
indeterminate (add .indeterminate).# Connect to a hub tela hub add "https://owlsnest.parkscomputing.com" tela connect owlsnest
.code-block.tok-comment, .tok-kw,
.tok-str.
The log panel is a dockable bottom-of-window container for multi-source log output. It is TelaVisor-specific in its current implementation but generalizes to "any docked panel with tabs and a collapse affordance". Tabs behave like the main tab bar (active accent underline). Individual tabs may be closable. The panel itself can be collapsed to a single-row header.
2026-04-11T02:20:02Z TelaVisor started 2026-04-11T02:20:02Z Profile loaded 2026-04-11T02:20:04Z Connected to owlsnest.parkscomputing.com 2026-04-11T02:20:04Z 4 agents registered
.status for per-tab live / idle markers. No new
classes needed..btn.btn-sm.btn-icon carrying a chevron glyph. No
special log-panel class needed — it is just an icon button. Because all icon buttons share the
same sizing, the chevron matches the height of the adjacent + attach-source button
and the Copy / Save / Clear buttons in the same strip..log-panel-collapsed-title appears
immediately to the left of the chevron so the docked panel is identifiable at a glance.style.height on collapse is
required so the CSS class rule takes effect..log-panel-tab-add.btn.btn-sm.btn-icon.
Dropdowns are small floating panels anchored to a trigger button. Used for user menus, theme pickers, attach-source popovers, context menus, and any short list of options where a full modal would be overkill. The menu itself is a surface card with an item list.
.menu.menu-header → one or more
.menu-item with optional .menu-sep dividers.
.active (accent color),
.danger (red, typically last item).
.menu-item-icon reserves a 16px column on the
left so items without icons align with items that have them.aria-expanded for screen
readers.
Two-column sidebar + detail is the canonical layout for any view with a selectable list and its detail pane. The sidebar is a fixed width; the detail fills the remaining space and scrolls.
Every interactive or state-bearing element in a TDL app falls into exactly one of four categories. The categories never share a location, so their visual styles never compete.
.btn — content-area button. Signal: elevation..status — content-area badge. Signal: flat inline + glyph prefix..chrome-btn — topbar icon button. Signal: persistent circular outline.
.brand-link — topbar brand mark. Signal: cursor + subtle brightness + focus
ring.Any state information that matters must be conveyed by both color and shape. Destructive actions get a ⚠ glyph. Up-to-date versions get a ✓. Outdated versions get a ↑. Status badges get a colored dot. This survives red-green colorblindness (WCAG 1.4.1) and the cost is negligible.
An element that is not clickable must not look clickable. No outlined
boxes around static labels. No filled pills for non-interactive state.
No hover states on text. No cursor: pointer on non-buttons.
Any clickable text outside the topbar must be underlined by default,
not only on hover. TDL uses accent green for primary links and
--text-muted for secondary links; blue is not used. The
brand link is the one documented exception.
Segmented mode toggles (Clients / Infrastructure, user / admin) live only in the topbar. Content-area navigation uses the main tab bar, never a segmented mode bar. The two patterns have different visual signatures so they never compete.
A view, form, or modal has exactly one .btn-primary. If the
design seems to need two, one of them is probably a Secondary or Danger.
Confirmation modals have exactly one .btn-destructive sibling
to a Cancel.
All timestamps in data, logs, and configs are ISO 8601 UTC. The client converts to the user's local time zone for display. Never store or transmit local time.
Some platforms render Unicode glyphs (⚙, ⚠, 📁) as colorful emoji. This breaks the monochrome chrome convention. Prefer inline SVG for any icon that must render consistently across platforms.
Modals stack. When a modal opens a child modal (for example, a confirmation dialog asking "Discard unsaved changes?" from inside an Application Settings modal), the child must render above its parent, not behind it. Implementations must use a shared modal stack that assigns ascending z-index per push, or give every nested overlay a strictly higher z-index than its parent. A confirmation dialog hidden behind its parent is a serious UX bug: from the user's perspective, clicking Cancel appears to do nothing.
While any modal is open, native window controls (close / quit / minimize)
must not dismiss the application directly. The close button on the OS
title bar, a Cmd+Q, and a window-level beforeunload should
all route through the active modal's cancel flow first, just as they
would for a native OS dialog. This prevents data loss from accidentally
quitting while a settings modal has unsaved edits. Implementation: bind
the window close event while a modal is active and either block the
close, or programmatically trigger the modal's Cancel handler.