Menu

The Menu system provides hierarchical navigation menus with support for dynamic content, search functionality, and customizable layouts. Perfect for creating complex user interfaces with multiple levels of navigation.

Dark Menu

registerMenu

Register a menu definition that can be displayed later with customizable structure and behavior.

-- Client-side
B2Lib.UI.registerMenu(menuId, menuData)

-- Server-side (via exports)
exports.b2lib:registerMenu(menuId, menuData)

Parameters:

  • menuId (string): Unique menu identifier for registration and display

  • menuData (table): Menu configuration object

    • title (string, optional): Menu title displayed in header (default: empty string)

    • subtitle (string, optional): Menu subtitle displayed below title (default: empty string)

    • type (string, optional): Menu layout type - 'list', 'grid', 'compact' (default: 'list')

    • position (string, optional): Menu position on screen (default: from config)

    • showSearch (boolean, optional): Enable search functionality (default: false)

    • showHeader (boolean, optional): Show menu header with title/subtitle (default: true)

    • showClose (boolean, optional): Show close button in header (default: true)

    • maxHeight (number, optional): Maximum menu height in pixels (default: 600)

    • width (number, optional): Menu width in pixels (default: from config)

    • items (table, optional): Array of menu items (default: empty array)

    • searchPlaceholder (string, optional): Search input placeholder text

    • enableKeyboard (boolean, optional): Enable keyboard navigation (default: true)

    • closeOnSelect (boolean, optional): Close menu when item selected (default: true)

Returns: boolean - Success status

Example:

-- Basic menu registration
local success = B2Lib.UI.registerMenu('main_menu', {
    title = 'Main Menu',
    subtitle = 'Select an option',
    items = {
        { id = 'inventory', label = 'Inventory', icon = 'package', description = 'View your items' },
        { id = 'settings', label = 'Settings', icon = 'settings', description = 'Configure preferences' },
        { id = 'help', label = 'Help', icon = 'help-circle', description = 'Get assistance' }
    }
})

-- Advanced menu with search and submenus
B2Lib.UI.registerMenu('player_management', {
    title = 'Player Management',
    subtitle = 'Administrative tools',
    type = 'list',
    position = 'center-left',
    showSearch = true,
    searchPlaceholder = 'Search players...',
    maxHeight = 500,
    width = 450,
    items = {
        {
            id = 'kick_player',
            label = 'Kick Player',
            icon = 'user-x',
            description = 'Remove player from server',
            type = 'submenu',
            submenu = 'kick_submenu'
        },
        {
            id = 'ban_player',
            label = 'Ban Player',
            icon = 'ban',
            description = 'Permanently ban player',
            type = 'submenu',
            submenu = 'ban_submenu'
        },
        {
            id = 'teleport',
            label = 'Teleport to Player',
            icon = 'map-pin',
            description = 'Teleport to selected player'
        }
    }
})

-- Grid layout menu
B2Lib.UI.registerMenu('vehicle_spawner', {
    title = 'Vehicle Spawner',
    type = 'grid',
    position = 'center',
    showSearch = true,
    items = {
        { id = 'adder', label = 'Adder', icon = 'car', category = 'sports' },
        { id = 'zentorno', label = 'Zentorno', icon = 'car', category = 'sports' },
        { id = 'insurgent', label = 'Insurgent', icon = 'truck', category = 'military' }
    }
})

showMenu

Display a registered menu with optional display options and context data.

-- Client-side
B2Lib.UI.showMenu(menuId, options)

-- Server-side (via exports)
exports.b2lib:showMenu(playerId, menuId, options)

Parameters:

  • playerId (number, server-side only): Player server ID to show menu to

  • menuId (string): Menu ID to display (must be registered first)

  • options (table, optional): Additional display options

    • position (string, optional): Override default menu position

    • contextData (table, optional): Dynamic data to pass to menu items

    • selectedIndex (number, optional): Initially selected item index

    • searchTerm (string, optional): Pre-populate search field

    • isSubMenu (boolean, optional): Internal flag for submenu navigation

Returns: boolean - Success status

Example:

-- Basic menu display
local success = B2Lib.UI.showMenu('main_menu')

