Main Layout Structure¶
What you'll learn
- What a container node is and why container nodes take over the position and size of their children.
- The role of
VBoxContainer,HBoxContainer,HSplitContainer, andLabel— the four UI building blocks the rest of the game's screens compose out of. - The Full Rect anchor preset and why it only applies to the root of a UI subtree, not to children inside a container.
- The difference between
SIZE_FILL(default cross-axis behavior) andSIZE_EXPAND_FILL(claim leftover main-axis space), and how mixing them gives you a top-bar/content/status-bar layout for free.
How it applies
- Resolution-independent UI by default. Container nodes plus the Full Rect anchor mean the layout described in this chapter renders identically at
1280×720,1920×1080, and any aspect-locked size in between. The artwork or asset team does not have to ship one PSD per resolution; the player on a 4K monitor and the player on a 720p laptop see the same proportions, just scaled. - Test-matrix collapse. With anchors and SIZE flags driving every position, QA does not need to retest "does the status bar stay at the bottom?" at every resolution — it stays there because the engine's layout pass is doing the math, not the developer. One bug at one resolution is one bug, not N bugs.
- Asset-team handoff. A UI artist mocking up the click-button screen in Figma can use the same
1280×720canvas and the same regions (top bar, content area, status bar). The engineer does not have to translate "this button in the mockup is X pixels from the top" — they translate to "this button is in the top bar" and the layout system handles the rest. - Localized text without rework. German and Russian strings are routinely 30–60% longer than the English equivalent. Containers grow to fit their children's preferred sizes; a button labeled "Buy" in English and "Achetez maintenant" in French will just be wider in French, with no manual repositioning of neighbors.
- Accessibility re-skinning. A player who increases system font size, or a developer adding a "large UI" toggle later, can scale a Label's
theme_override_font_sizes/font_sizeand watch every container grow to accommodate it. Rigid pixel positioning would either truncate the larger text or require a parallel layout pass per font size. - Splitter-driven UX.
HSplitContainergives players a draggable divider between two regions for free — no signal handling, no drag math. Idle-game players who want to widen the upgrade list at the expense of the building list get that affordance without engineering effort.
Concepts¶
Container nodes¶
A container node is a Control subclass whose entire job is to position and size its children. The container has zero visual presence — no fill, no border, no rendered output — it is invisible scaffolding. What it does have is a layout policy: rules for where each child goes given the container's own rectangle and the children's preferences.
The single most important consequence: when a Control is a child of a container, the container takes over its position and size. The child's anchors and offsets are ignored. The child's size flags (size_flags_horizontal, size_flags_vertical) influence the layout, but the container has the final word.
This produces the trade-off that makes Godot's UI work: you give up direct pixel control of children, and in exchange you get layouts that re-flow correctly when the window resizes, fonts change size, languages swap, or content gets longer.
Example
You drop a Button directly under a CanvasLayer and set its anchor preset to Center. The button sits dead-center of the window, scaling with canvas_items stretch. You drop the same Button under a VBoxContainer. The button no longer obeys the Center anchor — the VBoxContainer decides where it goes, in row order with its siblings. Removing the button from the container restores anchor-driven positioning. The container is the difference.
VBoxContainer, HBoxContainer, HSplitContainer, Label¶
The four nodes M1.4 introduces, in the order you will use them:
VBoxContainer— vertical box. Stacks children top-to-bottom. Cross-axis (horizontal) is full width by default.HBoxContainer— horizontal box. Stacks children left-to-right. Cross-axis (vertical) is full height by default. Use for top bars, button rows, inline label-and-value pairs.HSplitContainer— two-pane horizontal split with a draggable divider. Hardcoded for exactly two children: a third child is silently not laid out. Useful for "main content on the left, side panel on the right" layouts where you want the player to drag the divider.Label— plain text display. Not a container; a leaf Control with atextproperty. Use for read-only text: the resource counter, the status line, button labels (when not using aButton).
You will use them, in order, to build:
CanvasLayer
└── MainLayout (VBoxContainer)
├── TopBar (HBoxContainer)
├── ContentArea (HSplitContainer)
└── StatusBar (Label)
The MainLayout is the trunk. Inside, three children stack vertically: a top bar, the main content area, a status bar at the bottom.
Example
The shape "top bar / content / status bar" is the same shape every desktop application has used since the 1980s — Visual Studio, Photoshop, the Windows File Explorer. Idle games inherit it because players already know how to read it. The top bar is "what I have" (resources), the content is "what I do" (clicks, upgrades, buildings), the status bar is "what just happened" (last event, current goal). Nothing about Godot dictates this layout; the convention does, and the engine's container nodes are tuned for it.
The Full Rect anchor preset¶
Every Control has anchors — four edge values from 0 to 1 that define its rectangle relative to its parent. The Full Rect preset — (0, 0, 1, 1) with all offsets zero — makes the Control fill its parent's entire rectangle.
The catch: anchors only work when the parent is a regular Control, not a container. So you set Full Rect on the root of a UI subtree to make it claim the full window, and from there containers take over. In our tree:
MainLayoutis a child ofCanvasLayer.CanvasLayeris not a container, soMainLayout's anchors do matter — and Full Rect is what makes it claim the entire window.TopBar,ContentArea,StatusBarare children ofMainLayout, which is a container (VBoxContainer). Their anchors are ignored. The container decides their positions.
This is why you set the anchor preset exactly once, on MainLayout, and never touch it on the container children.
Example
You set Full Rect on TopBar thinking "I want it full width." Nothing visible changes — the anchor was already going to be ignored by MainLayout's VBoxContainer layout policy. You then set TopBar's size_flags_horizontal to SIZE_FILL (it already is, that's the default), and that is what makes TopBar claim the full width. The anchor click did nothing; the size-flag setting did the work.
SIZE_FILL vs SIZE_EXPAND_FILL¶
A container child's size_flags_* properties tell the parent container how that child wants to be sized:
SIZE_FILL(the default) — "fill the space the container gives me, exactly." On the main axis of aVBoxContainer, this means eachSIZE_FILLchild is exactly its minimum height; the container packs them tightly with leftover space at the bottom.SIZE_EXPAND_FILL— "claim leftover space on the main axis, then fill it." On aVBoxContainerwith mixed children, theSIZE_EXPAND_FILLchild grows to absorb everything left after theSIZE_FILLchildren take their minimum heights. MultipleSIZE_EXPAND_FILLsiblings split the leftover space proportionally to theirstretch_ratio(default1, so they share equally).
For our tree:
TopBar—SIZE_FILLvertical (default). Takes its minimum height.StatusBar—SIZE_FILLvertical (default). Takes its minimum height.ContentArea—SIZE_EXPAND_FILLvertical. Absorbs everything betweenTopBarandStatusBar.
Result: the top bar pins to the top, the status bar pins to the bottom, the content area takes whatever vertical space remains. As the window grows, the content area grows; as the window shrinks, the content area shrinks. The bars stay their natural sizes.
Example
Set both TopBar and StatusBar to SIZE_EXPAND_FILL. Now the leftover space is split between the three children, and the top bar/status bar grow to absorb part of it. The visual is wrong — bars should be bars, not panels. The fix is SIZE_EXPAND_FILL only on the child that is meant to grow.
Example
Set ContentArea's stretch_ratio to 2 and StatusBar's size_flags_vertical to SIZE_EXPAND_FILL with default stretch_ratio = 1. Both children expand. Of the leftover vertical space, ContentArea takes ⅔ and StatusBar takes ⅓. Useful for layouts where you want a chunky log panel at the bottom that grows with the window. Not what we want for a status bar — but the mechanism exists.
Walkthrough¶
You will perform these in your own Godot editor with scenes/main.tscn open from M1.3.
- In the Scene dock, the root
CanvasLayershould be selected. Right-click it and choose Add Child Node (or click the+icon at the top of the dock). The Create New Node dialog opens. - Type
VBoxContainerin the search field, double-click the result. AVBoxContainerappears as a child ofCanvasLayer. By default it is namedVBoxContainer. - With the new node selected, double-click its name in the Scene dock and rename it to
MainLayout. Press Enter. - With
MainLayoutselected, look at the 2D viewport toolbar (above the editing area). There is a Layout dropdown showing "Custom"; click it. From the menu, pick Anchor Preset → Full Rect (the icon shows a rectangle filling the parent). TheMainLayoutrectangle in the viewport now spans the full window outline. - Right-click
MainLayout→ Add Child Node → searchHBoxContainer→ create. Rename it toTopBar. - Right-click
MainLayout(notTopBar— you want a sibling, not a child) → Add Child Node → searchHSplitContainer→ create. Rename it toContentArea. - Right-click
MainLayout→ Add Child Node → searchLabel→ create. Rename it toStatusBar. The Scene dock now shows three children underMainLayout, in order:TopBar,ContentArea,StatusBar. - With
StatusBarselected, find the Inspector panel (right side by default). Find theTextproperty and typeStatus: ready. The label is too small to see in the viewport because there's no vertical space yet — that is fixed in the next step. - Select
ContentArea. In the Inspector, scroll to the Layout group → Container Sizing sub-group. FindVertical(under Size Flags) and click the dropdown; pick Expand Fill (the engine name forSIZE_EXPAND_FILL). TheContentArearectangle in the viewport snaps to the leftover space betweenTopBarandStatusBar. - Press
Ctrl+Sto save the scene. - Press
F5to launch. The game window opens at1280×720. You see: a thin bar at the top (emptyTopBar), a wide empty middle (theHSplitContainerwith no children — its splitter is invisible until it has two children), and the text "Status: ready" pinned at the very bottom of the window. - Resize the game window. The status bar stays pinned to the bottom; the top bar stays at the top; the middle grows or shrinks. Close the window.
Optional sanity check. Re-open
main.tscnin a text editor (outside Godot). The file should contain aMainLayoutnode withanchors_preset = 15(the numeric ID for Full Rect), three children, andContentAreawithsize_flags_vertical = 3(the numeric value forSIZE_EXPAND_FILL). If anything is missing, the in-editor change did not save — Ctrl+S in the editor and re-check.
Self-check quiz¶
Q1 — MainLayout (VBoxContainer) has three children in this order: TopBar (SIZE_FILL), ContentArea (SIZE_EXPAND_FILL), StatusBar (SIZE_FILL). The window is 720 pixels tall. TopBar reports a minimum height of 40 px; StatusBar reports a minimum height of 30 px. How tall is ContentArea?
A. 240 (window height ÷ 3, since there are three children).
B. 650 (720 − 40 − 30, the leftover after the two fixed-height bars).
C. 720 (it expands to fill the entire window, ignoring the bars).
D. 0 (the splitter has no children of its own, so the container collapses).
Reveal answer
B — 650. The VBoxContainer resolves layout in this order: every SIZE_FILL child takes its minimum size on the main axis, the leftover space is split among SIZE_EXPAND_FILL children proportionally to stretch_ratio. With one expanding child, the leftover (720 − 40 − 30 = 650) is its full vertical extent. A is wrong because the container does not divide space equally; size flags decide. C is wrong because EXPAND_FILL only consumes leftover space, not all space. D confuses an empty HSplitContainer (which still occupies its parent-allotted height) with a container that has no parent allotment.
Q2 — You add a fourth node to your scene, dragging it as a child of ContentArea. The Scene dock now shows ContentArea with three children: a Panel, then a Panel, then a VBoxContainer. What does HSplitContainer do with the third child?
A. Stacks all three children vertically with two draggable dividers.
B. Lays out the first two as the split panes; silently does not lay out the third (it renders at (0, 0) with size (0, 0)).
C. Throws a runtime error and refuses to render the scene.
D. Lays out the first two; the third floats at the parent's center.
Reveal answer
B — first two are the split panes; the third is not laid out. HSplitContainer is hardcoded for exactly two children — that is the whole point of a split container. Extras are silently ignored, sized to zero. A is wrong: there is no built-in container for an N-pane split with (N − 1) dividers; that is what nested splitters or a BoxContainer with manual separators is for. C is too dramatic — Godot is permissive about extra children; it just does not lay them out. D would be a reasonable behavior, but it is not what the engine does.
Q3 — You set Full Rect on MainLayout and it visibly fills the window. You then set Full Rect on TopBar. Nothing visible changes. Why?
A. Full Rect does not work on HBoxContainer — it only works on VBoxContainer.
B. The anchor preset was applied silently and is in effect; the visual is unchanged because the window is full-rect already.
C. TopBar is a child of a container (MainLayout), so its anchors are ignored — the container decides position and size.
D. Full Rect sets the position but not the size; you also have to drag the corners.
Reveal answer
C — container parents override anchors. Once TopBar is a child of MainLayout (a VBoxContainer), the container's layout policy decides where TopBar goes. Setting anchors on TopBar is harmless but has no effect; the size flags (size_flags_horizontal, size_flags_vertical) are what TopBar uses to communicate sizing preferences to its parent. A is wrong: anchors are a Control feature, not specific to a container type. B is wrong: anchors are set, they just do nothing in this context. D misdescribes anchors entirely.
Integration question¶
Q4 — open
In M1.3 you set the root of main.tscn to CanvasLayer. In M1.4 you set Full Rect on MainLayout (the VBoxContainer directly under CanvasLayer). Why does Full Rect work as expected here, given that "anchors are ignored when the parent is a container"?
Reveal expected answer
CanvasLayer is not a container — it is a render-layer separator. So MainLayout's anchors are consulted by the engine, which is what makes Full Rect actually fill the window. Once MainLayout (a container) has children, those children's anchors stop mattering, because their parent is a container. The Full Rect rule applies at exactly one level of the tree: the topmost UI Control whose parent is not a container. Below that level, size flags do all the work.
Glossary¶
Glossary
- container node
- A
Controlsubclass whose job is to lay out its children. Takes over each child's position and size based on the container's layout policy. Child anchors are ignored when the parent is a container. VBoxContainer- A container that stacks its children vertically (top to bottom). Each child gets the container's full width on the cross axis (default
SIZE_FILL); main-axis (vertical) sizing is driven by each child'ssize_flags_vertical. HBoxContainer- A container that stacks its children horizontally (left to right). Each child gets the container's full height on the cross axis (default
SIZE_FILL); main-axis (horizontal) sizing is driven by each child'ssize_flags_horizontal. HSplitContainer- A container with exactly two children separated by a draggable divider. Player can resize the split ratio at runtime. Extras beyond two children are silently not laid out.
Label- A plain text-display
Control. Not a container. Has atextproperty and theme-driven font properties. Use for any read-only text in the UI. - anchors
- Four properties (
anchor_left,anchor_top,anchor_right,anchor_bottom) on everyControl, valued 0–1, that define the Control's rectangle relative to its parent's. Only consulted when the parent is not a container. - Full Rect preset
- An anchor preset (
anchor_left=0, anchor_top=0, anchor_right=1, anchor_bottom=1, all offsets 0) that makes the Control fill its parent. Set via the Layout dropdown in the 2D viewport toolbar. Effective only when the parent is not a container. size_flags_horizontal/size_flags_vertical- Two properties on every
Controltelling its parent container how this child wants to be sized on each axis. Default isSIZE_FILLon both axes. SIZE_FILL- The default size flag. The child fills its allotted main-axis space exactly — no more, no less.
SIZE_EXPAND_FILL- Size flag combining EXPAND (claim leftover main-axis space) with FILL (occupy all of it). Multiple EXPAND_FILL siblings split leftover space proportionally to their
stretch_ratio(default 1). The engine's mechanism for "this child grows; everything else stays compact."