This app supports macOS, Windows, and Linux. This guide covers platform-specific patterns, utilities, and configuration.
| Platform | Title Bar | Window Controls | Decorations |
|---|---|---|---|
| macOS | Custom + vibrancy | Left (traffic) | Hidden |
| Windows | Custom | Right | Hidden |
| Linux | Native + toolbar | Native | Visible |
Design decisions:
- macOS is the primary development target
- No Windows code signing (configure your own certificates)
- Linux uses native decorations for desktop environment compatibility
- Keyboard shortcuts use
mod+prefix (Cmd on macOS, Ctrl elsewhere)
Use the platform hook for UI decisions:
import { usePlatform, getPlatform } from '@/hooks/use-platform'
// In components
function MyComponent() {
const platform = usePlatform()
if (platform === 'macos') {
return <MacOSSpecificUI />
}
// ...
}
// In callbacks or non-hook contexts
function handleAction() {
const platform = getPlatform()
// ...
}Convenience hooks are also available:
import { useIsMacOS, useIsWindows, useIsLinux } from '@/hooks/use-platform'
function MyComponent() {
const isMacOS = useIsMacOS()
// ...
}Use conditional compilation for platform-specific code:
// Compile-time checks (preferred for performance)
#[cfg(target_os = "macos")]
fn macos_only() {
// Only compiled on macOS
}
#[cfg(target_os = "windows")]
fn windows_only() {
// Only compiled on Windows
}
#[cfg(target_os = "linux")]
fn linux_only() {
// Only compiled on Linux
}
// Runtime checks (for dynamic behavior)
use crate::utils::platform;
if platform::is_macos() {
// macOS runtime behavior
}Use getPlatformStrings() for platform-appropriate UI labels:
import { getPlatformStrings, formatShortcut } from '@/lib/platform-strings'
import { usePlatform } from '@/hooks/use-platform'
function FileMenu() {
const platform = usePlatform()
const strings = getPlatformStrings(platform)
return (
<MenuItem>
{strings.revealInFileManager} {/* "Reveal in Finder" or "Show in Explorer" */}
</MenuItem>
)
}
// Format keyboard shortcuts for display
formatShortcut('macos', 'K') // "⌘K"
formatShortcut('windows', 'K') // "Ctrl+K"
formatShortcut('macos', 'S', ['shift', 'mod']) // "⇧⌘S"Available strings:
| Property | macOS | Windows | Linux |
|---|---|---|---|
revealInFileManager |
"Reveal in Finder" | "Show in Explorer" | "Show in Files" |
fileManagerName |
"Finder" | "Explorer" | "Files" |
modifierKey |
"Cmd" | "Ctrl" | "Ctrl" |
modifierKeySymbol |
"⌘" | "Ctrl" | "Ctrl" |
optionKey |
"Option" | "Alt" | "Alt" |
preferencesLabel |
"Preferences" | "Settings" | "Preferences" |
quitLabel |
"Quit" | "Exit" | "Quit" |
trashName |
"Trash" | "Recycle Bin" | "Trash" |
The title bar system uses platform detection to render appropriate controls:
TitleBar.tsx (router)
├── macOS: MacOSWindowControls (left) + TitleBarContent
├── Windows: TitleBarContent + WindowsWindowControls (right)
└── Linux: LinuxTitleBar (toolbar only, no window controls)
In development, use the forcePlatform prop to preview other platform layouts:
// Only works in development builds
<TitleBar forcePlatform="windows" />
<TitleBar forcePlatform="linux" />If building custom title bars, use the shared components:
import {
TitleBarLeftActions,
TitleBarRightActions,
TitleBarTitle,
} from '@/components/titlebar'
function CustomTitleBar() {
return (
<div data-tauri-drag-region className="...">
<TitleBarLeftActions />
<TitleBarTitle title="My App" />
<TitleBarRightActions />
</div>
)
}Windows uses backslashes (\) in paths, but the frontend expects forward slashes (/). Normalize paths when sending from Rust to React:
use crate::utils::platform::normalize_path_for_serialization;
#[tauri::command]
fn get_file_path() -> String {
let path = some_path();
// Converts "C:\Users\foo\file.txt" to "C:/Users/foo/file.txt"
normalize_path_for_serialization(&path)
}The frontend can then use paths consistently:
// Works on all platforms
const parts = filePath.split('/')Tauri v2 automatically merges platform-specific config files using JSON Merge Patch.
src-tauri/
├── tauri.conf.json # Base config (safe defaults)
├── tauri.macos.conf.json # macOS overrides
├── tauri.windows.conf.json # Windows overrides
└── tauri.linux.conf.json # Linux overrides
Base config (tauri.conf.json):
{
"app": {
"windows": [
{
"decorations": true,
"transparent": false
}
]
}
}macOS (tauri.macos.conf.json):
{
"app": {
"windows": [
{
"decorations": false,
"transparent": true,
"windowEffects": {
"effects": ["hudWindow"]
}
}
]
}
}Windows (tauri.windows.conf.json):
{
"app": {
"windows": [
{
"decorations": false,
"transparent": false
}
]
}
}Linux (tauri.linux.conf.json):
{
"app": {
"windows": [
{
"decorations": true,
"transparent": false
}
]
}
}JSON Merge Patch replaces arrays entirely, not element-by-element. Each platform config must include the complete windows array with all properties, not just overrides.
# Runs on current platform
npm run devBuilds are platform-specific. You can only build for your current OS (cross-compilation requires additional setup):
# Build for current platform
npm run buildThe GitHub Actions release workflow builds for all platforms:
| Platform | Runner | Output |
|---|---|---|
| macOS | macos-latest |
.dmg |
| Windows | windows-latest |
.msi |
| Linux | ubuntu-22.04 |
.AppImage |
See .github/workflows/release.yml for the full configuration.
Linux builds require additional system libraries:
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
patchelfWindows requires an additional CSS rule for drag regions to work with touch/pen input:
*[data-tauri-drag-region] {
app-region: drag;
}This is already included in the app's global styles.
For opacity transitions on Windows title bars, use transform-gpu to fix WebKit rendering quirks:
<div className="transform-gpu transition-opacity" />- Test with
forcePlatform- Verify layouts for all platforms during development - Use platform strings - Never hardcode platform-specific labels
- Normalize paths - Always convert Windows paths before sending to frontend
- Prefer conditional compilation - Use
#[cfg(...)]over runtime checks in Rust when possible - Keep Linux simple - Native decorations work best across desktop environments
- Document platform assumptions - Note any platform-specific behavior in comments
| File | Purpose |
|---|---|
src/hooks/use-platform.ts |
Platform detection hooks |
src/lib/platform-strings.ts |
Platform-specific UI strings |
src/components/titlebar/TitleBar.tsx |
Platform-aware title bar |
src-tauri/src/utils/platform.rs |
Rust platform utilities |
src-tauri/tauri.*.conf.json |
Platform-specific configs |