Beginner 14 min

Responsive layout

Use Flexbox, Grid, constraints, wrapping, and container queries without device-specific branches.

Let constraints do the work

Rows, columns, wrapping, intrinsic text measurement, min/max size, and percentage values adapt automatically. Prefer these rules before reaching for a container query.

src/view.rs
let actions = Element::row([
    Button::new("save", "Save", theme.button()).build(),
    Button::new("cancel", "Cancel", theme.ghost_button()).build(),
])
.flex_wrap(FlexWrap::Wrap)
.gap(8.0);

Element::column([
    Element::text("Account settings"),
    form,
    actions,
])
.width(percent(1.0))
.max_width(length(720.0))
.padding(Sides::length(24.0))
.gap(20.0)

Change presentation with a container query

A named container query is appropriate when narrow space requires a deliberate structural presentation change, such as turning a toolbar row into a column. Queries resolve against the nearest scope with the same identity.

src/view.rs
let scope = ContainerScopeId::new("toolbar");
let toolbar = Element::row(actions)
    .when(ContainerQuery::max_width(scope, 360.0), compact_patch)
    .container_scope(scope);

Design for touch as well as width

Keep controls large enough to tap, allow action rows to wrap, and avoid hard-coded desktop widths. Hit slop can enlarge interaction geometry without changing pixels or layout.

Reference files

These are the implementation and guide files used for this chapter.

crates/argui/examples/layout.rsdocs/ui/styling.mdcrates/argui-layout/src/lib.rs
Compiled example

Resize this compiled responsive layout

The Rust file below is imported verbatim by this page and compiled into the WebAssembly application running underneath it.

Open exact source
app_examples/docs-examples/src/examples/layout.rs
use argui::{
    paint::CornerRadii,
    runtime::{Context, LayoutSnapshot, Render},
    text::TextStyle,
    ui::{Element, FlexWrap, Sides, length, percent},
    widgets::default_theme,
};

#[derive(Default)]
pub struct Example {
    compact: bool,
}

impl Render for Example {
    fn render(&mut self, cx: &mut Context<Self>) -> Element {
        let themes = default_theme(cx.environment());
        let theme = themes.resolve(cx.environment().color_scheme);
        let width = if self.compact {
            percent(1.0)
        } else {
            length(180.0)
        };
        let card = |label| {
            Element::text(label)
                .text_style(TextStyle {
                    color: theme.primary_foreground,
                    weight: 650,
                    ..TextStyle::default()
                })
                .width(width)
                .grow(1.0)
                .padding(Sides::length(22.0))
                .background(theme.primary)
                .radius(CornerRadii::all(12.0))
        };
        Element::row([card("Flexible"), card("Responsive"), card("Retained")])
            .width(percent(1.0))
            .padding(Sides::length(24.0))
            .gap(12.0)
            .flex_wrap(FlexWrap::Wrap)
            .background(theme.background)
    }

    fn layout_changed(&mut self, layout: &LayoutSnapshot, cx: &mut Context<Self>) {
        let compact = layout.viewport_size().width < 540.0;
        if self.compact != compact {
            self.compact = compact;
            cx.notify();
        }
    }
}