Scroll

Scroll is the general-purpose scroll container.
It gives one child a scrollable viewport, clips the visible region, and lets the runtime track scroll offset for that node. Use it for long forms, article content, settings screens, horizontally scrolling strips, or any subtree that should remain fully built but only partly visible.
Use Scroll when building the whole child subtree is acceptable. Do not use it for very large uniform-height lists where virtualization matters. That is the job of LazyColumn.

Example

use fission::prelude::*;
use fission::op::FlexDirection;

let widget: Widget = Scroll {
    direction: FlexDirection::Column,
    flex_grow: 1.0,
    child: Some(
        Column {
            gap: Some(16.0),
            children: vec![
                Text::new("Section 1").into(),
                Text::new("Section 2").into(),
                Text::new("Section 3").into(),
            ],
            ..Default::default()
        }
        .into(),
    ),
    ..Default::default()
}
.into();

Field table

Field
Type
Meaning
Notes / default behavior
id
Option<WidgetId>
Stable widget identity used for scroll offset tracking.
Defaults to None.
child
Option<Widget>
The scrollable content.
Defaults to None.
direction
FlexDirection
Scroll axis.
Defaults to FlexDirection::Column, which means vertical scrolling.
width
Option<f32>
Fixed viewport width.
Defaults to None.
height
Option<f32>
Fixed viewport height.
Defaults to None.
show_scrollbar
bool
Whether to render a scrollbar indicator.
Defaults to true.
flex_grow
f32
Extra space the scroll viewport may absorb.
Defaults to 0.0. Set this when the scroll area should fill a panel.
flex_shrink
f32
How much the scroll viewport may shrink.
Defaults to 0.0.

How it differs from LazyColumn

Scroll still builds its entire child subtree. It simply makes that subtree scrollable. A LazyColumn goes further by virtualizing which items are lowered.
That means Scroll is the right default for normal content screens, especially when item heights vary or the subtree is not list-like. LazyColumn is the optimization path for large, uniform-height lists.

Specific advice

Give the scroll area room to exist. In many screen layouts, that means setting flex_grow: 1.0 so the viewport can expand to fill the panel rather than collapsing to the child's natural height.
Also avoid nesting two vertical scroll containers unless you are deliberately designing nested scroll behavior. It is usually harder to use and harder to test.

Programmatic reveal

Use ctx.effects.scroll_into_view(...) when a reducer needs to reveal a widget after state changes. The reducer only records the request. The runtime applies it after the next layout pass, when both the scroll container and target rectangles are known.
use fission::prelude::*;

#[fission_reducer(SelectPage)]
fn select_page(state: &mut DocumentState, action: SelectPage, ctx: &mut ReducerContext<DocumentState>) {
    state.selected_page = action.page;

    ctx.effects.scroll_into_view(ScrollIntoViewRequest {
        container: Some(WidgetId::explicit("document.canvas.scroll")),
        target: WidgetId::explicit(&format!("document.page.{}", action.page)),
        axis: ScrollAxis::Vertical,
        alignment: ScrollAlignment::Start,
        padding: [24.0, 24.0, 24.0, 24.0],
        behavior: ScrollBehavior::Instant,
        if_needed: false,
    });
}
Keep the scroll container and reveal targets on stable explicit IDs. If container is None, Fission uses the nearest matching scroll ancestor. ScrollAlignment::Nearest uses the smallest offset change that makes the target visible; Start, Center, End, and Fraction(_) place it deliberately inside the viewport.

Production checklist

For Scroll, review the fields that change behavior before treating the widget as finished: id, child, direction, width, height, show_scrollbar. The goal is to make the product rule visible in state and actions, not hidden inside ad-hoc construction code.
If this widget appears inside an interactive flow, keep the surrounding action binding in the parent component and test that the flow still has one clear reducer path.
When child widgets are generated from data, give reordered or filtered rows an explicit WidgetId so retained local state and scroll behavior do not drift between items.
Set id only when identity must be stable across filtering, reordering, diagnostics, or tests; otherwise let Fission derive identity from structure.
Check the semantics tree for the user-facing label or role that makes this widget understandable without relying only on pixels.
Prefer parent layout and design-system sizing first; use explicit size or spacing fields when the product layout requires that constraint on this widget specifically.
Add at least one component or harness test that confirms the visible text, semantic role, action dispatch, and layout constraint that matter for this widget in context.
If a screen starts repeating the same Scroll setup, extract a named component around this widget. That keeps the reference API small while making product code easier to read and safer for generated code to copy.
LazyColumn, Column, Row, and SafeArea.
Fission
A cross-platform, GPU-accelerated user interface framework for Rust. MIT licensed.
Copyright (c) 2026 Fission
Ready to use today. Widget APIs are expected to remain stable; some runtime and shell APIs may change before 1.0.0.
Fission 0.7.0