
#1の環境構築から数回にわたりTUIの構築についての備忘録記事を書いてきました。今回はTUIの見やすさを決める、レイアウト構成についてです。
第1回開発環境の構築:Ubuntu上で Rust × ratatui の開発環境構築方法・TUI上にHelloWorld表示まで
1. なぜ「分割」が必要なのか?
これまでの実装では、画面全体に一つの入力欄があるだけでした。しかし、実際のツールでは「上に入力、下に実行結果」や「左にメニュー、右に詳細」といったレイアウトが不可欠です。
Ratatuiでは、Layout という仕組みを使って、一枚の板(Rect)をパズルのように切り分けていきます。
2. Layoutの基本構造
まずは、最短で画面を上下(垂直)に分けるコードを見てみましょう。前々回の terminal.draw の中身を以下のように書き換えます。
前々回 入力受付の実装:【Ratatui】Rustで作るTUIツール:ユーザーからの文字入力を受け取る(Input Formの実装)
// 必要なインポートを追加
use ratatui::layout::{Layout, Constraint, Direction};
// --- terminal.draw の中身 ---
terminal.draw(|f| {
// 1. 画面全体(f.area())を「垂直方向」に分割する指示書を作る
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), // 上部は3行
Constraint::Min(3), // 残り全部(結果表示用Min指定で最小を指定し最大化)
])
.split(f.area()); // 実際に分割して、領域(Rect)の配列を得る
// 2. 分割された領域[0]に「入力欄」を描画
let input_block = Paragraph::new(input.as_str())
.block(Block::default().title(" Input ").borders(Borders::ALL));
f.render_widget(input_block, chunks[0]);
// 3. 分割された領域[1]に「結果(仮)」を描画
let output_block = Paragraph::new("ここに結果が表示されます")
.block(Block::default().title(" Result ").borders(Borders::ALL));
f.render_widget(output_block, chunks[1]);
})?;超シンプル分割UI
実行してみると、以下のように上側と下側の表示を作ることができます。 上側の入力欄の高さは3行にしてあり、全体に枠(上と下の1行)をつけているので入力できる欄が1行になっています。下側の欄は最小高さを3として、それ以上は自動的に最大化しています。

ウィンドウを上下に割ってみた例
レイアウト設定方法まとめ
上の例では行数で指定しましたが、割合などでの設定も可能です。ratatuiでのレイアウト指定方法にまとめてみました。今まで使ったことがなかったのですが、調べてみるとFill(n)で重みづけでの分割も可能みたいです。
レイアウト指定方法
Layoutは、親の領域(Rect)をどのように切り分けるか、以下の「制約(Constraint)」を組み合わせて決定します。
1. 方向(Direction)の決定
まずは縦に割るか、横に割るかを決めます。
Direction::Vertical: 上下に分割。Direction::Horizontal: 左右に分割。
例:上下に割る。
#横割り。
#後についている.constraints([]).split(f.area());は分割方法の指定と領域の配列にする処理。
Layout::default().direction(Direction::Vertical).constraints([]).split(f.area());2. サイズ指定の5つの「物差し」
.constraints([]) の中に入れる指定方法には、以下のバリエーションがあります。
| 指定方法 | 特徴 | 主な用途 |
Length(n) | 固定値指定。指定した文字(行)数で固定。 | ヘッダー・フッター・入力欄など |
Percentage(n) | 割合指定。全体のn%を確保。 | 画面を「3:7」や「5:5」で分けたい時 |
Ratio(a, b) | 比率指定。分数(a/b)で指定。 | Percentage(33) ではなく Ratio(1, 3) と書ける |
Min(n) | 最小値保証。最低n行確保し、余れば広がる。 | メインコンテンツ表示エリア |
Max(n) | 最大値制限。最大n行まで。余れば縮む。 | ポップアップや一時的な通知 |
Fill(n) | 重み付き配分。余った分を比率で分ける。 | 現代的なFlexboxに近い分割 |
レイアウトパターンのコア・コード集
コピペ用に具体的な分割例を置いておきます。
① 「ヘッダー・メイン・フッター」構成(縦割り)
ヘッダーとフッターを固定し、中身を可変にします。
Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), // 固定ヘッダー
Constraint::Min(0), // 残り全部(可変)
Constraint::Length(1), // 1行フッター
])② サイドバー付き構成(横割り)
左にメニュー、右に詳細を「3:7」で分けます。
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(30), // 左:サイドバー
Constraint::Percentage(70), // 右:メイン
])③ 入れ子(Nesting)でグリッドを作る
Layoutで分けた chunks[i] を、さらに別のLayoutで分割すれば、複雑なグリッドも作れます。
let vertical_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(f.area());
// 上半分の領域(vertical_chunks[0])を、さらに横に2分割する
let top_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(vertical_chunks[0]);④ 余白(Margin)を作る
画面の端に少し余裕を持たせると、デザインが洗練されますし可読性が上がります。
Layout::default()
.margin(1) // 上下左右に1マスの余白
.horizontal_margin(2) // 左右だけに2マスの余白➄ Fillでレイアウトしてみた
Fillに重みづけして分割してみた例です。Fixedは幅15指定してあるので、残りの領域を2:1の重みで埋めてくれています。

Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length(15), // 1. サイドバー(常に15文字分固定)
Constraint::Fill(2), // 2. メイン(残り領域の 2/3 を使う)
Constraint::Fill(1), // 3. サブ(残り領域の 1/3 を使う)
])
.split(f.area());最後に
今回までの4記事にわたり、TUI開発環境構築から、標準入力、コンボ入力、そして、レイアウトとTUI開発の基本をおさらいしてみました。次回は今回のレイアウト指定に合わせて、分割した画面でどちらに入力されているのか?を管理するフォーカスについての実装方法をまとめてみようと思います。
次回の内容までを習得できれば、TUI面の操作はほとんどできるようになったといえると思うので良ければ見ていってください。
次回 フォーカス管理: 関連記事は、2026年4月22日に公開予定 (あと8時間)
ここまで読んでいただきありがとうございます。
では、次の記事で。 lumenHero