Skip to main content
NativePHP Desktop provides a comprehensive menu system for creating native application menus. The menu API supports standard menu items, submenus, keyboard shortcuts, and platform-specific menu roles. The MenuBuilder class provides a fluent API for creating menus:
use Native\Desktop\Facades\Menu;

Menu::create(
    Menu::app(),
    Menu::file(),
    Menu::edit(),
    Menu::view(),
    Menu::window()
);

Creating Menus

Menus are created using the Menu class and registered with the native runtime:
1

Create a Menu

$menu = Menu::make(
    Menu::label('File')->submenu(
        Menu::link(route('new'), 'New File', 'CmdOrCtrl+N'),
        Menu::separator(),
        Menu::quit()
    )
);
2

Register the Menu

$menu->register();
Or use create() to make and register in one call:
Menu::create(
    Menu::label('File'),
    Menu::label('Edit')
);
The Menu class implements a hierarchical structure:
class Menu implements JsonSerializable, MenuItem
{
    protected array $items = [];
    protected string $label = '';

    public function label(string $label): self
    {
        $this->label = $label;
        return $this;
    }

    public function add(MenuItem $item): self
    {
        $this->items[] = $item;
        return $this;
    }

    public function register(): void
    {
        $items = $this->toArray()['submenu'];

        $this->client->post('menu', [
            'items' => $items,
        ]);
    }
}
NativePHP supports several menu item types:

Labels (Clickable Items)

Menu::label('Preferences', 'CmdOrCtrl+,')
    ->submenu(
        Menu::label('General'),
        Menu::label('Advanced')
    );
Links navigate to URLs within your application: The Link class implementation:
class Link extends MenuItem
{
    protected string $type = 'link';
    protected bool $openInBrowser = false;

    public function __construct(
        protected string $url,
        protected ?string $label,
        protected ?string $accelerator = null
    ) {}

    public function openInBrowser(bool $openInBrowser = true): self
    {
        $this->openInBrowser = $openInBrowser;
        return $this;
    }

    public function toArray(): array
    {
        return array_merge(parent::toArray(), [
            'url' => $this->url,
            'openInBrowser' => $this->openInBrowser,
        ]);
    }
}

Separators

Visual separators between menu items:
Menu::create(
    Menu::label('File')->submenu(
        Menu::label('New'),
        Menu::label('Open'),
        Menu::separator(),  // Visual divider
        Menu::label('Save'),
        Menu::separator(),
        Menu::quit()
    )
);

Checkboxes

Toggleable menu items:
Menu::checkbox('Dark Mode', checked: true, hotkey: 'CmdOrCtrl+Shift+D');
Menu::checkbox('Show Sidebar', checked: false, hotkey: 'CmdOrCtrl+B');

Radio Buttons

Mutually exclusive options:
Menu::label('View')->submenu(
    Menu::radio('List View', checked: true),
    Menu::radio('Grid View', checked: false),
    Menu::radio('Column View', checked: false)
);
NativePHP provides platform-aware menu roles that automatically handle standard functionality:

Application Menu

Menu::app()  // Creates standard app menu (macOS)

File Menu

Menu::file('File')->submenu(
    Menu::label('New', 'CmdOrCtrl+N'),
    Menu::label('Open', 'CmdOrCtrl+O'),
    Menu::separator(),
    Menu::close('Close Window')
);

Edit Menu

Menu::edit('Edit')->submenu(
    Menu::undo(),
    Menu::redo(),
    Menu::separator(),
    Menu::cut(),
    Menu::copy(),
    Menu::paste(),
    Menu::pasteAndMatchStyle()
);

View Menu

Menu::view('View')->submenu(
    Menu::reload('Reload'),
    Menu::fullscreen('Toggle Fullscreen'),
    Menu::devTools('Developer Tools')
);

Window Menu

Menu::window('Window')->submenu(
    Menu::minimize('Minimize'),
    Menu::close('Close')
);
Menu roles automatically adapt to platform conventions. For example, Menu::app() creates a standard application menu on macOS with About, Preferences, Services, and Quit items.

Common Menu Roles

The MenuBuilder provides shortcut methods for common actions:
// Application
Menu::quit('Quit App');
Menu::about('About');
Menu::hide('Hide App');
Menu::help('Help');

// File operations
Menu::close('Close');

