Every application with more than one button needs to organise its nodes in some way. JavaFX’s default layouts can achieve this with little to no effort, and with surprising flexibility. In fact, from dashboards with multiple charts, to games with terrain, characters and user controls, JavaFX’s layout classes can achieve an impressive variety of effects.
Layouts in JavaFX are containers that provide pre-built functionality to define the position, size and alignment of nodes such as charts, images and controls. JavaFX provides 9 default layout classes, including the base class Pane
, which provides only for absolute positioning.
Before I start, if you just need to see how to use one type of pane, feel free to jump ahead:
How to use this resource
There are numerous tutorials on the high-level functionality of all 9 layout panes, including from Oracle itself. What I’ll try to add with this article is a more thorough summary of the quirks, and how you can avoid (or use) them to make your user interfaces responsive (check out this complete guide on how to make JavaFX truly responsive using nothing more than in-built layouts and some basic code), but predictable. I’ve broken this tutorial down into two sections:
There are three nodes I won’t talk about in this article: ScrollPane
, TabPane
and SplitPane
. The primary use of these panes is to allow the user to control the viewport through which to view nodes, and not to lay them out.
General rules on using layouts
Choosing a layout should depend on two factors:
A good guiding principle for choosing a layout is to think about the general position you want for nodes in a layout, and to select a type of layout based on that.
Once you’ve chosen your layout type, you can fine-tune your choice based on the control you need over positioning, sizing and alignment of your nodes.
1. Layout Types
The layouts can be broken down into three types: block, grid and row/column layouts.

Each group thinks about nodes in a different way. And within a block, organises the finer-tuning of node positions differently.
Block Layouts
Block layouts are the simplest, having only one set of rules that defines the layout of their children. I’ve called them ‘block’ layouts because they do not segregate nodes into areas (grid layouts) or runs (row/column layouts).
That is to say, from the perspective of the layout logic, the pane is a single block.

In the case of the StackPane
, node position is controlled through setting alignment, whereas the AnchorPane
uses absolute positioning.
The AnchorPane’s absolute positioning means it can control both position and size, whereas the StackPane
can only control position.
Grid Layouts
Grid layouts are the most complex, and can allow for very detailed control of the position of nodes.

The TilePane
class is the easiest to use, but offers the least control over the positioning of the nodes.
In the case of the BorderPane
, users are granted coarse control over positioning, but are also guaranteed certain behaviour by default. This can be a good balance for default window-behaviour use cases.
The GridPane
allows for significant breadth in fine control of node position, but can be difficult and complex to use.
Row and Column Layouts
Finally, row and column layouts are designed to display nodes in a specified order, either vertically or horizontally.