-- Menu with custom position and context
B2Lib.UI.showMenu('player_management', {
    position = 'center-right',
    contextData = {
        currentPlayer = GetPlayerServerId(PlayerId()),
        hasAdminPerms = true
    }
})

-- Menu with pre-selected item and search
B2Lib.UI.showMenu('vehicle_spawner', {
    selectedIndex = 2,
    searchTerm = 'sports',
    contextData = {
        playerMoney = 50000,
        allowedCategories = {'sports', 'sedans'}
    }
})

-- Server-side menu display
exports.b2lib:showMenu(playerId, 'admin_panel', {
    contextData = {
        targetPlayer = targetPlayerId,
        adminLevel = GetPlayerAdminLevel(playerId)
    }
})

hideMenu

Hide the currently active menu with optional animation and cleanup.

-- Client-side
B2Lib.UI.hideMenu()

-- Server-side (via exports)
exports.b2lib:hideMenu(playerId)

Parameters:

  • playerId (number, server-side only): Player server ID to hide menu for

Returns: boolean - Success status

Example:

-- Hide current menu
local success = B2Lib.UI.hideMenu()

-- Server-side menu hiding
exports.b2lib:hideMenu(playerId)

Navigate to a registered submenu while maintaining navigation history.

-- Client-side
B2Lib.UI.navigateToSubmenu(submenuId)

-- Server-side (via exports)
exports.b2lib:navigateToSubmenu(playerId, submenuId)

Parameters:

  • playerId (number, server-side only): Player server ID

  • submenuId (string): Submenu ID to navigate to (must be registered)

Returns: boolean - Success status

Example:

-- Navigate to submenu
local success = B2Lib.UI.navigateToSubmenu('settings_submenu')

-- Server-side submenu navigation
exports.b2lib:navigateToSubmenu(playerId, 'admin_tools_submenu')

updateMenuItems

Update menu items dynamically for an active or registered menu.

-- Client-side
B2Lib.UI.updateMenuItems(menuId, items)

-- Server-side (via exports)
exports.b2lib:updateMenuItems(playerId, menuId, items)

Parameters:

  • playerId (number, server-side only): Player server ID

  • menuId (string): Menu ID to update items for

  • items (table): New items array to replace existing items

Returns: boolean - Success status

Example:

-- Update menu items with new data
local newItems = {
    { id = 'new_option', label = 'New Option', icon = 'plus' },
    { id = 'updated_option', label = 'Updated Option', icon = 'edit' }
}
local success = B2Lib.UI.updateMenuItems('main_menu', newItems)

-- Server-side item updates
local playerItems = GetPlayerInventoryItems(playerId)
exports.b2lib:updateMenuItems(playerId, 'inventory_menu', playerItems)

getActiveMenu

Get information about the currently active menu.

B2Lib.UI.getActiveMenu()

Returns: table|nil - Active menu data or nil if no menu is active

  • id (string): Menu identifier

  • title (string): Menu title

  • type (string): Menu type

  • position (string): Menu position

  • itemCount (number): Number of items

  • selectedIndex (number): Currently selected item index

  • searchTerm (string): Current search term

Example:

local activeMenu = B2Lib.UI.getActiveMenu()
if activeMenu then
    print('Active menu:', activeMenu.id)
    print('Selected item:', activeMenu.selectedIndex)
end

isMenuActive

Check if a specific menu is currently active.

B2Lib.UI.isMenuActive(menuId)

Parameters:

  • menuId (string, optional): Menu ID to check (if nil, checks if any menu is active)

Returns: boolean - True if specified menu (or any menu) is active

Example:

-- Check if any menu is active
local anyMenuActive = B2Lib.UI.isMenuActive()

-- Check if specific menu is active
local mainMenuActive = B2Lib.UI.isMenuActive('main_menu')

Menu items support various properties for different functionality and appearance:

local menuItem = {
    id = 'unique_item_id',           -- Unique item identifier (required)
    label = 'Item Label',            -- Display text (required)
    description = 'Item description', -- Optional description/tooltip
    icon = 'icon_name',              -- Optional icon name (Lucide icons)
    type = 'action',                 -- Item type: 'action', 'submenu', 'separator', 'header'
    value = 'item_value',            -- Optional value/data associated with item
    disabled = false,                -- Whether item is disabled
    hidden = false,                  -- Whether item is hidden
    color = 'default',               -- Item color: 'default', 'primary', 'success', 'warning', 'error'
    badge = '5',                     -- Optional badge text/number
    shortcut = 'Ctrl+S',            -- Optional keyboard shortcut display
    submenu = 'submenu_id',         -- Submenu ID for submenu items
    category = 'general',            -- Optional category for filtering
    metadata = {},                   -- Custom metadata object
    onClick = function(item, menu)   -- Optional click handler function
        -- Handle item selection
    end
}

