Flexbox

Every node lays out its children with flexbox unless its display says otherwise. The style keys map one to one onto the fields of Bevy's Node, which Bevy lays out with taffy, so the behavior follows the CSS flexbox spec. Keyword values are camelCase ("spaceBetween", "rowReverse").

Usage

<node style={{ flexDirection: "row", justifyContent: "center", gap: 10 }}>
  <node style={{ width: 40, height: 40, backgroundColor: "#7aa2f7" }} />
  <node style={{ flexGrow: 1, height: 40, backgroundColor: "#9ece6a" }} />
  <node style={{ width: 40, height: 40, backgroundColor: "#f7768e" }} />
</node>

Defaults

Unset keys keep Bevy's defaults:

Key Default
display "flex"
flexDirection "row"
flexWrap "nowrap"
flexGrow 0
flexShrink 1
flexBasis "auto"
justifyContent behaves as "flexStart"
alignItems behaves as "stretch"
alignContent behaves as "stretch"
alignSelf "auto"
gap 0

Compared with a web page:

  • Every node is a flex container. There is no inline layout: text is its own <text> element, a flex item like any other.
  • boxSizing defaults to "borderBox", so width includes padding and border (see Sizing).
  • The elements your app renders at the top level are children of a window-filling column that centers them horizontally, 16px apart. Give the top-level node width: "100%" and height: "100%" to take the whole window.

An unrecognized keyword in any key on this page falls back to the default and reports a devtools warning.

Display

display picks the layout model for the node's children:

  • "flex" (the default): flexbox, as described on this page.
  • "grid": CSS grid, see Grid.
  • "block": children stack vertically and stretch to the container's width, like CSS block flow.
  • "none": the node and its whole subtree take no space and are not drawn. To hide a node but keep its space, use opacity: 0 instead.

Direction and wrapping

flexDirection sets the main axis: "row" (left to right), "column" (top to bottom), "rowReverse" or "columnReverse".

flexWrap decides what happens when the items don't fit on the main axis:

  • "nowrap" (also spelled "noWrap", the default): one line; items shrink, then overflow.
  • "wrap": items move onto new lines along the cross axis.
  • "wrapReverse": like "wrap", with new lines added before the previous one.
<node style={{ width: 152, flexWrap: "wrap", gap: 8 }}>{swatches}</node>

Growing and shrinking

  • flexGrow (a number, default 0): the item's share of the free space on the main axis. 0 keeps its base size.
  • flexShrink (a number, default 1): the item's share of the shrinking when the line overflows. 0 keeps its base size.
  • flexBasis (a length, default "auto"): the base size before growing and shrinking. "auto" uses the item's width (in a row) or height (in a column), else its content size. A percentage is of the container's content box on the main axis.

There is no flex shorthand; set the keys you need. Columns that split a row equally:

const column: BevyStyle = { flexGrow: 1, flexBasis: 0 };

A flex item never shrinks below its content's minimum size, because minWidth and minHeight default to "auto". Set minWidth: 0 (or an overflowX of "hidden" or "scroll") to let it shrink further, for example a row with clipped text. See Sizing and Overflow.

Alignment

Key Set on Aligns
justifyContent container the items along the main axis
alignItems container each item across its line
alignSelf item this item across its line
alignContent container the wrapped lines along the cross axis

Accepted values:

  • justifyContent: "start", "end", "flexStart", "flexEnd", "center", "spaceBetween", "spaceAround", "spaceEvenly", "stretch". In flexbox, "stretch" behaves as "flexStart".
  • alignItems: "start", "end", "flexStart", "flexEnd", "center", "baseline", "stretch". With the default stretch, an item without its own cross size fills the line.
  • alignSelf: "auto" (use the container's alignItems) or any alignItems value.
  • alignContent: the same values as justifyContent; here "stretch" grows the lines to fill the container. It only has an effect when the items wrap onto several lines.

"start"/"end" and "flexStart"/"flexEnd" are different values. "start" and "end" are physical: layout is always left to right, so "start" is the left (or top) edge. "flexStart" and "flexEnd" follow flexDirection, so they swap sides under "rowReverse" and "columnReverse".

justifyItems and justifySelf have no effect on flex items; they belong to Grid.

Gaps

gap spaces the children of a flex or grid container, between items only, never at the container's edges. rowGap and columnGap set one axis and win over gap whatever order the keys are written in:

<node style={{ flexWrap: "wrap", gap: 8, rowGap: 16 }}>{tags}</node>

In a row, columnGap separates the items and rowGap separates the wrapped lines; in a column it is the other way around. The value is a length: a percentage is of the container's own content box on that axis, and "auto" is 0.

Animating

  • transition: { layout } on the items eases every rearrangement (a direction or alignment change, an inserted sibling, a wrap) from the old position to the new one. See Style transitions.
  • flexBasis, gap, rowGap and columnGap accept an { animated } binding, driven in pixels every frame without re-rendering React. See Animated values.
  • flexGrow, flexShrink and the keyword keys don't animate.

Limits

  • No order and no right-to-left layout: items lay out in tree order, left to right.
  • Keywords are camelCase only: "space-between" is not accepted.

See display, flexDirection, flexWrap, flexGrow, flexShrink, flexBasis, justifyContent, alignItems, alignSelf, alignContent, gap, rowGap and columnGap in the style reference.