$ cat /posts/tauri-20-custom-titlebar-building-native-looking-ui.md
[tags]Tauri 2.0

Tauri 2.0 Custom Titlebar Building Native-Looking UI

drwxr-xr-x2026-01-295 min0 views
Tauri 2.0 Custom Titlebar Building Native-Looking UI

Custom titlebars in Tauri 2.0 enable desktop applications to replace default system window decorations with branded, customizable UI controls providing unique visual identity while maintaining native functionality including window dragging, minimize/maximize/close buttons, and double-click behaviors—essential feature for applications requiring brand consistency, modern aesthetics, or custom window controls matching application design maintaining professional appearance users expect from native applications. Custom titlebar combines HTML/CSS design flexibility creating pixel-perfect branded headers with data-tauri-drag-region attribute enabling window dragging, JavaScript window control APIs handling button actions, platform-specific styling adapting to Windows, macOS, and Linux conventions, and accessibility considerations maintaining keyboard navigation and screen reader compatibility delivering custom window chrome without sacrificing usability. This comprehensive guide covers understanding titlebar architecture and platform differences, disabling default decorations in window configuration, creating draggable regions with CSS, implementing window control buttons with proper icons, handling window state changes showing maximized/restored states, adding custom menu buttons or search bars, styling for platform-specific conventions, implementing dark mode support, building reusable titlebar components, and creating real-world examples including branded titlebar with gradient, minimal titlebar with centered title, and VS Code-style titlebar with tabs maintaining native feel through custom design. Mastering custom titlebar patterns enables building professional desktop applications providing unique brand identity while maintaining familiar window management behaviors users expect. Before proceeding, understand window management and splash screens.

Basic Custom Titlebar Setup

Custom titlebar requires disabling default window decorations and creating HTML titlebar with drag region. Understanding basic architecture enables building functional custom titlebars maintaining window controls while providing design flexibility.

htmlbasic_titlebar.html
// src-tauri/tauri.conf.json
// Disable default window decorations
{
  "tauri": {
    "windows": [
      {
        "label": "main",
        "title": "My App",
        "decorations": false,  // Remove default titlebar
        "transparent": false,  // Optional: true for custom shaped windows
        "titleBarStyle": "Overlay",  // macOS: allows custom titlebar
        "width": 1200,
        "height": 800
      }
    ]
  }
}

// Basic HTML Titlebar Structure
// src/index.html or in your React/Vue component
<!DOCTYPE html>
<html>
  <head>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      }

      /* Custom titlebar */
      .titlebar {
        height: 40px;
        background: #2c2c2c;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 12px;
        user-select: none;
        -webkit-user-select: none;
      }

      /* Make titlebar draggable */
      .titlebar-dragregion {
        flex: 1;
        height: 100%;
        display: flex;
        align-items: center;
        data-tauri-drag-region
      }

      .titlebar-title {
        color: #ffffff;
        font-size: 13px;
        margin-left: 8px;
      }

      /* Window control buttons */
      .titlebar-controls {
        display: flex;
        height: 100%;
      }

      .titlebar-button {
        width: 46px;
        height: 100%;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        background: transparent;
        border: none;
        color: #ffffff;
        transition: background-color 0.15s;
      }

      .titlebar-button:hover {
        background: rgba(255, 255, 255, 0.1);
      }

      .titlebar-button.close:hover {
        background: #e81123;
      }

      .titlebar-button svg {
        width: 10px;
        height: 10px;
      }

      /* Main content area */
      .content {
        height: calc(100vh - 40px);
        padding: 20px;
        overflow-y: auto;
      }
    </style>
  </head>
  <body>
    <!-- Custom Titlebar -->
    <div class="titlebar">
      <!-- Draggable region -->
      <div class="titlebar-dragregion" data-tauri-drag-region>
        <div class="titlebar-title">My Application</div>
      </div>

      <!-- Window control buttons -->
      <div class="titlebar-controls">
        <button class="titlebar-button" id="minimize">
          <svg viewBox="0 0 10 1" fill="currentColor">
            <rect width="10" height="1" />
          </svg>
        </button>
        <button class="titlebar-button" id="maximize">
          <svg viewBox="0 0 10 10" fill="currentColor">
            <rect width="10" height="10" fill="none" stroke="currentColor" />
          </svg>
        </button>
        <button class="titlebar-button close" id="close">
          <svg viewBox="0 0 10 10" fill="currentColor">
            <line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" />
            <line x1="10" y1="0" x2="0" y2="10" stroke="currentColor" />
          </svg>
        </button>
      </div>
    </div>

    <!-- Main Content -->
    <div class="content">
      <h1>Custom Titlebar Example</h1>
      <p>This window has a custom titlebar!</p>
    </div>

    <script type="module">
      import { appWindow } from '@tauri-apps/api/window';

      // Minimize button
      document.getElementById('minimize').addEventListener('click', () => {
        appWindow.minimize();
      });

      // Maximize/Restore button
      document.getElementById('maximize').addEventListener('click', async () => {
        const isMaximized = await appWindow.isMaximized();
        if (isMaximized) {
          await appWindow.unmaximize();
        } else {
          await appWindow.maximize();
        }
      });

      // Close button
      document.getElementById('close').addEventListener('click', () => {
        appWindow.close();
      });
    </script>
  </body>
