or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-lifecycle.mdcore-ipc.mdevents.mdindex.mdmenu-system.mdsystem-integration.mdtesting.mdutilities.mdwindow-management.md

menu-system.mddocs/

0

# Menu System

1

2

The menu system provides comprehensive native menu functionality including menu bars, context menus, and system tray menus. It supports different menu item types with platform-specific behavior optimizations.

3

4

## Capabilities

5

6

### Menu Creation and Management

7

8

Create and manage application menus with support for hierarchical structures.

9

10

```typescript { .api }

11

/**

12

* Main menu class for creating menu bars and context menus

13

*/

14

class Menu {

15

/**

16

* Create a new menu

17

* @param opts - Menu creation options

18

* @returns Promise resolving to Menu instance

19

*/

20

static new(opts?: MenuOptions): Promise<Menu>;

21

22

/**

23

* Create a default application menu

24

* @returns Promise resolving to Menu instance with platform defaults

25

*/

26

static default(): Promise<Menu>;

27

28

/**

29

* Add menu items to the end of this menu

30

* @param items - Single item or array of items to add

31

*/

32

append<T extends MenuItemType>(items: T | T[]): Promise<void>;

33

34

/**

35

* Add menu items to the beginning of this menu

36

* @param items - Single item or array of items to add

37

*/

38

prepend<T extends MenuItemType>(items: T | T[]): Promise<void>;

39

40

/**

41

* Insert menu items at specific position

42

* @param items - Single item or array of items to insert

43

* @param position - Zero-based position to insert at

44

*/

45

insert<T extends MenuItemType>(items: T | T[], position: number): Promise<void>;

46

47

/**

48

* Remove a menu item from this menu

49

* @param item - Menu item instance to remove

50

*/

51

remove(item: MenuItemInstance): Promise<void>;

52

53

/**

54

* Remove menu item at specific position

55

* @param position - Zero-based position to remove

56

* @returns Removed menu item or null

57

*/

58

removeAt(position: number): Promise<MenuItemInstance | null>;

59

60

/**

61

* Get all menu items in this menu

62

* @returns Array of menu item instances

63

*/

64

items(): Promise<MenuItemInstance[]>;

65

66

/**

67

* Find menu item by ID

68

* @param id - Menu item identifier

69

* @returns Menu item instance or null if not found

70

*/

71

get(id: string): Promise<MenuItemInstance | null>;

72

73

/**

74

* Show this menu as a context menu

75

* @param at - Position to show menu (relative to window)

76

* @param window - Target window (defaults to current)

77

*/

78

popup(at?: Position, window?: Window): Promise<void>;

79

80

/**

81

* Set this menu as the application-wide menu

82

* @returns Previous app menu or null

83

*/

84

setAsAppMenu(): Promise<Menu | null>;

85

86

/**

87

* Set this menu as a window-specific menu (Windows/Linux only)

88

* @param window - Target window (defaults to current)

89

* @returns Previous window menu or null

90

*/

91

setAsWindowMenu(window?: Window): Promise<Menu | null>;

92

}

93

94

interface MenuOptions {

95

/** Menu identifier */

96

id?: string;

97

/** Initial menu items */

98

items?: MenuItemType[];

99

}

100

```

101

102

**Usage Examples:**

103

104

```typescript

105

import { Menu, MenuItem, Submenu } from '@tauri-apps/api/menu';

106

107

// Create basic menu

108

const menu = await Menu.new({

109

items: [

110

{ text: 'File', items: [

111

{ text: 'New', action: () => console.log('New file') },

112

{ text: 'Open', action: () => console.log('Open file') },

113

{ text: '---' }, // Separator

114

{ text: 'Exit', action: () => console.log('Exit') }

115

]},

116

{ text: 'Edit', items: [

117

{ text: 'Cut', accelerator: 'CmdOrCtrl+X' },

118

{ text: 'Copy', accelerator: 'CmdOrCtrl+C' },

119

{ text: 'Paste', accelerator: 'CmdOrCtrl+V' }

120

]}

121

]

122

});

123

124

// Set as application menu

125

await menu.setAsAppMenu();

126

127

// Create context menu

128

const contextMenu = await Menu.new({

129

items: [

130

{ text: 'Copy', action: () => navigator.clipboard.writeText('data') },

131

{ text: 'Paste', action: async () => {

132

const text = await navigator.clipboard.readText();

133

console.log('Pasted:', text);

134

}}

135

]

136

});

137

138

// Show context menu at mouse position

139

document.addEventListener('contextmenu', async (e) => {

140

e.preventDefault();

141

await contextMenu.popup({ x: e.clientX, y: e.clientY });

142

});

143

```

