If you have the main PhET libraries checked out (sun, etc.), there are a number of examples that show layout in action with common components.

If you're using a built version of Scenery, you'll need to add the relevant namespaces to objects. Almost everything here will need a scenery. prefix, e.g. new phet.scenery.Node() instead of new Node(). The built version of Scenery will include scenery, kite (for Shape), dot (for Vector2), axon (for Properties) and phetCore. The code examples here can use either the prefixed versions or non-prefixed.

All of the code examples here are editable. You can change the code and see the results immediately.

Many examples can be resized using drag handles. This adjusts the preferred size of the container.

FlowBox

A FlowBox is a layout container that lays out its children in a row or column (depending on the orientation). It can optionally wrap content to the next row/column when there is no more room (e.g. like text).

orientation

For horizontal line-based layout, use HBox:

For vertical line-based layout, use VBox:

NOTE: For FlowBox, the orientation provided is typically called the primary axis. This is the axis along which each Node in a line is positioned. The secondary axis is the opposite one, along which each Node is aligned. e.g. for an HBox, its primary axis is horizontal, and its secondary axis is vertical (so its elements will be positioned with increasing x values, and its align will control the y values).

For cases where the orientation needs to be determined programmatically, use FlowBox:

Resizing

The box adjusts to changing cell sizes:

Resizing/layout can be disabled with resize: false:

Invisible children

Invisible nodes are not included in layout/bounds by default:

Invisible nodes can be forced into the layout:

grow

Nodes with preferred sizes can be added, and the grow in layoutOptions will attempt to put extra space into that cell. If all nodes have the same grow, then all nodes will receive the same share of the extra space. Otherwise, extra space is distributed proportionally as defined by each node's grow value.

Extra space can be allocated proportionally (a node with 4 times the grow value will grow 4 times as fast):

stretch

stretch will have a resizable element take up the entire row/column size:

Constraining dimensions

