LeCodesdocs

UI containers

The structural elements: UIColumn stacks children vertically, UIRow horizontally, UIScrollable makes an overflow region pan, UISpacer eats free space. For a whole screen that scrolls, prefer UIScreen.makeScrollable() over wrapping everything in a UIScrollable — see screen-router.md.

At a glance

TypeScript
const screen = UIScreen([
  UIRow([
    UIText("Library").style({ fontSize: 24, fontWeight: 700, color: "white" }),
    UISpacer(),                                          // pushes the count to the far edge
    UIText("12 items").style({ color: "#888" }),
  ]).style({ px: 16, py: 12, alignItems: "center" }),

  UIScrollable([
    UIColumn([ UIText("First").style({ color: "white" }) ]).style({ p: 16 }),
    UIColumn([ UIText("Second").style({ color: "white" }) ]).style({ p: 16 }),
  ]).style({ flexGrow: 1, flexShrink: 1, gap: 8 })       // flexShrink: 1 or it overflows
    .onScroll(pos => console.log("scrolled to", pos)),
]).style({ bgColor: "black", pt: "safe-top" })

screen.open()

UIRow / UIColumn

TypeScript
UIColumn(): UIColumn
UIColumn(children: UINodeChild[]): UIColumn

UIRow(children: UINodeChild[]): UIRow          // no zero-arg overload — use UIRow([])

The only difference between the two is the default flexDirection (column vs row) — both take the full container style surface (UIContainerStyle = element + drawable + container layout props, see styling.md). Children entries that are null / undefined / false are skipped — see overview.md.

Avoid wrapper containers that only exist to align something — alignment is a container property:

TypeScript
UIRow([ UIColumn([]).style({ flexGrow: 1 }), label ])      // ✗ phantom spacer element
UIRow([ label ]).style({ justifyContent: "flex-end" })     // ✓

Managing children

All containers on this page share the same imperative child API (works before and after the element is on screen; no diffing):

TypeScript
c.append(...nodes: UINodeChild[]): this
c.insert(index: number, ...nodes: UINodeChild[]): this   // index into c.children
c.remove(...nodes: UINode[]): this                        // removes by identity
c.setContent(children: UINodeChild[]): this               // replace everything
c.children                                                 // readonly UINodeChild[]

setContent is the "re-render" primitive — build a fresh array (e.g. items.map(Row)) and swap it in. For long or unbounded data use UIVirtualizedList instead.

UIBox — deprecated

TypeScript
UIBox(children | style | (style, children)): UIBox   // legacy — use UIRow / UIColumn

A legacy container whose only distinction is defaulting justifyContent and alignItems to "center" (children centered on both axes). Kept for old projects; write new code with UIRow/UIColumn plus explicit alignment.

UIScrollable

A pannable overflow region inside an otherwise fixed layout — a list under a pinned header, a horizontal carousel. If the whole screen is one flow (article, feed), make the screen itself scrollable instead: pull-to-refresh (onRefresh) exists only on UIScreen.makeScrollable().

TypeScript
UIScrollable(): UIScrollable
UIScrollable(children: UINodeChild[]): UIScrollable

Extra styles on top of the container surface:

TypeScript
scrollDirection: "horizontal" | "vertical"      // default vertical
showScrollbar:   boolean
overscrollMode:  "none" | "absorb" | "default"  // edge behavior when dragged past the content

Scroll events (chainable, like all on*):

TypeScript
s.onScroll(cb: (scrollPosition: number) => void): UIScrollable   // logical px from the start edge
s.onScrollRelease(cb: () => void): UIScrollable                  // finger lifted
s.onOverscroll(cb: (delta: number) => void): UIScrollable        // dragged past the edge (px)
Note

there is no scrollTo — a UIScrollable's position can't be set programmatically. This is a current limitation. If you need programmatic scrolling (scrollTo / scrollToEnd / scrollToKey), use UIVirtualizedList.

Note

flexShrink defaults to 0 everywhere, so a vertical UIScrollable in a column overflows the screen instead of scrolling until it — and any wrapping containers between it and the screen — get flexShrink: 1.

Horizontal carousel:

TypeScript
UIScrollable(items.map(Card))
  .style({ scrollDirection: "horizontal", showScrollbar: false, gap: 12, px: 16 })

Draggable children inside a scroller need to claim their gesture direction or the scroll steals the pointer — see touch.md.

UISpacer

TypeScript
UISpacer(): UISpacer      // no children; its style surface is ElementStyle only — no background

Flexible empty space: it defaults to flexGrow: 1, eating free space along the parent's main axis. Reach for it only when plain alignment can't express the layout — one item pushed to the far end while the rest stay put:

TypeScript
UIRow([ title, UISpacer(), closeButton ])

If all children move together, justifyContent ("space-between", "flex-end", …) does the same job with no extra element.

See also