144

145

### Menu Item Types

146

147

Different types of menu items for various use cases.

148

149

```typescript { .api }

150

/**

151

* Standard menu item with text and action

152

*/

153

class MenuItem {

154

static new(opts: MenuItemOptions): Promise<MenuItem>;

155

156

text(): Promise<string>;

157

setText(text: string): Promise<void>;

158

isEnabled(): Promise<boolean>;

159

setEnabled(enabled: boolean): Promise<void>;

160

}

161

162

interface MenuItemOptions {

163

/** Menu item identifier */

164

id?: string;

165

/** Display text */

166

text: string;

167

/** Whether item is enabled */

168

enabled?: boolean;

169

/** Keyboard accelerator (e.g., 'CmdOrCtrl+N') */

170

accelerator?: string;

171

/** Action to execute when clicked */

172

action?: () => void;

173

}

174

175

/**

176

* Menu item with checkbox state

177

*/

178

class CheckMenuItem {

179

static new(opts: CheckMenuItemOptions): Promise<CheckMenuItem>;

180

181

isChecked(): Promise<boolean>;

182

setChecked(checked: boolean): Promise<void>;

183

}

184

185

interface CheckMenuItemOptions extends MenuItemOptions {

186

/** Initial checked state */

187

checked?: boolean;

188

}

189

190

/**

191

* Menu item with icon

192

*/

193

class IconMenuItem {

194

static new(opts: IconMenuItemOptions): Promise<IconMenuItem>;

195

196

setIcon(icon: Image | string): Promise<void>;

197

}

198

199

interface IconMenuItemOptions extends MenuItemOptions {

200

/** Menu item icon */

201

icon: Image | string;

202

}

203

204

/**

205

* Submenu containing other menu items

206

*/

207

class Submenu {

208

static new(opts: SubmenuOptions): Promise<Submenu>;

209

210

append<T extends MenuItemType>(items: T | T[]): Promise<void>;

211

prepend<T extends MenuItemType>(items: T | T[]): Promise<void>;

212

insert<T extends MenuItemType>(items: T | T[], position: number): Promise<void>;

213

items(): Promise<MenuItemInstance[]>;

214

}

215

216

interface SubmenuOptions {

217

/** Submenu identifier */

218

id?: string;

219

/** Submenu text */

220

text: string;

221

/** Whether submenu is enabled */

222

enabled?: boolean;

223

/** Child menu items */

224

items?: MenuItemType[];

225

}

226

227

/**

228

* Platform-specific predefined menu items

229

*/

230

class PredefinedMenuItem {

231

static new(opts: PredefinedMenuItemOptions): Promise<PredefinedMenuItem>;

232

}

233

234

interface PredefinedMenuItemOptions {

235

/** Predefined item type */

236

item: PredefinedMenuItemType;

237

/** Override default text */

238

text?: string;

239

}

240

241

type PredefinedMenuItemType =

242

| 'Copy'

243

| 'Cut'

244

| 'Paste'

245

| 'SelectAll'

246

| 'Undo'

247

| 'Redo'

248

| 'Minimize'

249

| 'Hide'

250

| 'HideOthers'

251

| 'ShowAll'

252

| 'CloseWindow'

253

| 'Quit'

254

| 'About'

255

| '---'; // Separator

256

```

257

258

**Usage Examples:**

259

260

