Menus

Dropdown menu bars with keyboard shortcuts and command handling.

let file_menu = Menu::new("File")
    .command_with_shortcut("file.save", "Save", "Ctrl+S")
    .command_with_shortcut("file.quit", "Quit", "Ctrl+Q");

View::menu_bar()
    .menu(file_menu)
    .on_select(|cmd| {
        match cmd {
            "file.save" => save(),
            "file.quit" => quit(),
            _ => {}
        }
    })
    .build()

Run with: cargo run -p telex-tui --example 20_menu_bar

Limitations

Note: The current menu API doesn't support visually disabling menu items. You can handle disabled commands in your on_select handler (see Disabled items section below), but disabled items will still appear clickable in the menu.

Basic menu bar

Create a menu bar with dropdown menus:

let file_menu = Menu::new("File")
    .command("file.new", "New")
    .command("file.open", "Open")
    .command("file.quit", "Quit");

View::menu_bar()
    .menu(file_menu)
    .on_select(with!(state => move |cmd: &str| {
        handle_command(cmd, &state);
    }))
    .build()

Each menu has a label and a list of commands:

Menu::new("Edit")
    .command("edit.undo", "Undo")
    .command("edit.redo", "Redo")
    .separator()
    .command("edit.cut", "Cut")
    .command("edit.copy", "Copy")
    .command("edit.paste", "Paste")

.separator() adds a visual divider between groups of commands.

Command IDs

Commands have unique IDs (strings) that identify them when selected:

.command("namespace.action", "Display Label")

Use namespaced IDs like "file.save", "edit.copy" to avoid collisions.

Keyboard shortcuts

Show shortcuts next to menu items:

Menu::new("File")
    .command_with_shortcut("file.new", "New", "Ctrl+N")
    .command_with_shortcut("file.save", "Save", "Ctrl+S")
    .command_with_shortcut("file.quit", "Quit", "Ctrl+Q")

The shortcut is displayed but not automatically bound - you handle the actual keybinding separately.

Handling commands

Respond when users select menu items:

let on_select = with!(count => move |cmd: &str| {
    match cmd {
        "counter.increment" => count.update(|n| *n += 1),
        "counter.decrement" => count.update(|n| *n -= 1),
        "counter.reset" => count.set(0),
        _ => {}
    }
});

View::menu_bar()
    .menu(counter_menu)
    .on_select(on_select)
    .build()

Multiple menus

Add several menus to the bar:

View::menu_bar()
    .menu(file_menu)
    .menu(edit_menu)
    .menu(view_menu)
    .menu(help_menu)
    .on_select(on_select)
    .build()

Users navigate left/right between menus with arrow keys.

Keyboard navigation

Menu bars support full keyboard control:

  • Tab - Focus the menu bar
  • ←/→ - Switch between menus
  • Enter - Open the focused menu
  • ↑/↓ - Navigate items within an open menu
  • Enter - Execute the selected item
  • Escape - Close the menu

Track which menu and item are active:

let active_menu = state!(cx, || None::<usize>);
let highlighted_item = state!(cx, || 0usize);

View::menu_bar()
    .menu(file_menu)
    .menu(edit_menu)
    .active_menu(active_menu.get())
    .selected_item(highlighted_item.get())
    .on_menu_change(with!(active_menu => move |idx| {
        active_menu.set(idx);
    }))
    .on_item_change(with!(highlighted_item => move |idx| {
        highlighted_item.set(idx);
    }))
    .on_select(on_select)
    .build()

This lets you track and control menu state programmatically.

Implementing actual shortcuts

The menu bar shows shortcuts but doesn't bind them. Use use_command to implement the actual keys:

cx.use_command(
    KeyBinding::key(KeyCode::Char('s')).ctrl(true),
    with!(data => move || save_file(&data))
);

cx.use_command(
    KeyBinding::key(KeyCode::Char('q')).ctrl(true),
    move || std::process::exit(0)
);

Disabled items

You can't disable items directly in the current API, but you can handle disabled state in your command handler:

let on_select = with!(can_undo => move |cmd: &str| {
    match cmd {
        "edit.undo" if !can_undo.get() => {
            // Show "Nothing to undo" message
        }
        "edit.undo" => perform_undo(),
        _ => {}
    }
});

Real-world example

Application menu from Example 20:

let file_menu = Menu::new("File")
    .command_with_shortcut("file.new", "New", "Ctrl+N")
    .command_with_shortcut("file.open", "Open", "Ctrl+O")
    .command_with_shortcut("file.save", "Save", "Ctrl+S")
    .separator()
    .command_with_shortcut("file.quit", "Quit", "Ctrl+Q");

let edit_menu = Menu::new("Edit")
    .command_with_shortcut("edit.undo", "Undo", "Ctrl+Z")
    .command_with_shortcut("edit.redo", "Redo", "Ctrl+Y")
    .separator()
    .command_with_shortcut("edit.cut", "Cut", "Ctrl+X")
    .command_with_shortcut("edit.copy", "Copy", "Ctrl+C")
    .command_with_shortcut("edit.paste", "Paste", "Ctrl+V");

let on_select = move |cmd: &str| {
    match cmd {
        "file.new" => create_new_file(),
        "file.open" => open_file_dialog(),
        "file.save" => save_current_file(),
        "file.quit" => std::process::exit(0),
        "edit.undo" => undo_last_action(),
        "edit.redo" => redo_last_action(),
        "edit.cut" => cut_selection(),
        "edit.copy" => copy_selection(),
        "edit.paste" => paste_from_clipboard(),
        _ => {}
    }
};

View::menu_bar()
    .menu(file_menu)
    .menu(edit_menu)
    .on_select(on_select)
    .build()

Layout

The menu bar appears as the first row of your app. Put it at the top of a vstack:

View::vstack()
    .child(View::menu_bar().menu(...).build())
    .child(View::boxed().flex(1).child(main_content).build())
    .child(status_bar)
    .build()

Tips

Use namespaced IDs - Prefix commands with their menu: "file.save", "edit.undo".

Separate common groups - Use .separator() to group related commands (New/Open/Save, then Quit).

Show shortcuts - Even if users don't use them, shortcuts communicate what's possible.

Implement the shortcuts - Don't just display them - actually bind the keys with use_command.

Keep menus short - 3-7 items per menu is ideal. More than 10 gets unwieldy.

Standard order - File, Edit, View, Help is convention. Users expect this.

Command naming - Use verbs: "Save", "Copy", "Quit", not nouns.

Next: Toasts