Overview of Godot UI containers

In this study guide

This is a reference page

You don't have to retain any of the information here. Rather, treat this as a tool to come back to when you need to build interfaces.
, you'll get an overview of all the containers available in Godot, and how to use them to build common UI elements. Containers are powerful as you can nest them inside each other to create complex layouts. They can be difficult to wrap your head around at first, so this guide will give you a head start.
All the examples below have a corresponding scene in the Godot project M8 for you to study. Don't forget that you can use "expand non default" to quickly check which properties have been modified.
You'll also find some tips and information about common problems with coding user interface: how to animate container children and how to code your own container.

All the containers

This section gives you an overview of all the available containers, so you can easily search and pick the most appropriate one for your needs.
While the descriptions will be short, you'll find examples for all the containers in the directory res://lessons_reference/UI_containers_samples. The descriptions also have tips and warn you of common pitfalls, so they're not repeats of the official documentation.
  • CenterContainer

    The CenterContainer ensures all control children are always in the center. If your control nodes do not have a minimum size, they'll be squished to zero!
    Good for: Modal windows and popups.
  • AspectRatioContainer

    AspectRatioContainer does resize a single child to fit a certain proportion that you can set. A proportion of 1 means the child will always be square, while 1.7 is close to cinema/HD aspect ratio. Values lower than 1 will look like portraits; approximately 0.6 is similar to an average smartphone's screen.
    Good for: Images, videos, or any content that needs to keep a specific aspect ratio.
  • HBoxContainer and VBoxContainer

    BoxContainer Allows for placing items horizontally or vertically one after the other. Probably the most used Container. VBoxContainer places items vertically, while HBoxContainer places them horizontally. You can set the spacing between children.
    Good for: Any kind of column such as menu items, or row such as a header with buttons.
  • GridContainer

    GridContainer allows for placing items in a grid. Each column and each row needs at least one child node to have a minimum size, or the row or column will be squished to zero. The number of default columns is 1, which means the default behavior is somewhat similar to a VBoxContainer.
    To create inventory UIs, a common technique when using GridContainer is to fill it in advance with a custom Slot control node (usually made of a Panel root node and a TextureRect child node for the item icon).
    Good for: Inventories, crafting UIs.
  • ScrollContainer

    ScrollContainer is a container that allows scrolling its children. It can scroll horizontally, vertically, or both. The scrollbars on the x or y axis can be set to show up when needed, always, or never.
    Good for: Any UI that can overflow the screen, such as a long list of items or a chat window. It's often good to wrap important screens with a ScrollContainer to ensure they work on all screen sizes.
  • HFlowContainer and VFlowContainer

    FlowContainer nodes imitate the default behavior of webpages: they wrap children when they reach the end of the container. You can set the spacing between children. The HFlowContainer flows children horizontally, while the VFlowContainer flows them vertically.
    Good for: Complicated UIs that need to adapt to different screen sizes; tags.
  • SplitContainer

    SplitContainer takes two children and adds a splitter between them. The user can drag the splitter to resize the children. Each side will not resize below the minimum size of the children. A collapsed property allows you to hide the first child (and disables the splitter).
    Good for: Side panels, such as the Godot editor's inspector.
  • PanelContainer

    PanelContainer is a simple container that draws a rectangle around its unique child. It's useful to give a background to controls. To give it internal margins, edit the StyleBox property.
    Good for: Windows, dialogs, and popups.
  • TabContainer

    TabContainer will automatically create tabs from children. Each tab uses the corresponding child's name as a title. It's possible to override the title with the set_tab_title() method from code, but not from the editor.
    Note that another control node, TabBar, can be used for more complicated setups; but it isn't automated and requires you to create tabs yourself in code.
    Good for: Options screens, settings screens, or any screen that needs tabs.
  • SubViewportContainer

    SubViewportContainer can host a Viewport node. Viewports can display a different scene than the one currently running. To use it, you need to add a Viewport node under it, and then a 2D or 3D scene under that. The Viewport node needs a size set.
    Good for: mini-maps, mixing 2D and 3D, or to set up couch co-op split-screen

Common challenges of creating UI Scenes

If you tried your hand at making your own UI scenes, you've probably asked yourself some of the questions in this section.

How do I ensure my UI does not move when the camera moves?

