desktop

Ship the same app to Windows, macOS and Linux, choosing a small Tauri or a familiar Electron shell.

PlatformsClientBuilt on Tauri 2, electron-vite

The desktop platform wraps your app/ in a native window for Windows, macOS and Linux. Pick the shell: Tauri (a small Rust binary around the system webview) or Electron (a bundled Chromium, with electron-vite).

import { desktop } from 'kitr/desktop'

export default defineKitrConfig({
  platforms: {
    site: web({ mode: 'spa' }),
    app: desktop({ shell: 'tauri', title: 'Acme', width: 1200, height: 800 }),
  },
})

Install

kitr add desktop app --shell tauri

First time? Run npm i -g kitr once. Inside a project, the global kitr runs your project's own version.

This installs @kitr/desktop and the shell's tooling, adds the router, platform and storage packages if missing, writes platforms.app into kitrrc.ts, registers the shell's drivers through kitr/desktop/provider, and scaffolds the native project under platforms/app/: src-tauri/ for Tauri, or main.ts, preload.ts and ipc.ts for Electron.

Run it

kitr dev app
kitr build app

First time? Run npm i -g kitr once. Inside a project, the global kitr runs your project's own version.

kitr dev app starts the Vite dev server and opens the window on it. kitr build app builds the web bundle into dist/app/client, then runs tauri build or electron-vite build and electron-builder for installers. kitr doctor checks the Rust toolchain for Tauri.

Options

Option Default Description
shell tauri tauri or electron
port 3200 Dev server port the window loads from
title, width, height, minWidth, minHeight The main window
appId Bundle identifier (reverse DNS)
devtools true Open devtools in development

Calling native code

Tauri: declare your Rust commands once and call them typed.

import { commandQuery, defineCommands } from 'kitr/desktop/tauri'

type Commands = { greet: (args: { name: string }) => string }
export const commands = defineCommands<Commands>()
export const greeting = commandQuery(commands.greet, 'greet')

Electron: one IPC schema shared by main, preload and the renderer (platforms/app/ipc.ts), handleIpc() in main, exposeIpc() in preload, and createIpcClient<Ipc>() in the app. Both degrade in a plain browser (ipc.available, isTauri()), so the same app/ ships to the web unchanged.