# How to Create a Custom Layout in Hyprland: A Complete Lua Guide

> Learn to create custom Hyprland layouts with Lua. Register your layout and manipulate windows using ctx:split() and target:place() for ultimate control.

- Repository: [Hypr Development/Hyprland](https://github.com/hyprwm/Hyprland)
- Tags: how-to-guide
- Published: 2026-07-23

---

**To create a custom layout in Hyprland, write a Lua script that calls `hl.layout.register()` with a `recalculate` callback that manipulates the layout context (`ctx`) to arrange windows using `ctx:split()` and `target:place()`.**

Hyprland’s tiling system is extensible through Lua scripts that hook into the compositor’s layout engine. When you create a custom layout in Hyprland, you define how windows subdivide the available screen space without touching the underlying C++ source. The `CLuaTiledAlgorithm` class in [`src/config/lua/layout/LuaLayoutProvider.hpp`](https://github.com/hyprwm/Hyprland/blob/main/src/config/lua/layout/LuaLayoutProvider.hpp) bridges your Lua code with Hyprland’s native `CLayoutManager`, passing a context object that exposes monitor geometry and window lists.

## How Hyprland Processes Custom Layouts

The layout system follows a three-stage pipeline that delegates geometry calculations to your Lua code while Hyprland handles the heavy lifting of window positioning and rendering.

### Layout Registration

When Hyprland initializes, the `LuaLayoutProvider` scans for Lua files and executes any calls to `hl.layout.register(name, config)`. This function binds a layout name (e.g., `"spiral"` or `"single_column"`) to a table containing at minimum a `recalculate` function. The provider stores these definitions and makes them available to the `CLayoutManager` defined in [`src/layout/LayoutManager.hpp`](https://github.com/hyprwm/Hyprland/blob/main/src/layout/LayoutManager.hpp).

### The Recalculation Cycle

Every time a window opens, closes, or a monitor changes resolution, `CLayoutManager` triggers a recalculation. For Lua-based layouts, this invokes your registered `recalculate` callback with a single argument: the **layout context** (`ctx`). The context contains `ctx.area` (the usable monitor rectangle) and `ctx.targets` (a list of tiled windows to position).

### Window Placement

Inside `recalculate`, you arrange windows by calling `target:place(rect)`, where `rect` is a geometry table with `x`, `y`, `width`, and `height` fields. The `ctx:split(area, side, ratio)` helper divides rectangles cleanly, returning a new geometry table while shrinking the remaining area for subsequent windows.

## The Layout Context API Reference

Understanding the `ctx` object is essential when you create a custom layout in Hyprland. The context provides the following properties and methods:

- **`ctx.area`**: A table representing the full usable tiling area for the current monitor, containing `x`, `y`, `w`, and `h` fields.
- **`ctx.targets`**: An array of window objects representing the currently tiled windows. Each target exposes `target:place(rect)` to set its final geometry.
- **`ctx:split(area, side, ratio)`**: Returns a new rectangle representing a portion of `area` split off from the specified `side` (`"left"`, `"right"`, `"top"`, or `"bottom"`). The `ratio` parameter (0.0 to 1.0) determines what fraction of the area to carve off.

## Creating a Minimal Single-Column Layout

Start with a simple layout that stacks windows vertically in equal strips. Save this as `~/.config/hypr/layouts/single_column.lua`:

```lua
hl.layout.register("single_column", {
    recalculate = function(ctx)
        local area = ctx.area
        local n = #ctx.targets
        
        if n == 0 then return end
        
        local height = area.h / n
        
        for i, target in ipairs(ctx.targets) do
            local rect = {
                x = area.x,
                y = area.y + (i - 1) * height,
                w = area.w,
                h = height
            }
            target:place(rect)
        end
    end,
    
    layout_msg = function(_, msg)
        return "single_column: no configurable options"
    end
})

```

The `layout_msg` function is mandatory and handles runtime commands sent via `hyprctl`, though it can return a static message if your layout requires no dynamic configuration.

## Implementing Advanced Patterns

For complex arrangements, recursively subdivide the remaining area. The spiral layout from [`example/layouts/spiral.lua`](https://github.com/hyprwm/Hyprland/blob/main/example/layouts/spiral.lua) demonstrates this pattern:

```lua
local opposite = { left = "right", right = "left", top = "bottom", bottom = "top" }

hl.layout.register("spiral", {
    recalculate = function(ctx)
        local n = #ctx.targets
        if n == 0 then return end
        
        local area = ctx.area
        local sides = {"left", "top", "right", "bottom"}
        
        for i, target in ipairs(ctx.targets) do
            if i == n then
                target:place(area)
            else
                local side = sides[((i - 1) % 4) + 1]
                local split_ratio = 0.58
                target:place(ctx:split(area, side, split_ratio))
                area = ctx:split(area, opposite[side], 1 - split_ratio)
            end
        end
    end,
    
    layout_msg = function(_, msg)
        -- Handle commands like "ratio 0.6" or "rotate"
        return "spiral: command received"
    end
})

```

This example uses `ctx:split` to carve out the main window, then reassigns the remaining area to create a Fibonacci-style spiral. For grid-based arrangements, examine [`example/layouts/grid.lua`](https://github.com/hyprwm/Hyprland/blob/main/example/layouts/grid.lua) in the repository, which calculates row and column counts dynamically based on window count.

## Loading and Activating Your Layout

Once you create a custom layout in Hyprland, you must load the script and activate the layout:

1. Place your Lua file in `~/.config/hypr/layouts/` (create the directory if needed).
2. Source the file in `~/.config/hypr/hyprland.conf` using an exec statement:

```ini
exec = lua ~/.config/hypr/layouts/single_column.lua

```

3. Switch to your layout using `hyprctl`:

```bash
hyprctl dispatch switchlayout single_column

```

Or bind it to a key in your configuration:

```ini
bind = $mainMod, L, exec, hyprctl dispatch switchlayout single_column

```

The `MonitorLayoutController` class in [`src/state/MonitorLayoutController.hpp`](https://github.com/hyprwm/Hyprland/blob/main/src/state/MonitorLayoutController.hpp) handles the transition between layouts, triggering a full recalculation when you switch.

## Key Source Files in the Hyprland Repository

The following files define the architecture that enables Lua-based layouts:

- **[`src/config/lua/layout/LuaLayoutProvider.hpp`](https://github.com/hyprwm/Hyprland/blob/main/src/config/lua/layout/LuaLayoutProvider.hpp)**: Declares `CLuaTiledAlgorithm`, the C++ class that wraps Lua layout scripts and exposes the `ctx` API to user code.
- **[`src/layout/LayoutManager.hpp`](https://github.com/hyprwm/Hyprland/blob/main/src/layout/LayoutManager.hpp)**: Contains `CLayoutManager`, which orchestrates the recalculation cycle and delegates tiling decisions to the active algorithm.
- **[`src/state/MonitorLayoutController.hpp`](https://github.com/hyprwm/Hyprland/blob/main/src/state/MonitorLayoutController.hpp)**: Manages per-monitor layout state and triggers recomputation when layouts switch or monitors change.
- **[`example/layouts/spiral.lua`](https://github.com/hyprwm/Hyprland/blob/main/example/layouts/spiral.lua)**: Reference implementation showing recursive area subdivision and state management.
- **[`example/layouts/grid.lua`](https://github.com/hyprwm/Hyprland/blob/main/example/layouts/grid.lua)**: Reference implementation showing dynamic grid calculations.

## Summary

- **Registration**: Use `hl.layout.register(name, { recalculate = func })` to define a new layout name and callback.
- **Context**: The `ctx` object provides `ctx.area` (screen geometry), `ctx.targets` (window list), and `ctx:split()` (area subdivision).
- **Placement**: Call `target:place(rect)` on each window object to set its position and size.
- **Activation**: Load Lua files via `exec` in [`hyprland.conf`](https://github.com/hyprwm/Hyprland/blob/main/hyprland.conf) and switch layouts using `hyprctl dispatch switchlayout`.
- **Performance**: Custom layouts run efficiently because Hyprland’s C++ engine handles the actual window moves and monitor updates based on your Lua geometry calculations.

## Frequently Asked Questions

### Can I write Hyprland layouts in Python instead of Lua?

No. Hyprland’s layout extension system specifically uses Lua scripting through the `LuaLayoutProvider` infrastructure. The `hl.layout.register` function and the context API are only exposed to the Lua runtime embedded within Hyprland. You must write layout scripts in Lua to interface with the `CLuaTiledAlgorithm` class.

### How do I debug a custom layout that is not working?

Check the Hyprland logs using `hyprctl log` or by running Hyprland from a terminal. Lua syntax errors or missing required fields (like `layout_msg`) will appear as runtime errors in the log output. Ensure your script file is executable and the path in your `exec` statement is correct. You can also add `print()` statements to your Lua code; output appears in the Hyprland log.

### Are custom Lua layouts slower than built-in C++ layouts?

No. While the tiling *logic* runs in Lua, the actual window positioning, damage tracking, and rendering remain native C++ operations handled by `CLayoutManager`. The Lua script only calculates geometry rectangles. For typical desktop usage with fewer than 50 windows, the overhead is negligible compared to the built-in dwindle or master layouts.

### Can I distribute my custom layout to other users?

Yes. Since layouts are self-contained Lua files, you can share them by distributing the `.lua` file. Users simply place it in their `~/.config/hypr/layouts/` directory and add the `exec` line to their configuration. Ensure you document any specific `layout_msg` commands your layout supports so users can configure it via `hyprctl`.