It is traditional to have a HUD wtith health and score over a game being played. However, if you use move the camera, the UI will slide off screen!
To prevent that, you should always use a CanvasLayer node as a parent of your UI. This will ensure the UI is always drawn on top of the game world.
CanvasLayer creates a new rendering context, so it's not affected by the camera. Like the name implies, anything within a CanvasLayer is in a different layer of its own (In 3D, adding a Controls automatically creates a new context, so it's not necessary to use CanvasLayer).

Should I always add a CanvasLayer as the root of my UI scene?

Because of the above, you might be tempted to make all your UI scenes have a CanvasLayer as the root.
If you do this, it will be harder to manage the UI; you won't be able to resize it or change its anchors from other scenes. Instead, always keep a Control node as the root of your UI as you normally would, and add a CanvasLayer in the main scene where you want to display the UI.

Should I fragment my UI in many scenes?

People coming from web development often think it's best to split the UI into many small components. In Godot, it's often better to use large monolithic UI scenes, and make liberal use of "Access as Unique Name" to target the children. This makes it much faster to move nodes around, change sizes, and so on.
Of course, do split in components where it makes sense, but not by default.

How can I preview a UI scene embedded in another in the editor?

Often, you would like to preview how a UI scene looks in the editor, but a lot of the functionality happens only in code. Running the scene after each change gets tedious. Ideally, you'd use the @tool annotation, but this is a little tricky.
While in a game, all children are ready after _ready(), the editor is more complicated. There are various cases in the editor where the children are not available, so if you try to change children nodes from a @tool script, you will get errors. To prevent most of those errors, await for the ready signal anytime you need to access a child. Here's an example: This is a hypothetical ChatLine scene with a title that you can edit in the editor:
@tool
extends HBoxContainer

@onready var _label: Label = %Label

## Title of the item. Gets assigned to the underlying label
@export var title := "": set = set_title

## [param title] property setter.
func set_title(new_title: String) -> void:
	title = new_title
	if not is_inside_tree():
		await ready
	_label.text = title
the method is_inside_tree() returns false if the scene is not ready yet. If the parent is not ready, we await the ready signal. This delays the rest of the method until ready is emitted.
You can test this scene by checking the editable_widget.tscn, it's associated script editable_widget.gd, and the scene where it is used: editable_widget_in_use.tscn. All three are in res://lessons_reference/UI_samples

Which container to use when?

Here are some examples of common UI elements that you might want to build. The descriptions are short, but you'll find the full scenes in the directory res://lessons_reference/UI_samples. Some of them will be slightly complex if you're just starting out, but I'm sure they'll come in handy in your projects.
If you think there's a common element that I haven't covered, please let me know in the comments.
  • A settings menu screen

    The core of a settings menu is a VBoxContainer with rows. Each row is a HBoxContainer with two children each: a label on the left, and some other control on the right. Don't forget to set both children as expand so they take the full width of the row.
    For more refinement, the entire thing can be put inside a ScrollContainer to ensure all options fit no matter what.
    See the example scene: res://lessons_reference/UI_samples/settings_screen.tscn. There's also a version with tabs: res://lessons_reference/UI_samples/settings_screen_with_tabs.tscn
  • A HUD

    You could use an HBoxContainer to place elements next to each other; for example, a character's portrait, and the bars to the right.
    To manage manually placing the bars in a pleasing way, you could break out of containers by inserting a plain Control node between the bars and the parent, and using anchors to place the health and mana bars.
    An intermediary plain Control is a good trick to escape the constraints of containers and place elements freely.
    See the example scene: res://lessons_reference/UI_samples/hud.tscn
  • A title screen

    A simple VBoxContainer with buttons might be enough, but in this example, I want to stagger the buttons. To do this, you could use HBoxContainer nodes as a parent of each Button; and then, in each row, add first a plain Control node, then the button. By setting both nodes to expand, you can control the spacing between each button and the left border by adjusting the Control node's size or stretch ratio. A higher stretch ratio means more space between buttons and the left border.
    See the example scene: res://lessons_reference/UI_samples/title_screen.tscn
  • Mobile side drawer

    The drawer's contents should in a ScrollContainer to ensure all its contents are accessible if there are many. Inside the ScrollContainer, an HBoxContainer can be used to place the buttons.
    Because you cannot animate children of a container, you'll need to use a Control node as the parent of the drawer. Place the drawer in the Control node, and move it outside the screen so it can slide in. You can now use Tween to animate the drawer.
    See the example scene: res://lessons_reference/UI_samples/mobile_animated_menu.tscn. This example is a bit elaborate, with a button that has toggle_mode on, used to open/close the menu.

