Skip to content
RunicGet Runic

Runic Docs Reference

Runic Menubar Icon - Size Specifications

Technical Reference for macOS Menubar Icon Rendering


macOS Menubar Guidelines

Apple Human Interface Guidelines

Recommended Sizes:

  • Standard: 18-22pt (square or proportional)
  • Detailed: Up to 28pt for complex icons
  • Spacing: 2pt padding from edges (safe area)

Display Considerations:

  • Retina (@2x): All icons must provide 2× pixel density assets
  • Non-Retina (@1x): Maintain backward compatibility
  • Dark Mode: Template rendering adapts automatically
  • Positioning: Vertically centered in menubar (22pt tall menubar)

Template Image Requirements:

  • Single-channel alpha (black with alpha channel)
  • System colorizes based on menubar theme
  • No embedded colors (except in vibrant mode)
  • Smooth anti-aliasing for curves

Runic Icon Specifications

Current Implementation

File: IconRenderer.swift (Lines 51-56)

private static let baseSize = NSSize(width: 38, height: 22)
private static let outputSize = NSSize(width: 38, height: 22)
private static let outputScale: CGFloat = 2

Analysis:

  • Points: 38×22pt (width × height)
  • Pixels (@2x): 76×44px
  • Aspect ratio: ~1.73:1 (rectangular, not square)
  • Actual rendered height: 22pt (matches macOS menubar height)

Why Rectangular?

  • Wider icon provides more space for usage bars
  • Horizontal orientation fits menubar layout
  • Wave shape naturally extends horizontally

Option A: Infinity Symbol + Provider Icon

Recommended Size: 40×24pt (80×48px @2x)

Dimensions Breakdown

Element Points Pixels @2x Notes
Total Width 40pt 80px +2pt from current for breathing room
Total Height 24pt 48px +2pt from current for clarity
Infinity Stroke 4pt 8px Thick enough to be visible, not overwhelming
Loop Diameter 14pt 28px Per loop (left and right)
Loop Radius 7pt 14px From center of each loop
Center Gap Width 10-12pt 20-24px Space for provider icon
Center Gap Height 12-14pt 24-28px Vertical space for provider icon
Provider Icon 8×8pt 16×16px Fits comfortably in center gap
Safe Padding 2pt 4px From edges to infinity loops

Scaling Options

Size Preset Points Use Case Legibility
Small 36×20pt Compact menubar ★★★☆☆
Medium 40×24pt Recommended ★★★★★
Large 44×28pt High-DPI/accessibility ★★★★★

Recommendation: Use 40×24pt as default for best balance.


Option B: Wave Logo (Current)

Current Size: 38×22pt (76×44px @2x)

Refinement Proposal: 42×26pt (84×52px @2x)

Element Current Proposed Change
Total Width 38pt 42pt +4pt
Total Height 22pt 26pt +4pt
Wave Stroke 18px 20px +2px (thicker)
Center Circle 32px 36px +4px (larger)
Bar Width 8px 10px +2px (more visible)
Bar Max Height 22-26px 26-32px +4-6px (taller)

Rationale:

  • Larger bars easier to read
  • More vertical space reduces cramping
  • Maintains aspect ratio (~1.6:1)

Option C: Hybrid

Recommended Size: 40×24pt (80×48px @2x)

Dimensions Breakdown

Element Points Pixels @2x Notes
Total Width 40pt 80px Same as Option A
Total Height 24pt 48px Same as Option A
Infinity Stroke 3pt 6px Thinner outline (frame only)
Loop Radius 7pt 14px Same as Option A
Center Bar Width 8pt 16px Narrower than gap (10-12pt)
Center Bar Height 12pt 24px Nearly full gap height
Bar Corner Radius 3pt 6px Rounded for modern look
Provider Badge 6×6pt 12×12px Small circle in corner
Badge Stroke 1.5pt 3px Outline around badge

Pixel-Perfect Rendering

Alignment Rules (@2x Scale)