List Menu

Standard vertical list layout with icons and descriptions.

B2Lib.UI.registerMenu('list_menu', {
    title = 'List Menu Example',
    type = 'list',
    items = {
        { id = 'item1', label = 'First Item', icon = 'file', description = 'First item description' },
        { id = 'item2', label = 'Second Item', icon = 'folder', description = 'Second item description' }
    }
})

Grid Menu

Grid layout for visual items like vehicles, weapons, or categories.

B2Lib.UI.registerMenu('grid_menu', {
    title = 'Grid Menu Example',
    type = 'grid',
    items = {
        { id = 'car1', label = 'Sports Car', icon = 'car' },
        { id = 'car2', label = 'SUV', icon = 'truck' },
        { id = 'car3', label = 'Motorcycle', icon = 'bike' }
    }
})

Compact Menu

Minimal layout for simple option lists.

B2Lib.UI.registerMenu('compact_menu', {
    title = 'Compact Menu',
    type = 'compact',
    items = {
        { id = 'yes', label = 'Yes' },
        { id = 'no', label = 'No' },
        { id = 'cancel', label = 'Cancel' }
    }
})

Server-Side Usage

Server-side menu functions allow you to manage menus for specific players:

-- Register menu on server (available to all players)
exports.b2lib:registerMenu('server_menu', {
    title = 'Server Menu',
    items = {
        { id = 'announce', label = 'Make Announcement', icon = 'megaphone' },
        { id = 'restart', label = 'Restart Server', icon = 'refresh-cw' }
    }
})

-- Show menu to specific player
exports.b2lib:showMenu(playerId, 'server_menu')

-- Update menu for specific player
local adminItems = GetAdminMenuItems(playerId)
exports.b2lib:updateMenuItems(playerId, 'admin_menu', adminItems)

-- Hide menu for specific player
exports.b2lib:hideMenu(playerId)

Configuration

Complete menu configuration options in config.lua:

Config.Menu = {
    position = 'center-left',    -- Default position for menus
    width = 400,                 -- Default menu width in pixels
    enableSearch = true,         -- Enable search functionality
    closeOnClick = true,         -- Close menu when clicking outside
    maxItems = 10               -- Maximum items per menu page
}

Config.ComponentStyles.menu = {
    width = '400px',             -- Menu width
    maxHeight = '500px',         -- Maximum menu height
    padding = '16px',            -- Internal padding
    borderRadius = 'xl',         -- Large corner rounding
    shadow = 'xl',               -- Large drop shadow
    borderWidth = '1px',         -- Border thickness
    
    header = {
        padding = '12px 16px',       -- Header padding
        titleFontSize = 'lg',        -- Title text size
        titleFontWeight = 'semibold' -- Title font weight
    },
    
    item = {
        padding = '12px 16px',       -- Item padding
        borderRadius = 'md',         -- Item corner rounding
        fontSize = 'sm',             -- Item text size
        iconSize = '16px',           -- Item icon size
        spacing = '12px'             -- Spacing between items
    }
}

Advanced Examples

Dynamic Player List Menu

local function createPlayerListMenu()
    local players = GetActivePlayers()
    local playerItems = {}
    
    for _, playerId in ipairs(players) do
        local playerName = GetPlayerName(playerId)
        local playerPing = GetPlayerPing(playerId)
        
        table.insert(playerItems, {
            id = 'player_' .. playerId,
            label = playerName,
            description = 'Ping: ' .. playerPing .. 'ms',
            icon = 'user',
            value = playerId,
            badge = tostring(playerId)
        })
    end
    
    B2Lib.UI.registerMenu('player_list', {
        title = 'Online Players',
        subtitle = #playerItems .. ' players online',
        showSearch = true,
        searchPlaceholder = 'Search players...',
        items = playerItems
    })
    
    B2Lib.UI.showMenu('player_list')
