Rust GUI frameworks
2022 Layout 2026: Shared across many frameworks
17

stretch to Taffy: one layout engine

CSS flexbox and grid as a reusable library.

Taffy is a layout engine: give it a tree of boxes with CSS-style properties (flexbox, grid, block) and it computes every box's size and position. It doesn't draw and doesn't know about windows. It grew out of the earlier stretch crate (2018) and is now the most widely shared layout layer in the book. Here is a 800×600 window with a fixed 200-pixel sidebar and content that fills the rest.

taffy-flex/src/bin/one.rs ✔ compiled · taffy 0.14 · rustc 1.95.0
use book_error::Result;
use taffy::prelude::*;

fn main() -> Result<()> {
    let mut tree: TaffyTree<()> = TaffyTree::new();

    let sidebar = tree.new_leaf(Style {
        size: Size { width: length(200.0), height: auto() },
        ..Default::default()
    })?;
    let content = tree.new_leaf(Style { flex_grow: 1.0, ..Default::default() })?;

    let root = tree.new_with_children(
        Style { size: Size { width: length(800.0), height: length(600.0) }, ..Default::default() },
        &[sidebar, content],
    )?;

    tree.compute_layout(root, Size::MAX_CONTENT)?;
    println!("sidebar: {:?}", tree.layout(sidebar)?.size);
    println!("content: {:?}", tree.layout(content)?.size);
    Ok(())
}
output
sidebar: Size { width: 200.0, height: 600.0 }
content: Size { width: 600.0, height: 600.0 }

Text is the tricky part, because a label's width depends on its font. Taffy handles that with a measure function: leaf nodes carry a context, and Taffy calls back to ask how big the content wants to be. A real toolkit calls its text engine there (cosmic-text, Parley). This one uses a fake 8 pixels per character.

taffy-flex/src/bin/two.rs ✔ compiled · taffy 0.14 · rustc 1.95.0
use book_error::Result;
use taffy::prelude::*;

fn main() -> Result<()> {
    let mut tree: TaffyTree<&str> = TaffyTree::new();

    // A text leaf carries *context*; taffy asks us how big it wants to be.
    let label = tree.new_leaf_with_context(Style::default(), "Hello, taffy")?;
    let root = tree.new_with_children(
        Style { size: Size { width: length(100.0), height: auto() }, ..Default::default() },
        &[label],
    )?;

    tree.compute_layout_with_measure(root, Size::MAX_CONTENT, |inputs, _id, ctx, style| {
        taffy::compute_leaf_layout(inputs, style, |_, _| 0.0, |_known, _available| match ctx {
            // A toolkit would call its text engine here (cosmic-text, parley...).
            Some(text) => Size { width: text.len() as f32 * 8.0, height: 16.0 },
            None => Size::ZERO,
        })
    })?;
    println!("label: {:?}", tree.layout(label)?.size);
    Ok(())
}
output
label: Size { width: 96.0, height: 16.0 }

gpui, Floem and Blitz (under Dioxus Native) depend on it directly, and Slint's lockfile includes it too. But shared by name does not mean shared by version. cargo tree -i taffy on two frameworks, against the latest release of 0.14:

gpui-hello: cargo tree -i taffy
taffy v0.9.0
└── gpui v0.2.2
    └── gpui-hello v0.0.0 (/Users/andy/gui/snippets/gpui-hello)
floem-counter: cargo tree -i taffy
taffy v0.4.4
└── floem v0.2.0
    └── floem-counter v0.0.0 (/Users/andy/gui/snippets/floem-counter)

Dioxus Native resolves 0.9.2 and Slint 0.10.1. That is four different Taffy versions across four frameworks, each the latest release of its framework, and none of them 0.14. Not every framework uses it: Freya has its own engine (torin), and Makepad has no Taffy in its lockfile at all.

crates.io first publish.