Skip to main content

ScrollView

layout.ScrollView clips a single child to a viewport and lets the user scroll vertically to reveal content that exceeds the available height.

Constructors

// Standalone constructor
sv := layout.NewScrollView(myVBox).WithScrollBar(true)

// Convenience builders — wrap a box directly
sv := layout.NewVBox(items...).AsScrollView().WithScrollBar(true)
sv := layout.NewHBox(cols...).AsScrollView() // horizontal content, vertical scroll

WithScrollBar

func (sv *ScrollView) WithScrollBar(show bool, anchor ...oat.Anchor) *ScrollView
ParameterDefaultEffect
showfalsetrue — display a single-column gutter bar
anchor (optional)oat.AnchorRightoat.AnchorLeft — move bar to the left edge

The bar uses the theme's Muted token for the track () and Accent FG for the thumb (). These colours are set by ApplyTheme and can be overridden with WithTrackColor / WithThumbColor.

WithTrackColor / WithThumbColor

func (sv *ScrollView) WithTrackColor(c latte.Color) *ScrollView
func (sv *ScrollView) WithThumbColor(c latte.Color) *ScrollView

Override the scroll bar colours for this specific ScrollView. Overrides survive SetTheme calls. Pass latte.ColorDefault to revert to theme-driven behaviour.

sv := layout.NewVBox(items...).AsScrollView().
WithScrollBar(true).
WithTrackColor(latte.Hex("#444466")).
WithThumbColor(latte.ColorBrightCyan)

Nesting with Border

Prefer Border(ScrollView(VBox)) over ScrollView(Border(VBox)). In the correct pattern the border chrome is fixed and only the VBox content scrolls. In the reverse pattern the entire Border — including its title row — scrolls off-screen as the user scrolls down.

// CORRECT — fixed border, scrolling content:
panel := layout.NewBorder(
layout.NewVBox(items...).AsScrollView().WithScrollBar(true),
).WithTitle("Items")

// VALID but unusual — entire border (including title) scrolls:
panel := layout.NewScrollView(
layout.NewBorder(layout.NewVBox(items...)).WithTitle("Items"),
).WithScrollBar(true)

VFill / FlexChild inside ScrollView

VFill and FlexChild behave differently depending on whether content overflows the viewport:

  • Content fits (no scrolling) — the full viewport height is passed to the child, so flex children expand normally.
  • Content overflows (scrolling active) — the child is measured unconstrained and flex children collapse to zero height.

Avoid VFill / FlexChild inside a ScrollView that is expected to scroll; use fixed-height children instead.

Focus and keyboard

ScrollView is always present in the Tab cycle. HandleKey returns false for all scroll keys when content fits the viewport, so arrow keys fall through to inter-widget focus cycling as usual. When content overflows, HandleKey consumes the scroll keys:

KeyAction
/ ±1 row
PgUp / PgDn±viewport height
Home / EndJump to top / bottom
Do not use FocusGuard on ScrollView

The focus tree is collected once at startup before any Measure/Render pass. A FocusGuard that checks contentH > viewportH would always return false at startup (both values are zero), permanently excluding the ScrollView from Tab cycling.

Programmatic scroll

sv.ScrollOffset() int        // current row offset
sv.ContentHeight() int // full unconstrained child height
sv.ScrollTo(off int) // set offset; clamped to [0, contentH-viewportH]