← Build log

Custom Title Bars and Window Management in Electron

When Removing Browser Chrome Means Building Your Own

By: Jeremy (TPM learning that “just remove the title bar” is never simple)


Here’s a lesson every TPM learns the hard way: The moment you customize a platform’s built-in UI components, you become responsible for implementing all their expected behaviors.

Building VoiceCatcher’s popup history window, I decided to remove the browser title bar to create a cleaner, more app-like experience. “Just set frame: false,” I thought. “How hard could it be?”

Four hours later, I had a beautiful frameless window that users couldn’t move or close.

This is the story of how a simple visual preference cascaded into implementing custom drag handling, window controls, and platform-specific behaviors. Because every pixel you control means every interaction you need to design.

The Setup: Why Remove the Title Bar?

Context: Our history window was designed to feel like an integrated part of VoiceCatcher, not a separate browser window. The default Electron title bar with standard browser buttons felt wrong—it screamed “web page” when we wanted “native app.”

The goal: Create a dedicated history interface that felt purpose-built, not browser-based.

The assumption: Removing the title bar would be a simple configuration change.

The reality: Removing the title bar removes ALL the functionality the title bar provided.

The Problem: When You Remove Chrome, You Remove Function

What We Thought We Were Removing

  • Generic browser appearance
  • Inconsistent branding
  • Unnecessary visual elements

What We Actually Removed

  • Window dragging capability (users couldn’t move the window)
  • Close button (users couldn’t close the window without CMD+W)
  • Window management cues (no visual indication this was a separate window)
  • Native accessibility features (screen readers lost window context)
  • Platform conventions (behavior varied between macOS/Windows/Linux)

The realization: UI chrome isn’t just visual—it’s functional infrastructure that users expect to work consistently.

The Solution: Custom Title Bar Implementation

The Architecture Decision

Instead of trying to partially restore browser chrome, we built a custom title bar that:

  1. Looks integrated with our neumorphic design system
  2. Behaves natively according to platform conventions
  3. Provides accessibility through proper ARIA attributes
  4. Enables customization for future features

The Technical Implementation

<!-- Custom Title Bar Structure -->
<div class="title-bar" style="-webkit-app-region: drag">
  <div class="title-content">
    <div class="title-icon">
      <svg class="file-text-icon">...</svg>
    </div>
    <div class="title-text">VoiceCatcher - Session History</div>
  </div>

  <div class="window-controls" style="-webkit-app-region: no-drag">
    <button class="neumorphic-close-button primary" onclick="window.api.closeHistoryWindow()">
      <svg class="x-icon">...</svg>
    </button>
  </div>
</div>

The CSS Architecture

.title-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 40px;
  padding: 0 16px;
  background: var(--neu-bg);
  border-bottom: 1px solid var(--neu-shadow-dark);

  /* Enable dragging for entire title bar */
  -webkit-app-region: drag;
  user-select: none;
}

.title-content {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
}

.window-controls {
  /* Disable dragging for interactive elements */
  -webkit-app-region: no-drag;
}

.neumorphic-close-button {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--neu-bg);
  border: none;
  box-shadow: var(--neu-shadow-raised);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

The Window Configuration

// Electron window creation
historyWindow = new BrowserWindow({
  width: 800,
  height: 700,
  frame: false, // Remove default title bar
  titleBarStyle: 'hidden', // Hide system title bar
  trafficLightPosition: { x: -100, y: -100 }, // Hide macOS buttons
  webPreferences: {
    nodeIntegration: false,
    contextIsolation: true,
    preload: path.join(__dirname, 'preload.js'),
  },
});

The TPM Framework: Custom UI Component Strategy

1. Audit What You’re Actually Removing

Before customizing any platform UI, list every behavior the original provides.

The UI Audit Prompt:

I want to remove [platform UI element] and replace it with custom implementation.
Audit what functionality this element provides:
1. Core interaction behaviors (click, drag, keyboard)
2. Accessibility features (screen reader, high contrast)
3. Platform conventions (macOS vs Windows vs Linux)
4. Edge cases and error states
5. Integration with system features

What are the implementation requirements for each?

2. Design for Platform Expectations, Not Just Visual Preferences

Custom UI needs to behave like native UI, even if it looks different.

The Platform Behavior Prompt:

I'm building a custom [UI component] for [Electron/desktop app].
Design implementation that:
1. Follows platform-specific conventions
2. Maintains accessibility standards
3. Handles edge cases native components handle
4. Provides clear user feedback for all states
5. Integrates with system window management

Include platform-specific considerations for macOS/Windows/Linux.

3. Progressive Enhancement Strategy

Build custom UI that degrades gracefully if features fail.

The Graceful Degradation Prompt:

My custom title bar needs these behaviors: [list requirements]
Design a progressive enhancement strategy:
1. Core functionality that must always work
2. Enhanced features that improve experience
3. Fallback behaviors for each potential failure
4. Detection methods for platform capabilities
5. Recovery strategies for broken states

The Technical Details: Making Drag Actually Work

The -webkit-app-region Pattern

/* Enable dragging on container */
.title-bar {
  -webkit-app-region: drag;
}