</html>

React Custom Titlebar Component

React titlebar component encapsulates custom titlebar logic with state management and reusability. Understanding component architecture enables building maintainable titlebars with proper window state tracking and responsive design.

typescriptTitlebar.tsx
// React Custom Titlebar Component
import React, { useState, useEffect } from 'react';
import { appWindow } from '@tauri-apps/api/window';
import './Titlebar.css';

interface TitlebarProps {
  title?: string;
}

const Titlebar: React.FC<TitlebarProps> = ({ title = 'My App' }) => {
  const [isMaximized, setIsMaximized] = useState(false);

  useEffect(() => {
    // Check initial maximize state
    appWindow.isMaximized().then(setIsMaximized);

    // Listen for resize events
    const unlisten = appWindow.onResized(() => {
      appWindow.isMaximized().then(setIsMaximized);
    });

    return () => {
      unlisten.then((fn) => fn());
    };
  }, []);

  const handleMinimize = () => {
    appWindow.minimize();
  };

  const handleMaximize = async () => {
    if (isMaximized) {
      await appWindow.unmaximize();
    } else {
      await appWindow.maximize();
    }
    setIsMaximized(!isMaximized);
  };

  const handleClose = () => {
    appWindow.close();
  };

  return (
    <div className="titlebar" data-tauri-drag-region>
      <div className="titlebar-content" data-tauri-drag-region>
        <div className="titlebar-icon">
          <img src="/icon.png" alt="App Icon" width="16" height="16" />
        </div>
        <div className="titlebar-title" data-tauri-drag-region>
          {title}
        </div>
      </div>

      <div className="titlebar-controls">
        <button
          className="titlebar-button"
          onClick={handleMinimize}
          aria-label="Minimize"
        >
          <MinimizeIcon />
        </button>
        <button
          className="titlebar-button"
          onClick={handleMaximize}
          aria-label={isMaximized ? 'Restore' : 'Maximize'}
        >
          {isMaximized ? <RestoreIcon /> : <MaximizeIcon />}
        </button>
        <button
          className="titlebar-button close"
          onClick={handleClose}
          aria-label="Close"
        >
          <CloseIcon />
        </button>
      </div>
    </div>
  );
};

// Icon Components
const MinimizeIcon = () => (
  <svg width="10" height="1" viewBox="0 0 10 1">
    <rect fill="currentColor" width="10" height="1" />
  </svg>
);