// Edit operations
Menu::undo('Undo');
Menu::redo('Redo');
Menu::cut('Cut');
Menu::copy('Copy');
Menu::paste('Paste');
Menu::pasteAndMatchStyle('Paste and Match Style');

// View operations
Menu::reload('Reload');
Menu::fullscreen('Toggle Fullscreen');
Menu::devTools('Toggle Developer Tools');

// Window operations
Menu::minimize('Minimize');

Keyboard Shortcuts

All menu items support keyboard shortcuts (accelerators):
Menu::label('Save', 'CmdOrCtrl+S');
Menu::label('Save As', 'CmdOrCtrl+Shift+S');
Menu::label('Preferences', 'CmdOrCtrl+,');

Platform-Aware Shortcuts

  • CmdOrCtrl - Command on macOS, Control on Windows/Linux
  • Cmd - Command (macOS only)
  • Ctrl - Control
  • Alt - Alt/Option
  • Shift - Shift

Modifier Combinations

Menu::label('Find', 'CmdOrCtrl+F');
Menu::label('Find Next', 'CmdOrCtrl+G');
Menu::label('Find Previous', 'CmdOrCtrl+Shift+G');
Menu::label('Zoom In', 'CmdOrCtrl+Plus');
Menu::label('Zoom Out', 'CmdOrCtrl+-');

Default Menu

Create a standard application menu with default items:
Menu::default();
This creates:
public function default(): void
{
    $this->create(
        $this->app(),
        $this->file(),
        $this->edit(),
        $this->view(),
        $this->window(),
    );
}

Handling Menu Events

Menu items dispatch events when clicked:
use Native\Desktop\Events\Menu\MenuItemClicked;

class MenuItemClicked implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public array $item,
        public array $combo = []
    ) {}

    public function broadcastOn()
    {
        return [new Channel('nativephp')];
    }
}
Listen for menu clicks in your event listener:
use Native\Desktop\Events\Menu\MenuItemClicked;

Event::listen(MenuItemClicked::class, function ($event) {
    $itemLabel = $event->item['label'];
    $hotkey = $event->combo;
    
    // Handle menu click
});
Create nested menu structures:
Menu::create(
    Menu::label('File')->submenu(
        Menu::label('New')->submenu(
            Menu::label('Text File'),
            Menu::label('Folder'),
            Menu::separator(),
            Menu::label('Project')
        ),
        Menu::label('Open'),
        Menu::separator(),
        Menu::quit()
    )
);

Dynamic Menus

Update menus based on application state:
$recentFiles = RecentFile::limit(5)->get();

$recentItems = $recentFiles->map(fn($file) => 
    Menu::link($file->path, $file->name)
);

Menu::create(
    Menu::label('File')->submenu(
        Menu::label('Open Recent')->submenu(...$recentItems),
        Menu::separator(),
        Menu::quit()
    )
);

Complete Example

Here’s a complete menu setup for a text editor application:
use Native\Desktop\Facades\Menu;

Menu::create(
    // Application menu (macOS)
    Menu::app(),
    
    // File menu
    Menu::label('File')->submenu(
        Menu::route('files.create', 'New File', 'CmdOrCtrl+N'),
        Menu::route('files.open', 'Open...', 'CmdOrCtrl+O'),
        Menu::separator(),
        Menu::route('files.save', 'Save', 'CmdOrCtrl+S'),
        Menu::route('files.save-as', 'Save As...', 'CmdOrCtrl+Shift+S'),
        Menu::separator(),
        Menu::close('Close Window')
    ),
    
    // Edit menu
    Menu::edit('Edit')->submenu(
        Menu::undo(),
        Menu::redo(),
        Menu::separator(),
        Menu::cut(),
        Menu::copy(),
        Menu::paste()
    ),
    
    // View menu
    Menu::view('View')->submenu(
        Menu::checkbox('Show Sidebar', true, 'CmdOrCtrl+B'),
        Menu::checkbox('Word Wrap', false),
        Menu::separator(),
        Menu::fullscreen(),
        Menu::devTools()
    ),
    
    // Window menu
    Menu::window('Window')->submenu(
        Menu::minimize(),
        Menu::label('Zoom')
    )
);
Menus are registered globally. Calling register() or create() will replace the existing menu structure.

Build docs developers (and LLMs) love