```typescript

261

import { MenuItem, CheckMenuItem, IconMenuItem, Submenu, PredefinedMenuItem } from '@tauri-apps/api/menu';

262

263

// Standard menu item

264

const newItem = await MenuItem.new({

265

text: 'New Document',

266

accelerator: 'CmdOrCtrl+N',

267

action: () => createNewDocument()

268

});

269

270

// Checkbox menu item

271

const darkModeItem = await CheckMenuItem.new({

272

text: 'Dark Mode',

273

checked: true,

274

action: () => toggleDarkMode()

275

});

276

277

// Icon menu item

278

const saveItem = await IconMenuItem.new({

279

text: 'Save',

280

icon: '/icons/save.png',

281

accelerator: 'CmdOrCtrl+S',

282

action: () => saveDocument()

283

});

284

285

// Submenu

286

const fileSubmenu = await Submenu.new({

287

text: 'File',

288

items: [

289

{ text: 'New', action: () => console.log('New') },

290

{ text: 'Open', action: () => console.log('Open') },

291

{ text: '---' }, // Separator

292

{ text: 'Recent', items: [

293

{ text: 'file1.txt' },

294

{ text: 'file2.txt' }

295

]}

296

]

297

});

298

299

// Predefined menu items

300

const copyItem = await PredefinedMenuItem.new({ item: 'Copy' });

301

const separator = await PredefinedMenuItem.new({ item: '---' });

302

const quitItem = await PredefinedMenuItem.new({

303

item: 'Quit',

304

text: 'Exit Application' // Override default text

305

});

306

```

307

308

### Dynamic Menu Management

309

310

Modify menus at runtime based on application state.

311

312

```typescript

313

import { Menu, MenuItem } from '@tauri-apps/api/menu';

314

315

// Create dynamic menu that updates based on state

316

async function createDynamicMenu() {

317

const menu = await Menu.new();

318

319

// Add initial items

320

await menu.append([

321

{ text: 'File', items: [] },

322

{ text: 'Edit', items: [] }

323

]);

324

325

return menu;

326

}

327

328

// Update menu based on application state

329

async function updateMenuForState(menu: Menu, hasOpenFile: boolean) {

330

// Get File submenu

331

const fileSubmenu = await menu.get('file-menu') as Submenu;

332

333

if (hasOpenFile) {

334

// Add file-specific items

335

await fileSubmenu.append([

336

{ text: 'Save', accelerator: 'CmdOrCtrl+S' },

337

{ text: 'Save As...', accelerator: 'CmdOrCtrl+Shift+S' },

338

{ text: 'Close', accelerator: 'CmdOrCtrl+W' }

339

]);

340

} else {

341

// Remove file-specific items

342

const items = await fileSubmenu.items();

343

for (const item of items) {

344

if (['Save', 'Save As...', 'Close'].includes(await item.text())) {

345

await fileSubmenu.remove(item);

346

}

347

}

348

}

349

}

350

351

// Enable/disable menu items

352

async function setMenuItemState(menu: Menu, itemId: string, enabled: boolean) {

353

const item = await menu.get(itemId);

354

if (item && 'setEnabled' in item) {

355

await item.setEnabled(enabled);

356

}

357

}

358

```

359

360

### Platform-Specific Behavior

361

362

Handle platform differences in menu systems.

363

364

```typescript

365

import { Menu, Submenu } from '@tauri-apps/api/menu';

366

367

async function createPlatformOptimizedMenu() {

368

const menu = await Menu.new();

369

370

// macOS typically has app name as first menu

371

if (navigator.platform.includes('Mac')) {

372

const appSubmenu = await Submenu.new({

373

text: 'MyApp',

374

items: [

375

{ item: 'About' },

376

{ item: '---' },

377

{ text: 'Preferences', accelerator: 'Cmd+,' },

378

{ item: '---' },

379

{ item: 'Hide' },

380

{ item: 'HideOthers' },

381

{ item: 'ShowAll' },

382

{ item: '---' },

383

{ item: 'Quit' }

384

]

385

});

386

await menu.append(appSubmenu);

387

}

388

389

// File menu

390

const fileSubmenu = await Submenu.new({

391

text: 'File',

392

items: [

393

{ text: 'New', accelerator: 'CmdOrCtrl+N' },

394

{ text: 'Open', accelerator: 'CmdOrCtrl+O' },

395

{ item: '---' },

396

{ text: 'Save', accelerator: 'CmdOrCtrl+S' }

397

]

398

});

399

await menu.append(fileSubmenu);

400

401

// Window menu (common on macOS)

402

if (navigator.platform.includes('Mac')) {

403

const windowSubmenu = await Submenu.new({

404

text: 'Window',

405

items: [

406

{ item: 'Minimize' },

407

{ item: 'CloseWindow' }

408

]

409

});

410

await menu.append(windowSubmenu);

411

}

412

413

return menu;

414

}

415

```