All coordinates must be even numbers at 2× to avoid sub-pixel rendering:

// GOOD: Aligns to pixel grid
let x = 20.0 // → 40px @ 2x ✓
let y = 12.0 // → 24px @ 2x ✓

// BAD: Sub-pixel rendering (blurry)
let x = 20.5 // → 41px @ 2x (half-pixel) ✗
let y = 12.3 // → 24.6px @ 2x (fractional) ✗

Helper Function (from IconRenderer.swift):

private static func snap(_ value: CGFloat) -> CGFloat {
    (value * self.outputScale).rounded() / self.outputScale
}

Usage:

let iconX = snap(15.5) // → 16.0 (aligned)
let iconY = snap(11.7) // → 12.0 (aligned)

SVG Viewbox Specifications

File: RunicMenubarIcon.svg

<svg xmlns="http://www.w3.org/2000/svg" viewBox="46 78 164 100" fill="none">

Analysis:

  • Viewbox: 46,78 → 210,178 (164×100 effective area)
  • Aspect ratio: 1.64:1
  • Renders to: 38×22pt via IconRenderer scaling

File: RunicMenubarIconInfinity.svg (new)

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 48" fill="none">

Analysis:

  • Viewbox: 0,0 → 80,48 (80×48 total area)
  • Aspect ratio: 1.67:1
  • Maps 1:1 to 40×24pt @ 2× (80×48px)
  • Simpler than current (no offset origin)

Advantages:

  • Clean origin at (0, 0)
  • Direct pixel mapping
  • Easier to edit and position elements

Provider Icon Sizing

In Infinity Design

Available Space: 10-12pt wide × 12-14pt tall (center gap)

Recommended Icon Size: 8×8pt (16×16px @2x)

Padding: 1-2pt around icon for breathing room

Existing Provider Icons (from IconRenderer.swift)

Provider Sigil Type Dimensions Fits in 8×8pt?
Claude Vertical slits 2×6pt each ✓ Yes
Codex Square eyes 3×3pt each ✓ Yes
Gemini Diamond 8pt diagonal ✓ Yes (exactly)
Cursor Arrow 6pt tall ✓ Yes
Copilot Circular eyes 6pt diameter ✓ Yes
Minimax Triangles 6×5pt ✓ Yes
OpenRouter Ring 8pt diameter ✓ Yes (exactly)
Groq Slash 2×8pt ⚠ Tight (8pt tall)
Zai Double slash 7pt tall ✓ Yes

Conclusion: All existing provider sigils fit comfortably in 8×8pt space.

In Hybrid Design (Badge)

Badge Size: 6×6pt (12×12px @2x)

Icon Size: 4×4pt (8×8px @2x) inside badge

Challenge: Icons need to scale down 50% from main design.

Solution:

  • Simplify icons for badge (minimal detail)
  • Use single dots or letters as fallback
  • Example: Claude = “C”, Codex = “<>”

Display Testing Matrix

Test on Real Devices

Display Resolution Scale Test Size Expected Result
MacBook Air M1 2560×1600 2× 40×24pt Sharp, clear
MacBook Pro 16” 3456×2234 2× 40×24pt Sharp, clear
iMac 27” 5K 5120×2880 2× 40×24pt Sharp, clear
Mac mini + 1080p 1920×1080 1× 40×24pt Clear, slight blur
Mac Studio + 4K 3840×2160 2× 40×24pt Sharp, clear

Size Comparison at Different DPI

Size @1× (72 DPI) @2× (144 DPI) Visual Result
18pt 18×18px 36×36px Minimum legible
22pt 22×22px 44×44px Standard menubar
24pt 24×24px 48×48px Recommended (clear)
28pt 28×28px 56×56px Large (accessibility)

Runic Recommended: 24pt height (Option A & C) or 26pt (Option B refined)


Color & Opacity Specifications

Template Mode (Default)

Colors:

  • Light Mode: NSColor.labelColor (black ~85% opacity)
  • Dark Mode: NSColor.labelColor (white ~85% opacity)
  • System Managed: No manual color specification

