CloudBlocks Theme System Specification¶
Audience: Contributors / Design System | Status: Stable — Internal | Verified against: v0.43.0
Status: Active Date: 2026-03 Supersedes: Visual styling sections of BRICK_DESIGN_SPEC.md and VISUAL_DESIGN_SPEC.md Related: CLOUDBLOCKS_SPEC_V2.md §7 (Color System — historical, excluded from public nav), ADR-0011 (internal, excluded from public docs)
0. Purpose¶
This specification defines the dual theme system for CloudBlocks — a visual cloud learning tool for beginners. It establishes two visual themes that share identical layout, geometry, and interaction — only visual tokens (colors, border-radius, shadows) differ.
Current Implementation (M22)¶
The codebase uses blueprint and workshop as theme variant names:
| Variant | Character | Default? |
|---|---|---|
workshop |
Light, clean, enterprise-grade — the default | ✅ |
blueprint |
Dark, saturated, playful — the original block-based aesthetic |
Planned rename: A future milestone will rename
blueprint→blocksandworkshop→professionalto better reflect each theme's identity. Until then,blueprint/workshopare the canonical code names.
Scope¶
| In scope | Out of scope |
|---|---|
| Theme token definitions (color, surface, border) | Layout or geometry changes between themes |
| CSS custom property architecture | Vendor-specific resource colors (see V2 spec §7) |
| Theme switching mechanism | Animation or motion token differences |
| Port visibility toggle per theme | Port dimensions (see V2 spec §2 — INVIOLABLE) |
1. Design Principles¶
- Tokens, not overrides — Every visual difference between themes is expressed through a named token. No ad-hoc color values.
- Same gauge — Layout grid, port dimensions, block sizes, container block proportions, and the Universal Port Standard (V2 spec §2) are theme-independent.
- Provider identity preserved — Resource colors come from the cloud provider (V2 spec §7). Themes only affect the chrome (UI shell, panels, canvas background), never the resource blocks themselves.
- Instant switch — Theme changes apply immediately with no page reload. Transition duration ≤ 200ms.
- Persistence — The selected theme is persisted in
localStorageand restored on next visit.
2. Theme Variants¶
2.1 Naming (Current Code)¶
| Variant | Description |
|---|---|
workshop |
Light background palette, subtle shadows, muted accents — the default |
blueprint |
Dark background palette, saturated colors, pronounced shadows |
2.2 Workshop Theme (Default)¶
The Workshop theme is the default learning environment. Visual cues are calm, readable, and instructional so beginners can focus on architecture concepts without visual overload.
Visual character:
- Light background palette (neutral grays, white surfaces)
- Subtle shadows (
rgba(0,0,0,0.1)) - Muted accent colors — blue primary, teal secondary
- High text contrast for readability (WCAG AA minimum)
- Clean, flat panel chrome without decorative elements
- Ports visible by default (
showPorts: true)
2.3 Blueprint Theme (Alternative)¶
The Blueprint theme preserves the original CloudBlocks block-building aesthetic. It is colorful, saturated, and fun.
Visual character:
- Dark background palette (slate/navy tones)
- Pronounced shadows with colored glow effects
- Vivid accent colors — bright blue primary, cyan secondary
- Playful visual density — the canvas feels like a building workspace
- Ports visible by default (
showPorts: true)
2.4 Port Visibility Toggle¶
Port visibility is independent of theme variant. Both themes default to showPorts: true, and the user can toggle it via the MenuBar grid button. Theme switching does not reset port visibility.
| Theme | Default showPorts |
User can override? |
|---|---|---|
workshop |
true |
Yes — via MenuBar grid button |
blueprint |
true |
Yes — via MenuBar grid button |
When the user switches themes, showPorts is not affected — it retains whatever value the user last set, regardless of the active theme.
3. Token Architecture¶
3.1 Token Interface¶
The ThemeTokens interface defines all theme-dependent visual properties. Both themes implement the full interface — no optional fields, no fallbacks.
export interface ThemeTokens {
// Backgrounds
'bg-app': string; // Application shell background
'bg-canvas': string; // Main canvas area
'bg-surface': string; // Panel and card backgrounds
'bg-surface-raised': string; // Elevated surfaces (dropdowns, modals)
'bg-overlay': string; // Modal backdrop overlay
// Text
'text-primary': string; // Primary content text
'text-secondary': string; // Secondary/descriptive text
'text-muted': string; // Disabled or hint text
'text-inverse': string; // Text on inverted backgrounds
// Borders
'border-default': string; // Standard element borders
'border-subtle': string; // Subtle dividers
'border-strong': string; // Emphasized borders (focus, active)
// Accents
'accent-primary': string; // Primary interactive elements
'accent-secondary': string; // Secondary interactive elements
'accent-success': string; // Success states
'accent-warning': string; // Warning states
'accent-error': string; // Error states
// Category colors (UI-only — NOT resource identity colors)
'cat-network': string; // Network category indicator
'cat-security': string; // Security category indicator
'cat-edge': string; // Edge/CDN category indicator
'cat-compute': string; // Compute category indicator
'cat-data': string; // Data/storage category indicator
'cat-messaging': string; // Messaging category indicator
'cat-operations': string; // Operations category indicator
}
Important: The
cat-*tokens are UI chrome colors (sidebar icons, palette badges). They are not the resource block colors. Resource blocks use provider-identity colors from V2 spec §7.
3.2 Token Values¶
Blueprint Theme (Dark)¶
export const blueprintTheme: ThemeTokens = {
'bg-app': '#0F172A',
'bg-canvas': '#0B1220',
'bg-surface': '#1E293B',
'bg-surface-raised': '#334155',
'bg-overlay': 'rgba(0, 0, 0, 0.6)',
'text-primary': '#F1F5F9',
'text-secondary': '#94A3B8',
'text-muted': '#64748B',
'text-inverse': '#0F172A',
'border-default': '#334155',
'border-subtle': '#1E293B',
'border-strong': '#475569',
'accent-primary': '#3B82F6',
'accent-secondary': '#06B6D4',
'accent-success': '#22C55E',
'accent-warning': '#EAB308',
'accent-error': '#EF4444',
'cat-network': '#3B82F6',
'cat-security': '#EF4444',
'cat-edge': '#F97316',
'cat-compute': '#8B5CF6',
'cat-data': '#14B8A6',
'cat-messaging': '#EAB308',
'cat-operations': '#64748B',
};
Workshop Theme (Light — Default)¶
export const workshopTheme: ThemeTokens = {
'bg-app': '#F8FAFC',
'bg-canvas': '#FFFFFF',
'bg-surface': '#F1F5F9',
'bg-surface-raised': '#FFFFFF',
'bg-overlay': 'rgba(0, 0, 0, 0.3)',
'text-primary': '#0F172A',
'text-secondary': '#475569',
'text-muted': '#94A3B8',
'text-inverse': '#F1F5F9',
'border-default': '#E2E8F0',
'border-subtle': '#F1F5F9',
'border-strong': '#CBD5E1',
'accent-primary': '#2563EB',
'accent-secondary': '#0891B2',
'accent-success': '#16A34A',
'accent-warning': '#CA8A04',
'accent-error': '#DC2626',
'cat-network': '#3B82F6',
'cat-security': '#EF4444',
'cat-edge': '#F97316',
'cat-compute': '#8B5CF6',
'cat-data': '#14B8A6',
'cat-messaging': '#EAB308',
'cat-operations': '#64748B',
};
3.3 Theme-Independent Tokens¶
Typography and motion tokens do not vary by theme. They are shared constants.
// Typography — identical across all themes
export const typography = {
fontUi: "'Inter', system-ui, -apple-system, sans-serif",
fontMono: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",
'text-xs': '11px',
'text-sm': '12px',
'text-base': '13px',
'text-md': '14px',
'text-lg': '16px',
'text-xl': '20px',
'text-2xl': '24px',
'text-3xl': '32px',
};
// Motion — identical across all themes
export const motion = {
'duration-fast': '100ms',
'duration-normal': '200ms',
'duration-slow': '300ms',
'easing-default': 'cubic-bezier(0.2, 0, 0, 1)',
'easing-decelerate': 'cubic-bezier(0, 0, 0, 1)',
'easing-accelerate': 'cubic-bezier(0.3, 0, 1, 1)',
};
4. Vendor Color Integration¶
4.1 Principle: Theme Does Not Own Resource Colors¶
Resource block colors are provider-identity colors, not theme tokens. They are defined in CLOUDBLOCKS_SPEC_V2.md §7 (historical, excluded from public nav) and are invariant across themes.
| Provider | Brand Primary | Source |
|---|---|---|
| Azure | #0078D4 |
Microsoft Fluent Design |
| AWS | #D86613 (Compute), #232F3E (Brand Dark) |
AWS Architecture Icons |
| GCP | #4285F4 |
Google Brand Colors |
The full service family → color mappings are in V2 spec §7.2–§7.4. This spec does not duplicate them.
4.2 Theme × Vendor Color Interaction¶
Themes affect how vendor colors appear on the canvas through contrast and context:
| Aspect | Workshop (light) | Blueprint (dark) |
|---|---|---|
| Canvas background | #FFFFFF |
#0B1220 |
| Block contrast | Vendor colors on white — good natural contrast | Vendor colors on dark — enhanced pop |
| Block shadows | rgba(0,0,0,0.08) — subtle, flat |
rgba(0,0,0,0.3) — deeper, 3D feel |
| Connection lines | Slightly desaturated on light | Full saturation on dark |
4.3 Rules¶
- Vendor colors are never modified by theme — the hex value of Azure
#0078D4is the same in both themes. - Face color derivation (lighten/darken for isometric faces) follows V2 spec §7.7 — same algorithm regardless of theme.
- Shadow and glow effects around blocks may differ by theme (stronger in Blueprint, subtle in Workshop) but the block color itself does not change.
- Connector colors are connection-type-based (see §5), not theme-based. Both themes use the same connector palette.
5. Connection Theme¶
Connector (connection line) colors are based on endpoint semantic type, not theme variant. Both themes use the same connector palette.
| Endpoint Semantic | Color | Usage |
|---|---|---|
http |
#3B82F6 (Blue) |
HTTP/REST traffic |
event |
#F59E0B (Amber) |
Event-driven / async messaging |
data |
#14B8A6 (Teal) |
Data flow / storage access |
Port (visual connection anchor) colors match the endpoint semantic and are defined in designTokens.ts:
export const PORT_COLOR_HTTP = '#3B82F6'; // Blue — HTTP traffic
export const PORT_COLOR_EVENT = '#F59E0B'; // Amber — event/async
export const PORT_COLOR_DATA = '#14B8A6'; // Teal — data/dataflow
export const PORT_COLOR_OCCUPIED = '#475569'; // Slate — occupied port (dimmed)
6. CSS Custom Property Architecture¶
6.1 Current State (TypeScript-only)¶
Theme tokens are consumed as TypeScript objects via getThemeTokens(variant). Components access them as:
6.2 Target State (CSS Custom Properties)¶
The target architecture uses CSS custom properties (--cb-* namespace) applied to :root, enabling both TypeScript and CSS consumption.
Naming Convention¶
Full property list:
:root {
/* Backgrounds */
--cb-bg-app: #f8fafc;
--cb-bg-canvas: #ffffff;
--cb-bg-surface: #f1f5f9;
--cb-bg-surface-raised: #ffffff;
--cb-bg-overlay: rgba(0, 0, 0, 0.3);
/* Text */
--cb-text-primary: #0f172a;
--cb-text-secondary: #475569;
--cb-text-muted: #94a3b8;
--cb-text-inverse: #f1f5f9;
/* Borders */
--cb-border-default: #e2e8f0;
--cb-border-subtle: #f1f5f9;
--cb-border-strong: #cbd5e1;
/* Accents */
--cb-accent-primary: #2563eb;
--cb-accent-secondary: #0891b2;
--cb-accent-success: #16a34a;
--cb-accent-warning: #ca8a04;
--cb-accent-error: #dc2626;
/* Category (UI chrome only) */
--cb-cat-network: #3b82f6;
--cb-cat-security: #ef4444;
--cb-cat-edge: #f97316;
--cb-cat-compute: #8b5cf6;
--cb-cat-data: #14b8a6;
--cb-cat-messaging: #eab308;
--cb-cat-operations: #64748b;
/* Typography (theme-independent) */
--cb-font-ui: 'Inter', system-ui, -apple-system, sans-serif;
--cb-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
/* Motion (theme-independent) */
--cb-duration-fast: 100ms;
--cb-duration-normal: 200ms;
--cb-duration-slow: 300ms;
}
Application Function¶
function applyTheme(variant: ThemeVariant): void {
const tokens = getThemeTokens(variant);
const root = document.documentElement;
for (const [key, value] of Object.entries(tokens)) {
root.style.setProperty(`--cb-${key}`, value);
}
// Persist selection
localStorage.setItem('cloudblocks:theme-variant', variant);
}
6.3 Migration Path¶
The migration from TypeScript-only to CSS custom properties is incremental:
- Phase 1: Add
applyTheme()function that sets--cb-*properties on:root. KeepgetThemeTokens()as the primary API. - Phase 2: New CSS should prefer
var(--cb-*)over TypeScript token access. - Phase 3: Gradually migrate existing inline styles to CSS custom property references.
No Big Bang migration — both access patterns coexist indefinitely.
7. Theme Switching¶
7.1 Mechanism¶
// In uiStore.ts
interface UiState {
themeVariant: ThemeVariant; // 'blueprint' | 'workshop'
showPorts: boolean; // independent of theme
setThemeVariant: (v: ThemeVariant) => void;
}
setThemeVariant triggers:
- Update Zustand store state
- Call
applyTheme(variant)to set CSS custom properties - Persist to
localStoragekeycloudblocks:theme-variant - React components re-render via Zustand subscription
7.2 Default¶
First-time visitors see the Workshop theme. The Blueprint theme is opt-in via the menu bar switcher.
7.3 Persistence¶
| Key | Value | Default |
|---|---|---|
cloudblocks:theme-variant |
'blueprint' or 'workshop' |
'workshop' |
On app init:
- Read
localStoragevalue - If valid variant → apply it
- If missing or invalid → apply
'workshop'
7.4 UI: Theme Switcher¶
The theme switcher lives in the View menu of the menu bar. It presents a simple toggle or radio group:
No separate settings page. One click to switch. Change applies instantly.
8. Future Rename Plan¶
A future milestone will rename the theme variants to be more descriptive:
| Current (M22) | Planned (future) | Reason |
|---|---|---|
workshop |
professional |
Better communicates enterprise-grade identity |
blueprint |
blocks |
Better communicates playful block-building identity |
Migration checklist (for the rename milestone):¶
- Update
ThemeVarianttype:'blueprint' | 'workshop'→'professional' | 'blocks' - Rename exports:
blueprintTheme→blocksTheme,workshopTheme→professionalTheme - Update
getThemeTokens()to use new names - Update
uiStore.tsdefault - Add localStorage migration for existing users
- Update all consumer files
- Update this spec
localStorage Migration (for rename milestone)¶
function migrateThemeVariant(stored: string | null): ThemeVariant {
if (stored === 'professional' || stored === 'blocks') return stored;
if (stored === 'workshop') return 'professional';
if (stored === 'blueprint') return 'blocks';
return 'professional'; // default
}
9. Accessibility¶
9.1 Contrast Requirements¶
Both themes must meet WCAG 2.1 AA contrast ratios:
| Pair | Minimum ratio | Workshop | Blueprint |
|---|---|---|---|
text-primary on bg-surface |
4.5:1 | #0F172A on #F1F5F9 = 15.4:1 ✅ |
#F1F5F9 on #1E293B = 11.0:1 ✅ |
text-secondary on bg-surface |
4.5:1 | #475569 on #F1F5F9 = 5.9:1 ✅ |
#94A3B8 on #1E293B = 4.6:1 ✅ |
text-muted on bg-surface |
3:1 (large text) | #94A3B8 on #F1F5F9 = 2.7:1 ⚠️ |
#64748B on #1E293B = 3.2:1 ✅ |
Note:
text-mutedin Workshop is below AA for small text. This is acceptable because muted text is used for non-essential hints and disabled states, not critical content. For small text, prefertext-secondary.
9.2 Focus Indicators¶
Focus indicators use accent-primary with a 2px solid outline offset by 2px:
| Theme | Focus color | On background |
|---|---|---|
| Workshop | #2563EB |
Light surfaces — clearly visible |
| Blueprint | #3B82F6 |
Dark surfaces — clearly visible |
10. Extension Points¶
10.1 Adding a New Theme¶
To add a new theme variant:
- Add the variant name to
ThemeVariantunion type - Create a new
ThemeTokensobject with all 24 tokens - Update
getThemeTokens()to handle the new variant - Add the variant to the menu bar switcher
- Port visibility (
showPorts) is theme-independent and does not need per-theme defaults
10.2 Adding New Tokens¶
To add a new token:
- Add the key to
ThemeTokensinterface - Add values to all theme objects
- Add the corresponding
--cb-*CSS custom property - TypeScript compiler will enforce completeness — missing values are compile errors
10.3 Future: Light/Dark Mode per Theme¶
If a future milestone requires light/dark modes within each theme style, the extension path is:
// Future — NOT part of current implementation
export type ThemeStyle = 'professional' | 'blocks';
export type ThemeMode = 'light' | 'dark';
export type ThemeVariant = `${ThemeStyle}-${ThemeMode}`;
This requires 4 token sets instead of 2 but uses the same ThemeTokens interface.
11. Summary¶
| Aspect | Decision |
|---|---|
| Default theme | Workshop (light, clean, enterprise) |
| Alternative theme | Blueprint (dark, playful, creative) |
| Theme dimensionality | 1D — variant only (no separate light/dark toggle) |
| Token count | 24 color tokens + 8 typography + 6 motion |
| CSS architecture | --cb-* custom properties on :root (target) |
| Vendor colors | Theme-independent — defined in V2 spec §7 |
| Connector colors | Theme-independent — defined by endpoint semantic type |
| Persistence | localStorage key cloudblocks:theme-variant |
| Default value | 'workshop' |
| Switching | Instant, via View menu |
| Port visibility | Per-theme default, user-overridable |
| Extension | New variants = new ThemeTokens object + union member |
| Accessibility | WCAG AA for primary/secondary text |
| Planned rename | workshop → professional, blueprint → blocks (future milestone) |
This specification is the authoritative reference for theme-related decisions in CloudBlocks. For resource color definitions, see CLOUDBLOCKS_SPEC_V2.md §7 (historical, excluded from public nav). For the architectural decision record, see ADR-0011 (internal, excluded from public docs).