diff --git a/src/content/docs/learn/window-menu.mdx b/src/content/docs/learn/window-menu.mdx
new file mode 100644
index 0000000000..81f0215420
--- /dev/null
+++ b/src/content/docs/learn/window-menu.mdx
@@ -0,0 +1,529 @@
+---
+title: Window Menu
+tableOfContents:
+ maxHeadingLevel: 4
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+Native application menus can be attached to both to a window or system tray. Available on desktop.
+
+## Creating a base-level menu
+
+To create a base-level native window menu, and attach to a window:
+
+
+
+
+Use the [`Menu.new`] static function to create a window menu:
+
+```javascript
+import { Menu } from '@tauri-apps/api/menu';
+
+const menu = await Menu.new({
+ items: [
+ {
+ id: 'quit',
+ text: 'Quit',
+ action: () => {
+ console.log('quit pressed');
+ },
+ },
+ ],
+});
+
+// If a window was not created with an explicit menu or had one set explicitly,
+// this menu will be assigned to it.
+menu.setAsAppMenu().then((res) => {
+ console.log('menu set success', res);
+});
+```
+
+
+
+
+
+```rust
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+use tauri::menu::{Menu, MenuItem};
+
+fn main() {
+ tauri::Builder::default()
+ .setup(|app| {
+ let menu = MenuBuilder::new(app)
+ .text("open", "Open")
+ .text("close", "Close")
+ .build()?;
+
+ app.set_menu(menu)?;
+
+ Ok(())
+ })
+}
+```
+
+
+
+
+## Listening to events on custom menu items
+
+Each custom menu item triggers an event when clicked. Use the `on_menu_event` API to handle them.
+
+
+
+
+```javascript
+import { Menu } from '@tauri-apps/api/menu';
+
+const menu = await Menu.new({
+ items: [
+ {
+ id: 'Open',
+ text: 'open',
+ action: () => {
+ console.log('open pressed');
+ },
+ },
+ {
+ id: 'Close',
+ text: 'close',
+ action: () => {
+ console.log('close pressed');
+ },
+ },
+ ],
+});
+
+await menu.setAsAppMenu();
+```
+
+
+
+
+
+```rust
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+use tauri::menu::{MenuBuilder};
+
+fn main() {
+ tauri::Builder::default()
+ .setup(|app| {
+ let menu = MenuBuilder::new(app)
+ .text("open", "Open")
+ .text("close", "Close")
+ .build()?;
+
+ app.set_menu(menu)?;
+
+ app.on_menu_event(move |app_handle: &tauri::AppHandle, event| {
+
+ println!("menu event: {:?}", event.id());
+
+ match event.id().0.as_str() {
+ "open" => {
+ println!("open event");
+ }
+ "close" => {
+ println!("close event");
+ }
+ _ => {
+ println!("unexpected menu event");
+ }
+ }
+ });
+
+ Ok(())
+ })
+}
+```
+
+
+
+
+## Creating a multi-level menu
+
+To create a multi-level menu, you can add some submenus to the menu item:
+
+
+
+
+```javascript
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+use tauri::{image::Image, menu::{CheckMenuItemBuilder, IconMenuItemBuilder, MenuBuilder, SubmenuBuilder}};
+
+fn main() {
+ tauri::Builder::default()
+ .setup(|app| {
+ let text_menu = SubmenuBuilder::new(app, "File")
+ .text("open", "Open")
+ .text("quit", "Quit")
+ .build()?;
+
+ let lang_str = "en";
+ let check_sub_item_1 = CheckMenuItemBuilder::new("English")
+ .id("en")
+ .checked(lang_str == "en")
+ .build(app)?;
+
+ let check_sub_item_2 = CheckMenuItemBuilder::new("Chinese")
+ .id("en")
+ .checked(lang_str == "en")
+ .enabled(false)
+ .build(app)?;
+
+ let icon_image = Image::from_bytes(include_bytes!("../icons/icon.png")).unwrap();
+
+ let icon_item = IconMenuItemBuilder::new("icon")
+ .icon(icon_image)
+ .build(app)?;
+
+ let check_menus = SubmenuBuilder::new(app, "language")
+ .item(&check_sub_item_1)
+ .item(&check_sub_item_2)
+ .build()?;
+
+
+ let menu = MenuBuilder::new(app)
+ .items(&[&text_menu, &check_menus, &icon_item])
+ .build()?;
+
+ app.set_menu(menu)?;
+
+ print!("Hello from setup");
+ Ok(())
+ })
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+```
+
+
+
+
+
+```rust
+use tauri::menu::{CheckMenuItemBuilder, MenuBuilder, SubmenuBuilder};
+
+fn main() {
+ tauri::Builder::default()
+ .setup(|app| {
+ let file_menu = SubmenuBuilder::new(app, "File")
+ .text("open", "Open")
+ .text("quit", "Quit")
+ .build()?;
+
+ let lang_str = "en";
+ let check_sub_item_1 = CheckMenuItemBuilder::new("English")
+ .id("en")
+ .checked(lang_str == "en")
+ .build(app)?;
+
+ let check_sub_item_2 = CheckMenuItemBuilder::new("Chinese")
+ .id("en")
+ .checked(lang_str == "en")
+ .enabled(false)
+ .build(app)?;
+
+ // Load icon from path
+ let icon_image = Image::from_bytes(include_bytes!("../icons/icon.png")).unwrap();
+
+ let icon_item = IconMenuItemBuilder::new("icon")
+ .icon(icon_image)
+ .build(app)?;
+
+ let other_item = SubmenuBuilder::new(app, "language")
+ .item(&check_sub_item_1)
+ .item(&check_sub_item_2)
+ .build()?;
+
+ let menu = MenuBuilder::new(app)
+ .items(&[&file_menu, &other_item,&icon_item])
+ .build()?;
+
+ app.set_menu(menu)?;
+
+ Ok(())
+ })
+}
+```
+
+Note that you need to enable `image-ico` or `image-png` feature to use this API:
+
+```toml title="src-tauri/Cargo.toml"
+[dependencies]
+tauri = { version = "...", features = ["...", "image-png"] }
+```
+
+
+
+
+## Creating predefined menu
+
+To use built-in (native) menu items that has predefined behavior by the operating system or Tauri:
+
+
+
+
+```javascript
+import { Menu, PredefinedMenuItem } from '@tauri-apps/api/menu';
+
+const copy = await PredefinedMenuItem.new({
+ text: 'copy-text',
+ item: 'Copy',
+});
+
+const separator = await PredefinedMenuItem.new({
+ text: 'separator-text',
+ item: 'Separator',
+});
+
+const undo = await PredefinedMenuItem.new({
+ text: 'undo-text',
+ item: 'Undo',
+});
+
+const redo = await PredefinedMenuItem.new({
+ text: 'redo-text',
+ item: 'Redo',
+});
+
+const cut = await PredefinedMenuItem.new({
+ text: 'cut-text',
+ item: 'Cut',
+});
+
+const paste = await PredefinedMenuItem.new({
+ text: 'paste-text',
+ item: 'Paste',
+});
+
+const select_all = await PredefinedMenuItem.new({
+ text: 'select_all-text',
+ item: 'SelectAll',
+});
+
+const menu = await Menu.new({
+ items: [copy, separator, undo, redo, cut, paste, select_all],
+});
+
+await menu.setAsAppMenu();
+```
+
+
+
+
+
+```rust
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+use tauri::menu::{MenuBuilder, PredefinedMenuItem};
+
+fn main() {
+ tauri::Builder::default()
+ .setup(|app| {
+ let menu = MenuBuilder::new(app)
+ .copy()
+ .separator()
+ .undo()
+ .redo()
+ .cut()
+ .paste()
+ .select_all()
+ .item(&PredefinedMenuItem::copy(app, Some("custom text"))?)
+ .build()?;
+ app.set_menu(menu)?;
+
+ Ok(())
+ })
+}
+```
+
+For more preset capabilities, please refer to the documentation [`PredefinedMenuItem`].
+
+:::tip
+The menu builder has dedicated methods to add each predefined menu item so you can call `.copy()` instead of `.item(&PredefinedMenuItem::copy(app, None)?)`.
+:::
+
+
+
+
+## Change menu status
+
+If you want to change the status of the menu, such as text, icon, or check status, you can `set_menu` again:
+
+
+
+
+```javascript
+import {
+ Menu,
+ CheckMenuItem,
+ IconMenuItem,
+ MenuItem,
+} from '@tauri-apps/api/menu';
+import { Image } from '@tauri-apps/api/image';
+
+let currentLanguage = 'en';
+
+const check_sub_item_en = await CheckMenuItem.new({
+ id: 'en',
+ text: 'English',
+ checked: currentLanguage === 'en',
+ action: () => {
+ currentLanguage = 'en';
+ check_sub_item_en.setChecked(currentLanguage === 'en');
+ check_sub_item_zh.setChecked(currentLanguage === 'cn');
+ console.log('English pressed');
+ },
+});
+
+const check_sub_item_zh = await CheckMenuItem.new({
+ id: 'zh',
+ text: 'Chinese',
+ checked: currentLanguage === 'zh',
+ action: () => {
+ currentLanguage = 'zh';
+ check_sub_item_en.setChecked(currentLanguage === 'en');
+ check_sub_item_zh.setChecked(currentLanguage === 'zh');
+ check_sub_item_zh.setAccelerator('Ctrl+L');
+ console.log('Chinese pressed');
+ },
+});
+
+// Load icon from path
+const icon = await Image.fromPath('../src/icon.png');
+const icon2 = await Image.fromPath('../src/icon-2.png');
+
+const icon_item = await IconMenuItem.new({
+ id: 'icon_item',
+ text: 'Icon Item',
+ icon: icon,
+ action: () => {
+ icon_item.setIcon(icon2);
+ console.log('icon pressed');
+ },
+});
+
+const text_item = await MenuItem.new({
+ id: 'text_item',
+ text: 'Text Item',
+ action: () => {
+ text_item.setText('Text Item Changed');
+ console.log('text pressed');
+ },
+});
+
+const menu = await Menu.new({
+ items: [
+ {
+ id: 'change menu',
+ text: 'change_menu',
+ items: [text_item, check_sub_item_en, check_sub_item_zh, icon_item],
+ },
+ ],
+});
+
+await menu.setAsAppMenu();
+```
+
+
+
+
+
+```rust
+// change-menu-status
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+use tauri::{
+ image::Image,
+ menu::{CheckMenuItemBuilder, IconMenuItem, MenuBuilder, MenuItem, SubmenuBuilder},
+};
+
+fn main() {
+ tauri::Builder::default()
+ .setup(|app| {
+ let check_sub_item_en = CheckMenuItemBuilder::with_id("en", "EN")
+ .checked(true)
+ .build(app)?;
+
+ let check_sub_item_zh = CheckMenuItemBuilder::with_id("zh", "ZH")
+ .checked(false)
+ .build(app)?;
+
+ let text_menu = MenuItem::with_id(
+ app,
+ "change_text",
+ &"Change menu".to_string(),
+ true,
+ Some("Ctrl+Z"),
+ )
+ .unwrap();
+
+ let icon_menu = IconMenuItem::with_id(
+ app,
+ "change_icon",
+ &"Change icon menu",
+ true,
+ Some(Image::from_bytes(include_bytes!("../icons/icon.png")).unwrap()),
+ Some("Ctrl+F"),
+ )
+ .unwrap();
+
+ let menu_item = SubmenuBuilder::new(app, "Change menu")
+ .item(&text_menu)
+ .item(&icon_menu)
+ .items(&[&check_sub_item_en, &check_sub_item_zh])
+ .build()?;
+ let menu = MenuBuilder::new(app).items(&[&menu_item]).build()?;
+ app.set_menu(menu)?;
+ app.on_menu_event(move |_app_handle: &tauri::AppHandle, event| {
+ match event.id().0.as_str() {
+ "change_text" => {
+ text_menu
+ .set_text("changed menu text")
+ .expect("Change text error");
+
+ text_menu
+ .set_text("changed menu text")
+ .expect("Change text error");
+ }
+ "change_icon" => {
+ icon_menu
+ .set_text("changed menu-icon text")
+ .expect("Change text error");
+ icon_menu
+ .set_icon(Some(
+ Image::from_bytes(include_bytes!("../icons/icon-2.png")).unwrap(),
+ ))
+ .expect("Change icon error");
+ }
+
+ "en" | "zh" => {
+ check_sub_item_en
+ .set_checked(event.id().0.as_str() == "en")
+ .expect("Change check error");
+ check_sub_item_zh
+ .set_checked(event.id().0.as_str() == "zh")
+ .expect("Change check error");
+ check_sub_item_zh.set_accelerator(Some("Ctrl+L"))
+ .expect("Change accelerator error");
+ }
+ _ => {
+ println!("unexpected menu event");
+ }
+ }
+ });
+
+ Ok(())
+ })
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+
+```
+
+
+
+
+[`PredefinedMenuItem`]: https://docs.rs/tauri/latest/tauri/menu/struct.PredefinedMenuItem.html
+[`Menu.new`]: https://v2.tauri.app/reference/javascript/api/namespacemenu/#new-2