/* Disable dragging on interactive elements */
.window-controls button {
  -webkit-app-region: no-drag;
}

/* Text elements need explicit non-dragging */
.title-text {
  -webkit-app-region: no-drag;
  pointer-events: none; /* Prevent text selection interference */
}

Why this matters: The webkit-app-region CSS property is Electron’s way of defining draggable areas. But it’s all-or-nothing—any clickable element needs explicit no-drag declaration.

The Close Button Implementation

// In preload.js - expose safe API to renderer
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('api', {
  closeHistoryWindow: () => ipcRenderer.send('close-history-window'),
});

// In main process - handle window closing
ipcMain.on('close-history-window', () => {
  if (historyWindow && !historyWindow.isDestroyed()) {
    historyWindow.close();
  }
});

Security note: Never expose raw ipcRenderer to the web content. Always use contextBridge for controlled API exposure.

Visual Feedback System

.neumorphic-close-button {
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.neumorphic-close-button:hover {
  transform: translateY(-1px);
  box-shadow: var(--neu-shadow-raised-hover);
}

.neumorphic-close-button:active {
  transform: translateY(0);
  box-shadow: var(--neu-shadow-pressed);
}

UX principle: Custom controls need stronger visual feedback than native controls because users have no learned expectations about how they behave.

The Business Impact: When Custom UI Affects User Confidence

Before: Frameless Window Without Custom Chrome

  • Users couldn’t move the window (confusion)
  • No clear close mechanism (frustration)
  • Window felt “broken” or “unfinished”
  • Accessibility tools couldn’t understand window context

After: Custom Title Bar Implementation

  • Intuitive window manipulation (drag anywhere on title bar)
  • Clear, consistent close mechanism
  • Professional, integrated appearance
  • Proper accessibility attributes and screen reader support

The User Experience Metrics

  • Window interaction confusion: Eliminated entirely
  • Feature adoption: History window usage increased (users felt more confident)
  • Perceived quality: Custom title bar reinforced “native app” perception
  • Cross-platform consistency: Behavior now consistent across macOS/Windows

Advanced: Platform-Specific Considerations

macOS Traffic Light Buttons

// Option 1: Hide completely
new BrowserWindow({
  titleBarStyle: 'hidden',
  trafficLightPosition: { x: -100, y: -100 },
});

// Option 2: Custom positioning
new BrowserWindow({
  titleBarStyle: 'hiddenInset',
  trafficLightPosition: { x: 16, y: 12 },
});

Windows Close/Minimize/Maximize

// Windows requires explicit window control implementation
if (process.platform === 'win32') {
  // Add minimize, maximize, close buttons to custom title bar
  // Handle Windows Aero Snap behavior
  // Implement Windows 11 rounded corners
}

Linux Window Manager Integration

// Linux has diverse window managers with different expectations
if (process.platform === 'linux') {
  // Test with GNOME, KDE, i3, etc.
  // Handle different theme systems
  // Ensure compatibility with accessibility tools
}

Common TPM Custom UI Mistakes (That I Made)

Mistake 1: Thinking Visual == Functional

Wrong thinking: “I just want to change how it looks.” Reality: Changing appearance often requires reimplementing functionality.

Fix: Audit functional requirements before visual customization.

Mistake 2: Designing for Single Platform

Wrong thinking: “I’ll design for macOS and figure out Windows later.” Reality: Platform differences affect fundamental interaction patterns.

Fix: Design for platform differences from the beginning, not as an afterthought.

Mistake 3: Ignoring Accessibility Requirements

Wrong thinking: “Custom UI is inherently more accessible because it’s designed specifically for our app.” Reality: Platform UI comes with accessibility built-in. Custom UI starts with zero accessibility.

Fix: Implement ARIA attributes, keyboard navigation, and screen reader support from day one.

Mistake 4: Underestimating Edge Cases

Wrong thinking: “Users will drag from the title bar and click the close button. Simple.” Reality: Users right-click, double-click, use keyboard shortcuts, resize windows, etc.

Fix: Test with real user interaction patterns, not just happy path scenarios.

The Psychology: Why Custom UI Affects Product Perception

Visual Consistency Drives Quality Perception

Custom title bars that match your app’s design system signal attention to detail and integrated experience.

VoiceCatcher example: The neumorphic close button matching our main UI buttons made the history window feel like a natural extension, not a separate tool.

Interaction Predictability Builds User Confidence

When custom UI behaves exactly like native UI, users focus on content rather than figuring out how to interact with the interface.

Measurement: Time to complete history-related tasks decreased after custom title bar implementation because users spent zero mental energy on window management.

Platform Convention Adherence Reduces Cognitive Load

Users have learned expectations about how window controls work. Meeting those expectations feels “right” even if the visuals are different.

Advanced: The Component Strategy for Custom UI

Reusable Title Bar Component

interface TitleBarProps {
  title: string;
  icon?: React.ReactNode;
  showClose?: boolean;
  showMinimize?: boolean;
  showMaximize?: boolean;
  onClose?: () => void;
  className?: string;
}

const TitleBar: React.FC<TitleBarProps> = ({
  title,
  icon,
  showClose = true,
  onClose,
  className = ''
}) => {
  return (
    <div className={`title-bar ${className}`} style={{ WebkitAppRegion: 'drag' }}>
      <div className="title-content">
        {icon && <div className="title-icon">{icon}</div>}
        <div className="title-text">{title}</div>
      </div>

      {showClose && (
        <div className="window-controls" style={{ WebkitAppRegion: 'no-drag' }}>
          <button
            className="neumorphic-close-button primary"
            onClick={onClose}
            aria-label="Close window"
          >
            <XIcon />
          </button>
        </div>
      )}
    </div>
  );
};

Platform-Aware Control Rendering

const PlatformTitleBar = ({ title, onClose }) => {
  const platform = process.platform;

  if (platform === 'darwin') {
    return <MacOSTitleBar title={title} onClose={onClose} />;
  } else if (platform === 'win32') {
    return <WindowsTitleBar title={title} onClose={onClose} />;
  } else {
    return <LinuxTitleBar title={title} onClose={onClose} />;
  }
};

The Prompts That Enable Custom UI Success

Functionality Audit Prompt

I want to replace [native UI element] with custom implementation.
Comprehensive audit:
1. What interactions does the native element support?
2. What accessibility features are built-in?
3. How does it behave differently across platforms?
4. What edge cases does it handle automatically?
5. How does it integrate with system window management?
6. What keyboard shortcuts does it respond to?

Create implementation requirements for each capability.

Custom Component Design Prompt

Design a custom [UI component] for Electron that:
- Matches our design system: [design specs]
- Supports these platforms: [macOS/Windows/Linux]
- Handles these interactions: [list required behaviors]
- Maintains accessibility for: [screen readers/keyboard/high contrast]
- Integrates with: [existing component system]

Include CSS, HTML structure, JavaScript behavior, and platform-specific considerations.

Testing Strategy Prompt

Create a testing strategy for custom title bar implementation:
1. Cross-platform behavior verification
2. Accessibility compliance checking
3. Edge case interaction scenarios
4. Performance impact measurement
5. Integration with existing window management

Include both automated and manual testing approaches.

Lessons Learned: Custom UI as Product Investment

Custom UI is Infrastructure, Not Styling

Building custom title bars isn’t a visual preference—it’s committing to maintaining UI infrastructure that platforms usually provide.

Development cost: Custom title bar implementation took 6 hours vs. 30 seconds for default title bar.

Maintenance cost: Every Electron update requires testing custom UI compatibility.

Value delivered: Professional, integrated user experience that reinforces product quality.

Platform Differences Are Product Features

Different platforms have different user expectations. Custom UI that ignores these differences feels foreign, not polished.

Learning: Windows users expect minimize/maximize buttons. macOS users expect different button positions. Linux users expect compatibility with diverse window managers.

Accessibility Can’t Be Added Later

Platform UI comes with accessibility built-in. Custom UI starts with zero accessibility and requires explicit implementation.

Implementation requirement: ARIA attributes, keyboard navigation, screen reader support, and high contrast compatibility must be built from the beginning.

The Meta-Lesson: Every Customization is a Commitment

The biggest insight from implementing custom title bars: Every pixel you customize becomes your responsibility to maintain.

When you remove platform chrome, you’re not just changing appearance—you’re committing to:

  • Implementing all the functionality that chrome provided
  • Maintaining compatibility across platform updates
  • Supporting accessibility features that were built-in
  • Testing edge cases that platforms handle automatically

The decision framework: Custom UI should only be implemented when the user experience value significantly exceeds the implementation and maintenance cost.

Practical Implementation for TPMs

Start with Platform Audit

Before customizing any platform UI, spend time understanding exactly what functionality it provides across all target platforms.

Design for Accessibility First

Custom UI that isn’t accessible isn’t professional. Build accessibility support before visual polish.

Test with Real Usage Patterns

Don’t just test the happy path. Test right-clicks, keyboard shortcuts, window resizing, and interaction with system features.

Plan for Platform Evolution

Platform UI standards change. Custom UI needs to evolve with platform conventions, not stay frozen in time.

The Bottom Line

Custom UI is powerful when it serves user needs better than platform defaults, but it comes with significant implementation and maintenance responsibilities.

VoiceCatcher’s custom title bar created a more integrated, professional experience that reinforced our product quality. But it also meant we became responsible for window behavior that Electron usually handles automatically.

As TPMs, we need to recognize that UI customization decisions are technical architecture decisions that affect long-term product development velocity and maintenance burden.

The custom title bar wasn’t just about visual branding—it was about taking control of the user experience in exchange for taking responsibility for the technical implementation.

When done well, custom UI becomes a competitive advantage. When done poorly, it becomes technical debt that affects every user interaction.


Jeremy is a technical product manager turned solo developer, building VoiceCatcher as a local-first voice transcription tool. Follow the journey at [project repo] or connect on [social links].

Next in this series: “Shared CSS Architecture: Building Multi-Window Consistency” - How we built a design system that scales across multiple Electron windows while maintaining build process integration and theme synchronization.