Tela Design Language Visual Reference
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.
1Design tokens
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.
Palette
Light theme surfaces
Dark theme surfaces
2Typography
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.
- Size: 28px
- Weight: 700
- Letter-spacing: -0.01em
- Size: 20px
- Weight: 700
- Size: 14-15px
- Weight: 600-700
h3.sub- Size: 13px
- Weight: 600
- Transform: uppercase, 0.06em letter-spacing
- Color:
--text-muted
- Size: 13px
- Line-height: 1.55
--text-muted, never reduce
opacity.3Buttons
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- Where: content area only, one per form / modal / settings card.
- Examples: Save, Create, Connect, Apply, Add Hub.
.btn- Where: content area.
- Rule: a modal always has exactly one Primary and one Secondary.
.btn.btn-danger::before) for
colorblind-safe identification. Hover commits to filled red.- Where: content area, next to the item being acted on.
- Examples: Delete token, Remove machine, Revoke access.
- Escalation: for irreversible actions, Danger opens a confirmation modal
containing a
.btn-destructive.
.btn.btn-destructive- Where: confirmation modals only, never in the main content area.
- Sibling: always paired with a
.btnlabeled "Cancel". - Examples: Delete permanently, Discard changes, Remove forever.
.btn.btn-icontitle attribute for the action name.- Where: content-area toolbars and list rows.
- Do not confuse with
.chrome-btn(topbar-only circular button).
.btn-sm4Links
Links 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.- Where: body copy, modals, help text, menu items, anywhere non-topbar.
- Examples: "Learn more", "View documentation", "Open in browser", inline references in help text.
.link-muted--text-muted so it sits quietly beside surrounding body content. Hover brightens to
--text.
- Where: footers, metadata cross-references, breadcrumb tails, secondary navigation.
- Examples: "Privacy", "Terms", "Back to list", inline cross-references.
The file share is configured in Client Settings. See the file sharing guide for details.
- Every link is underlined by default, not only on hover.
- Links are accent green or muted, never blue. Blue hyperlinks are a web convention that predates TDL.
- Hover thickens the underline; it does not remove or add it.
- Links keep their color regardless of visited state. No visited styling.
- A link navigates or reveals. If the element commits a state change, use a
.btn, not a.link. - The brand link
.brand-linkis the one exception — no underline, ever.
5Status and badges
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- Variants:
.status-online(accent),.status-degraded(warn),.status-error(danger),.status-offline(muted). - Examples: Agent ONLINE, Hub CONNECTING, Session ERROR.
.status-current /
.status-outdated
::before. The glyph is the primary signal for colorblind-safe identification.
- Rule: applied to the installed version only. The available (latest) version is rendered in default text color with no status decoration.
- Examples: Installed Tools card, sidebar agent version, topbar TelaVisor version.
.chip--surface-alt, no border. Explicitly not a state indicator — use
.status for state.
- Why not outlined? Outlined boxes around text are a web convention for interactive tags (GitHub labels, Stack Overflow). A border is a false affordance.
- Examples: "2 hubs" next to an agent, platform labels, region or environment tags.
.dot.status. Use alone
when space is tight or when the dot is paired with a name instead of a state label.- Variants:
.dot-online,.dot-degraded,.dot-error,.dot-offline. - Accessibility: when used alone, pair it with a tooltip (
title) so the meaning is reachable.
6Form inputs
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.- Label: above the input, 12px 600.
- Hint: below the input, 11px muted.
- Focus: accent border + 3px accent ring.
.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.7Cards
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- Structure:
.card-title→.card-desc→.card-body→.card-footer. - Padding: 20px 22px.
.card.card-danger- Where: "Danger zone" sections of settings pages.
8Modals
Modals 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).
Standard modal
Add hub
Confirmation modal (destructive)
Delete token
tk_01K8ABYZ and disconnect any session using
it. This cannot be undone.
9Tables
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 |
10Tabs and toolbars
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.
Tab bar
Toolbar
11Sidebar list items
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.
12Topbar
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.
Light topbar
Dark topbar
Brand link .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 button .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 bar .mode-bar
The 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.
13Feedback
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- Position: fixed near the top-center of the window.
- Duration: 3-5 seconds auto-dismiss, or manual dismiss for errors.
Pair Agent to add one.
.empty-state14Progress and code
.progresswidth on .progress-bar) or
indeterminate (add .indeterminate).- Examples: file upload / download progress, update install progress.
# Connect to a hub tela hub add "https://owlsnest.parkscomputing.com" tela connect owlsnest
.code-block.tok-comment, .tok-kw,
.tok-str.
15Log panel
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.
Expanded panel
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
Collapsed panel
.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.- Collapsed state: the label
.log-panel-collapsed-titleappears immediately to the left of the chevron so the docked panel is identifiable at a glance. - Caveat: clearing any inline
style.heighton collapse is required so the CSS class rule takes effect.
.log-panel-tab-add.btn.btn-sm.btn-icon.
16Dropdown menus
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- Structure: optional
.menu-header→ one or more.menu-itemwith optional.menu-sepdividers. - Item states: plain,
.active(accent color),.danger(red, typically last item). - Icon column:
.menu-item-iconreserves a 16px column on the left so items without icons align with items that have them.
aria-expanded for screen
readers.
17Layout primitives
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.
18Invariant rules
Category separation
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.
Color + glyph redundancy
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.
No false affordances
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.
Every link is underlined
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.
Mode bars live in the topbar
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.
One primary per context
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.
ISO UTC everywhere
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.
No emoji in chrome
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.
Modal stacking (z-order)
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.
Modals capture window chrome
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.
TelaVisor