Skip to content

ZStack

A ZStack draws its children on top of each other. The first child is at the bottom, the last one on top. The stack is as large as its largest child and places every child inside that box by one alignment.

Use a ZStack whenever two views share the same place: a plate behind a label, a caption over a picture, a badge on the corner of an icon. ui.ZStack returns an Overlay, the view type that the API section lists.

ZStack ZStack
// A plate behind content. The unframed Box is greedy and fills the
// whole box; the alignment centers the text on it.
plate := ui.ZStack(
	ui.Box().Background(ui.ColorAccent).CornerRadius(14),
	ui.Text("Centered").Foreground(ui.ColorOnAccent),
).Align(geom.Center).Frame(150, 110)

// A caption over a picture, here a gradient plate, in the bottom
// leading corner. The padding of the text column keeps it off the edge.
caption := ui.ZStack(
	ui.Box().
		Background(ui.LinearGradient(ui.Fade(ui.ColorAccent, 0.3), ui.ColorAccent)).
		CornerRadius(14),
	ui.VStack(
		ui.Text("Harbor").FontSize(17).Foreground(ui.ColorOnAccent),
		ui.Text("12 photos").FontSize(13).Foreground(ui.ColorOnAccent),
	).Gap(2).Padding(12),
).Align(geom.BottomLeading).Frame(150, 110)

// A badge on the top trailing corner of an icon tile. Without a frame,
// the overlay is as large as its largest child, the padded tile.
tile := ui.ZStack(
	ui.Box().Background(ui.ColorControl).CornerRadius(16),
	ui.Icon(outline.Bell).Size(32),
).Align(geom.Center).Frame(80, 80)
badge := ui.ZStack(
	ui.VStack(tile).Padding(8),
	ui.Badge("3").Color(ui.ColorDanger),
).Align(geom.TopTrailing)

return ui.HStack(
	zstackTile(plate, "Plate"),
	zstackTile(caption, "Caption"),
	zstackTile(ui.VStack(badge).Align(geom.Center).Frame(150, 110), "Badge"),
).Gap(20)

Full demo on GitHub ↗ · run it with go run ./docs/gallery -demo zstack

Usage

Size

Every child is measured with the same loose constraints: the space the parent offers, narrowed by Frame and the Min…/Max… modifiers of the ZStack, minus its padding. The ZStack then takes the width of its widest child and the height of its tallest one, plus its padding, within those same modifiers.

A greedy child takes everything it is offered. An unframed Box is greedy on both axes, so it fills the whole ZStack. That is how you paint a plate behind content:

ui.ZStack(
	ui.Box().Background(ui.ColorSurface),
	content,
).Align(geom.Center).Frame(200, 120)

Because the Box is greedy, it also makes the ZStack as large as the space it is offered. Give the ZStack a Frame, as above, or let its parent bound it. On an axis the parent leaves unbounded, such as the main axis of a stack for a child without Flex, the Box collapses to nothing and the plate disappears. The padding of a ZStack insets every child, the plate included; to keep the plate at the edge and the content off it, pad the content instead, as the caption in the demo does.

Alignment

Align takes a geom.Alignment and uses both components, because a ZStack has no stacking axis. geom.Center centers every child, geom.BottomLeading puts every child in the bottom left corner, and so on. The zero value, which is also the default, is geom.TopLeading.

The alignment applies to all children alike. To place two children differently, give each one the size of the box and align its content inside it, or nest another ZStack with its own alignment. In the demo, the badge tile is a ZStack that centers the bell, inside a ZStack that pins the badge to the top trailing corner.

Flex

Flex on a ZStack works as everywhere else: the parent stack gives the ZStack a share of its leftover space.

ui.HStack(
	sidebar,
	ui.ZStack(mapView, pin).Align(geom.Center).Flex(1),
)

Flex on a child of a ZStack does nothing. It is the one documented exception to the rule that a view never accepts a modifier it then ignores. A ZStack has no main axis, so there is no remainder to divide. To make a child fill the ZStack, use a greedy child such as an unframed Box; it is already offered the whole box of the ZStack.

Drawing and input

Children draw in order, so the last child covers the ones before it. Hit testing walks the other way: where two children overlap, the topmost interactive child under the pointer receives the click. A child that does not handle input, such as a Text or a Box, does not block one below it. The ZStack itself is not a hit target, so a click on a part that no interactive child covers reaches whatever lies behind it.

Background, Border, CornerRadius and Shadow style the box of the ZStack itself. Clip(true) confines the children, for drawing and for input, to the rectangle of the ZStack, its padding included. The clip is not rounded by CornerRadius.

On-screen keyboard

A direct child that is an OnScreenKeyboard gets special treatment. While it shows, the other children are laid out in the part of the box it does not cover, so the field you type into and the button below it stay visible. Window is a ZStack, so this is the usual composition:

ui.Window(form, ui.OnScreenKeyboard()).Align(geom.Bottom)

Only a bottom or top alignment reserves room, and only when the height of the ZStack is bounded. A keyboard wrapped in another container is not recognized. AvoidKeyboard(false) turns the behavior off and lets the keyboard cover the content, which suits a full screen preview that must not re-flow while somebody types:

ui.ZStack(preview, field, ui.OnScreenKeyboard()).
	Align(geom.Bottom).AvoidKeyboard(false)

API

Constructors

ConstructorDescription
func Window(content ...gift.View) OverlayWindow wraps the root view of an application in the window background.
func ZStack(children ...gift.View) OverlayZStack draws its children on top of each other, first child at the bottom.

Methods

MethodDescription
AvoidKeyboard(v bool) OverlayAvoidKeyboard says whether the other children make room for an OnScreenKeyboard that is a direct child of this overlay.

Common modifiers: Align, Background, Border, Clip, CornerRadius, Flex, Frame, Key, MaxHeight, MaxWidth, MinHeight, MinWidth, Padding, PaddingInsets, Shadow. See Modifiers.

ui.Overlay on pkg.go.dev ↗

Related