Each of these comes with options to control the spacing between the nodes in the series. In the case of the FlowPane, users can also control the space between rows or columns in multi-row and multi-column settings.
Within a layout type, there are usually multiple ways to layout nodes in the same way. So, once I’ve chosen the rough positions of nodes, I tend to choose layouts based on how I want each component to behave when the layout pane is resized.
2. Choose your layout based on how it controls its children
Panes are designed to control the position, size and alignment (relative position) of children in a variety of ways. These affect how it will move and resize your nodes when the pane is resized
It’s always worth bearing in mind that windows are frequently resized by users significantly smaller or larger than you might have planned.ff
Pane | Layout Positions Children | Resizes Children | Aligns Children | Has sub-regions | Children can overlap |
---|---|---|---|---|---|
AnchorPane | ✔ | ✔ | ✔ | ||
StackPane | ✔ | ✔ | |||
BorderPane | ✔ | ✔ | ✔ | ✔ | |
GridPane | ✔ | ✔ | (virtual) | ||
TilePane | ✔ | ✔ | (virtual) | ||
FlowPane | ✔ | ✔ | |||
HBox | ✔ | ✔ | ✔ | ||
VBox | ✔ | ✔ | ✔ |
One common issue with using layouts is trying to resize controls. If you’re having trouble getting your layout to resize a Control
like a Button
, take a look at the dropdown to fix it.
I have spent hours googling why something won’t expand to fit an HBox in my time. If it’s a Control
, the default behaviour is for the maximum size to reflect the preferred size. So the Control
won’t stretch.
To fix this, you’ll need to modify the maximum width and height.
Unconstrained resizing of controls:
Set the width and height to Double.MAX_VALUE
. This can be done programatically by invoking setMaxHeight(Double value)
and setMaxWidth(Double value)
. It can be also be done in FXML by setting maxHeight="Infinity"
and maxWidth="Infinity"
.
Constrained resizing of controls:
If you want your Control
to resize, but you don’t want it to look stupidly large, use setMaxHeight(Double value)
and setMaxWidth(Double value)
to specify a real maximum.
This will override the control’s default behaviour not to resize. You can also do this with FXML. If you want it to resize to a maximum size of 200 px, simply set maxHeight="200"
and maxWidth="200"
.
Once you’ve chosen a layout, you’ll want to use it… In the next section, I’ll run through how to control the size, position and alignment of nodes in each of JavaFX’s layouts.
A deep dive into each layout
The Pane
class is the base class for all layouts and will not apply any layout behaviour to its children. For this reason, the Pane
is useful when you need to position a node absolutely, but you don’t want any additional resizing behaviour, or alignment.
Each layout pane extends the Pane class and provides additional functionality layered on top of the core behaviour defined in the Pane
. There isn’t any magic to layout panes: they achieve the layout behaviour they want by manually calculating the layoutX
and layoutY
properties of every child node according to a set of rules specific to the layout they’re trying to create.
In each of the sections below, I’ll go through the behaviour and how the rules can help you use these layouts as they were designed.
I’ll go through them by Type.
Block Layouts
These are incredibly useful for deciding yourself where nodes should be displayed, and are frequently useful in designing custom content areas.
AnchorPane
The AnchorPane
class is a simple layout with three guiding principles:

In addition to absolute positioning using layoutX
and layoutY
, the AnchorPane
has four methods that define the layout of its children.
These define the absolute position of the edge of a child with respect to the same edge of the AnchorPane.

These methods can be used for both positioning and sizing nodes.
To change the order overlapping nodes are rendered, use getChildren()
to get the list of children, and change the order.
a. Positioning:
Setting one or two anchors on a node will have the effect of positioning the node with respect to the layout bounds of the AnchorPane
.

Setting an anchor to zero will glue the specified side of a node to the same side of the AnchorPane
.
Warning: Anchor-setting methods accept double values, which can be negative, but it might not have the effect you think.
If I try setting a negative left anchor, I might expect that the node would look like it’s sliding off the left edge of the AnchorPane
. Instead, the AnchorPane
tries to resize itself to accommodate the node.
The top node, for which I set a negative anchor, still looks like it’s glued to the left of the pane. And the other nodes just look like they’ve moved right instead.

To successfully accomplish the desired effect of having an item sliding out of the left hand side of an AnchorPane
, you’ll need to combine sliding with clipping.
a. Sizing:
Setting three anchors on a node will have the effect of stretching the node between the two opposing anchors.

This can be extremely useful because it overrides any maximum or minimum width or height on the node.
You can stretch nodes in both directions by using all four anchor points.
StackPane
The StackPane
is a simple layout pane similar to the AnchorPane
. Both panes are relatively simple layouts that support rendering of nodes that overlap each other. However, where the AnchorPane
focused on absolute positioning, the StackPane
focuses on alignment-based positioning of nodes.
To make the most of the StackPane
, I usually sort alignment and then positioning, so I in this case, I’ll go through alignment first, and then positioning.
The StackPane
also won’t resize nodes. Unlike the AnchorPane
, which can stretch nodes by enforcing absolute positioning on a child’s bounds, the StackPane’s focus on alignment means it won’t adjust the size of any nodes. So, no sizing section here either.
a. Alignment:
By default, all nodes are aligned in the centre of the StackPane.

To change the default alignment of nodes, you will need to use the method setAlignment(Pos value)
on the StackPane
in question.

To get nodes exactly where you want them, the temptation is to modify the position of object using the translateX
and translateY
properties. In fact when I started with StackPanes, I regularly hooked in listeners to the StackPane width that would adjust the translate properties of nodes. Stop! And use margins instead.
To adjust nodes positions, the static method StackPane.setMargin(Node child, Insets insets)
can be called, referencing the node to align, and the preferred alignment.
b. Positioning:
The StackPane
does not support absolute positioning of nodes, and any values of layoutX
or layoutY
are ignored by the StackPane
. You can fine-tune the positions of nodes by setting margins on a node-by-node basis, or by changing the translateX
and translateY
values of nodes.
Margins
I think the safest way to work with the StackPane is to use margins rather than manually modifying the translate properties of an object. In this case, you can manually create a transparent margin around each node by invoking the static method StackPane.setMargin(Node node, Insets margin)
.

I usually find this manages the tweaking I need, although you can manually modify the horizontal and vertical position yourself.
Manual modifications
You can make adjustments o the position of child nodes by using the translateX and translateY properties. This is especially useful for animations, or transitions where you’re adding or removing nodes.
These can be quite useful but bear in mind they’re applied after the layout bounds of the StackPane
have been calculated, so nodes with modified translate properties may be rendered outside the bounds of the StackPane
.
Tip: When a translate property is modified, it is applied after all alignment and insets, so remember to take these into account when you’re calculating the amount to modify these properties.
Grid Layouts
Grid layouts are useful for giving the user a familiar experience, by creating interfaces they’ve seen before, such as windows (border panes), galleries (tile panes) and forms (grid panes).
BorderPane
The BorderPane
is designed to have top and bottom regions of fixed height, and left and right regions of fixed width. The effect is to have a centre of varying size as the BorderPane
changes dimensions.

Let’s go through how to set positions, sizing and alignment with the BorderPane
.
a. Positioning:
Unlike the panes we’ve seen to far, which have used the getChildren()
method to access the children of the node, this won’t work with the BorderPane
.
As a result of the design intention to host children in a specific way, we need to tell the BorderPane
which area to add the node to. The five methods for setting nodes in the BorderPane
are:
Only one node can be stored in each region of the BorderPane
, so calling any of these methods multiple times will remove the original node, and replace it with the most recent assignment.
b. Sizing:
The sizing behaviour of the BorderPane is one of the reasons it is particularly good for a standard window layout. It’s designed to accommodate a menu bar of a fixed height but a variable width, and a navigation window of a fixed width but varying height.
Because of that exact design intention, the BorderPane’s effect on node size depends on where in the BorderPane
the node is placed. Here are the specifics about how the BorderPane
sizes its children:
Top and Bottom
Height: The nodes in the top and bottom panes are fixed at their preferred height.
Width: The width of nodes in the top and bottom regions is adjusted within the its resizeable range dependant on the width of the BorderPane
.
If the BorderPane is asked to shrink below the minimum width of the top/bottom nodes, they’re still rendered faithfully at their minimum size.
Left and Right
Height: The height of nodes in the left and right regions is adjusted within the its resizeable range dependant on the height of the BorderPane
. It is calculated as the height of the BorderPane
minus the heights of the top and bottom regions.
If the BorderPane
is asked to shrink below the minimum height of the left/right nodes, they’re still rendered faithfully at their minimum size.
Width: The nodes in the left and right panes are fixed at their preferred width.
Center
Width: the width of the center region is calculated as the width of the BorderPane
minus the preferred width of the right and left nodes added together.
Height: the height of the center region is calculated as the height of the border pane minus the preferred height of the top and bottom nodes added together.
If the BorderPane
is asked to shrink below the minimum height or width of the centre node, it’ll still be rendered faithfully at its minimum size.
c. Alignment:
The BorderPane will create space around nodes in their regions under the following circumstances:
- The width of the BoderPane is larger than the maximum width of a top or bottom node.
- The height of the BorderPane is larger than the maximum height of a left or right node.
- The space remaining in the middle is larger than the maximum dimensions of the centre node.
If the size of any BorderPane
region is greater than the maximum size of the node ir contains, it will be positioned according to the following rules by default:
The alignment of any node within the BorderPane
region can be set using the static method BorderPane.setAlignment(Node node, Pos alignment)
method, or by setting BorderPane.alignment="<alignment>"
in FXML.
GridPane
The GridPane
layout provides the finest level of control for nodes in a grid layout. This includes capability to set column- and row-specific properties for alignment and sizing, in addition to node-specific row- and column-spanning behaviour.
GridPanes are the most complex of JavaFX’s default layouts, with multiple layers of positional and sizing control.
Almost all of these involve creating a RowConstraints or a ColumnConstraints object.
These can be added to a GridPane by calling getColumnConstraints().add(ColumnConstraints constraint)
or getRowConstraints().add(RowConstraints constraint)
.
Constraints are added to columns and rows in the order they’re added to the GridPane
. Adding empty constraints such as new ColumnConstraints()
to a GridPane
will have the effect of not setting any constraints. This is useful if you want to skip over rows or columns.
a. Positioning:
Positioning content in a GridPane can be achieved in four ways:
Setting the cell in which a Node should be placed
At the coarsest level, positioning a node is achieved by setting its location in the grid. This can be achieved by setting its row location, column location, or both simultaneously.
//set both positions simultaneously GridPane.setConstraints(myNode, 0, 1); //set column position GridPane.setColumnIndex(myNode, 0); //set row position GridPane.setRowIndex(myNode, 1);
Altering the size of a column or a row
By default, column and row sizes will be calculated based on the preferred width and height of their contents. This behaviour can be overridden by creating ColumnConstraints
and RowConstraints
objects, and applying them to the GridPane
.
Using the RowConstraints
object, the height of a row can be specified as either pixel dimensions using setPrefHeight(Double value)
or a percent of the gridpane’s total height using setPercentHeight(Double value)
.
Similar methods are available for the ColumnConstraints
object.
Spanning columns and rows
The position of a node inside the grid can be changed by instructing the GridPane
to set it to span multiple columns or rows.
This is set using the static GridPane
methods GridPane.setRowSpan(Node child, Integer value)
and GridPane.setColumnSpan(Node child, Integer value)
.
Padding between rows and columns
Finally, the position of nodes can be tuned by altering the padding between rows and columns of a GridPane
.
These can be set using setHgap(double value)
and setVgap(double value)
in addition to the normal padding around the edge of the GridPane, which can be set using setPadding(Insets value)
.
a. Sizing:
Sizing nodes and columns can again be successfully achieved in multiple ways. Both ways require the creation of RowConstraints
and ColumnConstraints
objects.
By default, a GridPane
will accommodate nodes at their preferred size, and will not stretch them to fill columns. It will also expand all columns and rows equally into available space so this is useful for tuning positional behaviour.
Filling rows and columns to fill unoccupied space
A row can be instructed to fill any unoccupied space by creating a RowConstraints
object, and invoking setVgrow(true)
.
Likewise, a column can be instructed to fill any unoccupied space by creating a ColumnConstraints
object, and invoking setHgrow(true)
.
Space is distributed between rows and columns using the following logic:
- Distribute space evenly between all rows or columns with
Priority.ALWAYS
. - If no columns/rows have
Priority.ALWAYS
, or space is remaining, distribute the remaining space between rows or columns withPriority.SOMETIMES
.
Growing nodes vertically and horizontally into available space within their cell
A row can be instructed to grow all nodes vertically into unoccupied space by creating a RowConstraints
object (or modifying an existing one), and invoking setFillHeight(true)
.
Again, in a similar way, a column can be instructed to fill any unoccupied horizontal space by creating a ColumnConstraints
object (or modifying an existing one), and invoking setFillWidth(true)
.
c. Alignment:
Finally, nodes within rows can be instructed to align vertically (and within columns, align horizontally). These are set using the HPos
and VPos
enums.
One last time, a row can be instructed to align nodes vertically by creating a RowConstraints
object (or modifying an existing one), and invoking setValignment(VPos value)
.
And a column can be instructed to align nodes horizontally by creating a ColumnConstraints
object (or modifying an existing one), and invoking setHalignment(HPos value)
.
TilePane
A TilePane
calculates its layout by adding tiles (virtual regions inside which our nodes get rendered) in one dimension and then wrapping tiles into additional rows or columns. Tiles are of a consistent size throughout the pane.
Varying the position, size and alignment of nodes in side a TilePane can be achieved by tuning the padding between tiles, or by setting node-specific margins.
a. Positioning:
The distance between nodes can be adjusted by setting the hgap
and vgap
properties of the TilePane
instance that contains our nodes.