const MaximizeIcon = () => (
  <svg width="10" height="10" viewBox="0 0 10 10">
    <rect
      fill="none"
      stroke="currentColor"
      width="9"
      height="9"
      x="0.5"
      y="0.5"
    />
  </svg>
);

const RestoreIcon = () => (
  <svg width="10" height="10" viewBox="0 0 10 10">
    <g>
      <rect
        fill="none"
        stroke="currentColor"
        width="6"
        height="6"
        x="2"
        y="2"
      />
      <rect
        fill="currentColor"
        width="6"
        height="1"
        x="2"
        y="0"
      />
      <rect
        fill="currentColor"
        width="1"
        height="4"
        x="8"
        y="0"
      />
      <rect
        fill="currentColor"
        width="3"
        height="1"
        x="6"
        y="0"
      />
    </g>
  </svg>
);

const CloseIcon = () => (
  <svg width="10" height="10" viewBox="0 0 10 10">
    <line
      x1="1"
      y1="1"
      x2="9"
      y2="9"
      stroke="currentColor"
      strokeWidth="1.5"
    />
    <line
      x1="9"
      y1="1"
      x2="1"
      y2="9"
      stroke="currentColor"
      strokeWidth="1.5"
    />
  </svg>
);

export default Titlebar;

// Titlebar.css
.titlebar {
  height: 40px;
  background: #2c2c2c;
  display: flex;
  align-items: center;
  justify-content: space-between;
  user-select: none;
  -webkit-user-select: none;
}

.titlebar-content {
  flex: 1;
  display: flex;
  align-items: center;
  padding: 0 12px;
  height: 100%;
}

.titlebar-icon {
  display: flex;
  align-items: center;
  margin-right: 8px;
}

.titlebar-title {
  font-size: 13px;
  color: #ffffff;
  font-weight: 400;
}

.titlebar-controls {
  display: flex;
  height: 100%;
}

.titlebar-button {
  width: 46px;
  height: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: transparent;
  border: none;
  color: #ffffff;
  transition: background-color 0.15s;
}

.titlebar-button:hover {
  background: rgba(255, 255, 255, 0.1);
}

.titlebar-button.close:hover {
  background: #e81123;
  color: #ffffff;
}

.titlebar-button:active {
  background: rgba(255, 255, 255, 0.2);
}

.titlebar-button.close:active {
  background: #c50f1f;
}

// Usage in App
import Titlebar from './components/Titlebar';

function App() {
  return (
    <div className="app">
      <Titlebar title="My Application" />
      <div className="content">
        {/* Your app content */}
      </div>
    </div>
  );
}

Platform-Specific Titlebar Styles

Different operating systems have distinct titlebar conventions requiring platform-specific styling. Understanding platform differences enables building titlebars feeling native on each OS maintaining familiar patterns users expect from Windows, macOS, or Linux applications.