end

-- Update player list every 5 seconds
CreateThread(function()
    while true do
        if B2Lib.UI.isMenuActive('player_list') then
            createPlayerListMenu()
        end
        Wait(5000)
    end
end)

Multi-Level Admin Menu

-- Main admin menu
B2Lib.UI.registerMenu('admin_main', {
    title = 'Admin Panel',
    subtitle = 'Administrative tools',
    items = {
        {
            id = 'player_management',
            label = 'Player Management',
            icon = 'users',
            description = 'Manage online players',
            type = 'submenu',
            submenu = 'admin_players'
        },
        {
            id = 'server_management',
            label = 'Server Management',
            icon = 'server',
            description = 'Server administration',
            type = 'submenu',
            submenu = 'admin_server'
        },
        {
            id = 'vehicle_management',
            label = 'Vehicle Management',
            icon = 'car',
            description = 'Spawn and manage vehicles',
            type = 'submenu',
            submenu = 'admin_vehicles'
        }
    }
})

-- Player management submenu
B2Lib.UI.registerMenu('admin_players', {
    title = 'Player Management',
    subtitle = 'Select a player action',
    items = {
        { id = 'kick', label = 'Kick Player', icon = 'user-x', description = 'Remove player from server' },
        { id = 'ban', label = 'Ban Player', icon = 'ban', description = 'Permanently ban player' },
        { id = 'teleport', label = 'Teleport to Player', icon = 'map-pin', description = 'Teleport to player location' },
        { id = 'spectate', label = 'Spectate Player', icon = 'eye', description = 'Watch player activity' }
    }
})

-- Server management submenu
B2Lib.UI.registerMenu('admin_server', {
    title = 'Server Management',
    subtitle = 'Server administration tools',
    items = {
        { id = 'announce', label = 'Server Announcement', icon = 'megaphone', description = 'Send message to all players' },
        { id = 'restart', label = 'Restart Server', icon = 'refresh-cw', description = 'Restart the server', color = 'warning' },
        { id = 'weather', label = 'Change Weather', icon = 'cloud', description = 'Modify weather conditions' },
        { id = 'time', label = 'Change Time', icon = 'clock', description = 'Adjust server time' }
    }
})

Inventory Menu with Categories

local function createInventoryMenu(playerItems)
    local categories = {}
    local menuItems = {}
    
    -- Group items by category
    for _, item in ipairs(playerItems) do
        if not categories[item.category] then
            categories[item.category] = {}
        end
        table.insert(categories[item.category], item)
    end
    
    -- Create category headers and items
    for categoryName, items in pairs(categories) do
        -- Add category header
        table.insert(menuItems, {
            id = 'header_' .. categoryName,
            label = categoryName:upper(),
            type = 'header',
            icon = 'folder'
        })
        
        -- Add category items
        for _, item in ipairs(items) do
            table.insert(menuItems, {
                id = item.name,
                label = item.label,
                description = 'Quantity: ' .. item.count,
                icon = item.icon or 'package',
                badge = tostring(item.count),
                value = item,
                category = categoryName
            })
        end
        
        -- Add separator after category
        table.insert(menuItems, {
            id = 'sep_' .. categoryName,
            type = 'separator'
        })
    end
    
    B2Lib.UI.registerMenu('inventory', {
        title = 'Inventory',
        subtitle = 'Your items',
        showSearch = true,
        searchPlaceholder = 'Search items...',
        maxHeight = 600,
        items = menuItems
    })
    
    B2Lib.UI.showMenu('inventory')
end

Context-Aware Vehicle Menu