Opacity Modifiers:

  • Normal State: 100% (uses labelColor directly)
  • Stale Data: 55% opacity
  • Disabled: 30% opacity

Vibrant Mode (Optional)

Pressure Color Palette:

State Usage Range Hex Color RGB Opacity
Safe 0-50% #14B8A6 (20, 184, 166) 100%
Warning 50-80% #FFB84D (255, 184, 77) 100%
Critical 80-100% #FF4F70 (255, 79, 112) 100%
Stale Any (inherit) — 55%

Secondary Colors (from current design):

Element Color Hex RGB
Primary Stroke Teal 500 #14B8A6 (20, 184, 166)
Accent Fill Teal 400 #2DD4BF (45, 212, 191)
Dark Fill Teal 700 #0F766E (15, 118, 110)

Animation Specifications

Morph Animations (Launch/Close)

Current Implementation (Lines 668-755):

  • Duration: 0.5s (morphBucketCount = 200 frames)
  • Frame rate: 60 FPS
  • Easing: Linear (custom easing possible)

Infinity Morph:

  • Start: Three separate ribbons
  • End: Infinity symbol
  • Method: Bezier curve interpolation

Cache:

  • 200 morph frames × 3 designs = 600 cached images
  • Limited by PerformanceConstants.morphCacheSize (512)

Pulse Animations (Syncing)

Parameters:

// Subtle pulse during sync
let pulse = sin(CACurrentMediaTime() * 2) * 0.15 + 0.85
iconColor = baseColor.withAlphaComponent(pulse)

Specs:

  • Frequency: 0.5 Hz (2-second period)
  • Amplitude: ±15% opacity
  • Baseline: 85% opacity

Current Implementation (Lines 204-205):

// Disabled to prevent flicker
let opacityMultiplier = 1.0

If Re-enabled:

  • Duration: 0.2s per blink
  • Repetitions: 3× for critical errors
  • Opacity: 100% ↔ 30%

Accessibility Specifications

High Contrast Mode

macOS System Setting: Increase Contrast

Icon Adjustments:

  • Stroke Width: +1pt (thicker)
  • Opacity: Boost to 100% (remove transparency)
  • Colors: Use maximum saturation

Example:

if NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast {
    strokeWidth = 5.0 // instead of 4.0
    opacity = 1.0 // instead of 0.85
}

Reduced Motion

macOS System Setting: Reduce Motion

Icon Adjustments:

  • Disable all animations (blink, pulse, morph)
  • Use static icons only
  • Instant state changes (no transitions)

Example:

if NSWorkspace.shared.accessibilityDisplayShouldReduceMotion {
    // Skip morph animation, show final state immediately
    return IconRenderer.makeIcon(..., blink: 0, wiggle: 0, tilt: 0)
}

VoiceOver

Icon Description Template:

Option A:

"Runic monitoring icon. Infinity symbol with [Provider Name] in center. Usage level: [Color] ([Percentage]%). [Status]."

Example:

"Runic monitoring icon. Infinity symbol with Claude in center. Usage level: Orange (65%). Data is current."

Option B:

"Runic monitoring icon. Session usage: [X]%, Weekly usage: [Y]%, Credits remaining: [Z]%. [Status]."

Implementation:

statusItem.button?.accessibilityLabel = """
    Runic monitoring icon. Infinity symbol with \(providerName) in center. \
    Usage level: \(pressureColorName) (\(percentage)%). \(staleness).
    """

File Format Specifications

SVG Export Settings

For Infinity Icon:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 80 48"
     fill="none"
     width="80"
     height="48">
  <!-- Icon content -->
</svg>

Requirements:

  • Encoding: UTF-8
  • Namespace: xmlns=“http://www.w3.org/2000/svg”
  • ViewBox: Match output pixels (80×48)
  • Precision: 2 decimal places max
  • Optimization: Remove metadata, comments in production