416

417

### Menu Events and Actions

418

419

Handle menu item clicks and state changes.

420

421

```typescript

422

import { Menu, CheckMenuItem } from '@tauri-apps/api/menu';

423

424

// Create menu with event handlers

425

async function createMenuWithEvents() {

426

const menu = await Menu.new({

427

items: [

428

{

429

text: 'View',

430

items: [

431

{

432

text: 'Show Sidebar',

433

checked: true,

434

action: async () => {

435

const item = await menu.get('sidebar-toggle') as CheckMenuItem;

436

const isChecked = await item.isChecked();

437

438

// Toggle sidebar visibility

439

const sidebar = document.getElementById('sidebar');

440

if (sidebar) {

441

sidebar.style.display = isChecked ? 'block' : 'none';

442

}

443

444

console.log('Sidebar:', isChecked ? 'shown' : 'hidden');

445

}

446

},

447

{

448

text: 'Zoom In',

449

accelerator: 'CmdOrCtrl+Plus',

450

action: () => {

451

document.body.style.zoom = String(parseFloat(document.body.style.zoom || '1') + 0.1);

452

}

453

},

454

{

455

text: 'Zoom Out',

456

accelerator: 'CmdOrCtrl+-',

457

action: () => {

458

document.body.style.zoom = String(Math.max(0.5, parseFloat(document.body.style.zoom || '1') - 0.1));

459

}

460

},

461

{

462

text: 'Reset Zoom',

463

accelerator: 'CmdOrCtrl+0',

464

action: () => {

465

document.body.style.zoom = '1';

466

}

467

}

468

]

469

}

470

]

471

});

472

473

return menu;

474

}

475

```

476

477

## Type Definitions

478

479

```typescript { .api }

480

type MenuItemType =

481

| MenuItemOptions

482

| CheckMenuItemOptions

483

| IconMenuItemOptions

484

| SubmenuOptions

485

| PredefinedMenuItemOptions;

486

487

type MenuItemInstance =

488

| MenuItem

489

| CheckMenuItem

490

| IconMenuItem

491

| Submenu

492

| PredefinedMenuItem;

493

494

type Position = LogicalPosition | PhysicalPosition | { x: number; y: number };

495

```

496

497

## Error Handling

498

499

Menu operations can fail due to platform limitations or invalid parameters:

500

501

```typescript

502

import { Menu, MenuItem } from '@tauri-apps/api/menu';

503

504

try {

505

const menu = await Menu.new();

506

await menu.append({ text: 'Test Item' });

507

await menu.setAsAppMenu();

508

} catch (error) {

509

// Common error scenarios:

510

// - Invalid menu structure

511

// - Platform doesn't support operation

512

// - Menu item not found

513

// - Permission denied

514

console.error('Menu operation failed:', error);

515

}

516

```

517

518

## Best Practices

519

520

### Performance

521

- Cache menu instances rather than recreating them

522

- Use `get()` to find menu items instead of iterating through `items()`

523

- Batch menu modifications when possible

524

525

### User Experience

526

- Use standard accelerators (Ctrl+C, Ctrl+V, etc.)

527

- Follow platform conventions for menu organization

528

- Provide visual feedback for checkable items

529

- Use separators to group related functionality

530

531

### Platform Integration

532

- Use `Menu.default()` as a starting point for standard menus

533

- Test menu behavior on all target platforms

534

- Handle platform-specific limitations gracefully

535

- Use predefined menu items when available for better integration