Maximums can be applied to constrain this growing (it won't grow past the max content dimension):

Minimums can also force a certain expansion:

Minimums and maximums also apply on the secondary axis:

Content minimums and maximums can be applied to all layout cells in the FlowBox by using layoutOptions: { minContentWidth: {{NUMBER}}, minContentHeight: {{NUMBER}} }. For example, setting minContentWidth in a FlowBox's layoutOption constrains all of its layout cells to a certain width ( with margin and spacing added to calculate the total width). This occurs even if the children are smaller and do not occupy the full width. However, this method still allows the FlowBox to dynamically resize if the children grow. A common mistake is to use preferredWidth / preferredHeightto size any content that will end up inside a layout container. These options will get overwritten by a parent with resizable: true, which is the default option value for all FlowBoxes.

A good thing to note is that the content width/height can multiply the width/height of the parent container when wrapping occurs.

justify

justify controls how extra space is allocated around cells (after any possible growing has been done):

wrap

wrap will shift content that doesn't fit the preferred size into new rows/columns (try resizing it to be less wide):

align

align controls how cells are positioned along the secondary axis:

For horizontal boxes, the values are: 'left', 'right', 'center', 'origin'. For vertical boxes, the values are: 'top', 'bottom', 'center', 'origin'. See LayoutAlign for more details

For the 'origin' align, it is useful to note that the origin is at the top-left of the rectangles, at the center of circles, and at the left of the baseline of text. For the above example of an HBox, the align will control the vertical positioning of Nodes, and thus will only care about the Y component of the origin. It places the origin of all of the Nodes at the same Y value (top of rectangles, center of circles, baseline of text).

justifyLines

justifyLines controls how lines are positioned along the secondary axis (null will default to a stretch):

spacing

spacing controls extra space that can be added between cells:

lineSpacing

lineSpacing adds space between rows/columns, which applies when wrapped:

Margins

Margins can also be added to every cell (margin affects all 4 sides, xMargin affects left/right, yMargin affects top/bottom):

Per-cell layout options

Margins can also be applied to individual cells:

Similarly, alignment can also be customized by individual cells:

These options use the default on the container, but can be overridden by cells:

Separators

Separators are also available for easy of use (separators at the visible start/end, and duplicates will be marked as visible: false, while all other separators will be marked as visible: true):

Additionally, arbitrary nodes can be made to act like separators by passing isSeparator: true in its layoutOptions

GridBox

A GridBox is a layout container that lays out its children in a grid. It has numbered rows and columns (both starting at zero), which define cells where nodes can be placed. Most layout options are available in either the horizontal or vertical direction, and can be specified for each cell.

Cell coordinates

Can be constructed with absolute coordinates (which can include gaps):

The x value indicates the column (starting at index 0), and the y value indicates the row (starting at index 0).

NOTE: Skipped rows/columns will be collapsed and won't apply (except for any spacing added):

rows

Grids can be constructed by specifying all the children in rows (null for gaps):

columns

or with columns:

autoRows/autoColumn

Additionally, if a certain number of rows/columns are desired, autoRows / autoColumns can be used to wrap and position the children based on this (auto-filling all spaces)

addRow/addColumn

Rows and columns can also be added dynamically in a similar way (rows will be below all current content, columns will be to the right of all current content):

insertRow/insertColumn

Rows and columns can also be directly inserted by index:

removeRow/removeColumn

Or removed by index:

line/cell getters

Assorted operations can get the row/column of a child Node, or get all of the Nodes contained within a specific row or column:

grow

Grids by default don't auto-expand all rows/columns in size to the preferred size, but they can with a similar style to FlowBox, where grow applies to both the x and y dimensions:

This space can be grown in specified rows/columns only (and independently) with xGrow or yGrow:

stretch

Use stretch (or xStretch / yStretch) to grow a cell dynamically with the preferred dimensions to match the row/column. stretch must be paired with a grow value to be activated.

sizable

Additionally, widthSizable / heightSizable can be used to turn off resizing in a component (particularly useful if you want to set a preferredWidth/preferredHeight on it that won't change):

align

Cells can be aligned in a similar way to FlowBox, but in both dimensions:

horizontalSpan/verticalSpan

Cells can take up more than one row/column with the horizontalSpan / verticalSpan layout options:

spacing

Grids can have consistent internal spacing:

Grids can have different spacing on each dimension:

Grids can have custom arrays adjusting the spacing between every single row/column:

Margins

Similar to FlowBox, grids can have margins applied to all elements:

Or can have margins specified on individual elements:

Sizables

Certain Node subtypes are sizable: they can be adjusted to different preferred sizes/bounds, which are equal to or larger than their minimum sizes. When the preferred size is set, the Node should adjust its own layout so that it takes up that size. This includes FlowBox (VBox/HBox), GridBox, Panel, and a growing list of Nodes.

Sizable nodes will either mix in WidthSizable, HeightSizable, or Sizable (indicating that both width and height can be adjusted). This provides preferred and minimum sizes in BOTH local and parent coordinate frames (e.g. preferredWidth / localPreferredWidth). These separate coordinate frame versions will be kept in sync (and are backed by Properties).

Typically Nodes will compute their own minimum width/height. The local version of this (e.g. localMinimumWidth) is considered the "primary" one, since Nodes usually do layout in their local coordinate frame. This means that when a Node's transform changes, its minimum sizes will be adjusted to match the equivalent localMinimum sizes.

Typically layout containers (but also clients, manually) will set the preferred sizes on a Node, thus the parent version will be primary (e.g. when a sizable Node is transformed, its localPreferredWidth will be adjusted to match the preferredWidth after the transformation).

Some Nodes by default have this "sizability" turned off, and clients can do this manually with widthSizable/heightSizable/sizable:false. Layout containers should not attempt to set preferred sizes when the Node is not considered "sizable".

For instance, Rectangles mix in Sizable, but are marked as sizable: false by default, so it won't take up space even when a container has a larger preferred size. They can be made resizable with sizable: true:

NOTE: This is different than the stretch layout option. stretch will potentially change what preferred size it will set to a Node. sizable on a Node will prevent that setting of preferred size.

layoutOptions

Layout options can be set on individual child Nodes of a layout container. These will override the container's default for any options included. These can be mutated after (by setting the entire thing with node.layoutOptions =or with mutateLayoutOptions).

mutateLayoutOptions is recommended when applying additional adjustments to layoutOptions, so that it won't by accident remove any layout options that were set earlier OR some set by the container. For example, GridBox with rows/columns/autoRows/autoColumns will set some layout options, and using gridBox.layoutOptions = may wipe out the positioning information for the child.

layoutOrigin

FlowBox/GridBox will typically lay out content so that the origin (0,0) of the FlowBox/GridBox is at the upper-left. There are some exceptions to this.

The default behavior:

When using align: origin, the Y-origin of the entire FlowBox will be at the Y-origin of the first wrapped line of Nodes.

NOTE: The origin of the Circle nodes below is at the center of the Circle. The origin of the Text nodes below (created by bigText/normalText) is the left end of the baseline of the text (since the word "Text" doesn't have any descenders, this will be at the bottom).

When using align: origin, the origin of the entire GridBox will be at the origin of the first cell:

The position of this origin can be shifted by using layoutOrigin

Constraint

NOTE: Constraints are lower-level, and besides ManualConstraint will likely not be needed in most cases.

Constraints are the foundational layer that handles layout logic for scenery elements. If you search through the PhET codebase you can find constraints such as: ButtonConstraint,CarouselConstraint, SliderConstraint, and many more. All of these constraints extend LayoutConstraint, and importantly, override the method layout. Its important to understand that the motor of the layout logic is housed in a constraint, not only for debugging, but also to empower developers to use existing constraints or create their own constraint as is desirable.

In general, try to not place a Node in multiple layout containers, as this can lead to unexpected behavior (might trigger infinite loops when the layout constraints fight back-and-forth with where a Node should be placed). It will generally be detected (in the case of FlowBox/GridBox) and will trigger an eager error.

ManualConstraint

Sometimes more fine-grained control is needed over the layout of Nodes than what is provided by the built-in layout containers.

ManualConstraint is for hooking up imperative-style layout code (e.g. someNode.left = otherNode.right) so that it correctly auto-updates whenever one of those values may have changed. It also works across different Node parents and coordinate frames. A good indication that ManualConstraint may be useful is when a developer is linking to multiple BoundsProperty, or connecting view positions for elements in different coordinate frames.

Some layout examples where ManualConstraint is particularly useful:

It will also robustly handle cases where the Nodes are not connected (and will not run the layout), so the constraint can be created before all of the Node structure is finalized.

Children are dynamically added/removed in this demo, and child1 is constantly translated.

FlowConstraint

Want to use a FlowBox (HBox/VBox), but the nodes don't have the same parent? Use FlowConstraint. It is the layout constraint used by FlowBox.

Example of using FlowBox-like layout with disconnected Nodes that share some ancestor (with qualities similar to ManualConstraint):

GridConstraint

Want to use a GridBox, but the nodes don't have the same parent? Use GridConstraint. It is the layout constraint used by GridBox.

Example of using GridBox-like layout with disconnected Nodes that share some ancestor (with qualities similar to ManualConstraint):

AlignBox

alignBounds

AlignBox on its own will position content within a specific bounding box (its alignBounds):

Alignment

Content can be aligned within this:

Margins

And margins can be specified on each side:

Margins can also be used on their own (without alignBounds / align) to just create a Node with larger bounds:

AlignBox can also be used with preferred dimensions instead of an explicit alignBounds:

and can be dynamically resizable (off by default because it's usually not used for that purpose in legacy code):