Skip to content

Collapse borders in a layout

A common layout for applications is to split up the screen into panes, with borders around each pane. Often this leads to making UIs that look disconnected. E.g., the following layout:

problem

Created by the following code:

fn draw(frame: &mut Frame) {
// create a layout that splits the screen into 2 equal columns and the right column
// into 2 equal rows
let [left, right] = Layout::horizontal([Constraint::Fill(1); 2]).areas(frame.area());
let [top_right, bottom_right] = Layout::vertical([Constraint::Fill(1); 2]).areas(right);
frame.render_widget(Block::bordered().title("Left Block"), left);
frame.render_widget(Block::bordered().title("Top Right Block"), top_right);
frame.render_widget(Block::bordered().title("Bottom Right Block"), bottom_right);
}

We can do better though, by collapsing borders. E.g.:

solution

Starting with Ratatui 0.30, collapsing borders has become much easier thanks to the new merge_borders method and Spacing::Overlap. The recipe is simple:

  1. Import Spacing and MergeStrategy.

    use ratatui::{
    layout::{Constraint, Layout, Spacing},
    symbols::merge::MergeStrategy,
    widgets::Block,
    DefaultTerminal, Frame,
    };
  2. Use Spacing::Overlap(1) in your layout to make borders overlap.

    // Use Spacing::Overlap(1) to make the borders overlap
    let [left, right] = Layout::horizontal([Constraint::Fill(1); 2])
    .spacing(Spacing::Overlap(1))
    .areas(frame.area());
    let [top_right, bottom_right] = Layout::vertical([Constraint::Fill(1); 2])
    .spacing(Spacing::Overlap(1))
    .areas(right);
  3. Add .merge_borders(MergeStrategy::Exact) to your blocks to automatically merge borders (see MergeStrategy documentation for details about the different strategies).

    // Use merge_borders(MergeStrategy::Exact) to automatically handle border merging
    let left_block = Block::bordered()
    .title("Left Block")
    .merge_borders(MergeStrategy::Exact);
    let top_right_block = Block::bordered()
    .title("Top Right Block")
    .merge_borders(MergeStrategy::Exact);
    let bottom_right_block = Block::bordered()
    .title("Bottom Right Block")
    .merge_borders(MergeStrategy::Exact);

Setting merge_borders to MergeStrategy::Exact or MergeStrategy::Fuzzy automatically handles all the complex border joining logic. The Spacing::Overlap(1) ensures that adjacent borders occupy the same space, allowing them to be merged.

The full code for this example is available at https://github.com/ratatui/ratatui-website/blob/main/code/recipes/how-to-collapse-borders

collapse-borders.rs
use std::time::Duration;
use color_eyre::Result;
use ratatui::crossterm::event::{self, Event};
use ratatui::{
layout::{Constraint, Layout, Spacing},
symbols::merge::MergeStrategy,
widgets::Block,
DefaultTerminal, Frame,
};
/// This example shows how to use the new Ratatui v0.30 border merging feature to collapse borders
/// between widgets.
/// See https://ratatui.rs/how-to/layout/collapse-borders for more info
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(draw)?;
if key_pressed()? {
return Ok(());
}
}
}
fn key_pressed() -> Result<bool> {
Ok(event::poll(Duration::from_millis(16))? && matches!(event::read()?, Event::Key(_)))
}
fn draw(frame: &mut Frame) {
// create a layout that splits the screen into 2 equal columns and the right column
// into 2 equal rows
// Use Spacing::Overlap(1) to make the borders overlap
let [left, right] = Layout::horizontal([Constraint::Fill(1); 2])
.spacing(Spacing::Overlap(1))
.areas(frame.area());
let [top_right, bottom_right] = Layout::vertical([Constraint::Fill(1); 2])
.spacing(Spacing::Overlap(1))
.areas(right);
// Use merge_borders(MergeStrategy::Exact) to automatically handle border merging
let left_block = Block::bordered()
.title("Left Block")
.merge_borders(MergeStrategy::Exact);
let top_right_block = Block::bordered()
.title("Top Right Block")
.merge_borders(MergeStrategy::Exact);
let bottom_right_block = Block::bordered()
.title("Bottom Right Block")
.merge_borders(MergeStrategy::Exact);
frame.render_widget(left_block, left);
frame.render_widget(top_right_block, top_right);
frame.render_widget(bottom_right_block, bottom_right);
}