Scrollbar Widget Support
Status
Blocked. Scrollbar overlays require frame-level rendering, which MVU abstracts away.
Problem
Scrollbar widgets in RatatuiRuby render as overlays on the same area as content:
# In RatatuiRuby (imperative) frame.render_widget(paragraph, frame.area) frame.render_widget(scrollbar, frame.area) # Overlays on right edge
In Rooibos MVU, View returns a widget tree. The runtime renders all widgets sequentially. When a scrollbar is added as a sibling to a list inside a block:
# In Rooibos (MVU) tui.block(children: [list, scrollbar])
The scrollbar renders inside the content area, not on the border. It also inherits layout properties (takes up width, changes sibling positioning).
Why This Happens
-
No frame access in View. View is a pure function that returns widgets. It cannot call
frame.render_widgetfor overlay positioning. -
Children are siblings. Block’s
childrenare laid out sequentially, not stacked. -
Scrollbar is position-aware. It needs to know the rendered area to position itself at the right edge.
Workarounds Considered
1. Overlay Widget (Not Implemented)
A dedicated overlay widget that renders children on top of each other:
tui.overlay(base: list, overlay: scrollbar)
Problem: Would need to track which child gets which area and handle z-ordering.
2. Block with Scrollbar Option
Add scrollbar support directly to Block:
tui.block( children: [list], scrollbar: { content_length: 100, position: 10 } )
The Block would render its children, then overlay the scrollbar on the right border.
Advantages: - Scrollbar naturally belongs with the bordered container - No new widget type needed - Block already handles border rendering
Disadvantages: - Would need to be added to RatatuiRuby, or - Would need to intercept TUI facade and Block.new
3. Custom Widget (Current Pattern)
Users implement scrollbar rendering in a custom widget that has frame access:
class ScrollableList def render(area) # Split area, render list, then overlay scrollbar end end
Problem: Breaks the pure View pattern. Custom widgets are RatatuiRuby-level, not MVU-level. Duplicates logic from Scrollbar.
4. Provide Frame to View
Pass the frame to View, so it can call render_widget for overlay positioning.
module MyFragment View = -> (model, tui, frame) { tui.block( children: [tui.list(items: model.items)], scrollbar: { content_length: model.items.length, position: model.offset } ) } end
Problem: Breaks the pure View-as-returned-tree pattern.
Recommendation
Investigate more options.
Open Questions
-
Should scrollbar position be
offset(scroll window position) orselected_index? -
Should scrollbar automatically infer
content_lengthfrom list children? -
How to handle horizontal scrollbars?
See Also
-
no_stateful_widgets.md — MVU scroll offset patterns