VerticalList

Updated: Sep 24, 2026
Use VerticalList as the standard host for a vertically scrolling collection of list rows. It combines a ScrollView fixed to the vertical axis with a row container that applies the standard edge inset and defines the collection’s focus boundary.
The viewport remains full width, allowing the list to reach both horizontal screen edges while its rows stay correctly inset. ScrollView owns scrolling, scroll-boundary fades, the scrollbar, header inset, keyboard scrolling, and scroll-to-focused behavior. This shared behavior keeps focus and directional navigation consistent across lists.
For a collection of rows, prefer VerticalList. For arbitrary content that may overflow, use ScrollView directly.

Usage

import { ListItem, VerticalList } from '@wearables-ui-toolkit/mrbd';

export function BriefingList() {
  return (
    <VerticalList ariaLabel="Briefing">
      <ListItem title="Morning brief" subtitle="Ready" />
      <ListItem title="Team notes" subtitle="Updated" />
      <ListItem title="Saved items" subtitle="Six items" />
    </VerticalList>
  );
}
Typically, the application maps rows directly from data. Keep rows adjacent so the built-in list rhythm remains consistent; neither the list nor the caller adds spacing between them:
<VerticalList ariaLabel="Conversations" onScroll={setScrollOffset}>
  {conversations.map(conversation => (
    <ListItem
      key={conversation.id}
      title={conversation.name}
      subtitle={conversation.preview}
      timestamp={conversation.timestamp}
      avatarSrc={conversation.photoUrl}
      avatarAlt={conversation.name}
      onClick={() => open(conversation.id)}
    />
  ))}
</VerticalList>
Inside a SubNavigationPager, enable the header inset. Focused rows then settle below the subnavigation header instead of underneath it:
<VerticalList ariaLabel="Messages" insetForHeader headerHeight={headerHeight}>
  {rows}
</VerticalList>
For extra space before the first row or after the last row, style the row container. This preserves the full-width viewport and avoids an unnecessary wrapper:
<VerticalList ariaLabel="Settings" contentClassName="settingsRows">
  {rows}
</VerticalList>
.settingsRows {
  padding-block: var(--uit-spacing-medium);
}

Props

The complete prop type is VerticalListProps. It extends ScrollViewProps with children and orientation removed. The axis is fixed to vertical, and the redeclared children prop provides rows for the row container. See ScrollView for the complete inherited surface.
Like ScrollView, VerticalList has a closed prop set. It does not spread extra DOM attributes onto the frame, viewport, or row container. The component forwards its ref to the scrolling viewport, not the outer frame or row container.

Own props

PropTypeDefaultDescription
children
ReactNode
—
Rows rendered inside the row container.
contentClassName
string
''
Class added to the row container alongside the built-in list edge inset. Prefer it for block padding at the beginning and end of the list.
contentStyle
CSSProperties
—
Inline styles applied to the row container.

Notable inherited props

PropTypeDefaultDescription
ariaLabel
string
—
Accessible name for the scroll region. The component does not infer a name from its rows.
insetForHeader
boolean
false
Reserves top padding so rows begin below an overlapping header.
headerHeight
number
Standard header height
Height in pixels of the reserved inset. Set it to the overlapping header’s height. The component reads it only when insetForHeader is set.
fadingEdgeEnabled
boolean
true
Controls whether the top and bottom fades are drawn.
topFadingEdgeLength
number
Standard fade length
Length in pixels of the fade at the top of the list.
bottomFadingEdgeLength
number
Standard fade length
Length in pixels of the fade at the bottom of the list.
scrollbarEnabled
boolean
true
Controls whether the vertical scrollbar can appear. It renders only when the rows overflow.
showScrollbar
boolean
—
Alias for scrollbarEnabled. scrollbarEnabled takes precedence when both are set.
height
number or string
'100%'
Height of the list frame. Prefer sizing the parent and retaining this default.
width
number or string
'100%'
Width of the list frame.
className
string
''
Class applied to the outer frame.
scrollContainerClassName
string
''
Class applied to the scrolling viewport.
style
CSSProperties
—
Inline styles applied to the outer frame after width and height.
tabIndex
number
-1
Tab index of the scrolling viewport.
onScroll
(scrollTop: number) => void
—
Called while the list scrolls with the viewport’s vertical scroll offset.
leftFadingEdgeLength and rightFadingEdgeLength remain available on the inherited type but have no effect. The component draws fades only on its active axis, which is always vertical.

Behavior

Inset without clipping. The row container applies inline padding of var(--uit-spacing-large) on both sides. Rows remain inset while the viewport, fades, and full-bleed row backgrounds extend edge to edge. Avoid adding horizontal padding around or inside the list because it double-insets the rows. contentClassName appears alongside the built-in class, making it suitable for spacing at the list’s ends. Inline contentStyle takes precedence over both.
Focus boundary. The row container defines the collection’s focus boundary. When focus reaches the first or last row, the list settles at the true scroll boundary instead of stopping early. It realigns after container animations finish, so the first row does not remain partially dimmed beneath the top fade.
Scroll to focused row. As focus moves through the list, VerticalList scrolls the focused row into the area within the active fade insets. If the focused row cannot move focus farther in the pressed direction but the list can still scroll there, the list scrolls by up to half a viewport instead of moving focus out of the collection.
Keyboard. When the viewport holds focus, Up and Down scroll by one step, PageUp and PageDown scroll by one viewport, and Home and End move to the boundaries. At an axis boundary, an Up or Down press asks the surrounding directional navigation to move focus out of the region rather than consuming the key. Paging and boundary keys remain owned by the list.
Fades and scrollbar. The top and bottom fades reflect the distance from each scroll boundary and appear only when more content exists beyond that edge. The scrollbar renders only when rows overflow, appears during scrolling, and hides after a short idle delay.
Header inset.insetForHeader adds headerHeight as top padding and combines it with the page content-origin offset. A list under an overlapping header therefore begins below the header while retaining its full scroll range.
Sizing. Width and height fill the parent by default, so the parent owns the available list area. Size the parent responsively from available space. Rows own their height, and the list adds no space between them.

Accessibility

  • The scrolling viewport has the region role and receives its accessible name from ariaLabel. Supply a label that identifies the collection because the component does not infer one from the rows.
  • tabIndex defaults to -1. The region stays out of the tab sequence but can receive programmatic focus and focus from directional navigation.
  • Fades and the scrollbar are decorative and hidden from assistive technology.
  • Each row owns its role and accessible name. See ListItem for row accessibility.

Application responsibilities

  • Let the list extend edge to edge and rely on its built-in row inset. This preserves the intended full-bleed scroll treatment.
  • Provide an ariaLabel that describes the collection so the region has a clear accessible name.
  • Enable insetForHeader for an overlapping header and pass that header’s actual height so focused rows remain visible.
  • Add end padding through contentClassName or contentStyle with spacing tokens.
  • Give mapped rows stable keys so focus remains attached to the same item after data updates.

Avoid

  • Do not wrap the list in another vertical scroll container or place a second vertical scroller inside it. Same-axis nested scrollers are an unsupported composition because scrolling and directional navigation need one owner per route.
  • Avoid left or right padding intended to inset rows; the row container already provides that inset.
  • Avoid gaps, dividers, or rounded wrappers between list rows so the standard list rhythm and focus movement remain consistent.
  • Do not size the list to a fixed device height. Size its parent from available space so the list remains responsive.
  • Avoid generated class names. Use contentClassName, scrollContainerClassName, or className to target the supported element.