PlatformControl PositionHeightClose Button Color
WindowsRight side32-40pxRed hover (#e81123)
macOSLeft side (traffic lights)38-44pxRed, Yellow, Green dots
Linux (GNOME)Right side36-42pxThemed colors
Linux (KDE)Configurable36-42pxThemed colors
typescriptPlatformTitlebar.tsx
// Platform-specific titlebar styling
import { platform } from '@tauri-apps/api/os';
import { useEffect, useState } from 'react';

interface TitlebarProps {
  title: string;
}

const PlatformTitlebar: React.FC<TitlebarProps> = ({ title }) => {
  const [os, setOs] = useState<string>('windows');

  useEffect(() => {
    platform().then((platformName) => {
      setOs(platformName);
    });
  }, []);

  const isMacOS = os === 'darwin';
  const isWindows = os === 'win32';
  const isLinux = os === 'linux';

  return (
    <div className={`titlebar titlebar-${os}`} data-tauri-drag-region>
      {/* macOS: Traffic lights on left */}
      {isMacOS && (
        <div className="titlebar-macos-controls">
          <button className="macos-button close" onClick={handleClose}>
            <span></span>
          </button>
          <button className="macos-button minimize" onClick={handleMinimize}>
            <span></span>
          </button>
          <button className="macos-button maximize" onClick={handleMaximize}>
            <span></span>
          </button>
        </div>
      )}

      {/* Title - centered on macOS, left on Windows/Linux */}
      <div
        className={`titlebar-title ${
          isMacOS ? 'titlebar-title-center' : 'titlebar-title-left'
        }`}
        data-tauri-drag-region
      >
        {!isMacOS && (
          <img src="/icon.png" alt="icon" className="titlebar-icon" />
        )}
        {title}
      </div>

      {/* Windows/Linux: Controls on right */}
      {(isWindows || isLinux) && (
        <div className="titlebar-controls">
          <button className="titlebar-button" onClick={handleMinimize}>
            <MinimizeIcon />
          </button>
          <button className="titlebar-button" onClick={handleMaximize}>
            <MaximizeIcon />
          </button>
          <button className="titlebar-button close" onClick={handleClose}>
            <CloseIcon />
          </button>
        </div>
      )}
    </div>
  );
};

// Platform-specific CSS
/* Base titlebar */
.titlebar {
  height: 40px;
  display: flex;
  align-items: center;
  user-select: none;
  -webkit-user-select: none;
}

/* macOS specific */
.titlebar-darwin {
  height: 44px;
  background: transparent;
  -webkit-app-region: drag;
}

.titlebar-macos-controls {
  display: flex;
  gap: 8px;
  padding-left: 12px;
  z-index: 10;
}

.macos-button {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  position: relative;
}

.macos-button.close {
  background: #ff5f56;
}

.macos-button.minimize {
  background: #ffbd2e;
}

.macos-button.maximize {
  background: #27c93f;
}

.macos-button:hover::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.1);
  border-radius: 50%;
}

.titlebar-title-center {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  font-size: 13px;
  font-weight: 500;
}

/* Windows specific */
.titlebar-win32 {
  height: 32px;
  background: #2c2c2c;
}

/* Linux specific */
.titlebar-linux {
  height: 38px;
  background: #3c3c3c;
}

Advanced Titlebar with Menus and Search

Advanced titlebars integrate menus, search bars, or custom controls alongside window buttons. Understanding advanced patterns enables building VS Code or Chrome-style titlebars with tabs and functionality maintaining clean interface maximizing content space.

typescriptAdvancedTitlebar.tsx
// Advanced titlebar with menu and search
interface MenubarTitlebarProps {
  title: string;
  onSearch?: (query: string) => void;
}

const MenubarTitlebar: React.FC<MenubarTitlebarProps> = ({ 
  title,
  onSearch 
}) => {
  const [searchOpen, setSearchOpen] = useState(false);
  const [searchQuery, setSearchQuery] = useState('');

  const handleSearch = (e: React.FormEvent) => {
    e.preventDefault();
    onSearch?.(searchQuery);
  };

  return (
    <div className="advanced-titlebar" data-tauri-drag-region>
      {/* Menu section */}
      <div className="titlebar-menu">
        <button className="menu-button">File</button>
        <button className="menu-button">Edit</button>
        <button className="menu-button">View</button>
        <button className="menu-button">Help</button>
      </div>

      {/* Center section with title or search */}
      <div className="titlebar-center" data-tauri-drag-region>
        {searchOpen ? (
          <form onSubmit={handleSearch} className="search-form">
            <input
              type="text"
              placeholder="Search..."
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              className="search-input"
              autoFocus
            />
            <button type="button" onClick={() => setSearchOpen(false)}>
              ✕
            </button>
          </form>
        ) : (
          <div className="titlebar-title">{title}</div>
        )}
      </div>

      {/* Right section with icons and controls */}
      <div className="titlebar-actions">
        <button
          className="icon-button"
          onClick={() => setSearchOpen(true)}
          title="Search"
        >
          🔍
        </button>
        <button className="icon-button" title="Settings">
          ⚙️
        </button>

        <div className="window-controls">
          <button className="titlebar-button" onClick={handleMinimize}>
            <MinimizeIcon />
          </button>
          <button className="titlebar-button" onClick={handleMaximize}>
            <MaximizeIcon />
          </button>
          <button className="titlebar-button close" onClick={handleClose}>
            <CloseIcon />
          </button>
        </div>
      </div>
    </div>
  );
};