Bonus 1: Animating container children

How to animate container children? You can't! Containers control their children, so there is simply no good way to animate their children. If you try, you'll find that they snap back to their original position as soon as the container updates.
But there's a workaround: insert a Control node as the parent of the children you want to animate. The Control will be constrained by the parent container, but the children inside can use anchors to move freely.
You can see the example scene in res://lessons_reference/UI_samples/animated_buttons.tscn. Another example of an animated UI element is in the mobile side drawer example mobile_animated_menu.tscn.
There are also plugins that can help you animate UI elements in the asset library, but I haven't tried them.

Bonus 2: Custom containers

What if you can't find a container of your dreams? No problem, Godot gives you the tools to make your own container! You can extend the Container class and create any layout you want.
In order to do this, we need to:
  1. Create a function that places all children depending on some custom logic
  2. Connect this function to the NOTIFICATION_SORT_CHILDREN event, which is dispatched anytime the container resizes or children are added or removed.

The Container node: important methods

There are three methods that are important when creating a custom container:
  • _notification()
  • fit_child_in_rect()
  • queue_sort()
_notification() is called by Godot on all nodes for a large number of events. It receives the event code as an argument, which allows us to react to it. In our case, what interests us is the NOTIFICATION_SORT_CHILDREN event. This event is sent when the children of the container need to be sorted, and is called any time a child is added, removed, moved, or the container is resized.
You use it like so in your custom container node:
func _notification(what: int) -> void:
	if what == NOTIFICATION_SORT_CHILDREN:
		# do something with the children here
fit_child_in_rect() is called by Godot to ensure a Control child is constrained to the size you specify. Here's a very simple (and pretty useless) custom container that stacks all children on top of each other:
@tool
extends Container

func _notification(what: int) -> void:
	if what == NOTIFICATION_SORT_CHILDREN:
		for child in get_children():
			if child is Control:
				# We create a geometrical rectangle from the point (0,0)
				# which is top left, down to the size of this container.
				# this fills the entire container
				var child_rectangle := Rect2(Vector2.ZERO, size)
				fit_child_in_rect(child, child_rectangle)
While Godot will call _notification() for all native events, it won't when your own custom properties change. If you need the container to recalculate children in reaction to your code, call queue_sort(), which will dispatch the NOTIFICATION_SORT_CHILDREN event. This is particularly useful in setters for properties that affect the layout. For example, you might have a container with a special max_child_width property, and you want the children to be re-sorted and resized when it changes:
func max_child_width := 250:
  set = set_max_child_width(value):
    max_child_width = value
    queue_sort()
With this, the children will be sorted and resized when the value is modified.

A simple container node example

I've included a CircleContainer node in the project files. It places its children in a circle. It also has two properties: radius and angle_offset, which can be used to define how far from the center children are, as well as where the circle begins.
To place the children, the code is somewhat similar to what you did for the random loot module: we get an angle for each child, multiply that by the radius value, and place the child there.
You'll find this example in res://lessons_reference/UI_samples/custom_container.tscn
Just reading won't give the full effect! Make sure to open those scenes and explore them, or try to reproduce them.
I believe this rapid fire scenes and explanations will give you a strong foundation to handle UI in your own games or applications. Can't wait to see what you make with it!
Nathan
Founder and teacher at GDQuest

Lesson Q&A

Use this space for questions related to what you're learning. For any other type of support (website, learning platform, payments, etc...) please get in touch using the contact form.

  • Last code block in Bonus 2 is incorrectWatchinofoyeIt should be something like this instead : ```gdscript var max_child_width := 250: set(value): max_child_width = value queue_sort() ``` 2 0 Sep. 14, 2024
  • How is the child height of grid_container changed?humble-pelicanIn the res://lessons_reference/UI_containers_samples/grid_container.tscn file, the button in the second line is of a different height from the other buttons. I found that to change the height, I need to change the y of Custom Minumum Size, but I saw no change in the file. How did this happen? 1 0 Aug. 22, 2024
  • What are "tags"?near-octopusHFlowContainer and VFlowContainer says: "Good for: Complicated UIs that need to adapt to different screen sizes; tags." But what are tags? 1 0 Aug. 19, 2024
Site is in BETA!found a bug?