Overflow
overflowX and overflowY decide, per axis, what happens to children that
extend past a node's box: they spill out, get clipped, or make the node a
scroll container. A scroll container scrolls with the mouse wheel and touch
drags, takes a controlled offset from React, and can show a draggable
scrollbar through the scrollbar style.
Usage
function Log({ lines }: { lines: string[] }) {
return (
<node
style={{
flexDirection: "column",
height: 240,
overflowY: "scroll",
scrollbar: "default",
}}
>
{lines.map((line, i) => (
<text key={i}>{line}</text>
))}
</node>
);
}Overflow modes
Each axis takes one of four values:
| Value | Content outside the box | Minimum size as a flex or grid item |
|---|---|---|
"visible" (default) |
Drawn | Its content's minimum size |
"clip" |
Clipped | Its content's minimum size |
"hidden" |
Clipped | 0 |
"scroll" |
Clipped and scrollable | 0 |
- Clipping cuts at the padding box (inside the border) on that axis only:
overflowX: "clip"lets content spill vertically. - Clipped content is neither drawn nor hit by the pointer.
"clip"and"hidden"cut the same pixels; they differ in layout. As a flex or grid item, a"clip"node keeps its content's minimum size as its own, while a"hidden"or"scroll"node can be shrunk below it by its parent. A"hidden"axis is not scrollable.- An unrecognized value falls back to
"visible"and reports anoverflowdevtools warning. There is nooverflowshorthand: set both axes.
Scroll containers
A "scroll" axis offsets the children by the node's scroll offset. The
offset runs from 0 to the content's size minus the box's size and is
clamped to that range; when the content fits, there is nothing to scroll.
Absolutely positioned children scroll with the rest.
Wheel and touch
- The mouse wheel scrolls the topmost scroll container under the cursor that has room to scroll on the wheeled axis. A container whose content fits lets the wheel through to the containers beneath it and, past the last one, to the 3D scene.
- A container that can scroll on that axis takes the wheel even when it is already at the end, so the wheel never chains to an outer container.
- A vertical wheel doesn't scroll a container that only scrolls horizontally; that needs a horizontal wheel or trackpad gesture.
- The
scrollStepprop sets how many logical pixels one wheel notch scrolls,20by default. Trackpads report pixels, which are used as is. - A node with an
onWheelhandler under the cursor takes the wheel: no container beneath it scrolls. See Mouse events. - When a container scrolls,
PointerCapture::wheel_capturedis set for the frame, so a world system gating on it (a zoom camera) ignores that wheel. - On a touch screen, dragging a finger over a container scrolls it with the finger. A touch that moves more than 8px becomes a scroll, and its tap no longer clicks.
Controlled offset
const [scrollTop, setScrollTop] = useState(0);
<node
style={{ height: 240, overflowY: "scroll" }}
scrollTop={scrollTop}
onScroll={(e) => setScrollTop(e.scrollTop)}
>
{rows}
</node>;scrollTopandscrollLeft(logical pixels) move the offset when their value changes. Re-rendering with the same value does nothing, so they never fight the user's wheel; keep the state in sync throughonScrollso the next value you set is a real change.- A value past the end lands on the end once layout has run, and
onScrollthen reports the real offset. Setting a largescrollTopin the same render that appends rows pins a log to its bottom. onScrollreceives{ scrollTop, scrollLeft }whenever the offset changes: wheel, touch, scrollbar, or an eased scroll. A value you set yourself that lands in range is not echoed back.- The scroll props are accepted on
<node>,<button>,<text>,<image>,<canvas>,<svg>,<portal>and<anchor>.
transition: { scroll } eases the offset toward each new wheel or
scrollTop/scrollLeft target instead of jumping (dragging the scrollbar
still snaps). Don't also feed onScroll back into the same controlled axis
then: the round trip fights the ease. See
Style transitions.
The scrollbar
Bevy draws no scrollbar on its own. The scrollbar style adds one per
"scroll" axis, hidden while the content fits. The thumb is draggable and
clicking the track pages.
"none"(the default): no visible bar."default": a neutral bar, 12px thick: a faint dark track and a gray, rounded thumb.- An object, to configure it:
const bar: ScrollbarStyle = {
track: { backgroundColor: "#00000088", borderRadius: 8 },
thumb: {
backgroundColor: "#7aa2f7",
borderRadius: 8,
hover: { backgroundColor: "#89b4fa" },
pressed: { backgroundColor: "#b4befe" },
},
thickness: 10,
};
<node style={{ height: 180, overflowY: "scroll", scrollbar: bar }} />;| Field | Default | Effect |
|---|---|---|
track |
faint dark | The groove's look |
thumb |
gray pill | The handle's look |
thickness |
12 |
Bar width across its axis, in logical pixels |
minThumbLength |
24 |
The thumb never gets shorter, so it stays grabbable |
position |
"gutter" |
"gutter" reserves space; "float" overlays |
verticalSide |
"right" |
"left" or "right" edge for the vertical bar |
horizontalSide |
"bottom" |
"top" or "bottom" edge for the horizontal bar |
track and thumb take only backgroundColor, borderColor (one color
or a per-side object), borderRadius and border (both four-sided
values), plus hover and pressed overlays of the same four fields;
pressed, active while the bar is dragged, wins over hover. Unset fields
keep the defaults: the thumb's radius stays half the thickness. An unknown
field or keyword is ignored with a scrollbar devtools warning.
The bar draws above the container and its siblings, but under anything
with a higher globalZIndex.
Gutter and scrollbarWidth
scrollbarWidth (a number of logical pixels, default 0) reserves a
gutter on each "scroll" axis: on the right for overflowY, at the bottom
for overflowX. The content lays out beside it. It draws nothing by
itself.
- A
scrollbarwithposition: "gutter"reserves itsthicknessthis way, so content never runs under the bar. An explicitscrollbarWidthwins over it,0included. position: "float"reserves nothing and draws the bar over the content.- The gutter is always on the right or bottom edge. A bar moved to the
left or top with
verticalSideorhorizontalSidecovers the content's edge while the gutter stays empty on the other side, so useposition: "float"there and pad the content yourself.
Limits
- The wheel only reaches UI on the window: scroll containers inside a
<surface>don't scroll with the wheel, and a UI camera with an offset viewport is not accounted for. Drive them withscrollTopandscrollLeft. - No scroll chaining, overscroll, or scroll snapping.
- The scrollbar is placed assuming the container's parent has no border; a border on the parent offsets the bar by its width.
See overflowX,
overflowY,
scrollbarWidth and
scrollbar in the style
reference.
bevy-react