Skip to main content

Stack (Container)

A container groups multiple children together. There are four layout modes:

TypeLayoutUse case
vstackVertical ↕Title + subtitle, list rows
hstackHorizontal ↔Icon + text, left/right alignment
zstackLayered ⊕Background image + foreground content
tstackRich text concatenation ✏️Mixed fonts / colors within a single text run

Common properties​

PropertyDescription
ChildrenAny components inside, nestable
SpacingDistance between children
Padding (padding/pacings)Inner padding on 4 sides, e.g. 10,10,10,10
Alignmentcenter / leading / trailing etc.
Background / Corner radiusContainer background
Material (blur)Frosted glass background (thick / thin / regular)
Rotation / Offset / OpacityVisual tweaks
Open URL (openUrl)Makes the whole container tappable — opens a URL when tapped. See the dedicated section below

openurl​

All stack containers (vstack / hstack / zstack / tstack / grid) support the Open URL property. Once set, the entire container becomes a tappable region; when the user taps it, iOS opens the URL.

Why on the container and not on a button?

The button component can only run JS — it can't open URLs (it's built on AppIntent, a system limitation). So "tap to navigate" is always done through stack containers.

Common URLs​

URLBehavior
https://2f0.cnOpens a web page
weixin://Opens WeChat
mailto:foo@bar.comComposes an email
omniwidgets://runjs/medium/{Widget.id}Re-runs the current widget's JS
omniwidgets://openmini?...Opens a WeChat Mini Program (the system appends &from=omni automatically)
${link}Decided by your JS

omniwidgets:// and the legacy awidget:// are fully equivalent, so existing links don't need any changes.

Example: make a "button"​

The simplest "button": a zstack + text + openUrl.

zstack (Open URL: https://2f0.cn)
└─ text ("Visit the site")

A bit fancier: icon + text + rounded background + navigation.

hstack (Background: blue, Corner radius: 10, Padding: 10,10,15,15, Open URL: weixin://)
├─ icon (message.fill, white)
└─ text ("Open WeChat", white)

vstack — Vertical​

The most common. Children stack top to bottom.

┌─────────┐
│ text 1 │
│ text 2 │ ← vstack
│ icon │
└─────────┘

hstack — Horizontal​

Children stack left to right. Combine with spacer for "edge alignment".

[ icon | text | spacer | button ]   ← hstack

zstack — Layered​

Children are stacked on top of each other — later ones on top. Common uses:

  • Background image + text
  • Translucent overlay
  • Progress bar (base color + progress color)
   ┌───────┐
│ bg │ ← first layer (background)
│ txt │ ← second layer (on top)
└───────┘

tstack — Rich text concatenation​

Totally different from the other three — tstack is not a layout container. It glues multiple text children into a single continuous text run (built on SwiftUI's Text + Text). Each segment can have its own font size / font / color, but everything is laid out and wrapped as a single piece of text.

Three text components in a vstack / hstack are 3 independent Text views — they wrap and align independently. Three text components in a tstack are 1 single text run — they won't wrap between segments, and the whole thing only wraps when the entire run is too long.

Children​

Only text is accepted (other types are ignored). There's no separator between children — if you want a space, add it inside the value.

Example: big number + small unit​

tstack:
├─ text (content: "32", size 36, color primary)
└─ text (content: "°C", size 14, color gray)

Renders: 32°C (big number, small unit, snug together). With an hstack, the small unit would be centered separately and float far from the number — not natural.

Example: bold one word​

tstack:
├─ text (content: "You have ")
├─ text (content: "${count}", large size, red color)
└─ text (content: " new messages")

Renders: You have 5 new messages, with the number standing out.

Notes​

  • Want a space between segments? Add it to the start or end of the value — tstack won't add one for you.
  • Want them to wrap onto separate lines? Use vstack, not tstack.
  • tstack ignores icon / image / containers — only text is recognized.

Containers can also loop​

Any container (vstack / hstack / zstack / grid) can be given a loop data source to repeat its children N times over an array. See Loop.

Example: classic "icon + title + subtitle" card​

hstack:
├─ icon (large)
└─ vstack:
├─ text ("Title")
└─ text ("Subtitle", small size, gray)

Example: cover image + title (zstack)​

zstack:
├─ image (cover, fill)
└─ vstack:
├─ spacer
└─ text ("Title", white)

Result: text floats at the bottom of the cover image.