Skip to main content

List

Lists are continuous, vertical indexes of text and images. Follows the Material Design 3 Lists specification.

Usage​

import { List, ListItem, ListDivider } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'

export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<List>
<ListItem headlineText="Photos" supportingText="Jan 9, 2024" />
<ListDivider />
<ListItem headlineText="Recipes" supportingText="Jan 17, 2024" />
<ListDivider />
<ListItem headlineText="Work" supportingText="Jan 28, 2024" />
</List>
</ThemeProvider>
</SafeAreaProvider>
)
}

List item templates​

List items auto-detect their height from the content provided:

TemplatePropsMin height
One-lineheadlineText only56dp
Two-line+ supportingText or overlineText72dp
Three-line+ both, or multi-line supportingText88dp

Headline​

Give the list a section title with headline. It shows in titleSmall and onSurfaceVariant above the items, with the same start padding as ListItem. On web it renders as <h2>. Use headingLevel to change the level:

import { List, ListItem } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'

export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<List headline="App preference">
<ListItem headlineText="Language" supportingText="English" />
<ListItem headlineText="Dark mode" />
</List>
<List headline="News preference">
<ListItem headlineText="Country" supportingText="India" />
</List>
</ThemeProvider>
</SafeAreaProvider>
)
}

Leading and trailing content​

Pass any ReactNode as leadingContent or trailingContent:

import { List, ListItem } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { MaterialDesignIcons } from '@react-native-vector-icons/material-design-icons'

export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<List>
<ListItem
headlineText="Wi-Fi"
supportingText="Connected"
leadingContent={<MaterialDesignIcons name="wifi" size={24} />}
trailingSupportingText="On"
/>
</List>
</ThemeProvider>
</SafeAreaProvider>
)
}

Interactive items​

When onPress is provided, the item renders as a Pressable with state-layer feedback:

import { List, ListItem } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { Alert } from 'react-native'

export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<List>
<ListItem
headlineText="Settings"
onPress={() => Alert.alert('Tapped')}
/>
</List>
</ThemeProvider>
</SafeAreaProvider>
)
}

Selection controls in a row​

To let the full row toggle a Checkbox, Radio or Switch, give the press to the row:

  1. Put onPress on the ListItem.
  2. Give the control value and no onValueChange.
  3. Give the row role and aria-checked.

A control with value and no onValueChange only shows the state. It takes no press, so a tap on the control goes to the row. It also leaves the tab order and is hidden from assistive technology. The row announces the role and the state in its place.

import { Checkbox, List, ListItem, Switch } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { useState } from 'react'

export default function App() {
const [wifi, setWifi] = useState(true)
const [news, setNews] = useState(false)
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<List>
<ListItem
headlineText="Wi-Fi"
role="switch"
aria-checked={wifi}
onPress={() => setWifi(!wifi)}
trailingContent={<Switch value={wifi} />}
/>
<ListItem
headlineText="Newsletter"
role="checkbox"
aria-checked={news}
onPress={() => setNews(!news)}
leadingContent={<Checkbox value={news} />}
/>
</List>
</ThemeProvider>
</SafeAreaProvider>
)
}
Give the row the role and the state

The hidden control does not announce anything. Without role and aria-checked on the row, a screen reader announces a button and no checked state.

A disabled control is never hidden. Give the control an onValueChange when it must take the press itself.

Inset dividers​

Use insetStart to align the divider with text that follows a leading icon:

import { List, ListItem, ListDivider } from '@rootnative/components'
import { mdiResolver } from '@rootnative/components/mdi'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { MaterialDesignIcons } from '@react-native-vector-icons/material-design-icons'

export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider iconResolver={mdiResolver}>
<List>
<ListItem
headlineText="Item 1"
leadingContent={<MaterialDesignIcons name="folder" size={24} />}
/>
<ListDivider insetStart />
<ListItem
headlineText="Item 2"
leadingContent={<MaterialDesignIcons name="folder" size={24} />}
/>
</List>
</ThemeProvider>
</SafeAreaProvider>
)
}

List Props​

PropTypeDefaultRequiredDescription
childrenReactNode-YesContent rendered inside the list container.
headlinestring-NoSection headline in `titleSmall` and `onSurfaceVariant`, above the items. It has the same start and end padding as `ListItem`. Omit it for a list with no title.
headingLevelenum2NoThe outline level of the headline on web, where it renders as `<hN>`. Native has no heading levels and ignores it.
headlineStyleStyleProp<TextStyle>-NoStyle applied to the headline `Text`.
styleStyleProp<ViewStyle>-No-
hrefAttrs{ target?: string; rel?: string; download?: string | boolean; }-NoWeb only. The `target`, `rel` and `download` of the `<a>` that a view with an `href` renders, for example under Expo Router's `<Link asChild>`. `target: '_blank'` opens the link in a new tab. Native ignores it.
onKeyDown(event: { nativeEvent: { key?: string; }; }) => void-No-

ListItem Props​

PropTypeDefaultRequiredDescription
headlineTextstring-YesPrimary text displayed on the list item.
supportingTextstring-NoSecondary text displayed below the headline.
overlineTextstring-NoText displayed above the headline (e.g. category label).
trailingSupportingTextstring-NoShort text displayed at the trailing edge (e.g. "100+", timestamp).
leadingContentReactNode-NoContent rendered before the text block (icon, avatar, image, checkbox).
trailingContentReactNode-NoContent rendered after the text block (icon, switch, checkbox).
onPress() => void-NoWhen provided, the item becomes interactive (Pressable). Omit to render as a plain View.
disabledbooleanNoDisables the press interaction and reduces opacity. Only effective when `onPress` is provided.
containerColorstring-NoOverride the container (background) color. State-layer colors (hover, press) are derived automatically.
contentColorstring-NoOverride the headline text color. Overline, supporting, and trailing-supporting text keep their MD3 muted variants (`onSurfaceVariant`) — those are intentionally distinct from the primary content color.
supportingTextNumberOfLinesnumber1NoMaximum number of lines for supportingText before truncating.
styleStyleProp<ViewStyle>-No-
hrefAttrs{ target?: string; rel?: string; download?: string | boolean; }-NoWeb only. The `target`, `rel` and `download` of the `<a>` that a view with an `href` renders, for example under Expo Router's `<Link asChild>`. `target: '_blank'` opens the link in a new tab. Native ignores it.
onKeyDown(event: { nativeEvent: { key?: string; }; }) => void-No-

ListDivider Props​

ListDivider is an alias of the standalone Divider component and takes the same props. New code should import Divider directly.

PropTypeDefaultRequiredDescription
orientationenum'horizontal'NoAxis the divider is drawn along. A vertical divider stretches to the height of its parent, so it must sit inside a row-direction container.
insetStartnumber | booleanfalseNoInset from the leading edge (start for horizontal, top for vertical). `true` applies the MD3 list inset (56dp), which aligns the divider with list text that follows a leading icon. A number sets the inset in dp.
insetEndnumber | booleanfalseNoInset from the trailing edge (end for horizontal, bottom for vertical). Same units as `insetStart`.
thicknessnumber1NoLine thickness in dp.
containerColorstringtheme.colors.outlineVariantNoOverride the line color.
styleStyleProp<ViewStyle>-No-
hrefAttrs{ target?: string; rel?: string; download?: string | boolean; }-NoWeb only. The `target`, `rel` and `download` of the `<a>` that a view with an `href` renders, for example under Expo Router's `<Link asChild>`. `target: '_blank'` opens the link in a new tab. Native ignores it.
onKeyDown(event: { nativeEvent: { key?: string; }; }) => void-No-