TilePane
with hgap
and vgap
set (here the tile size is the same as the node size)Tile sizes can also be changed as a method of altering the positioning of nodes, although users should be aware that this will also change the sizes of nodes (within their resizeable range).

Tile sizes can be altered by invoking setPrefTileWidth(double value)
and setPrefTileHeight(double value)
. These sizes aren’t guaranteed, and if the TilePane
is reduced in size to a point where it cannot accommodate the tiles, they will be reduced in size.
a. Sizing:
Unlike many other layouts, the TilePane
will by default try to stretch tiles that are smaller than the tile size within their resizeable range.
If the tileprefWidth
and tilePrefHeight
properties are smaller than the preferred dimensions of the nodes, it will also attempt to shrink nodes that are larger within their resizeable range.
c. Alignment:
If the default tile size is greater than the maximum size of any node, the node will be distributed within the tile according to the alignment it has been set.

TilePane
where all the nodes have been set to have an alignment of Pos.TOP_LEFT
The alignment is set on a per-node basis using the static TilePane
method TilePane.setAlignment(Pos value)
. The default alignment is Pos.CENTER
, although if you use the static method TilePane.getAlignment(Node node)
on a node where it has not been explicitly set, it will return null (this is also true for other panes).
Row & Column Layouts
Row and column layouts display their content in an order across a single dimension. The FlowPane
wraps content that does not fit in the primary dimension by creating additional rows or columns. VBoxes and HBoxes do not.
FlowPane
The FlowPane
is designed to give your layout a responsive feel, changing the length of a row or column to suit the size of the FlowPane
itself. It’s great for similar-looking items that need to be shuffled around.
By default, the FlowPane
will fill nodes into rows, and then make additional rows if necessary. For a FlowPane
that produces columns of content rather than rows, create a FlowPane
and call setOrientation(Orientation.VERTICAL)
.
A FlowPane won’t try to resize nodes, so there’s no sizing section here.
a. Positioning:
Because FlowPanes can be used to pack items into rows or columns, it internally refers to them as ‘runs’. If the orientation is horizontal, a run is horizontal. If it’s vertical, a run is vertical.
The basic rule of FlowPanes is this: make a run as long as you can before the next node would go over the maximum allowed length, and then use that node as the first item in the next run.
You can set the margin of a FlowPane
by invoking setMargin(Insets insets)
to create a space around the contents where it will not try to fill nodes. This uses an Insets
object, which can specify margins for top, bottom, left and right separately, or together.

FlowPane
with vertical orientation.You can determine where the FlowPane will allocate leftover space is allocated by using the method setAlignment(Pos value)
. The image above shows a vertical FlowPane
with a Pos.TOP_LEFT
alignment, meaning the space is distributed to the bottom and the right.
Run Length
You can control the length of a run by invoking setPrefWrapLength(Double value)
, which works regardless of the orientation you’ve set.

FlowPane
, it’ll be the left and right margins.Vertical and horizontal gaps
The FlowPane
doesn’t distinguish between orientation when setting gaps, so they are absolute. That means the gap between vertical elements is set using setVgap(Double value)
and the gaps between horizontal elements is set using setHgap(Double value)
.

