How to Implement Custom Bézier Curves in Hyprland Animations

Hyprland allows you to define custom cubic Bézier curves using the hl.curve() Lua helper and apply them to any animation via the bezier field in hl.animation().

Hyprland's animation system provides fine-grained control over window transitions through user-defined Bézier curves. By leveraging the Lua configuration API, you can create custom easing functions that govern acceleration and deceleration across any animation leaf. This guide walks through the exact implementation details found in the Hyprland source code, from parsing in LuaBindingsConfigRules.cpp to runtime verification using hyprctl.

How Bézier Curves Work in Hyprland

Under the hood, Hyprland implements the classic cubic Bézier formula using two control points you specify. When an animation leaf is created, the compositor looks up your curve name using Animation::mgr()->bezierExists(name) and applies the stored control points to drive the timing function.

The curve definitions are stored centrally in the AnimationTree managed by src/config/shared/animation/AnimationTree.hpp and src/config/shared/animation/AnimationTree.cpp. This central storage allows the same curve name to be reused across multiple animation leaves—such as windows, layers, and fade effects—ensuring consistent timing throughout your desktop environment.

Defining a Custom Bézier Curve

Syntax and Structure

You define curves using the hl.curve() helper in your Lua configuration. According to the source in src/config/lua/bindings/LuaBindingsConfigRules.cpp, the parser validates that your table contains type = "bezier" and extracts the control points from the points array.

Each curve requires:

  • A unique name (e.g., "myFastEase")
  • A type field set to "bezier"
  • A points array containing two pairs of floats: { {x0, y0}, {x1, y1} }

Choosing Control Points

Control points are pairs of floats in the range [0, 1]:

  • The first point {x0, y0} controls the initial acceleration (how quickly the animation starts).
  • The second point {x1, y1} controls the final deceleration (how the animation eases to a stop).

Values outside the [0, 1] range may produce unexpected results, as the cubic Bézier implementation expects normalized coordinates.

-- Define a custom Bézier curve called "myFastEase"
hl.curve(
    "myFastEase",
    {
        type   = "bezier",
        points = { {0.15, 0.0}, {0.1, 1.0} }   -- (x0,y0) and (x1,y1)
    }
)

Applying Curves to Animation Leaves

Once defined, reference your curve in any animation leaf using the bezier field. The hl.animation() function accepts the leaf type, speed, and your custom curve name.

hl.animation({
    leaf    = "windows",
    enabled = true,
    speed   = 5.0,
    bezier  = "myFastEase"
})

Because curves are stored centrally in the AnimationTree, you can apply "myFastEase" to multiple leaves—such as windows, fade, and layersOut—without redefining the control points.

Verifying Your Configuration

After reloading your configuration with hyprctl reload, verify that your curves loaded correctly:

hyprctl animations

This command outputs all defined curves with their control points, as implemented in src/debug/HyprCtl.cpp. The output format lists the name, X0, Y0, X1, and Y1 values for each curve, allowing you to confirm that LuaBindingsConfigRules.cpp parsed your specification correctly.

Complete Configuration Example

Here is a practical setup defining three distinct curves and applying them to different UI elements:

-- 1️⃣ Define custom curves
hl.curve("easeOutQuint", {
    type   = "bezier",
    points = { {0.23, 1.0}, {0.32, 1.0} }
})

hl.curve("linear", {
    type   = "bezier",
    points = { {0.0, 0.0}, {1.0, 1.0} }
})

hl.curve("quick", {
    type   = "bezier",
    points = { {0.15, 0.0}, {0.1, 1.0} }
})

-- 2️⃣ Wire the curves to animation leaves
hl.animation({ leaf = "windows",   enabled = true, speed = 4.8, bezier = "easeOutQuint" })
hl.animation({ leaf = "fade",      enabled = true, speed = 3.0, bezier = "linear" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "quick", style = "fade" })

Summary

  • Define curves with hl.curve(name, {type="bezier", points={{x0,y0},{x1,y1}}}) in your Lua configuration.
  • Store centrally in the AnimationTree (src/config/shared/animation/AnimationTree.cpp) for reuse across multiple animation types.
  • Apply curves via the bezier field in hl.animation() to control timing for windows, fades, and layer shells.
  • Verify loading using hyprctl animations to inspect parsed control points.
  • Parse location is src/config/lua/bindings/LuaBindingsConfigRules.cpp, which validates the type field and extracts coordinates.

Frequently Asked Questions

What is the valid range for Bézier control points in Hyprland?

Control points must be pairs of floats in the range [0, 1], representing the relative coordinates of the cubic Bézier control handles. Values outside this range are not standard for the easing implementation and may cause undefined behavior in the animation timing.

Can I use the same custom curve for multiple animation types?

Yes. Once defined with hl.curve(), the curve is stored in the global AnimationTree and can be referenced by any animation leaf—such as windows, fade, or layersOut—through the bezier field. This ensures consistent acceleration and deceleration across different UI elements without duplicate definitions.

How do I debug if my Bézier curve is not loading?

Run hyprctl animations to inspect the loaded curves and their control points. If your curve is missing, verify that your Lua table includes type = "bezier", as the parser in src/config/lua/bindings/LuaBindingsConfigRules.cpp strictly validates this field before extracting the points array.

Where does Hyprland store the parsed Bézier definitions?

The parsed curves are stored in the global animation tree implemented in src/config/shared/animation/AnimationTree.cpp, which provides the bezierExists() lookup method. When an animation leaf is instantiated, Hyprland queries this central store to retrieve the control points for the specified curve name.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →