Layout System
Wijjit’s layout engine borrows ideas from flexbox and native GUI toolkits. Instead of placing characters manually, you declare stacks, frames, and elements; the engine computes bounds, applies padding, and takes care of scrolling. This chapter covers the primitives available through wijjit.layout.
Mental model
Template tags (
{% vstack %},{% frame %}, etc.) emit layout nodes backed by objects inwijjit.layout.engine.Nodes form a tree rooted at either a stack or frame. Each node specifies width/height behaviour (a fixed number of character cells, percentages,
fill,auto) plus alignment hints.On render the engine performs:
Constraint pass – bottom-up traversal calling
calculate_constraintson each node to derive minimal and preferred sizes.Assignment pass – top-down traversal calling
assign_boundsto give every node absolute(x, y, width, height)coordinates inside the terminal.
Elements store their
Boundsand use them during painting. Scroll managers and focus navigation also use these bounds for hit testing.
Sizing options
Any width/height attribute accepts:
Integers – fixed character/row counts (
width=40).Percentages – strings like
"50%"relative to the parent dimension.“fill” – consume remaining space after fixed/percent siblings have been laid out.
“auto” – use the element’s intrinsic size (
Element.get_intrinsic_size). Most text-based widgets default to auto height and fill width.
Margins, padding, spacing
marginadds space outside a container. Accepts single ints or tuples(top, right, bottom, left).paddingadds inner space between the container border and its children.spacingcontrols the gap between stacked children.
These attributes are present on both {% vstack %} and {% hstack %}, so you can fine-tune spacing without sprinkling blank labels.
Stacks
{% vstack %}Arranges children vertically (default
width="fill",height="auto").align_hdetermines how children are aligned horizontally (left,center,right,stretch).align_vcontrols how the group sits inside the stack’s allotted height ("stretch"by default).{% hstack %}Arranges children horizontally.
{% hstack %}accepts ajustifyattribute (flex-start– the default – plusflex-end,center,space-between,space-around,space-evenly) to distribute children along the main axis, as well aswrap,gap,row_gap, andcolumn_gap. You can also push items to the edges by inserting a spacer made from an empty{% hstack width="fill" %}{% endhstack %}between tool groups.
Stacks can contain other stacks, frames, inputs, or display elements. They map to wijjit.layout.engine.VStack / wijjit.layout.engine.HStack.
Grid
{% grid %}Arranges children into a fixed grid, filling cells left-to-right, top-to-bottom. Attributes:
rows(default2),cols(default2),row_gap/column_gap(blank rows/columns between cells;column_gapmatches{% hstack %}),width(default"fill"),height(default"auto"),padding,margin, andalign_h/align_v(cell alignment,"stretch"by default).Wrap a child in
{% colspan cols=N %}or{% rowspan rows=N %}to make it span multiple cells:{% grid rows=2 cols=3 row_gap=1 column_gap=2 %} {% colspan cols=2 %} {% frame border="double" %}Wide header{% endframe %} {% endcolspan %} {% frame border="single" %}A{% endframe %} {% frame border="single" %}B{% endframe %} {% frame border="single" %}C{% endframe %} {% frame border="single" %}D{% endframe %} {% endgrid %}
Note
Use
column_gapfor the horizontal gap, matching{% hstack %}.
Frames
{% frame %} wraps content in a bordered box. The border attribute selects the border (single, double, rounded, or none). Beyond visual polish, frames:
Support scrolling –
scrollable=trueenables vertical scrollbars automatically when content exceeds height.Manage titlebars –
title="System Status"reserves a line for labels/icons.Provide padding defaults – interior padding is
(0, 1, 0, 1)so text does not touch borders.
Internally frames rely on wijjit.layout.frames.Frame with wijjit.layout.frames.FrameStyle controlling margins, titles, and scrollbar visibility.
Scroll management
Any ScrollableElement (frames, text areas, log views) integrates with wijjit.layout.scroll.ScrollManager. When scrollable=True:
Wheel events and
PgUp/PgDnkeys update the scroll offset.Scrollbars render on the right edge if
show_scrollbar=True.Scroll position is preserved across re-renders because scroll offset is an ephemeral element-state prop that survives reconciliation.
Use scrollable frames to wrap long markdown blocks, tables, or nested layouts.
Horizontal scrolling
Frames also support horizontal scrolling when content exceeds the frame width. Enable it with these attributes:
overflow_x- Control horizontal overflow behaviour:"auto"- Show horizontal scrollbar only when content exceeds width"scroll"- Always show horizontal scrollbar"hidden"- Clip content without scrollbar (default)
show_scrollbar_x- Explicitly control horizontal scrollbar visibility (default:False)
{% frame width=60 height=15 scrollable=true
overflow_x="auto" show_scrollbar_x=true %}
Content that may exceed the frame width...
{% endframe %}
Keyboard controls for horizontal scrolling:
Left/Right arrows - Scroll horizontally (when frame has focus)
Shift+Left/Right - Page scroll horizontally
Shift+Mouse wheel - Horizontal scroll via mouse
Horizontal scroll offset is preserved across re-renders alongside the vertical scroll offset, since both are ephemeral element-state props that survive reconciliation.
See examples/advanced/horizontal_scroll_demo.py for a demonstration of combined vertical and horizontal scrolling.
Alignment & distribution
align_h and align_v exist on both stacks and frames:
stretch– child fills available dimension (default in many cases).left,center,right– typical alignments.top,middle,bottom– vertical equivalents.
For fine-grained control, mix fixed-width children with width="fill" siblings to push items to edges (toolbar patterns).
Responsive techniques
Percentages – combine
width="30%"andwidth="70%"columns in anhstackto mimic CSS grid behaviour.Breakpoints – inspect the terminal size with
shutil.get_terminal_size()(e.g.shutil.get_terminal_size().columns) inside your view and switch templates/layouts when the terminal is narrow.Conditional stacks – use Jinja
{% if %}to swap between vertical and horizontal arrangements when space is limited.
Debugging layouts
Dump the rendered element tree without running the app:
wijjit tree app.py(add--jsonfor a machine-readable form). It is the quickest way to see how your template nested and which node ended up where.Temporarily set
border="double"andtitleattributes to visualize container boundaries.Log bounds: each element exposes
element.boundsafter a render; printing them inside handlers can reveal unexpected sizes.
Checklist
Start with a
frameor rootvstackthat fills the screen.Use nested stacks to represent rows/columns.
Assign
width/heightonly where you need explicit sizing; let other nodes auto-measure.Turn on scrolling whenever content is unbounded.
When performance matters, minimize deeply nested frames—stacks are lighter.
With the layout system under control, you can focus on the widgets themselves. Continue to Components for a catalogue of available elements.