// CSS for advanced titlebar
.advanced-titlebar {
  height: 40px;
  background: #2c2c2c;
  display: flex;
  align-items: center;
  user-select: none;
}

.titlebar-menu {
  display: flex;
  padding: 0 8px;
  gap: 4px;
}

.menu-button {
  padding: 6px 12px;
  background: transparent;
  border: none;
  color: #ffffff;
  font-size: 13px;
  cursor: pointer;
  border-radius: 4px;
}

.menu-button:hover {
  background: rgba(255, 255, 255, 0.1);
}

.titlebar-center {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 20px;
}

.search-form {
  display: flex;
  align-items: center;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 4px;
  padding: 4px 8px;
  width: 100%;
  max-width: 400px;
}

.search-input {
  flex: 1;
  background: transparent;
  border: none;
  color: #ffffff;
  font-size: 13px;
  outline: none;
}

.titlebar-actions {
  display: flex;
  align-items: center;
  gap: 4px;
}

.icon-button {
  width: 32px;
  height: 32px;
  background: transparent;
  border: none;
  cursor: pointer;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.icon-button:hover {
  background: rgba(255, 255, 255, 0.1);
}

.window-controls {
  display: flex;
  height: 100%;
  margin-left: 8px;
}

Custom Titlebar Best Practices

  • Add data-tauri-drag-region: Mark draggable areas enabling window movement
  • Proper Height: Use 32-44px matching platform conventions
  • No Click-Through: Prevent clicks passing through titlebar to content
  • Keyboard Access: Support Alt+F4, Command+Q, and window shortcuts
  • Track Window State: Update maximize icon when window state changes
  • Platform Consistency: Match OS conventions for button placement
  • Accessibility: Add ARIA labels and keyboard navigation support
  • Double-Click Behavior: Handle double-click on titlebar to maximize
  • Dark Mode Support: Provide light and dark theme variants
  • Performance: Optimize rendering avoiding layout thrashing
Platform Testing Required: Custom titlebars behave differently across Windows, macOS, and Linux. Always test on all target platforms ensuring proper drag behavior, button placement, and keyboard shortcuts work as expected. macOS traffic lights require special handling!

Next Steps

Conclusion

Mastering custom titlebars in Tauri 2.0 enables building professional desktop applications providing unique brand identity while maintaining native window management functionality users expect creating polished user interfaces distinguishing your application from generic system windows. Custom titlebar combines HTML/CSS design flexibility creating pixel-perfect branded headers with data-tauri-drag-region attribute enabling window dragging, JavaScript window control APIs handling minimize/maximize/close actions, platform-specific styling adapting to Windows, macOS, and Linux conventions, and accessibility maintaining keyboard navigation and screen reader compatibility delivering custom window chrome without sacrificing usability or familiar behaviors. Understanding titlebar patterns including basic setup with disabled decorations and drag regions, React components with state management tracking window maximized state, platform detection styling appropriately for each OS, advanced integration with menus and search bars, and best practices maintaining accessibility and performance establishes foundation for building professional desktop applications delivering branded experiences maintaining native feel through familiar window controls and behaviors users trust. Your Tauri applications now possess powerful custom titlebar capabilities enabling features like branded window chrome, platform-specific styling, integrated menus and controls, modern aesthetics, and professional appearance delivering desktop experiences maintaining brand consistency while preserving native functionality users expect from professional applications!

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Categories

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.