Export Tools:

  • Illustrator: Export As → SVG → “Responsive” viewBox
  • Figma: Export SVG → Outline stroke
  • Sketch: Export SVG → “Use relative dimensions”

NSImage Import

IconRenderer.swift Pattern:

private static let infinityLogoTemplate: NSImage? = {
    guard let url = Bundle.main.url(
        forResource: "RunicMenubarIconInfinity",
        withExtension: "svg"),
          let image = NSImage(contentsOf: url) else {
        return nil
    }
    return image
}()

Rendering:

infinityLogo.draw(
    in: rect,
    from: .zero,
    operation: .sourceOver,
    fraction: 1.0
)

Testing Checklist

Visual Testing

  • Renders sharply on Retina display (@2x)
  • Renders acceptably on non-Retina display (@1x)
  • Template mode adapts to light menubar theme
  • Template mode adapts to dark menubar theme
  • Vibrant mode displays correct colors
  • All provider icons visible and recognizable
  • Stale state reduces opacity to 55%
  • Status indicators (minor, major, critical) render correctly
  • No blurry edges (pixel-aligned)
  • Smooth anti-aliasing on curves

Size Testing

  • Legible at 18pt (minimum)
  • Clear at 22pt (standard)
  • Excellent at 24pt (recommended)
  • Excellent at 28pt (large)
  • Scales proportionally at all sizes
  • No cropping at edges
  • Maintains aspect ratio

Accessibility Testing

  • High contrast mode increases visibility
  • Reduced motion disables animations
  • VoiceOver announces icon state correctly
  • Tooltip shows detailed information on hover
  • Keyboard shortcut opens menu

Performance Testing

  • First render <10ms
  • Cached render <0.5ms
  • Cache hit rate >80%
  • Memory footprint <5MB (cache + templates)
  • No memory leaks (verify with Instruments)
  • Animation maintains 60 FPS

Summary

Size: 40×24pt (80×48px @2x)

  • Optimal balance of clarity and menubar fit
  • +2pt from current for improved legibility
  • Works well for all three design options

Rendering:

  • Template mode by default (system theme adaptation)
  • Vibrant mode as user preference option
  • 2× scale for Retina displays
  • Pixel-perfect alignment

Provider Icons:

  • 8×8pt in infinity center gap
  • All existing sigils fit comfortably
  • Clear visibility and recognition

Colors:

  • Pressure mapping: Teal (safe) → Orange (warning) → Red (critical)
  • Stale state: 55% opacity
  • Template: NSColor.labelColor (automatic)

Performance:

  • Cache 50-100 icon variations
  • Render in <10ms (first) or <0.5ms (cached)
  • 60 FPS animations

Accessibility:

  • High contrast support
  • Reduced motion compliance
  • VoiceOver descriptions
  • Keyboard shortcut access

Reference Implementation

// Complete size configuration for Option A
private static let baseSize = NSSize(width: 40, height: 24)
private static let outputSize = NSSize(width: 40, height: 24)
private static let outputScale: CGFloat = 2

// Infinity path specifications
private static let infinityStrokeWidth: CGFloat = 4
private static let infinityLoopRadius: CGFloat = 7
private static let infinityCenterGapWidth: CGFloat = 12
private static let infinityCenterGapHeight: CGFloat = 14

// Provider icon specifications
private static let providerIconSize: CGFloat = 8
private static let providerIconPadding: CGFloat = 2

// Color specifications
private static let safeColor = NSColor(calibratedRed: 0.08, green: 0.72, blue: 0.65, alpha: 1.0) // #14B8A6
private static let warningColor = NSColor(calibratedRed: 1.00, green: 0.72, blue: 0.30, alpha: 1.0) // #FFB84D
private static let criticalColor = NSColor(calibratedRed: 1.00, green: 0.31, blue: 0.44, alpha: 1.0) // #FF4F70
private static let staleOpacity: CGFloat = 0.55

This completes the comprehensive size specification document for Runic’s menubar icon designs.

Read the source ↗ Checked against Runic 2.13.5 · 9730f91