local function createVehicleMenu(playerData)
    local vehicleItems = {}
    
    -- Add owned vehicles
    for _, vehicle in ipairs(playerData.ownedVehicles) do
        table.insert(vehicleItems, {
            id = 'owned_' .. vehicle.plate,
            label = vehicle.name,
            description = 'Plate: ' .. vehicle.plate,
            icon = 'car',
            badge = 'OWNED',
            color = 'success',
            value = vehicle
        })
    end
    
    -- Add rental vehicles if player has rental permissions
    if playerData.canRent then
        table.insert(vehicleItems, {
            id = 'separator_rental',
            type = 'separator'
        })
        
        for _, vehicle in ipairs(GetRentalVehicles()) do
            table.insert(vehicleItems, {
                id = 'rental_' .. vehicle.model,
                label = vehicle.name,
                description = 'Rental: $' .. vehicle.price .. '/hour',
                icon = 'key',
                badge = 'RENTAL',
                color = 'warning',
                value = vehicle
            })
        end
    end
    
    B2Lib.UI.registerMenu('vehicle_menu', {
        title = 'Vehicle Management',
        subtitle = playerData.ownedVehicles and #playerData.ownedVehicles .. ' owned vehicles' or 'No vehicles',
        showSearch = true,
        items = vehicleItems
    })
    
    B2Lib.UI.showMenu('vehicle_menu', {
        contextData = playerData
    })
end

Search-Enabled Command Menu

local function createCommandMenu()
    local commands = {
        { name = '/tp', description = 'Teleport to coordinates', category = 'teleport' },
        { name = '/tpplayer', description = 'Teleport to player', category = 'teleport' },
        { name = '/heal', description = 'Heal yourself', category = 'health' },
        { name = '/armor', description = 'Give armor', category = 'health' },
        { name = '/car', description = 'Spawn vehicle', category = 'vehicles' },
        { name = '/dv', description = 'Delete vehicle', category = 'vehicles' }
    }
    
    local menuItems = {}
    for _, cmd in ipairs(commands) do
        table.insert(menuItems, {
            id = cmd.name,
            label = cmd.name,
            description = cmd.description,
            icon = 'terminal',
            category = cmd.category,
            value = cmd.name
        })
    end
    
    B2Lib.UI.registerMenu('command_menu', {
        title = 'Command Reference',
        subtitle = 'Available commands',
        showSearch = true,
        searchPlaceholder = 'Search commands...',
        items = menuItems
    })
    
    B2Lib.UI.showMenu('command_menu')
end

Events

The Menu system triggers comprehensive events for handling user interactions:

b2lib:menu-item-selected

Triggered when user selects a menu item.

AddEventHandler('b2lib:menu-item-selected', function(data)
    print('Menu item selected:', data.item.id)
    print('Menu ID:', data.menuId)
    print('Item data:', json.encode(data.item))
    
    -- Handle different item types
    if data.item.type == 'submenu' then
        print('Navigating to submenu:', data.item.submenu)
    else
        print('Action item selected:', data.item.value)
    end
end)

b2lib:menu-closed

Triggered when a menu is closed.

AddEventHandler('b2lib:menu-closed', function(menuId)
    print('Menu closed:', menuId)
    
    -- Cleanup or save state
    if menuId == 'settings_menu' then
        SavePlayerSettings()
    end
end)

Triggered when user searches in a menu.

AddEventHandler('b2lib:menu-search', function(data)
    print('Menu search:', data.searchTerm)
    print('Results count:', data.resultCount)
    
    -- Update menu based on search
    if data.menuId == 'player_list' then
        UpdatePlayerListSearch(data.searchTerm)
    end
end)

b2lib:submenu-navigated

Triggered when navigating to a submenu.

AddEventHandler('b2lib:submenu-navigated', function(data)
    print('Navigated to submenu:', data.submenuId)
    print('From menu:', data.parentMenuId)
    
    -- Load submenu data
    if data.submenuId == 'admin_players' then
        LoadPlayerManagementData()
    end
end)

Best Practices

  1. Use Descriptive Menu IDs: Choose clear, unique identifiers that describe the menu's purpose

  2. Implement Search for Large Lists: Enable search functionality for menus with more than 10 items

  3. Provide Clear Item Descriptions: Include helpful descriptions for complex or ambiguous menu items

  4. Use Appropriate Icons: Select relevant Lucide icons that enhance visual understanding

  5. Group Related Items: Use separators and headers to organize menu items logically

  6. Handle Menu Events: Always implement event handlers for menu interactions

  7. Update Dynamic Content: Refresh menu items when underlying data changes

  8. Consider Performance: Avoid creating menus with hundreds of items; use pagination instead

  9. Test Keyboard Navigation: Ensure all menu functions work with keyboard-only input

  10. Provide Context Data: Pass relevant context information to menu items for dynamic behavior

