Layout
View SourceTermUI provides a declarative layout system for positioning and sizing components.
Basic Layout
Vertical Stacking
Stack elements from top to bottom:
stack(:vertical, [
text("Header"),
text("Body"),
text("Footer")
])Output:
Header
Body
FooterHorizontal Stacking
Stack elements from left to right:
stack(:horizontal, [
text("Left"),
text(" | "),
text("Right")
])Output:
Left | RightNested Layouts
Combine stacks for complex layouts:
stack(:vertical, [
text("=== Header ==="),
stack(:horizontal, [
text("[Sidebar]"),
text(" "),
text("[Main Content]")
]),
text("=== Footer ===")
])Output:
=== Header ===
[Sidebar] [Main Content]
=== Footer ===Constraints
Control how space is allocated using constraints.
Fixed Size
Exact number of cells:
alias TermUI.Layout.Constraint
stack(:horizontal, [
{text("Fixed"), Constraint.length(10)},
{text("Rest"), Constraint.fill()}
])Percentage
Proportion of available space:
stack(:horizontal, [
{left_panel, Constraint.percentage(30)},
{right_panel, Constraint.percentage(70)}
])Fill
Take all remaining space:
stack(:horizontal, [
{sidebar, Constraint.length(20)}, # Fixed 20 columns
{content, Constraint.fill()} # Rest of the space
])Ratio
Proportional distribution:
stack(:horizontal, [
{panel_a, Constraint.ratio(1)}, # 1 part
{panel_b, Constraint.ratio(2)}, # 2 parts
{panel_c, Constraint.ratio(1)} # 1 part
])
# Results in 25%, 50%, 25% distributionMin and Max
Set bounds on size:
# At least 10, at most 50
Constraint.percentage(30)
|> Constraint.with_min(10)
|> Constraint.with_max(50)Common Layout Patterns
Header-Body-Footer
def view(state) do
stack(:vertical, [
{render_header(state), Constraint.length(3)},
{render_body(state), Constraint.fill()},
{render_footer(state), Constraint.length(1)}
])
end
defp render_header(state) do
text("=== My Application ===", Style.new(fg: :cyan, attrs: [:bold]))
end
defp render_body(state) do
stack(:vertical, [
text("Main content here"),
text("..."),
])
end
defp render_footer(state) do
text("[Q]uit [H]elp", Style.new(fg: :bright_black))
endSidebar Layout
def view(state) do
stack(:horizontal, [
{render_sidebar(state), Constraint.length(25)},
{render_main(state), Constraint.fill()}
])
end
defp render_sidebar(state) do
stack(:vertical, [
text("Navigation", Style.new(attrs: [:bold])),
text(""),
text("• Dashboard"),
text("• Settings"),
text("• Help")
])
end
defp render_main(state) do
text("Main content area")
endTwo-Column Layout
def view(state) do
stack(:horizontal, [
{left_column(state), Constraint.percentage(50)},
{right_column(state), Constraint.percentage(50)}
])
endDashboard Grid
def view(state) do
stack(:vertical, [
# Top row - three equal panels
{stack(:horizontal, [
{cpu_gauge(state), Constraint.ratio(1)},
{memory_gauge(state), Constraint.ratio(1)},
{disk_gauge(state), Constraint.ratio(1)}
]), Constraint.length(5)},
# Bottom row - two panels
{stack(:horizontal, [
{process_list(state), Constraint.percentage(60)},
{network_stats(state), Constraint.percentage(40)}
]), Constraint.fill()}
])
endCentered Content
def view(state) do
# Horizontal centering with fill on both sides
stack(:horizontal, [
{text(""), Constraint.fill()},
{render_dialog(state), Constraint.length(40)},
{text(""), Constraint.fill()}
])
endText Alignment
Align text within available space:
alias TermUI.Layout.Alignment
# Left aligned (default)
text("Left", alignment: :left)
# Center aligned
text("Center", alignment: :center)
# Right aligned
text("Right", alignment: :right)Box Drawing
Create bordered containers:
def render_box(title, content) do
stack(:vertical, [
text("┌─ #{title} " <> String.duplicate("─", 20) <> "┐"),
stack(:horizontal, [
text("│ "),
content,
text(" │")
]),
text("└" <> String.duplicate("─", 24) <> "┘")
])
endResponsive Layouts
Adapt layout based on terminal size:
def view(%{width: width} = state) when width < 80 do
# Narrow layout - vertical stacking
stack(:vertical, [
render_sidebar(state),
render_main(state)
])
end
def view(state) do
# Wide layout - horizontal stacking
stack(:horizontal, [
{render_sidebar(state), Constraint.length(25)},
{render_main(state), Constraint.fill()}
])
endHandle resize events:
def event_to_msg(%Event.Resize{width: w, height: h}, _state) do
{:msg, {:resize, w, h}}
end
def update({:resize, width, height}, state) do
{%{state | width: width, height: height}, []}
endEmpty Space
Add spacing between elements:
# Empty line
text("")
# Multiple empty lines
stack(:vertical, [
text("First"),
text(""),
text(""),
text("Second")
])
# Horizontal space
stack(:horizontal, [
text("Label:"),
text(" "), # 3 spaces
text("Value")
])Conditional Rendering
Show/hide elements based on state:
def view(state) do
stack(:vertical, [
text("Header"),
if state.show_details do
render_details(state)
else
text("")
end,
text("Footer")
])
endOr use list filtering:
def view(state) do
elements = [
text("Header"),
state.show_details && render_details(state),
text("Footer")
]
stack(:vertical, Enum.filter(elements, & &1))
endPerformance Tips
1. Avoid Deep Nesting
Flatten layouts where possible:
# Less efficient
stack(:vertical, [
stack(:vertical, [
stack(:vertical, [
text("Deeply nested")
])
])
])
# More efficient
stack(:vertical, [
text("Flat")
])2. Use Constraints Sparingly
Only specify constraints when needed:
# Simple case - no constraints needed
stack(:vertical, [
text("Line 1"),
text("Line 2")
])
# Complex case - constraints needed
stack(:horizontal, [
{sidebar, Constraint.length(20)},
{content, Constraint.fill()}
])3. Memoize Complex Layouts
For layouts that don't change often:
def view(state) do
stack(:vertical, [
render_static_header(), # Cached internally
render_dynamic_content(state) # Recomputed each frame
])
end
# Static content can be module attribute
@header text("My Application", Style.new(fg: :cyan))
defp render_static_header, do: @header