🎨 Flexbox & Grid Layouts
FlexLöve provides a powerful CSS-like layout system using flexbox and grid positioning. Build complex responsive UIs with nested containers and automatic layout calculations.
Basic Flex Layout
-- Create a main window with vertical flex layout
local window = FlexLove.new({
x = "10%",
y = "10%",
width = "80%",
height = "80%",
themeComponent = "framev3",
positioning = "flex",
flexDirection = "vertical",
gap = 20,
padding = { horizontal = 20, vertical = 20 }
})
-- Add a title
FlexLove.new({
parent = window,
text = "Advanced Layout Example",
textAlign = "center",
textSize = "3xl",
width = "100%"
})
True Grid Layout
-- Create a true grid with positioning = "grid"
local gridContainer = FlexLove.new({
parent = window,
positioning = "grid",
gridRows = 3,
gridColumns = 3,
columnGap = 5,
rowGap = 5,
height = "80%",
alignItems = "stretch"
})
-- Add grid items (auto-flow into cells)
for i = 1, 9 do
FlexLove.new({
parent = gridContainer,
themeComponent = "buttonv2",
text = "Cell " .. i,
textAlign = "center",
textSize = "md",
onEvent = function(_, event)
if event.type == "release" then
print("Grid cell " .. i .. " clicked")
end
end
})
end
Grid with Headers (Schedule-style)
local Color = FlexLove.Color
local Theme = FlexLove.Theme
-- Example data
local columnHeaders = { "Mon", "Tue", "Wed" }
local rowHeaders = { "Task A", "Task B", "Task C" }
-- Calculate grid dimensions: +1 for header row and column
local numRows = #rowHeaders + 1
local numColumns = #columnHeaders + 1
local scheduleGrid = FlexLove.new({
parent = window,
positioning = "grid",
gridRows = numRows,
gridColumns = numColumns,
columnGap = 2,
rowGap = 2,
height = "80%",
alignItems = "stretch"
})
local accentColor = Theme.getColor("primary")
local textColor = Theme.getColor("text")
-- Top-left corner cell (empty)
FlexLove.new({ parent = scheduleGrid })
-- Column headers
for _, header in ipairs(columnHeaders) do
FlexLove.new({
parent = scheduleGrid,
text = header,
textColor = textColor,
textAlign = "center",
backgroundColor = Color.new(0, 0, 0, 0.3),
border = { top = true, right = true, bottom = true, left = true },
borderColor = accentColor,
textSize = 12
})
end
-- Data rows
for i, rowHeader in ipairs(rowHeaders) do
-- Row header
FlexLove.new({
parent = scheduleGrid,
text = rowHeader,
backgroundColor = Color.new(0, 0, 0, 0.3),
textColor = textColor,
textAlign = "center",
border = { top = true, right = true, bottom = true, left = true },
borderColor = accentColor,
textSize = 10
})
-- Data cells
for j = 1, #columnHeaders do
FlexLove.new({
parent = scheduleGrid,
text = tostring((i * j) % 5),
textAlign = "center",
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.5, 0.5, 0.5, 1.0),
textSize = 12,
themeComponent = "buttonv2",
onEvent = function(elem, event)
if event.type == "click" then
local newValue = (tonumber(elem.text) + 1) % 10
elem:updateText(tostring(newValue))
end
end
})
end
end
💡 Tip: Use positioning = "grid" with
gridRows and gridColumns to create true
grid layouts. Children auto-flow into cells left-to-right,
top-to-bottom. Perfect for tables, schedules, and structured data
displays.
🎭 Theme System
FlexLöve includes a powerful 9-patch NinePatch theming system with automatic state management (normal, hover, pressed, disabled).
Initialize with Theme
local FlexLove = require("FlexLove")
-- Initialize with a built-in theme
FlexLove.init({
baseScale = { width = 1920, height = 1080 },
theme = "space" -- Options: "space", "metal"
})
Using Theme Components
-- Create a themed button (automatic state handling)
local button = FlexLove.new({
themeComponent = "buttonv2",
text = "Themed Button",
width = "20vw",
height = "10vh",
textAlign = "center",
onEvent = function(elem, event)
if event.type == "release" then
print("Button clicked!")
end
end
})
-- Available theme components:
-- "buttonv1", "buttonv2", "buttonv3"
-- "framev1", "framev2", "framev3"
-- "inputv1", "inputv2"
-- "cardv1", "cardv2"
-- "panel"
Theme Switching
local themes = { "space", "metal" }
local themeIndex = 1
-- Create theme toggle button
FlexLove.new({
themeComponent = "buttonv2",
text = "Switch Theme",
onEvent = function(_, event)
if event.type == "release" then
themeIndex = (themeIndex % #themes) + 1
FlexLove.setTheme(themes[themeIndex])
print("Switched to:", themes[themeIndex])
end
end
})
💡 Tip: Theme components automatically handle hover, pressed, and disabled states using 9-patch image scaling for perfect corners at any size.
✨ State Management
Build interactive UIs with state tracking, counters, toggles, and dynamic updates. Perfect for game menus and settings screens.
Counter with State
local state = {
counter = 0,
isToggled = false,
inputValue = ""
}
-- Create counter display
local counterSection = FlexLove.new({
positioning = "flex",
flexDirection = "horizontal",
justifyContent = "space-between",
alignItems = "center",
gap = 10
})
local counterText = FlexLove.new({
parent = counterSection,
text = "Counter: " .. state.counter,
textSize = "lg"
})
-- Increment button
FlexLove.new({
parent = counterSection,
themeComponent = "buttonv2",
text = "Increment",
width = "25%",
onEvent = function(_, event)
if event.type == "release" then
state.counter = state.counter + 1
counterText.text = "Counter: " .. state.counter
print("Counter incremented to:", state.counter)
end
end
})
Toggle Switch
-- Create toggle button with color change
local toggleButton = FlexLove.new({
positioning = "flex",
justifyContent = "center",
alignItems = "center",
width = 60,
height = 30,
backgroundColor = state.isToggled and "#48bb78" or "#a0aec0",
borderRadius = 15,
padding = { horizontal = 5 },
onEvent = function(elem, event)
if event.type == "release" then
state.isToggled = not state.isToggled
-- Update button color
elem.backgroundColor = state.isToggled and "#48bb78" or "#a0aec0"
print("Toggle switched to:", state.isToggled)
end
end
})
FlexLove.new({
parent = toggleButton,
text = state.isToggled and "ON" or "OFF",
textAlign = "center",
textSize = "sm",
color = "#ffffff"
})
Text Input with State
-- Input field that updates state
local inputField = FlexLove.new({
themeComponent = "inputv2",
text = state.inputValue,
textAlign = "left",
textSize = "md",
width = "50%",
onEvent = function(_, event)
if event.type == "textinput" then
state.inputValue = state.inputValue .. event.text
print("Input value:", state.inputValue)
elseif event.type == "keypressed" and event.key == "backspace" then
state.inputValue = state.inputValue:sub(1, -2)
end
end
})
💡 Tip: Use local state tables to manage UI state and update element properties directly when state changes.
📜 Scrollable Content
Create smooth scrolling containers with backdrop blur effects and overflow handling for long content lists.
Scrollable Container
local Color = FlexLove.Color
-- Create main window with backdrop blur
local window = FlexLove.new({
x = "25%",
y = "10%",
width = "50vw",
height = "80vh",
themeComponent = "framev3",
positioning = "flex",
flexDirection = "vertical",
gap = 10,
backdropBlur = { radius = 10, quality = 10 },
backgroundColor = Color.new(0.1, 0.1, 0.1, 0.8)
})
-- Create scroll container
local scrollContainer = FlexLove.new({
parent = window,
width = "90%",
height = "70%",
positioning = "flex",
flexDirection = "vertical",
overflowY = "scroll", -- Enable vertical scrolling
gap = 5,
padding = { horizontal = 10, vertical = 5 },
themeComponent = "framev3",
backgroundColor = Color.new(0.2, 0.2, 0.2, 0.5)
})
Add Scrollable Items
-- Add multiple items to demonstrate scrolling
for i = 1, 30 do
local text = string.format(
"Item %d - This is scrollable content that exceeds the container bounds",
i
)
FlexLove.new({
parent = scrollContainer,
text = text,
textAlign = "start",
textSize = "md",
width = "100%",
textColor = Color.new(0.9, 0.9, 0.9, 1),
padding = { vertical = 5 },
themeComponent = i % 3 == 0 and "panel" or "cardv2",
backgroundColor = i % 3 == 0
and Color.new(0.3, 0.3, 0.3, 0.7)
or Color.new(0.4, 0.4, 0.4, 0.5)
})
end
-- Footer with instructions
FlexLove.new({
parent = window,
text = "Scroll using the mouse wheel or drag the scrollbar",
textAlign = "center",
textSize = "sm",
textColor = Color.new(0.7, 0.7, 0.7, 1)
})
💡 Tip: Use overflowY = "scroll" to
enable vertical scrolling. The backdrop blur creates a nice
glassmorphism effect behind the container.
🎚️ Sliders & Controls
Implement draggable sliders with value tracking, perfect for settings menus and adjustable parameters.
Create a Slider Control
local function createSlider(parent, label, min, max, initialValue)
local value = initialValue or min
local normalized = (value - min) / (max - min)
local row = FlexLove.new({
parent = parent,
width = "100%",
height = "5vh",
positioning = "flex",
flexDirection = "horizontal",
justifyContent = "space-between",
alignItems = "center",
gap = 10
})
-- Label
FlexLove.new({
parent = row,
text = label,
textAlign = "start",
textSize = "md",
width = "30%"
})
local sliderContainer = FlexLove.new({
parent = row,
width = "50%",
height = "100%",
positioning = "flex",
flexDirection = "horizontal",
alignItems = "center",
gap = 5
})
-- Slider track
local sliderTrack = FlexLove.new({
parent = sliderContainer,
width = "80%",
height = "75%",
positioning = "flex",
flexDirection = "horizontal",
themeComponent = "framev3"
})
-- Fill bar (visual indicator)
local fillBar = FlexLove.new({
parent = sliderTrack,
width = (normalized * 100) .. "%",
height = "100%",
themeComponent = "buttonv1"
})
-- Value display
local valueDisplay = FlexLove.new({
parent = sliderContainer,
text = string.format("%d", value),
textAlign = "center",
textSize = "md",
width = "15%"
})
-- Handle drag events
local function updateValue(mx)
local normalized = (mx - sliderTrack.x) / sliderTrack.width
normalized = math.max(0, math.min(1, normalized))
value = min + (normalized * (max - min))
-- Update visuals
fillBar.width = (normalized * 100) .. "%"
valueDisplay.text = string.format("%d", value)
end
sliderTrack.onEvent = function(elem, event)
if event.type == "press" or event.type == "drag" then
updateValue(event.x)
end
end
return value
end
Using Multiple Sliders
-- Create settings window
local settingsWindow = FlexLove.new({
x = "10%",
y = "10%",
width = "80%",
height = "80%",
themeComponent = "framev3",
positioning = "flex",
flexDirection = "vertical",
gap = 20,
padding = { horizontal = "5%", vertical = "3%" }
})
-- Title
FlexLove.new({
parent = settingsWindow,
text = "Settings",
textAlign = "center",
textSize = "3xl"
})
-- Create sliders for different settings
createSlider(settingsWindow, "Volume", 0, 100, 75)
createSlider(settingsWindow, "Brightness", 0, 100, 50)
createSlider(settingsWindow, "Sensitivity", 10, 200, 100)
💡 Tip: Use event.type == "drag" to
track mouse movement while pressed for smooth slider interaction.
⌨️ Input & Event Handling
Handle mouse, keyboard, and touch events with FlexLöve's comprehensive event system. Includes focus management and input field support.
Mouse Event Handling
local inputState = {
mousePos = { x = 0, y = 0 },
isHovered = false,
hoverCount = 0
}
-- Create hoverable area
local hoverArea = FlexLove.new({
width = "30%",
height = "100%",
backgroundColor = "#4a5568",
borderRadius = 8,
onEvent = function(elem, event)
if event.type == "mousemoved" then
inputState.mousePos.x = event.x
inputState.mousePos.y = event.y
elseif event.type == "mouseenter" then
inputState.isHovered = true
inputState.hoverCount = inputState.hoverCount + 1
elem.backgroundColor = "#48bb78" -- Green on hover
elseif event.type == "mouseleave" then
inputState.isHovered = false
elem.backgroundColor = "#4a5568" -- Back to gray
elseif event.type == "release" then
print("Clicked at:", event.x, event.y)
end
end
})
-- Display mouse position
FlexLove.new({
text = "Mouse: (" .. inputState.mousePos.x .. ", " .. inputState.mousePos.y .. ")",
textAlign = "left"
})
Keyboard Input
local keyState = {
lastKey = "",
textInput = ""
}
-- Input field with keyboard handling
local inputField = FlexLove.new({
themeComponent = "inputv2",
text = keyState.textInput,
width = "50%",
onEvent = function(elem, event)
if event.type == "textinput" then
keyState.textInput = keyState.textInput .. event.text
keyState.lastKey = event.text
elem.text = keyState.textInput
elseif event.type == "keypressed" then
keyState.lastKey = event.key
if event.key == "backspace" then
keyState.textInput = keyState.textInput:sub(1, -2)
elem.text = keyState.textInput
elseif event.key == "return" then
print("Submitted:", keyState.textInput)
keyState.textInput = ""
elem.text = ""
elseif event.key == "escape" then
elem:blur() -- Remove focus
end
end
end
})
Touch Events (Mobile Support)
local touchState = {
touchPos = { x = 0, y = 0 },
isTouching = false
}
-- Touchable area
local touchArea = FlexLove.new({
width = "100%",
height = "100%",
backgroundColor = "#4a5568",
borderRadius = 8,
onEvent = function(_, event)
if event.type == "touch" then
touchState.isTouching = true
touchState.touchPos.x = event.x
touchState.touchPos.y = event.y
print("Touch started at:", event.x, event.y)
elseif event.type == "touchmoved" then
touchState.touchPos.x = event.x
touchState.touchPos.y = event.y
elseif event.type == "touchreleased" then
touchState.isTouching = false
print("Touch ended")
end
end
})
Available Event Types
-- Mouse events
"press" -- Mouse button pressed
"release" -- Mouse button released
"drag" -- Mouse moved while pressed
"mousemoved" -- Mouse position changed
"mouseenter" -- Mouse entered element bounds
"mouseleave" -- Mouse left element bounds
"wheel" -- Mouse wheel scrolled
-- Keyboard events
"keypressed" -- Key pressed (includes key name)
"keyreleased" -- Key released
"textinput" -- Text character entered
-- Touch events (mobile)
"touch" -- Touch started
"touchmoved" -- Touch position changed
"touchreleased" -- Touch ended
-- Focus events
"focus" -- Element gained focus
"blur" -- Element lost focus
💡 Tip: Use elem:focus() and
elem:blur() to programmatically control input field
focus. The event object contains contextual data like
x, y, key, etc.
⚡ Performance Tips
Best practices for optimizing FlexLöve applications and avoiding common performance pitfalls.
Immediate Mode vs Retained Mode
-- Retained Mode (default) - elements persist between frames
FlexLove.init({
baseScale = { width = 1920, height = 1080 }
})
-- Immediate Mode - elements recreated each frame
FlexLove.init({
baseScale = { width = 1920, height = 1080 },
immediateMode = true
})
Batch Element Creation
-- ❌ Bad: Creating elements in love.draw() (retained mode)
function love.draw()
local button = FlexLove.new({ ... }) -- DON'T DO THIS
end
-- ✅ Good: Create once, reuse
local button
function love.load()
button = FlexLove.new({ ... })
end
function love.draw()
FlexLove.draw() -- Just draw existing elements
end
Limit Layout Recalculations
-- Avoid changing layout properties every frame
local element = FlexLove.new({ ... })
-- ❌ Bad: Causes relayout every frame
function love.update(dt)
element.width = math.sin(love.timer.getTime()) * 100
end
-- ✅ Good: Update only when needed
function love.update(dt)
if someCondition then
element.width = newWidth
end
end
Use Element Pooling for Dynamic Content
-- Create a pool of reusable elements
local itemPool = {}
for i = 1, 50 do
itemPool[i] = FlexLove.new({
parent = scrollContainer,
visible = false,
text = "",
width = "100%",
themeComponent = "cardv2"
})
end
-- Reuse elements instead of creating new ones
function updateList(items)
for i, item in ipairs(items) do
if itemPool[i] then
itemPool[i].text = item.name
itemPool[i].visible = true
end
end
-- Hide unused elements
for i = #items + 1, #itemPool do
itemPool[i].visible = false
end
end
💡 Tip: Use retained mode when you want the lightest footprint possible, immediate mode is much faster to prototype with and relative low overhead.
🔗 More Examples
Find all complete working examples in the examples/ directory:
-
advanced_layout.lua- Complex nested layouts with flex and grid -
theme_custom_components.lua- Theme switching and custom components -
stateful_ui.lua- Interactive state management patterns -
scrollable_content.lua- Scrolling with backdrop blur -
slider_example.lua- Slider controls for settings menus -
input_handling.lua- Comprehensive input event handling -
performance_example.lua- Performance optimization techniques