Troubleshooting

Problem: Menu doesn't appear when calling showMenu()

Solutions:

-- Check if menu is registered
if not B2Lib.UI.isMenuRegistered('my_menu') then
    print('Menu not registered!')
    B2Lib.UI.registerMenu('my_menu', menuData)
end

-- Verify menu ID spelling
B2Lib.UI.showMenu('my_menu') -- Correct
-- B2Lib.UI.showMenu('my-menu') -- Wrong if registered with underscore

-- Check for conflicting UI elements
B2Lib.UI.hideMenu() -- Close any existing menu first
B2Lib.UI.showMenu('my_menu')

Problem: updateMenuItems() doesn't refresh the display

Solutions:

-- Ensure menu is active before updating
if B2Lib.UI.isMenuActive('my_menu') then
    B2Lib.UI.updateMenuItems('my_menu', newItems)
else
    print('Menu not active, cannot update items')
end

-- Force menu refresh
B2Lib.UI.hideMenu()
B2Lib.UI.registerMenu('my_menu', { items = newItems })
B2Lib.UI.showMenu('my_menu')

Problem: Submenu navigation not working properly

Solutions:

-- Ensure submenu is registered before navigation
B2Lib.UI.registerMenu('main_menu', mainMenuData)
B2Lib.UI.registerMenu('sub_menu', subMenuData) -- Register submenu separately

-- Check submenu ID in item definition
local menuItem = {
    id = 'settings',
    label = 'Settings',
    type = 'submenu',
    submenu = 'sub_menu' -- Must match registered submenu ID
}

-- Handle navigation events
AddEventHandler('b2lib:menu-item-selected', function(data)
    if data.item.type == 'submenu' then
        if not B2Lib.UI.isMenuRegistered(data.item.submenu) then
            print('Submenu not registered:', data.item.submenu)
        end
    end
end)

Search Functionality Not Working

Problem: Menu search doesn't filter items correctly

Solutions:

-- Enable search in menu registration
B2Lib.UI.registerMenu('searchable_menu', {
    title = 'Searchable Menu',
    showSearch = true, -- Must be enabled
    searchPlaceholder = 'Search items...',
    items = menuItems
})

-- Ensure items have searchable properties
local searchableItem = {
    id = 'item1',
    label = 'Searchable Label', -- Searched by default
    description = 'Searchable description', -- Also searched
    category = 'searchable_category', -- Include category for better search
    keywords = 'additional search terms' -- Custom search keywords
}

Performance Issues with Large Menus

Problem: Menu becomes slow with many items

Solutions:

-- Implement pagination for large datasets
local function createPaginatedMenu(allItems, page, itemsPerPage)
    local startIndex = (page - 1) * itemsPerPage + 1
    local endIndex = math.min(startIndex + itemsPerPage - 1, #allItems)
    local pageItems = {}
    
    for i = startIndex, endIndex do
        table.insert(pageItems, allItems[i])
    end
    
    -- Add navigation items
    if page > 1 then
        table.insert(pageItems, 1, {
            id = 'prev_page',
            label = '← Previous Page',
            icon = 'chevron-left'
        })
    end
    
    if endIndex < #allItems then
        table.insert(pageItems, {
            id = 'next_page',
            label = 'Next Page →',
            icon = 'chevron-right'
        })
    end
    
    return pageItems
end

-- Use virtual scrolling for very large lists
B2Lib.UI.registerMenu('large_menu', {
    title = 'Large Dataset',
    maxHeight = 400, -- Limit height to enable scrolling
    items = createPaginatedMenu(largeDataset, 1, 20)
})

Problem: Menu appears in wrong position or off-screen

Solutions:

-- Use valid position values
local validPositions = {
    'top-left', 'top-center', 'top-right',
    'center-left', 'center', 'center-right',
    'bottom-left', 'bottom-center', 'bottom-right'
}

-- Override position when showing menu
B2Lib.UI.showMenu('my_menu', {
    position = 'center' -- Override default position
})

-- Check screen resolution compatibility
B2Lib.UI.registerMenu('responsive_menu', {
    title = 'Responsive Menu',
    position = 'center', -- Safe default position
    width = math.min(400, GetScreenWidth() * 0.8) -- Responsive width
})

Last updated