Toolbox Styling Inspired by Wasmer.io
Recently I had to develop an internal web app called “Toolbox” to manage and monitor various development VMs and containers. While functional, the initial design was quite basic and utilitarian. To enhance the user experience, I decided to undertake a complete redesign of the Toolbox interface, drawing inspiration from the modern and visually appealing design language of Wasmer.io.
Here’s the comprehensive design document outlining the decisions, styles, and implementation strategy for transforming the Toolbox UI to align with the Wasmer.io aesthetic.
# Toolbox Wasmer.io Styling Design
**Date:** 2025-10-25
**Status:** Design Complete - Ready for Implementation
**Design Type:** UI/UX Refresh - Wasmer.io Aesthetic
## 1. Overview
Transform the toolbox dashboard with Wasmer.io-inspired modern aesthetic while preserving all functional requirements. The redesign applies a cohesive visual language featuring soft gradients, rounded corners, purple accents, and clean typography across the entire interface.
**Design Goals:**
- Apply Wasmer.io aesthetic to entire dashboard
- Maintain existing split-pane functional layout
- Preserve all current features (status indicators, log streaming, resizable panels)
- Add theme toggle for log panel (dark terminal ↔ light Wasmer)
- Zero build tools - CSS with custom properties only
## 2. Design Decisions Summary
| Aspect | Decision | Rationale |
| ----------------- | --------------------------- | ------------------------------------------------------- |
| Scope | Entire dashboard | Consistent, polished experience throughout |
| Layout | Keep split-pane | Functional layout works well, just needs visual refresh |
| Status indicators | Circular with glow | Familiar pattern, just update colors |
| Log panel theme | Toggle (dark/light) | Flexibility for user preference |
| CSS approach | Full rewrite with variables | Clean slate for perfect Wasmer alignment |
| Styling method | CSS custom properties | Native browser support, no compilation needed |
## 3. Design System Tokens
### 3.1 Color Palette
```css
:root {
/* Primary Colors */
--primary: #6b4cf6; /* Purple accent */
--primary-hover: #5a3fd5; /* Darker purple on hover */
--secondary: #e8d7ff; /* Light purple */
/* Neutrals */
--background: #ffffff;
--surface: #ffffff;
--text-primary: #1a1423; /* Nearly black */
--text-muted: #777777;
--border-light: #e5e7eb;
/* Gradients */
--gradient-start: #f5f3ff; /* Soft lavender */
--gradient-end: #ffffff; /* White */
--gradient-bg: linear-gradient(
180deg,
var(--gradient-start) 0%,
var(--gradient-end) 100%
);
/* Status Colors */
--status-green: #10b981; /* Running */
--status-red: #ef4444; /* Stopped */
--status-yellow: #f59e0b; /* Unknown */
/* Dark Theme (Log Panel) */
--dark-bg: #1e1e1e;
--dark-surface: #2d2d2d;
--dark-border: #3d3d3d;
--dark-text: #d4d4d4;
--dark-text-muted: #9ca3af;
}
```
### 3.2 Typography
```css
:root {
/* Font Families */
--font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'Fira Code', 'Monaco', 'Courier New', monospace;
/* Font Weights */
--font-weight-normal: 400;
--font-weight-medium: 600;
--font-weight-bold: 700;
/* Font Sizes */
--text-xs: 13px;
--text-sm: 14px;
--text-base: 15px;
--text-lg: 18px;
--text-xl: 20px;
}
```
### 3.3 Spacing & Layout
```css
:root {
/* Border Radius */
--radius-small: 8px;
--radius-medium: 12px;
--radius-large: 16px;
--radius-pill: 999px;
/* Shadows */
--shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
--shadow-hover: 0 6px 24px rgba(0, 0, 0, 0.08);
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
/* Spacing Scale (4px base) */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 12px;
--space-lg: 16px;
--space-xl: 24px;
--space-2xl: 32px;
}
```
### 3.4 Transitions
```css
:root {
--transition-fast: 0.15s ease;
--transition-base: 0.2s ease;
--transition-slow: 0.3s ease;
}
```
## 4. Component Specifications
### 4.1 Overall Layout
**Background:**
- Gradient: `var(--gradient-bg)`
- Applied to `body` element
- Creates soft lavender-to-white fade
**Grid Structure:**
```css
.top-section {
height: 60%; /* adjustable */
}
.container {
display: grid;
grid-template-columns: 220px 1fr 1fr;
gap: 16px;
padding: 20px;
height: 100%;
}
```
**Panel Base Class:**
```css
.panel {
background: var(--surface);
border-radius: var(--radius-large);
box-shadow: var(--shadow);
padding: var(--space-xl);
}
```
### 4.2 Sidebar Module List
**Container:**
```css
.sidebar {
background: var(--surface);
border-radius: var(--radius-large);
box-shadow: var(--shadow);
padding: var(--space-xl);
overflow-y: auto;
}
```
**Heading:**
```css
.sidebar h2 {
font-family: var(--font-family);
font-weight: var(--font-weight-bold);
font-size: var(--text-lg);
color: var(--text-primary);
margin-bottom: var(--space-lg);
}
```
**Module Items:**
```css
.module-item {
display: flex;
align-items: center;
padding: var(--space-md) var(--space-lg);
margin-bottom: var(--space-sm);
background: #f9fafb;
border-radius: var(--radius-medium);
cursor: pointer;
transition: all var(--transition-base);
font-family: var(--font-family);
font-size: var(--text-base);
}
.module-item:hover {
background: var(--gradient-start);
}
.module-item.active {
background: var(--primary);
color: white;
}
```
**Status Indicators:**
```css
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 10px;
transition: all var(--transition-base);
}
.status-indicator.green {
background: var(--status-green);
box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);
}
.status-indicator.red {
background: var(--status-red);
box-shadow: 0 0 8px rgba(244, 67, 54, 0.5);
}
.status-indicator.yellow {
background: var(--status-yellow);
box-shadow: 0 0 8px rgba(245, 158, 11, 0.5);
}
```
### 4.3 Control Panel
**Container:**
```css
.control-panel {
background: var(--surface);
border-radius: var(--radius-large);
box-shadow: var(--shadow);
padding: var(--space-xl);
overflow-y: auto;
}
.control-panel h2 {
font-family: var(--font-family);
font-weight: var(--font-weight-bold);
font-size: var(--text-xl);
color: var(--text-primary);
margin-bottom: var(--space-lg);
}
```
**Action Buttons:**
_Primary (Start, Screenshot):_
```css
.action-btn.primary {
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius-pill);
padding: var(--space-md) var(--space-xl);
font-family: var(--font-family);
font-weight: var(--font-weight-medium);
font-size: var(--text-base);
cursor: pointer;
transition: all var(--transition-base);
width: 100%;
text-align: center;
}
.action-btn.primary:hover {
background: var(--primary-hover);
box-shadow: var(--shadow-hover);
}
```
_Secondary (Stop, Status):_
```css
.action-btn.secondary {
background: transparent;
color: var(--primary);
border: 2px solid var(--primary);
border-radius: var(--radius-pill);
padding: var(--space-md) var(--space-xl);
font-family: var(--font-family);
font-weight: var(--font-weight-medium);
font-size: var(--text-base);
cursor: pointer;
transition: all var(--transition-base);
width: 100%;
}
.action-btn.secondary:hover {
background: var(--gradient-start);
}
```
_Tertiary (Logs):_
```css
.action-btn.tertiary {
background: #f9fafb;
color: var(--text-primary);
border: 1px solid var(--border-light);
border-radius: var(--radius-pill);
padding: var(--space-md) var(--space-xl);
font-family: var(--font-family);
font-weight: var(--font-weight-medium);
font-size: var(--text-base);
cursor: pointer;
transition: all var(--transition-base);
width: 100%;
}
.action-btn.tertiary:hover {
background: #f3f4f6;
}
```
**Button Layout:**
```css
.actions {
display: flex;
flex-direction: column;
gap: var(--space-md);
}
```
**Input Fields:**
```css
.custom-actions input {
padding: var(--space-md) var(--space-lg);
border: 1px solid var(--border-light);
border-radius: var(--radius-medium);
font-family: var(--font-family);
font-size: var(--text-base);
width: 100%;
transition: all var(--transition-base);
}
.custom-actions input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(107, 76, 246, 0.1);
}
.custom-actions input::placeholder {
color: #9ca3af;
}
```
### 4.4 JSON Result Viewer
**Container:**
```css
.result-viewer {
background: var(--surface);
border-radius: var(--radius-large);
box-shadow: var(--shadow);
padding: var(--space-xl);
overflow-y: auto;
}
.result-viewer h3 {
font-family: var(--font-family);
font-weight: var(--font-weight-medium);
font-size: var(--text-lg);
color: var(--text-primary);
margin-bottom: var(--space-lg);
}
```
**JSON Display:**
```css
.json-output {
background: #f9fafb;
padding: var(--space-lg);
border-radius: var(--radius-medium);
overflow-x: auto;
font-family: var(--font-mono);
font-size: var(--text-sm);
line-height: 1.6;
color: var(--text-primary);
}
/* Syntax highlighting */
.json-output .key {
color: var(--primary);
}
.json-output .string {
color: var(--text-primary);
}
.json-output .number {
color: #14b8a6; /* Teal */
}
.json-output .boolean {
color: var(--status-yellow);
}
.json-output .null {
color: var(--text-muted);
}
```
**Custom Scrollbar:**
```css
.json-output::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.json-output::-webkit-scrollbar-track {
background: #f3f4f6;
border-radius: 4px;
}
.json-output::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 4px;
}
.json-output::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
```
**Empty State:**
```css
.json-output.empty::before {
content: 'No response yet';
display: block;
color: var(--text-muted);
font-family: var(--font-family);
font-size: var(--text-sm);
margin-bottom: var(--space-sm);
}
```
### 4.5 Resizable Divider
**Styling:**
```css
.divider {
height: 6px;
background: linear-gradient(180deg, #f3f4f6 0%, #e5e7eb 100%);
cursor: ns-resize;
position: relative;
transition: all var(--transition-base);
}
.divider:hover {
background: var(--primary);
}
.divider::before {
content: '⋯';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: #9ca3af;
font-size: 18px;
transition: color var(--transition-base);
}
.divider:hover::before {
color: white;
}
```
### 4.6 Bottom Log Panel
**Container Base:**
```css
.bottom-section {
overflow: hidden;
}
.log-panel {
height: 100%;
display: flex;
flex-direction: column;
border-radius: var(--radius-large) var(--radius-large) 0 0;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.06);
transition: background var(--transition-base);
}
```
**Header:**
```css
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-lg) var(--space-xl);
border-bottom: 1px solid var(--border-light);
transition: all var(--transition-base);
}
.log-header h3 {
font-family: var(--font-family);
font-weight: var(--font-weight-medium);
font-size: var(--text-lg);
transition: color var(--transition-base);
}
```
**Controls:**
```css
.log-controls {
display: flex;
gap: var(--space-sm);
align-items: center;
}
.log-controls select {
padding: var(--space-sm) var(--space-md);
border-radius: var(--radius-medium);
border: 1px solid var(--border-light);
font-family: var(--font-family);
font-size: var(--text-sm);
cursor: pointer;
transition: all var(--transition-base);
}
.log-controls select:focus {
outline: none;
border-color: var(--primary);
}
.log-btn {
padding: var(--space-sm) var(--space-lg);
background: var(--status-green);
color: white;
border: none;
border-radius: var(--radius-pill);
font-family: var(--font-family);
font-weight: var(--font-weight-medium);
font-size: var(--text-sm);
cursor: pointer;
transition: all var(--transition-base);
}
.log-btn.active {
background: var(--status-red);
}
.log-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.clear-btn {
padding: var(--space-sm) var(--space-lg);
background: var(--status-yellow);
color: white;
border: none;
border-radius: var(--radius-pill);
font-family: var(--font-family);
font-weight: var(--font-weight-medium);
font-size: var(--text-sm);
cursor: pointer;
transition: all var(--transition-base);
}
```
**Theme Toggle Button:**
```css
.theme-toggle-btn {
width: 36px;
height: 36px;
border-radius: var(--radius-pill);
border: none;
background: transparent;
font-size: 18px;
cursor: pointer;
transition: all var(--transition-base);
display: flex;
align-items: center;
justify-content: center;
}
.theme-toggle-btn:hover {
background: rgba(107, 76, 246, 0.1);
}
```
**Light Theme:**
```css
.log-panel.theme-light {
background: var(--surface);
}
.log-panel.theme-light .log-header {
background: var(--surface);
border-bottom-color: var(--border-light);
}
.log-panel.theme-light .log-header h3 {
color: var(--text-primary);
}
.log-panel.theme-light .log-output {
background: #f9fafb;
color: var(--text-primary);
}
.log-panel.theme-light .log-line {
border-bottom: 1px solid #f3f4f6;
}
.log-panel.theme-light .log-empty {
color: var(--text-muted);
}
```
**Dark Theme:**
```css
.log-panel.theme-dark {
background: var(--dark-bg);
}
.log-panel.theme-dark .log-header {
background: var(--dark-surface);
border-bottom-color: var(--dark-border);
}
.log-panel.theme-dark .log-header h3 {
color: var(--dark-text);
}
.log-panel.theme-dark .log-output {
background: var(--dark-bg);
color: var(--dark-text);
}
.log-panel.theme-dark .log-line {
border-bottom: 1px solid var(--dark-surface);
}
.log-panel.theme-dark .log-empty {
color: var(--dark-text-muted);
}
.log-panel.theme-dark .log-controls select {
background: var(--dark-surface);
border-color: var(--dark-border);
color: var(--dark-text);
}
```
**Log Output Area:**
```css
.log-output {
flex: 1;
overflow-y: auto;
padding: var(--space-lg);
font-family: var(--font-mono);
font-size: var(--text-sm);
line-height: 1.5;
}
.log-empty {
text-align: center;
padding: var(--space-2xl);
font-family: var(--font-family);
}
.log-line {
padding: var(--space-sm) 0;
white-space: pre-wrap;
word-break: break-word;
}
.log-line:hover {
background: rgba(107, 76, 246, 0.05);
}
```
**Scrollbar (Dark Theme):**
```css
.log-panel.theme-dark .log-output::-webkit-scrollbar {
width: 10px;
}
.log-panel.theme-dark .log-output::-webkit-scrollbar-track {
background: var(--dark-bg);
}
.log-panel.theme-dark .log-output::-webkit-scrollbar-thumb {
background: #555;
border-radius: 5px;
}
.log-panel.theme-dark .log-output::-webkit-scrollbar-thumb:hover {
background: #777;
}
```
## 5. HTML Structure Updates
### 5.1 Add Inter Font
In `<head>` section:
```html
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
rel="stylesheet"
/>
```
### 5.2 Button Classes
Update button markup to use new classes:
```html
<!-- Primary button -->
<button class="action-btn primary" @click="executeAction('start')">
Start
</button>
<!-- Secondary button -->
<button class="action-btn secondary" @click="executeAction('stop')">
Stop
</button>
<!-- Tertiary button -->
<button class="action-btn tertiary" @click="executeAction('logs')">Logs</button>
```
### 5.3 Theme Toggle
Add to log panel header:
```html
<div class="log-header">
<h3>Live Logs</h3>
<div class="log-controls">
<select v-model="logModule" @change="switchLogStream">
<option value="">-- Select Module --</option>
<option v-for="module in modules" :key="module" :value="module">
{{ module }}
</option>
</select>
<button
@click="toggleLogStream"
:disabled="!logModule"
:class="['log-btn', {active: isStreaming}]"
>
{{ isStreaming ? 'Stop Stream' : 'Start Stream' }}
</button>
<button @click="clearLogs" class="clear-btn">Clear</button>
<button
@click="toggleLogTheme"
class="theme-toggle-btn"
:title="logPanelTheme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'"
>
{{ logPanelTheme === 'dark' ? '☀️' : '🌙' }}
</button>
</div>
</div>
```
### 5.4 Log Panel Theme Class
Update log panel container:
```html
<div class="log-panel" :class="'theme-' + logPanelTheme">
<!-- content -->
</div>
```
## 6. Vue.js Updates
### 6.1 Data Properties
Add to `data()`:
```javascript
data() {
return {
// ... existing properties
logPanelTheme: localStorage.getItem('log-theme') || 'dark',
}
}
```
### 6.2 Methods
Add theme toggle method:
```javascript
methods: {
// ... existing methods
toggleLogTheme() {
this.logPanelTheme = this.logPanelTheme === 'dark' ? 'light' : 'dark';
localStorage.setItem('log-theme', this.logPanelTheme);
}
}
```
## 7. Implementation Strategy
### 7.1 Phase 1: CSS Foundation (Task 9 Update)
1. Replace entire `static/style.css` with new Wasmer-styled CSS
2. Define all CSS custom properties in `:root`
3. Implement base styles and reset
4. Add Inter font loading
**Deliverable:** New style.css with complete Wasmer design system
### 7.2 Phase 2: HTML Updates (Task 7 Update)
1. Update `templates/index.html`:
- Add Inter font link
- Update button classes (primary, secondary, tertiary)
- Add theme toggle button
- Update log panel with theme class binding
**Deliverable:** Updated HTML template with Wasmer structure
### 7.3 Phase 3: Vue.js Updates (Task 8 Update)
1. Update `static/app.js`:
- Add `logPanelTheme` data property
- Add `toggleLogTheme()` method
- Load theme preference from localStorage on mount
**Deliverable:** Updated Vue app with theme toggle functionality
### 7.4 Phase 4: Testing & Refinement
1. Test all interactive elements (buttons, hover states, transitions)
2. Verify theme toggle works correctly
3. Check responsive behavior
4. Validate color contrast for accessibility
5. Test in different browsers
**Deliverable:** Fully functional Wasmer-styled toolbox
## 8. Migration from Current Design
### 8.1 What Changes
| Component | Current | New (Wasmer) |
| ----------------- | ----------------------- | --------------------------------- |
| Background | Plain white | Lavender-to-white gradient |
| Panels | White with gray borders | White cards with soft shadows |
| Buttons | Blue (#2196F3) | Purple (#6B4CF6) with pill shapes |
| Border radius | 4px | 16px (large), 12px (medium) |
| Font | System fonts | Inter (Google Fonts) |
| Status indicators | Colors only | Colors + glow effects |
| Log panel | Dark only | Toggleable dark/light |
### 8.2 What Stays the Same
| Component | Preserved |
| ---------------- | ------------------------------------------------ |
| Layout structure | Split-pane grid (sidebar \| control \| result) |
| Functionality | All actions, WebSocket streaming, status polling |
| Resizable panels | Divider drag behavior |
| Module system | Core modules and plugin architecture |
| Vue.js approach | CDN-based, no build tools |
## 9. Visual Reference
Based on Wasmer.io design elements:
- Reference image: `/Users/atd/wk/screenshotpro/docs/wasmer.io-navbar-and-herosection.png`
- Key inspiration: Floating rounded navbar, soft shadows, lavender gradients, clean typography
**Design Principles Applied:**
1. **Generous white space** - Increased padding throughout
2. **Soft shadows** - Subtle depth without harsh lines
3. **Rounded corners** - 16px on major containers, 12px on nested elements
4. **Purple accent** - Used sparingly for primary actions and active states
5. **Clean typography** - Inter font with clear hierarchy
6. **Smooth transitions** - 0.2s ease on all interactive elements
## 10. Accessibility Considerations
### 10.1 Color Contrast
All text meets WCAG AA standards:
- Primary text (#1A1423) on white: 16.7:1
- Muted text (#777) on white: 4.6:1
- White text on purple (#6B4CF6): 4.8:1
### 10.2 Interactive Elements
- All buttons have `:focus` states with visible outlines
- Hover states provide clear feedback
- Theme toggle has descriptive `title` attribute
- Status indicators include text labels (not just color)
### 10.3 Keyboard Navigation
- All interactive elements are keyboard accessible
- Tab order follows logical flow
- Focus styles are visible and clear
## 11. Browser Compatibility
**Supported Browsers:**
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
**CSS Features Used:**
- CSS Custom Properties (variables)
- CSS Grid
- Flexbox
- Transitions
- Box-shadow
- Border-radius
All features have excellent modern browser support. No polyfills needed.
## 12. Performance Considerations
**Optimizations:**
- Single CSS file (no imports or build step)
- Minimal use of expensive properties (filters, backdrop-blur)
- Hardware-accelerated transitions (transform, opacity)
- Font display: swap (prevents FOIT)
- Debounced resize events
**Expected Performance:**
- First paint: <100ms (CSS loads fast)
- Interaction response: <16ms (smooth 60fps)
- Font loading: Non-blocking with fallback
## 13. Future Enhancements
Post-MVP ideas for further refinement:
1. **Animation polish**: Subtle entrance animations for panels
2. **Dark mode (full dashboard)**: Not just log panel, entire UI
3. **Custom themes**: Allow user to customize primary color
4. **Compact mode**: Reduce padding for smaller screens
5. **Print styles**: Clean print view of logs/results
## 14. Success Criteria
The redesign is successful when:
- ✅ Entire dashboard has cohesive Wasmer aesthetic
- ✅ All original functionality preserved (no regressions)
- ✅ Theme toggle works smoothly for log panel
- ✅ All interactions feel polished (smooth transitions)
- ✅ Colors meet accessibility standards
- ✅ No build tools required (pure CSS)
- ✅ Loads quickly (<200ms to first paint)
- ✅ Works in all modern browsers
---
**Design Status:** ✅ Complete
**Next Step:** Update Tasks 7, 8, 9 in implementation plan to reflect Wasmer styling
```
```