Responsive Layouts
Terminals come in wildly different widths. The sidebar that reads well at
160 columns is suffocating at 60. ftui-layout’s responsive layer provides
CSS-style breakpoints and two helper types — Responsive<T> and
ResponsiveLayout — for swapping configurations at those boundaries.
Breakpoints
Breakpoint is ordered from smallest to largest and maps to a column
range:
| Breakpoint | Default min width | Typical use |
|---|---|---|
Xs | < 60 cols | Minimal / ultra-narrow |
Sm | 60–89 cols | Compact layouts |
Md | 90–119 cols | Standard terminal width |
Lg | 120–159 cols | Wide terminals |
Xl | 160+ cols | Ultra-wide / tiled |
The thresholds are configurable via Breakpoints { sm, md, lg, xl };
Xs implicitly starts at width 0.
0 60 90 120 160
┼──── Xs ──┼──── Sm ──┼──── Md ──┼──── Lg ──┼──── Xl ─▶Responsive<T> — values by breakpoint
Responsive<T> holds a base value and optional overrides per breakpoint:
use ftui_layout::{Responsive, Breakpoint};
let columns: Responsive<u16> = Responsive::new(2)
.with(Breakpoint::Md, 3)
.with(Breakpoint::Lg, 4)
.with(Breakpoint::Xl, 5);
assert_eq!(columns.resolve(Breakpoint::Xs), &2);
assert_eq!(columns.resolve(Breakpoint::Md), &3);
assert_eq!(columns.resolve(Breakpoint::Xl), &5);It works for any T — u16 for widths, bool for
“show-sidebar?”, a whole theme struct, anything.
ResponsiveLayout — Flex swapping
The common case is “use this Flex when it’s narrow, this other one when
it’s wide.” ResponsiveLayout is the purpose-built helper:
use ftui_layout::{Breakpoint, Constraint, Flex, ResponsiveLayout};
use ftui_render::Rect;
// Narrow: single column. Wide: sidebar + main. Ultra-wide: sidebar + main
// + inspector.
let layout = ResponsiveLayout::new(
// base (Xs) — single column
Flex::vertical().constraints([Constraint::Fill]),
)
.at(
Breakpoint::Md,
Flex::horizontal().constraints([
Constraint::Fixed(30), // sidebar
Constraint::Fill, // main
]),
)
.at(
Breakpoint::Lg,
Flex::horizontal().constraints([
Constraint::Fixed(30), // sidebar
Constraint::Fill, // main
Constraint::Fixed(40), // inspector
]),
);
let area = Rect::new(0, 0, 140, 40);
let split = layout.split(area);
// Automatically classifies area.width into Lg and solves that Flex.
assert_eq!(split.rects.len(), 3);Important methods:
layout.at(bp, flex) // override the Flex at a breakpoint
layout.split(area) // classify by width, then solve
layout.split_for(bp, area) // override classification, e.g. for tests
layout.classify(width) // which breakpoint owns this width?
layout.layout_for(bp) // peek the Flex used for `bp`
layout.detect_transition(old_width, new_width) // did we cross a boundary?detect_transition is the hook for animating between layouts or firing
telemetry when the UX shape changes.
When to use Responsive<T> vs ResponsiveLayout
Same shape, different sizes → Responsive<u16>
You always have a sidebar-plus-main split. Only the sidebar width changes
with the terminal. One Flex, constraints computed with
Responsive<u16>.
Different shapes entirely → ResponsiveLayout
At Xs you hide the sidebar. At Md you show it. At Lg you add an
inspector column. The number of rects and their meaning changes, so the
whole Flex swaps.
Demo reference
The demo showcase ships a responsive screen — search the showcase for
"responsive" or "breakpoints" to see live examples wired to
resize events. See demo-showcase for how to
run and navigate those screens.
Pitfalls
Avoid layout thrashing near thresholds. If your content is 89 cells
wide and the Sm/Md threshold is 90, a single resize can flip
breakpoints every frame. Use detect_transition to debounce, or widen
your thresholds so the hysteresis is honest.
The breakpoint is derived from width only. Height does not feed
into Breakpoint classification. If you need vertical responsiveness
(short terminal vs. tall), build a separate Responsive<Flex> keyed off
area.height yourself.
Where to go next
The Flex API that ResponsiveLayout wraps.
Content-aware sizing that combines with breakpoints.
Intrinsic sizingA coarser kind of responsiveness for pane workspaces.
Pane intelligence modesHow widgets receive the solved Rects.
How this piece fits in layout.
Layout overview