FlexLöve Examples

🎨 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.

View Complete Example →

🎭 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.

View Complete Example →

✨ 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.

View Complete Example →

📜 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.

View Complete Example →

🎚️ 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.

View Complete Example →

⌨️ 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.

View Complete Example →

⚡ 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.

View Complete Example →

🔗 More Examples

Find all complete working examples in the examples/ directory: