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:
| Template | Props | Min height |
|---|---|---|
| One-line | headlineText only | 56dp |
| Two-line | + supportingText or overlineText | 72dp |
| Three-line | + both, or multi-line supportingText | 88dp |
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:
- Put
onPresson theListItem. - Give the control
valueand noonValueChange. - Give the row
roleandaria-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>
)
}
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
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | ReactNode | - | Yes | Content rendered inside the list container. |
headline | string | - | No | Section 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. |
headingLevel | enum | 2 | No | The outline level of the headline on web, where it renders as `<hN>`. Native has no heading levels and ignores it. |
headlineStyle | StyleProp<TextStyle> | - | No | Style applied to the headline `Text`. |
style | StyleProp<ViewStyle> | - | No | - |
hrefAttrs | { target?: string; rel?: string; download?: string | boolean; } | - | No | Web 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
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
headlineText | string | - | Yes | Primary text displayed on the list item. |
supportingText | string | - | No | Secondary text displayed below the headline. |
overlineText | string | - | No | Text displayed above the headline (e.g. category label). |
trailingSupportingText | string | - | No | Short text displayed at the trailing edge (e.g. "100+", timestamp). |
leadingContent | ReactNode | - | No | Content rendered before the text block (icon, avatar, image, checkbox). |
trailingContent | ReactNode | - | No | Content rendered after the text block (icon, switch, checkbox). |
onPress | () => void | - | No | When provided, the item becomes interactive (Pressable). Omit to render as a plain View. |
disabled | boolean | | No | Disables the press interaction and reduces opacity. Only effective when `onPress` is provided. |
containerColor | string | - | No | Override the container (background) color. State-layer colors (hover, press) are derived automatically. |
contentColor | string | - | No | Override 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. |
supportingTextNumberOfLines | number | 1 | No | Maximum number of lines for supportingText before truncating. |
style | StyleProp<ViewStyle> | - | No | - |
hrefAttrs | { target?: string; rel?: string; download?: string | boolean; } | - | No | Web 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.
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
orientation | enum | 'horizontal' | No | Axis the divider is drawn along. A vertical divider stretches to the height of its parent, so it must sit inside a row-direction container. |
insetStart | number | boolean | false | No | Inset 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. |
insetEnd | number | boolean | false | No | Inset from the trailing edge (end for horizontal, bottom for vertical). Same units as `insetStart`. |
thickness | number | 1 | No | Line thickness in dp. |
containerColor | string | theme.colors.outlineVariant | No | Override the line color. |
style | StyleProp<ViewStyle> | - | No | - |
hrefAttrs | { target?: string; rel?: string; download?: string | boolean; } | - | No | Web 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 | - |