Radial Menu
The Radial Menu module provides sophisticated circular, gesture-based menu interfaces with dynamic positioning, multi-level navigation, and comprehensive interaction handling. Perfect for weapon wheels, quick actions, immersive gameplay interactions, and creating intuitive radial interfaces that enhance user experience with smooth animations and intelligent layout management.

showRadial
Display a radial menu with comprehensive configuration options and advanced positioning control.
-- Client-side
B2Lib.UI.showRadial(options)
-- Server-side (via exports)
exports.b2lib:showRadial(playerId, options)
Parameters:
playerId
(number, server-side only): Player server ID to show radial menu tooptions
(table, required): Comprehensive radial menu configuration object
Configuration Options:
id
(string, optional): Unique radial menu identifier for management and events (default: auto-generated UUID)items
(table, optional): Array of radial menu items with full configuration (default: empty array)radius
(number, optional): Base radius in pixels with responsive scaling (default: 100)style
(string, optional): Visual style preset ('default', 'minimal', 'modern', 'classic') (default: 'default')showLabels
(boolean, optional): Display item labels with positioning control (default: true)showCenter
(boolean, optional): Show center circle with interactive capabilities (default: true)showConnectors
(boolean, optional): Display lines connecting items to center with styling (default: false)centerIcon
(string, optional): Lucide icon to display in center circlecenterText
(string, optional): Text to display in center with formatting supportcenterSize
(number, optional): Center circle size in pixels with scaling (default: 60)itemSize
(number, optional): Item circle size in pixels with responsive scaling (default: 50)animationDuration
(number, optional): Animation duration in milliseconds (default: 300)expansionDelay
(number, optional): Delay between item animations in milliseconds (default: 50)selectionDeadzone
(number, optional): Center deadzone radius in pixels (default: 35)enableDynamicRadius
(boolean, optional): Auto-adjust radius based on item count (default: false)radiusPerItem
(number, optional): Additional radius per item when dynamic (default: 8)minRadius
(number, optional): Minimum radius when dynamic scaling (default: 80)maxRadius
(number, optional): Maximum radius when dynamic scaling (default: 180)position
(table, optional): Custom positioning with anchor pointstheme
(string, optional): Theme override ('auto', 'light', 'dark')zIndex
(number, optional): Z-index for menu layering (default: 1000)closeOnSelection
(boolean, optional): Auto-close after item selection (default: true)enableHapticFeedback
(boolean, optional): Controller vibration feedback (default: true)contextData
(table, optional): Context data passed to event handlers
Returns: boolean - Success status indicating radial menu display completion
Example:
-- Basic radial menu
local success = B2Lib.UI.showRadial({
items = {
{ label = 'Inventory', value = 'inventory', icon = 'package' },
{ label = 'Phone', value = 'phone', icon = 'smartphone' },
{ label = 'Vehicle', value = 'vehicle', icon = 'car' },
{ label = 'Settings', value = 'settings', icon = 'settings' }
}
})
-- Advanced weapon wheel with comprehensive configuration
B2Lib.UI.showRadial({
id = 'weapon_wheel',
items = {
{ label = 'Pistol', value = 'weapon_pistol', icon = 'gun', badge = '250', badgeColor = '#10b981' },
{ label = 'Rifle', value = 'weapon_rifle', icon = 'rifle', badge = '120', badgeColor = '#f59e0b' },
{ label = 'Shotgun', value = 'weapon_shotgun', icon = 'shotgun', badge = '45', badgeColor = '#ef4444' },
{ label = 'Melee', value = 'weapon_melee', icon = 'sword', disabled = false }
},
radius = 120,
centerIcon = 'crosshair',
centerText = 'Weapons',
showConnectors = true,
animationDuration = 250,
expansionDelay = 30,
style = 'modern',
enableHapticFeedback = true,
contextData = {
playerId = PlayerId(),
currentWeapon = GetSelectedPedWeapon(PlayerPedId())
}
})
-- Dynamic radius radial menu with conditional items
B2Lib.UI.showRadial({
items = getPlayerActions(), -- Custom function returning many items
enableDynamicRadius = true,
radiusPerItem = 10,
minRadius = 100,
maxRadius = 200,
showLabels = true,
centerText = 'Actions',
position = { x = 960, y = 540, anchor = 'center' },
theme = 'auto'
})
-- Server-side radial menu for admin tools
exports.b2lib:showRadial(playerId, {
id = 'admin_tools',
items = {
{ label = 'Player Management', value = 'players', icon = 'users', badge = tostring(#GetPlayers()) },
{ label = 'Vehicle Spawn', value = 'vehicles', icon = 'car' },
{ label = 'Teleport Tools', value = 'teleport', icon = 'zap' },
{ label = 'Weather Control', value = 'weather', icon = 'cloud' }
},
centerIcon = 'shield-check',
centerText = 'Admin',
radius = 130,
showConnectors = true
})
hideRadial
Hide the currently active radial menu with smooth animation and proper cleanup.
-- Client-side
B2Lib.UI.hideRadial()
-- Server-side (via exports)
exports.b2lib:hideRadial(playerId)
Parameters:
playerId
(number, server-side only): Player server ID to hide radial menu for
Returns: boolean - Success status indicating radial menu hide completion
Example:
-- Hide the current radial menu
local success = B2Lib.UI.hideRadial()
-- Hide radial menu when player moves
CreateThread(function()
while true do
Wait(100)
if IsControlPressed(0, 21) then -- Sprint
B2Lib.UI.hideRadial()
end
end
end)
-- Server-side hide for specific player
exports.b2lib:hideRadial(playerId)
updateRadialData
Update context data for the currently active radial menu with real-time data refresh.
B2Lib.UI.updateRadialData(contextData)
Parameters:
contextData
(table, required): Updated context data object with new values
Returns: boolean - Success status indicating data update completion
Example:
-- Update radial context data dynamically
local success = B2Lib.UI.updateRadialData({
currentWeapon = GetSelectedPedWeapon(PlayerPedId()),
ammoCount = GetAmmoInPedWeapon(PlayerPedId(), GetSelectedPedWeapon(PlayerPedId())),
playerHealth = GetEntityHealth(PlayerPedId())
})
-- Update vehicle radial data
B2Lib.UI.updateRadialData({
vehicle = GetVehiclePedIsIn(PlayerPedId(), false),
engineHealth = GetVehicleEngineHealth(vehicle),
fuelLevel = GetVehicleFuelLevel(vehicle)
})
isRadialVisible
Check if a radial menu is currently visible and get active menu information.
B2Lib.UI.isRadialVisible()
Returns: boolean|table - False if no radial menu is active, or table with active menu data
id
(string): Current radial menu identifieritemCount
(number): Number of menu itemsradius
(number): Current menu radiuscontextData
(table): Current context data
Example:
local radialState = B2Lib.UI.isRadialVisible()
if radialState then
print('Radial menu is visible:', radialState.id)
print('Item count:', radialState.itemCount)
print('Radius:', radialState.radius)
else
print('No radial menu is currently visible')
end
updateRadialItems
Update a radial menu's items dynamically while maintaining position and state.
B2Lib.UI.updateRadialItems(items)
Parameters:
items
(table, required): Updated array of radial menu items
Returns: boolean - Success status indicating items update completion
Example:
-- Update weapon wheel based on current weapons
local weapons = getCurrentWeapons() -- Custom function
B2Lib.UI.updateRadialItems(weapons)
-- Update admin tools based on permissions
local adminItems = getAdminItems(getPlayerAdminLevel()) -- Custom function
B2Lib.UI.updateRadialItems(adminItems)
Radial Menu Item Structure
Radial menu items support comprehensive properties for advanced functionality and visual customization:
local radialItem = {
label = 'Item Label', -- Display text (required)
value = 'item_value', -- Item value/ID for identification (required)
icon = 'icon_name', -- Lucide icon name (required)
description = 'Item description', -- Optional tooltip description text
disabled = false, -- Whether item is disabled/non-interactive
color = '#ffffff', -- Custom item text color
backgroundColor = '#333333', -- Custom item background color
borderColor = '#666666', -- Custom item border color
badge = '5', -- Optional badge text/number for notifications
badgeColor = '#ff0000', -- Badge background color
badgeTextColor = '#ffffff', -- Badge text color
shortcut = 'F1', -- Optional keyboard shortcut display
progress = 75, -- Optional progress indicator (0-100)
progressColor = '#10b981', -- Progress bar color
condition = function(contextData) -- Optional condition function for dynamic visibility
return contextData.hasPermission
end,
onClick = function(contextData) -- Optional custom click handler
print('Custom action executed')
end,
submenu = { -- Optional submenu items for nested navigation
{ label = 'Sub Item 1', value = 'sub1', icon = 'circle' },
{ label = 'Sub Item 2', value = 'sub2', icon = 'square' }
},
style = { -- Optional custom styling
fontSize = '14px',
fontWeight = 'bold',
iconSize = '20px',
borderRadius = '50%'
},
animation = { -- Optional custom animation settings
duration = 300,
delay = 0,
easing = 'ease-out'
}
}
Server-Side Usage
The Radial Menu module provides comprehensive server-side functionality for managing player-specific radial menus and administrative tools:
-- Show radial menu to specific player
exports.b2lib:showRadial(playerId, {
id = 'admin_tools',
items = {
{ label = 'Player Management', value = 'players', icon = 'users', badge = tostring(#GetPlayers()) },
{ label = 'Vehicle Spawn', value = 'vehicles', icon = 'car' },
{ label = 'Teleport Tools', value = 'teleport', icon = 'zap' },
{ label = 'Weather Control', value = 'weather', icon = 'cloud' },
{ label = 'Time Control', value = 'time', icon = 'clock' }
},
centerIcon = 'shield-check',
centerText = 'Admin Tools',
radius = 140,
showConnectors = true,
contextData = {
adminLevel = getPlayerAdminLevel(playerId),
playerCount = #GetPlayers(),
serverTime = os.time()
}
})
-- Hide radial menu for specific player
exports.b2lib:hideRadial(playerId)
-- Show radial menu to multiple players
local function showRadialToPlayers(playerIds, options)
for _, playerId in ipairs(playerIds) do
exports.b2lib:showRadial(playerId, options)
end
end
-- Show radial menu to all admins
local function showAdminRadial(options)
local players = GetPlayers()
for _, playerId in ipairs(players) do
if IsPlayerAceAllowed(playerId, 'admin') then
exports.b2lib:showRadial(tonumber(playerId), options)
end
end
end
-- Example: Dynamic admin radial based on permissions
RegisterNetEvent('admin:showRadialMenu')
AddEventHandler('admin:showRadialMenu', function()
local source = source
local adminLevel = getPlayerAdminLevel(source)
if adminLevel < 1 then
TriggerClientEvent('b2lib:notify', source, {
type = 'error',
message = 'Insufficient permissions'
})
return
end
local items = {}
-- Basic admin tools
if adminLevel >= 1 then
table.insert(items, { label = 'Player List', value = 'players', icon = 'users' })
table.insert(items, { label = 'Kick Player', value = 'kick', icon = 'user-x', color = '#f59e0b' })
end
-- Advanced admin tools
if adminLevel >= 2 then
table.insert(items, { label = 'Ban Player', value = 'ban', icon = 'shield-off', color = '#ef4444' })
table.insert(items, { label = 'Vehicle Spawn', value = 'vehicles', icon = 'car' })
table.insert(items, { label = 'Teleport', value = 'teleport', icon = 'zap' })
end
-- Super admin tools
if adminLevel >= 3 then
table.insert(items, { label = 'Weather', value = 'weather', icon = 'cloud' })
table.insert(items, { label = 'Time', value = 'time', icon = 'clock' })
table.insert(items, { label = 'Server Control', value = 'server', icon = 'server', color = '#ef4444' })
end
exports.b2lib:showRadial(source, {
id = 'admin_radial',
items = items,
centerIcon = 'shield-check',
centerText = 'Admin Level ' .. adminLevel,
radius = 120 + (#items * 5), -- Dynamic radius based on item count
enableDynamicRadius = true,
contextData = {
adminLevel = adminLevel,
playerId = source,
timestamp = os.time()
}
})
end)
-- Example: Zone-based radial menus
local function showZoneRadial(coords, radius, radialOptions)
local players = GetPlayers()
for _, playerId in ipairs(players) do
local playerPed = GetPlayerPed(playerId)
if playerPed and DoesEntityExist(playerPed) then
local playerCoords = GetEntityCoords(playerPed)
local distance = #(playerCoords - coords)
if distance <= radius then
exports.b2lib:showRadial(tonumber(playerId), radialOptions)
end
end
end
end
-- Example: Event-based radial management
RegisterNetEvent('server:showVehicleRadial')
AddEventHandler('server:showVehicleRadial', function(vehicleNetId)
local source = source
local vehicle = NetworkGetEntityFromNetworkId(vehicleNetId)
if DoesEntityExist(vehicle) then
local model = GetEntityModel(vehicle)
local displayName = GetDisplayNameFromVehicleModel(model)
local plate = GetVehicleNumberPlateText(vehicle)
exports.b2lib:showRadial(source, {
id = 'vehicle_radial',
items = {
{ label = 'Enter Vehicle', value = 'enter', icon = 'car' },
{ label = 'Lock/Unlock', value = 'lock', icon = 'lock' },
{ label = 'Open Hood', value = 'hood', icon = 'wrench' },
{ label = 'Open Trunk', value = 'trunk', icon = 'package' },
{ label = 'Vehicle Info', value = 'info', icon = 'info' }
},
centerIcon = 'car',
centerText = displayName,
radius = 110,
contextData = {
vehicle = vehicle,
model = model,
plate = plate,
playerId = source
}
})
end
end)
-- Example: Timed radial menus
local function showTimedRadial(playerId, options, duration)
exports.b2lib:showRadial(playerId, options)
SetTimeout(duration, function()
exports.b2lib:hideRadial(playerId)
end)
end
-- Example: Permission-based radial items
local function getPermissionBasedItems(playerId)
local items = {}
-- Basic items for all players
table.insert(items, { label = 'Inventory', value = 'inventory', icon = 'package' })
table.insert(items, { label = 'Phone', value = 'phone', icon = 'smartphone' })
-- VIP items
if IsPlayerAceAllowed(playerId, 'vip') then
table.insert(items, { label = 'VIP Lounge', value = 'vip', icon = 'crown', color = '#fbbf24' })
end
-- Police items
if IsPlayerAceAllowed(playerId, 'police') then
table.insert(items, { label = 'Police Radio', value = 'radio', icon = 'radio', color = '#3b82f6' })
table.insert(items, { label = 'Arrest', value = 'arrest', icon = 'handcuffs', color = '#ef4444' })
end
-- Medic items
if IsPlayerAceAllowed(playerId, 'medic') then
table.insert(items, { label = 'Medical Kit', value = 'medkit', icon = 'heart-pulse', color = '#10b981' })
table.insert(items, { label = 'Revive', value = 'revive', icon = 'heart', color = '#f59e0b' })
end
return items
end
Events
The Radial Menu module provides comprehensive event handling for monitoring and responding to radial menu interactions:
Client Events
-- Radial menu item selected event
AddEventHandler('b2lib:radial-item-selected', function(data)
print('Radial item selected:', data.value)
print('Radial ID:', data.id)
print('Item data:', json.encode(data.item))
print('Context data:', json.encode(data.contextData))
print('Selection angle:', data.angle)
print('Selection time:', data.selectionTime)
-- Handle different radial menu types
if data.id == 'weapon_wheel' then
if data.value == 'weapon_pistol' then
GiveWeaponToPed(PlayerPedId(), GetHashKey('WEAPON_PISTOL'), 250, false, true)
B2Lib.UI.notify({ type = 'success', message = 'Equipped Pistol' })
elseif data.value == 'weapon_rifle' then
GiveWeaponToPed(PlayerPedId(), GetHashKey('WEAPON_ASSAULTRIFLE'), 250, false, true)
B2Lib.UI.notify({ type = 'success', message = 'Equipped Assault Rifle' })
end
elseif data.value == 'inventory' then
TriggerEvent('inventory:open')
elseif data.value == 'phone' then
TriggerEvent('phone:open')
elseif data.value == 'vehicle' then
showVehicleRadialMenu()
end
end)
-- Radial menu opened event
AddEventHandler('b2lib:radial-opened', function(data)
print('Radial menu opened:', data.id)
print('Item count:', data.itemCount)
print('Radius:', data.radius)
print('Center position:', json.encode(data.centerPosition))
-- Disable certain controls while radial is open
CreateThread(function()
while B2Lib.UI.isRadialVisible() do
Wait(0)
DisableControlAction(0, 1, true) -- LookLeftRight
DisableControlAction(0, 2, true) -- LookUpDown
DisableControlAction(0, 24, true) -- Attack
DisableControlAction(0, 25, true) -- Aim
DisableControlAction(0, 142, true) -- MeleeAttackAlternate
DisableControlAction(0, 106, true) -- VehicleMouseControlOverride
end
end)
-- Show help text
if data.id == 'weapon_wheel' then
B2Lib.UI.textUI({
text = 'Move mouse to select weapon, click to equip',
position = 'bottom'
})
end
end)
-- Radial menu closed event
AddEventHandler('b2lib:radial-closed', function(data)
print('Radial menu closed:', data.id)
print('Close reason:', data.reason) -- 'selection', 'escape', 'outside-click', 'manual'
print('Duration open:', data.duration)
-- Re-enable controls
EnableAllControlActions(0)
-- Hide help text
B2Lib.UI.hideTextUI()
-- Clean up radial-specific resources
if data.id == 'weapon_wheel' then
-- Clean up weapon wheel state
TriggerEvent('weapons:cleanupWheel')
elseif data.id == 'vehicle_radial' then
-- Clean up vehicle interaction state
TriggerEvent('vehicle:cleanupRadial')
end
end)
-- Radial menu item hovered event
AddEventHandler('b2lib:radial-item-hovered', function(data)
print('Item hovered:', data.value)
print('Item label:', data.item.label)
print('Hover angle:', data.angle)
print('Distance from center:', data.distance)
-- Show item description in UI
if data.item.description then
B2Lib.UI.textUI({
text = data.item.description,
position = 'bottom'
})
end
-- Update center text with item info
if data.item.badge then
B2Lib.UI.updateRadialCenter({
text = data.item.label .. ' (' .. data.item.badge .. ')',
icon = data.item.icon
})
end
-- Haptic feedback for controllers
if data.contextData and data.contextData.enableHapticFeedback then
TriggerEvent('gamepad:vibrate', 100, 0.1)
end
end)
-- Radial menu item unhovered event
AddEventHandler('b2lib:radial-item-unhovered', function(data)
print('Item unhovered:', data.value)
-- Reset center text
B2Lib.UI.updateRadialCenter({
text = data.originalCenterText,
icon = data.originalCenterIcon
})
-- Hide item description
B2Lib.UI.hideTextUI()
end)
-- Radial menu updated event
AddEventHandler('b2lib:radial-updated', function(data)
print('Radial menu updated:', data.id)
print('Update type:', data.updateType) -- 'items', 'data', 'style'
print('Changes:', json.encode(data.changes))
end)
Server Events
-- Player radial menu shown event
AddEventHandler('b2lib:radial-player-shown', function(playerId, radialId, contextData)
print('Radial menu shown to player', playerId, ':', radialId)
-- Log admin radial usage
if radialId:find('admin') then
print('Admin radial menu shown to player', playerId)
-- Log to admin activity system
logAdminActivity(playerId, 'radial_menu_opened', radialId)
end
-- Track radial menu usage
trackRadialUsage(playerId, radialId, 'opened')
end)
-- Player radial menu item selected event
AddEventHandler('b2lib:radial-player-selected', function(playerId, radialId, itemValue, contextData)
print('Player', playerId, 'selected item:', itemValue, 'from radial:', radialId)
-- Handle server-side radial actions
if radialId == 'admin_radial' then
handleAdminRadialAction(playerId, itemValue, contextData)
elseif radialId == 'vehicle_radial' then
handleVehicleRadialAction(playerId, itemValue, contextData)
end
-- Track item selection
trackRadialUsage(playerId, radialId, 'item_selected', itemValue)
end)
-- Player radial menu closed event
AddEventHandler('b2lib:radial-player-closed', function(playerId, radialId, reason)
print('Radial menu closed for player', playerId, ':', radialId, 'reason:', reason)
-- Track radial menu usage
trackRadialUsage(playerId, radialId, 'closed', reason)
end)
Configuration
Complete Radial Menu configuration options in config.lua
:
--- Radial Menu configuration
--- @type table
Config.RadialMenu = {
defaultRadius = 100, -- Default radius in pixels with responsive scaling
defaultItemSize = 50, -- Default item size in pixels
defaultCenterSize = 60, -- Default center size in pixels with scaling
animationDuration = 300, -- Default animation duration in milliseconds
expansionDelay = 50, -- Default expansion delay between items in milliseconds
selectionDeadzone = 35, -- Default selection deadzone radius in pixels
showLabels = true, -- Show item labels by default
showCenter = true, -- Show center circle by default
showConnectors = false, -- Show connector lines by default
enableDynamicRadius = false, -- Enable dynamic radius adjustment by default
closeOnSelection = true, -- Close menu after item selection
enableHapticFeedback = true, -- Enable controller vibration feedback
maxItems = 12, -- Maximum number of items in a single radial
minRadius = 80, -- Minimum radius when using dynamic sizing
maxRadius = 200, -- Maximum radius when using dynamic sizing
radiusPerItem = 8, -- Additional radius per item when dynamic
labelDistance = 25, -- Distance of labels from items in pixels
connectorOpacity = 0.3, -- Opacity of connector lines (0-1)
selectionSensitivity = 0.8, -- Mouse sensitivity for selection (0-1)
keyboardNavigation = true, -- Enable keyboard navigation support
touchSupport = true, -- Enable touch/mobile support
debugMode = false -- Enable debug visualization
}
-- Radial menu styling configuration
Config.ComponentStyles.radialMenu = {
-- Container styling
container = {
zIndex = '1000', -- Z-index for layering and modal behavior
userSelect = 'none', -- Disable text selection for better UX
pointerEvents = 'auto', -- Enable pointer events for interaction
position = 'fixed', -- Fixed positioning for screen overlay
top = '0', -- Full screen coverage
left = '0',
width = '100vw',
height = '100vh',
display = 'flex',
alignItems = 'center',
justifyContent = 'center'
},
-- Center circle styling
center = {
borderRadius = '50%', -- Perfect circular shape
borderWidth = '2px', -- Border thickness
borderStyle = 'solid', -- Solid border style
fontSize = 'sm', -- Text font size
fontWeight = 'medium', -- Text font weight
transition = 'all 0.2s ease', -- Smooth transitions for all properties
cursor = 'default', -- Default cursor style
display = 'flex', -- Flexbox for centering content
alignItems = 'center', -- Vertical centering
justifyContent = 'center', -- Horizontal centering
flexDirection = 'column', -- Stack icon and text vertically
textAlign = 'center', -- Center text alignment
boxShadow = '0 4px 12px rgba(0,0,0,0.15)', -- Subtle shadow
backdropFilter = 'blur(4px)' -- Background blur effect
},
-- Item styling
item = {
borderRadius = '50%', -- Perfect circular shape
borderWidth = '2px', -- Border thickness
borderStyle = 'solid', -- Solid border style
fontSize = 'xs', -- Label font size
fontWeight = 'medium', -- Label font weight
transition = 'all 0.2s ease', -- Smooth transitions for all properties
cursor = 'pointer', -- Pointer cursor for interactivity
iconSize = '20px', -- Icon size within items
labelOffset = '8px', -- Label distance from item circle
display = 'flex', -- Flexbox for centering content
alignItems = 'center', -- Vertical centering
justifyContent = 'center', -- Horizontal centering
position = 'absolute', -- Absolute positioning for circular layout
boxShadow = '0 2px 8px rgba(0,0,0,0.1)', -- Subtle shadow
backdropFilter = 'blur(2px)', -- Background blur effect
transform = 'scale(1)', -- Default scale for animations
transformOrigin = 'center' -- Transform origin for scaling
},
-- Item hover styling
itemHover = {
transform = 'scale(1.1)', -- Scale up on hover
boxShadow = '0 4px 16px rgba(0,0,0,0.2)', -- Enhanced shadow on hover
zIndex = '10' -- Bring hovered item to front
},
-- Item disabled styling
itemDisabled = {
opacity = '0.5', -- Reduced opacity for disabled items
cursor = 'not-allowed', -- Not-allowed cursor
transform = 'scale(0.9)' -- Slightly smaller scale
},
-- Label styling
label = {
position = 'absolute', -- Absolute positioning relative to item
fontSize = 'xs', -- Small font size
fontWeight = 'medium', -- Medium font weight
textAlign = 'center', -- Center text alignment
whiteSpace = 'nowrap', -- Prevent text wrapping
pointerEvents = 'none', -- Disable pointer events on labels
textShadow = '0 1px 2px rgba(0,0,0,0.5)', -- Text shadow for readability
transition = 'all 0.2s ease' -- Smooth transitions
},
-- Connector line styling
connector = {
strokeWidth = '2px', -- Line thickness
opacity = '0.3', -- Line opacity for subtle appearance
transition = 'all 0.2s ease', -- Smooth transitions
strokeDasharray = 'none', -- Solid line by default
strokeLinecap = 'round' -- Rounded line caps
},
-- Badge styling
badge = {
borderRadius = '50%', -- Circular badge shape
fontSize = 'xs', -- Small badge font size
fontWeight = 'bold', -- Bold badge text
minWidth = '18px', -- Minimum badge width
height = '18px', -- Badge height
top = '-6px', -- Badge position from top
right = '-6px', -- Badge position from right
position = 'absolute', -- Absolute positioning
display = 'flex', -- Flexbox for centering
alignItems = 'center', -- Vertical centering
justifyContent = 'center', -- Horizontal centering
border = '2px solid white', -- White border for contrast
boxShadow = '0 1px 3px rgba(0,0,0,0.3)' -- Shadow for depth
},
-- Progress indicator styling
progress = {
position = 'absolute', -- Absolute positioning
top = '0', -- Full coverage of item
left = '0',
width = '100%',
height = '100%',
borderRadius = '50%', -- Circular progress
background = 'conic-gradient(from 0deg, transparent 0deg, var(--progress-color) 0deg, transparent 0deg)', -- Conic gradient for progress
opacity = '0.7', -- Semi-transparent
pointerEvents = 'none' -- Don't interfere with interactions
},
-- Animation presets
animations = {
fadeIn = 'fadeIn 0.3s ease-out',
fadeOut = 'fadeOut 0.2s ease-in',
scaleIn = 'scaleIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)',
scaleOut = 'scaleOut 0.2s ease-in',
slideIn = 'slideIn 0.3s ease-out',
slideOut = 'slideOut 0.2s ease-in',
rotateIn = 'rotateIn 0.3s ease-out',
bounceIn = 'bounceIn 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55)'
},
-- Color themes
themes = {
default = {
centerBackground = 'rgba(255, 255, 255, 0.95)',
centerBorder = 'rgba(0, 0, 0, 0.1)',
centerText = '#374151',
itemBackground = 'rgba(255, 255, 255, 0.9)',
itemBorder = 'rgba(0, 0, 0, 0.1)',
itemText = '#374151',
connectorColor = 'rgba(0, 0, 0, 0.2)',
labelText = '#374151'
},
dark = {
centerBackground = 'rgba(31, 41, 55, 0.95)',
centerBorder = 'rgba(255, 255, 255, 0.1)',
centerText = '#f3f4f6',
itemBackground = 'rgba(31, 41, 55, 0.9)',
itemBorder = 'rgba(255, 255, 255, 0.1)',
itemText = '#f3f4f6',
connectorColor = 'rgba(255, 255, 255, 0.2)',
labelText = '#f3f4f6'
}
}
}
Advanced Examples
Weapon Wheel System
-- Create a comprehensive weapon wheel
local function createWeaponWheel()
local playerPed = PlayerPedId()
local weapons = {}
-- Get player's weapons
for _, weaponHash in ipairs(Config.Weapons) do
if HasPedGotWeapon(playerPed, weaponHash, false) then
local weaponData = Config.WeaponData[weaponHash]
table.insert(weapons, {
label = weaponData.label,
value = weaponData.name,
icon = weaponData.icon,
hash = weaponHash,
ammo = GetAmmoInPedWeapon(playerPed, weaponHash)
})
end
end
-- Add unarmed option
table.insert(weapons, 1, {
label = 'Unarmed',
value = 'unarmed',
icon = 'hand',
hash = GetHashKey('WEAPON_UNARMED')
})
B2Lib.UI.showRadial({
id = 'weapon_wheel',
items = weapons,
radius = 120,
centerIcon = 'crosshair',
centerText = 'Weapons',
showConnectors = true,
animationDuration = 200,
expansionDelay = 25,
enableDynamicRadius = true,
minRadius = 100,
maxRadius = 160
})
-- Handle weapon selection
AddEventHandler('b2lib:radial-item-selected', function(data)
if data.radialId == 'weapon_wheel' then
local weaponHash = data.item.hash
SetCurrentPedWeapon(playerPed, weaponHash, true)
B2Lib.UI.notify({
type = 'info',
message = 'Equipped: ' .. data.item.label
})
end
end)
end
-- Bind to key
RegisterKeyMapping('weaponwheel', 'Open Weapon Wheel', 'keyboard', 'TAB')
RegisterCommand('weaponwheel', function()
createWeaponWheel()
end)
Vehicle Interaction Radial
-- Create a vehicle interaction radial menu
local function createVehicleRadial()
local playerPed = PlayerPedId()
local vehicle = GetVehiclePedIsIn(playerPed, false)
if vehicle == 0 then
vehicle = GetClosestVehicle(GetEntityCoords(playerPed), 5.0, 0, 71)
if vehicle == 0 then
B2Lib.UI.notify({
type = 'error',
message = 'No vehicle nearby'
})
return
end
end
local isInVehicle = GetVehiclePedIsIn(playerPed, false) ~= 0
local isLocked = GetVehicleDoorLockStatus(vehicle) == 2
local engineOn = GetIsVehicleEngineRunning(vehicle)
local items = {}
if isInVehicle then
-- Inside vehicle options
table.insert(items, {
label = engineOn and 'Turn Off Engine' or 'Turn On Engine',
value = 'toggle_engine',
icon = engineOn and 'engine-off' or 'engine',
color = engineOn and '#ff6b6b' or '#51cf66'
})
table.insert(items, {
label = 'Toggle Lights',
value = 'toggle_lights',
icon = 'lightbulb'
})
table.insert(items, {
label = 'Open Hood',
value = 'open_hood',
icon = 'wrench'
})
table.insert(items, {
label = 'Open Trunk',
value = 'open_trunk',
icon = 'package'
})
table.insert(items, {
label = 'Exit Vehicle',
value = 'exit_vehicle',
icon = 'exit'
})
else
-- Outside vehicle options
table.insert(items, {
label = 'Enter Vehicle',
value = 'enter_vehicle',
icon = 'car',
disabled = isLocked
})
table.insert(items, {
label = isLocked and 'Unlock' or 'Lock',
value = 'toggle_lock',
icon = isLocked and 'unlock' or 'lock'
})
table.insert(items, {
label = 'Inspect',
value = 'inspect',
icon = 'search'
})
if not isLocked then
table.insert(items, {
label = 'Open Hood',
value = 'open_hood',
icon = 'wrench'
})
table.insert(items, {
label = 'Open Trunk',
value = 'open_trunk',
icon = 'package'
})
end
end
B2Lib.UI.showRadial({
id = 'vehicle_radial',
items = items,
centerIcon = 'car',
centerText = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)),
radius = 110,
showConnectors = false
})
-- Handle vehicle actions
AddEventHandler('b2lib:radial-item-selected', function(data)
if data.radialId == 'vehicle_radial' then
if data.value == 'toggle_engine' then
SetVehicleEngineOn(vehicle, not engineOn, false, true)
elseif data.value == 'toggle_lights' then
local lightsOn = IsVehicleLightOn(vehicle)
SetVehicleLights(vehicle, lightsOn and 1 or 2)
elseif data.value == 'open_hood' then
SetVehicleDoorOpen(vehicle, 4, false, false)
elseif data.value == 'open_trunk' then
SetVehicleDoorOpen(vehicle, 5, false, false)
elseif data.value == 'enter_vehicle' then
TaskEnterVehicle(playerPed, vehicle, -1, -1, 1.0, 1, 0)
elseif data.value == 'exit_vehicle' then
TaskLeaveVehicle(playerPed, vehicle, 0)
elseif data.value == 'toggle_lock' then
SetVehicleDoorsLocked(vehicle, isLocked and 1 or 2)
elseif data.value == 'inspect' then
-- Show vehicle info
local health = GetVehicleEngineHealth(vehicle)
local fuel = GetVehicleFuelLevel(vehicle)
B2Lib.UI.notify({
type = 'info',
title = 'Vehicle Info',
message = string.format('Health: %.0f%% | Fuel: %.0f%%', health/10, fuel)
})
end
end
end)
end
-- Bind to key
RegisterKeyMapping('vehicleradial', 'Vehicle Radial Menu', 'keyboard', 'Y')
RegisterCommand('vehicleradial', function()
createVehicleRadial()
end)
Emote Radial Menu
-- Create an emote radial menu
local function createEmoteRadial()
local emoteCategories = {
{
label = 'Greetings',
value = 'greetings',
icon = 'hand-wave',
submenu = {
{ label = 'Wave', value = 'wave', icon = 'hand' },
{ label = 'Salute', value = 'salute', icon = 'salute' },
{ label = 'Handshake', value = 'handshake', icon = 'handshake' },
{ label = 'Hug', value = 'hug', icon = 'heart' }
}
},
{
label = 'Dance',
value = 'dance',
icon = 'music',
submenu = {
{ label = 'Dance 1', value = 'dance1', icon = 'music' },
{ label = 'Dance 2', value = 'dance2', icon = 'music' },
{ label = 'Dance 3', value = 'dance3', icon = 'music' }
}
},
{
label = 'Gestures',
value = 'gestures',
icon = 'hand-point',
submenu = {
{ label = 'Point', value = 'point', icon = 'hand-point' },
{ label = 'Thumbs Up', value = 'thumbsup', icon = 'thumbs-up' },
{ label = 'Thumbs Down', value = 'thumbsdown', icon = 'thumbs-down' },
{ label = 'Facepalm', value = 'facepalm', icon = 'face-palm' }
}
},
{
label = 'Poses',
value = 'poses',
icon = 'user',
submenu = {
{ label = 'Lean Wall', value = 'leanwall', icon = 'wall' },
{ label = 'Sit', value = 'sit', icon = 'chair' },
{ label = 'Lay Down', value = 'laydown', icon = 'bed' }
}
}
}
B2Lib.UI.showRadial({
id = 'emote_radial',
items = emoteCategories,
centerIcon = 'smile',
centerText = 'Emotes',
radius = 100,
showLabels = true
})
-- Handle emote selection
AddEventHandler('b2lib:radial-item-selected', function(data)
if data.radialId == 'emote_radial' then
if data.item.submenu then
-- Show submenu
B2Lib.UI.showRadial({
id = 'emote_submenu',
items = data.item.submenu,
centerIcon = data.item.icon,
centerText = data.item.label,
radius = 90
})
end
elseif data.radialId == 'emote_submenu' then
-- Play emote
local emoteName = data.value
TriggerEvent('emotes:play', emoteName)
B2Lib.UI.notify({
type = 'success',
message = 'Playing emote: ' .. data.item.label
})
end
end)
end
-- Bind to key
RegisterKeyMapping('emoteradial', 'Emote Radial Menu', 'keyboard', 'F3')
RegisterCommand('emoteradial', function()
createEmoteRadial()
end)
Admin Tools Radial
-- Create an admin tools radial menu
local function createAdminRadial()
-- Check admin permissions
if not isPlayerAdmin() then -- Custom function
B2Lib.UI.notify({
type = 'error',
message = 'Insufficient permissions'
})
return
end
local adminTools = {
{
label = 'Player Management',
value = 'players',
icon = 'users',
badge = tostring(#GetActivePlayers()),
badgeColor = '#3b82f6'
},
{
label = 'Vehicle Spawn',
value = 'vehicles',
icon = 'car',
color = '#10b981'
},
{
label = 'Teleport',
value = 'teleport',
icon = 'zap',
color = '#f59e0b'
},
{
label = 'Weather',
value = 'weather',
icon = 'cloud',
color = '#06b6d4'
},
{
label = 'Time',
value = 'time',
icon = 'clock',
color = '#8b5cf6'
},
{
label = 'Noclip',
value = 'noclip',
icon = 'ghost',
color = '#ef4444'
},
{
label = 'Invisible',
value = 'invisible',
icon = 'eye-off',
color = '#6b7280'
},
{
label = 'God Mode',
value = 'godmode',
icon = 'shield',
color = '#fbbf24'
}
}
B2Lib.UI.showRadial({
id = 'admin_radial',
items = adminTools,
centerIcon = 'shield-check',
centerText = 'Admin',
radius = 130,
enableDynamicRadius = true,
showConnectors = true,
animationDuration = 250
})
-- Handle admin tool selection
AddEventHandler('b2lib:radial-item-selected', function(data)
if data.radialId == 'admin_radial' then
if data.value == 'players' then
-- Show player management menu
showPlayerManagementMenu()
elseif data.value == 'vehicles' then
-- Show vehicle spawn menu
showVehicleSpawnMenu()
elseif data.value == 'teleport' then
-- Show teleport options
showTeleportMenu()
elseif data.value == 'weather' then
-- Show weather control
showWeatherMenu()
elseif data.value == 'time' then
-- Show time control
showTimeMenu()
elseif data.value == 'noclip' then
-- Toggle noclip
toggleNoclip()
elseif data.value == 'invisible' then
-- Toggle invisibility
toggleInvisibility()
elseif data.value == 'godmode' then
-- Toggle god mode
toggleGodMode()
end
end
end)
end
-- Bind to key (admin only)
RegisterKeyMapping('adminradial', 'Admin Radial Menu', 'keyboard', 'F6')
RegisterCommand('adminradial', function()
createAdminRadial()
end)
Best Practices
Optimize Item Count - Keep radial menus to 8-12 items for optimal usability and visual clarity
Use Meaningful Icons - Choose clear, recognizable Lucide icons that represent actions intuitively
Implement Smart Grouping - Use submenus and logical grouping for related functionality and complex workflows
Provide Rich Visual Feedback - Use colors, badges, progress indicators, and animations to convey information effectively
Test Cross-Platform - Ensure radial menus work seamlessly on various screen sizes, resolutions, and input methods
Handle Edge Cases - Validate states, permissions, and context before showing menus to prevent errors
Balance Radius Sizing - Use appropriate radius values that balance visibility with screen space efficiency
Optimize Animation Performance - Keep animations smooth and responsive while avoiding performance overhead
Implement Proper Cleanup - Clean up event handlers, timers, and resources when menus are closed
Use Consistent Styling - Maintain visual consistency across different radial menus in your resource
Provide Keyboard Shortcuts - Include keyboard alternatives for accessibility and power users
Test Input Methods - Verify functionality with mouse, keyboard, and gamepad controllers
Implement Context Awareness - Use condition functions and dynamic items based on player state and permissions
Consider Mobile Users - Ensure touch-friendly interactions and appropriate sizing for mobile devices
Monitor Performance - Track radial menu usage and optimize based on player behavior and system performance
Troubleshooting
Radial Menu Not Showing
Configuration Issues
Verify the options parameter is provided and contains valid data structure
Check that items array contains properly formatted item objects with required properties
Ensure radius values are positive numbers within reasonable ranges (50-300px)
Validate that all required properties (label, value, icon) are present in items
Display Problems
Ensure no other modal dialogs or UI elements are blocking the display
Check for conflicting z-index values with other UI components
Verify the radial menu isn't being hidden immediately after being shown
Test with fixed positioning and simple configurations first
Debug Information
Check console for error messages from B2Lib.Debug system
Enable debug mode in configuration to see detailed radial operations
Verify B2Lib is properly initialized and all dependencies are loaded
Test with minimal radial configurations to isolate issues
Selection and Interaction Issues
Event Handler Problems
Verify event handlers are registered before showing radial menus
Check that item values are unique within each radial menu
Ensure event handler functions are properly defined and accessible
Test event firing with console logging to verify event flow
Input Blocking
Ensure mouse/controller input is not blocked by other scripts or UI elements
Check for conflicting control disabling from other resources
Verify deadzone settings are appropriate for your input method
Test with different input devices (mouse, gamepad, touch)
Selection Sensitivity
Adjust selectionSensitivity configuration for better responsiveness
Check that selection deadzone is not too large or too small
Verify mouse cursor positioning is accurate and responsive
Test selection with different radius sizes and item counts
Performance and Animation Issues
Frame Rate Problems
Avoid too many items in a single radial menu (recommended max: 12)
Optimize item data calculations and avoid expensive operations in condition functions
Use reasonable animation durations (200-400ms) to balance smoothness and responsiveness
Monitor CPU usage during radial menu operations
Memory Management
Clean up event handlers when radial menus are no longer needed
Avoid storing large objects in context data or item properties
Implement proper resource cleanup on resource stop/restart
Monitor memory usage during extended radial menu usage
Animation Smoothness
Use CSS transforms instead of changing position properties for better performance
Implement proper easing functions for natural-feeling animations
Avoid animating too many properties simultaneously
Test animations on lower-end hardware to ensure compatibility
Visual and Styling Issues
Icon and Color Problems
Check that icon names are valid Lucide icons and properly spelled
Verify color values are in correct format (#hex, rgba, or named colors)
Test with different theme modes (dark/light) to ensure proper contrast
Ensure custom styling doesn't conflict with default B2Lib styles
Layout and Positioning
Ensure radius values are appropriate for screen size and item count
Check that labels don't overlap with items or extend outside screen bounds
Verify center circle sizing is proportional to overall radial size
Test positioning on different screen resolutions and aspect ratios
Theme Integration
Verify theme colors are properly applied and accessible
Check contrast ratios for accessibility compliance
Test with custom themes and ensure proper fallback behavior
Ensure theme switching works correctly during runtime
Server-Side Integration Issues
Network Synchronization
Account for network latency when showing server-side radial menus
Implement proper error handling for failed network operations
Use appropriate timeouts for server-side radial menu operations
Verify player connection status before sending radial menu data
Permission Validation
Verify player exists and is connected before showing server-side radials
Implement proper permission checks on both client and server sides
Handle permission changes during active radial menu sessions
Validate context data on server side to prevent exploitation
Resource Conflicts
Check for conflicts with other resources using radial menus
Ensure proper resource dependencies are declared in fxmanifest.lua
Test radial menu functionality after resource restarts and updates
Monitor for conflicts with other UI systems and input handlers
Last updated