FlowPane
, the gap between runs will be set using vgap
This can get confusing if you start switching orientations if you wanted the functionality to differentiate between runs and items-in-a-run.
Gap between items in a run
For a horizontal FlowPane
(default), use setVgap()
and for a vertical FlowPane
, use setHgap()
.
Gap between runs
For a horizontal FlowPane
(default), use setHgap()
and for a vertical FlowPane
, use setVgap()
.
b. Alignment:
If nodes of different sizes are included in the FlowPane, the FlowPane will make runs to accommodate the largest member of that run. That means if it’s a vertical FlowPane, it’ll make runs wide enough for the widest member. If it’s a horizontal FlowPane, it’ll make runs tall enough for the tallest member.
Frustratingly, the FlowPane asks that you set the alignment of columns (in a vertical orientation) and rows (in a horizontal orientation) separately.
Here’s a FlowPane
with a vertical orientation, using a HPos.LEFT
column alignment.

HBox
The HBox positions nodes horizontally from left to right. Node resizing and alignment preferences can be set, while the order of elements can be changed by altering the order of elements in the ObservableList
children.
Unlike the FlowPane
, the HBox
has a single run of horizontal nodes. However, they have more control over the size of their children.
a. Positioning:
The horizontal order of the child nodes is left-to-right, and is the same as the order of the items in the ObservableList
By default the order of the nodes is the order the children were added.
a. Sizing:
Sizing can be accomplished both horizontally and vertically for the HBox.
Vertical
An HBox
will by default resize its children vertically to their maximum allowed height (bounded by the height of the HBox
itself, and their maximum height property). This functionality can be turned off by invoking setFillHeight(false)
.
This is set for an HBox instance, and will apply to all nodes in that HBox.
Horizontal
Nodes can request to be resized to fill any additional horizontal space, on a node-by-node basis.
A node can request to fill any remaining horizontal space inside an HBox
by calling the static method Hbox.setHgrow(Node node, Priority value)
.
Priority is an enum with the values SOMETIMES
, ALWAYS
and NEVER
. Space is distrubuted in the following way:
- Distribute space evenly between all nodes with
Priority.ALWAYS
. - If no nodes have
Priority.ALWAYS
, or space is remaining, distribute the remaining space between nodes withPriority.SOMETIMES
. - Allocate the remaining space according to the HBox’s alignment
c. Alignment:
You can set the alignment of the HBox
by calling setAlignment(Pos value)
.
VBox
The VBox positions nodes vertically, top-to-bottom. I’ll give you the whistle-stop tour, but checkout the HBox drop-down if you want a more detailed guide to the functionality. The internal logic is identical, but horizontal rather than vertical.
The VBox
has a single run of vertical nodes. Just like the HBox
, is has more control over the size of its children than a vertical FlowPane
.
a. Sizing:
Sizing can be accomplished both horizontally and vertically for the VBox
.
Horizontal
To stop a VBox
stretching its children horizontally, use setFillWidth(false)
. This is set for an VBox
instance, and will apply to all nodes in that VBox
.
Vertical
You can request that nodes stretch to fit any remaining vertical space in a VBox
by setting Vbox.setHgrow(Node node, Priority value)
. Space is distributed using the same rules as for an HBox
(but vertically…)
c. Alignment:
You can set the alignment of the VBox by calling setAlignment(Pos value)
.
Conclusions
JavaFX has 9 pre-defined containers that provide functionality to define the position, size and alignment of nodes such as charts, images and controls. JavaFX’s layouts can achieve a huge variety of functionality and positional control.
There are three basic types of layouts: grids, rows and blocks. They define the way the layout ‘thinks’ about positioning your nodes. Sketching out a container and using this to decide the sort of layout you need is usually a great way to start.
Once you’ve chosen the basic way you want your pane to look (a grid, a row or a block) I’d recommend choosing your layouts based on how they respond to being resized. Unless you’ve made them explicitly fixed-size, users will always surprise you with bugs they pick up making windows ridiculously big (or small).