Quick answer: The most common cause is that the child node inside the ScrollContainer does not have a minimum size larger than the ScrollContainer itself. ScrollContainer only scrolls when its child's minimum size exceeds its own dimensions.
Here is how to fix Godot scrollcontainer not scrolling. You built a UI panel with a ScrollContainer in Godot 4, added child elements that clearly extend beyond the visible area, and nothing scrolls. The scroll bar may not appear at all, or it appears but dragging and mouse wheel input do nothing. This is one of the most frequently reported UI issues in Godot 4, and it comes down to how the engine calculates whether scrolling is necessary.
The Symptom
Your ScrollContainer sits in the scene tree with one or more child nodes inside it. The content is visually larger than the container — you can see elements getting clipped at the bottom or right edge — but scrolling does not work. The mouse wheel has no effect. Touch dragging does nothing on mobile. The scrollbar either does not appear or appears as a full-height track with no draggable thumb.
In the editor, the child content might look correct in the 2D viewport. But at runtime, the ScrollContainer behaves as though there is nothing to scroll. Sometimes the content even collapses to zero height, leaving the container completely empty.
What Causes This
ScrollContainer in Godot 4 determines whether to enable scrolling based on its child’s minimum size, not its actual rendered size. If the child node’s minimum size is smaller than or equal to the ScrollContainer’s own size, the engine concludes there is nothing to scroll and disables the scrollbar.
The most common scenario is placing a Control node or a Panel directly inside the ScrollContainer. These nodes have a minimum size of zero by default, so even if they contain visible content, the ScrollContainer sees them as fitting entirely within its bounds. The child gets clipped, but the container does not know it needs to scroll.
Another frequent cause is using size_flags_vertical = SIZE_EXPAND_FILL on the child. This flag tells the child to expand to fill its parent — which means it sizes itself to match the ScrollContainer exactly, making scrolling mathematically unnecessary. The child should instead be allowed to grow beyond the container based on its own content.
A third cause is having scroll_vertical_enabled or scroll_horizontal_enabled set to false in the Inspector. This explicitly disables the scrolling axis even when the child size would otherwise trigger it.
The Fix
Step 1: Use a container child that reports its minimum size correctly. The recommended approach is to place a VBoxContainer (for vertical scrolling) or HBoxContainer (for horizontal scrolling) as the direct child of the ScrollContainer. These containers automatically calculate their minimum size based on their children:
# Scene tree structure
# ScrollContainer
# → VBoxContainer
# → Label (item 1)
# → Label (item 2)
# → Label (item 3)
# → ... more items
Step 2: Set the correct size flags on the child container. The child VBoxContainer should use SIZE_EXPAND_FILL for the horizontal axis (so it stretches to the ScrollContainer’s width) but leave the vertical axis alone. Do not set vertical expand fill, or the child will match the parent height and never trigger scrolling:
@onready var scroll = $ScrollContainer
@onready var vbox = $ScrollContainer/VBoxContainer
func _ready():
# Fill width, but let height grow with content
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
# Do NOT set size_flags_vertical = SIZE_EXPAND_FILL
# Add items dynamically
for i in range(50):
var label = Label.new()
label.text = "Item %d" % i
vbox.add_child(label)
Step 3: If using a custom Control child, set custom_minimum_size. When you cannot use a VBoxContainer (for example, you have a custom-drawn Control), you must manually set the minimum size to reflect the total content height:
extends Control
var content_height: float = 0.0
func _ready():
# Calculate your total content height
content_height = _calculate_content_height()
custom_minimum_size.y = content_height
func _calculate_content_height() -> float:
# Return the total height of all your content
return items.size() * item_height + padding
Step 4: Verify scroll axes are enabled. In the Inspector, select the ScrollContainer and confirm that scroll_vertical_enabled is checked (or scroll_horizontal_enabled if you need horizontal scrolling):
# Enable scrolling via code if needed
scroll.scroll_vertical_enabled = true
scroll.scroll_horizontal_enabled = false
# You can also set the scroll value programmatically
scroll.scroll_vertical = 0 # Scroll to top
Why This Works
Godot’s layout system is built on a minimum-size negotiation model. Every Control node reports a minimum size, and parent containers use those values to decide how to allocate space. ScrollContainer specifically compares its own size to its child’s minimum size: if the child reports a minimum size of 800 pixels vertically and the ScrollContainer is only 400 pixels tall, the engine creates a 400-pixel scrollable region.
VBoxContainer and HBoxContainer participate correctly in this system because they sum up the minimum sizes of all their children plus separation spacing. When you add 50 Labels to a VBoxContainer, it reports a minimum height equal to the total of all label heights plus gaps. The ScrollContainer sees that combined height, recognizes it exceeds the visible area, and enables the scrollbar.
The SIZE_EXPAND_FILL flag on the vertical axis breaks scrolling because it tells the layout engine the child wants to be exactly as tall as its parent. This overrides the content-based minimum size calculation, resulting in a child that is always the same height as the ScrollContainer — which means zero scrollable overflow.
Related Issues
If your ScrollContainer works but UI elements inside it are not responding to clicks correctly, see our guide on fixing UI clicks passing through to the game world. Mouse filter settings on child elements can interfere with scroll input handling.
For RichTextLabel content that should scroll inside a container, check out fixing BBCode rendering in RichTextLabel — a RichTextLabel with fit_content enabled works well as a ScrollContainer child because it reports its minimum height based on the formatted text content.
If your focus navigation (Tab key) is not working correctly within the scrolled content, our post on fixing focus order when tabbing through UI controls covers how to manage focus within scrollable panels.
Minimum size drives scrolling. Let the child grow taller than the container.