ListItem

Display rows with titles, subtitles, and icons

ListItem builds consistent rows from titles, subtitles, icons, images, and shared size or theme variants.

Features

  • One size prop for all styles

  • Icons before or after content

  • Themes, animations, and Group support

  • Style variants, including outlined

  • Child color, size, and variant through Apply

Installation

ListItem is already installed in tamagui:

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ListItem.tsx (registry item "listitem")
// npm dependencies: @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item
import { ListItem } from "../components/tamagui/ListItem";

@tamagui/list-item holds the unstyled half: useListItem and the parts, with no styled ListItem of its own. Import from it when you are building your own list item rather than using ours; see Build your own.

Usage

Basic usage involves providing children or using the title and subTitle props.

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ListItem.tsx (registry item "listitem")
// components/tamagui/Separator.tsx (registry item "separator")
// npm dependencies: @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item @tamagui/separator
import { YGroup } from 'tamagui';
import { ListItem } from "../components/tamagui/ListItem";
import { Separator } from "../components/tamagui/Separator";
import { ChevronRight } from './components/icons'; // Example icon (generate your own, see Lucide Icons)
export default () => <YGroup self="center" borderWidth={1} borderColor="border-color" rounded="4" width={240} size="md">
<YGroup.Item>
<ListItem title="Star" icon={ChevronRight} />
</YGroup.Item>
<YGroup.Item>
<ListItem title="Moon" subTitle="Subtitle for Moon" iconAfter={ChevronRight} />
</YGroup.Item>
<YGroup.Item>
<ListItem>Custom children content</ListItem>
</YGroup.Item>
</YGroup>;

Sizing

The size prop adjusts padding, minimum height, and the default size for the text and icons inside. It accepts theme size tokens (e.g., "3", "4").

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ListItem.tsx (registry item "listitem")
// npm dependencies: @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item
import { ListItem } from "../components/tamagui/ListItem";
export default () => <ListItem size="lg" title="Large List Item" />;

Title and subTitle

Use the title and subTitle props for a structured layout. These props accept ReactNode, so you can pass strings or more complex JSX.

  • If title or subTitle is provided, children render inside the main text area, under the title and subtitle.
  • Strings render through ListItem.Title and ListItem.Subtitle. Anything else renders as you passed it, so you can style it yourself.
// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ListItem.tsx (registry item "listitem")
// npm dependencies: @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item
import { Avatar } from 'tamagui';
import { ListItem } from "../components/tamagui/ListItem";
import { User } from './components/icons';
export default () => <ListItem icon={<Avatar circular size="sm"> <Avatar.Image src="/placeholder.png" /> <Avatar.Fallback backgroundColor="background-focus" /> </Avatar>} title="User Profile" subTitle="View and edit your profile details." iconAfter={User} size="md" />;

Icon theming

Icons can be passed to icon (before content) or iconAfter (after content).

  • Automatic sizing and spacing: icons are sized from the ListItem’s size. Override with iconSize. Spacing comes from the frame gap, so it applies between all direct frame children, not only around icons.
  • Scaling: scaleIcon (number, default 1) adjusts the icon size relative to its base size.
  • Component props: a component passed as an icon receives size (the calculated pixel size) and color.
// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ListItem.tsx (registry item "listitem")
// npm dependencies: @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item
import { ListItem } from "../components/tamagui/ListItem";
import { Star, Settings } from './components/icons';
export default () => <>
<ListItem icon={Star} title="Default Icon Size" size="md" />
<ListItem icon={Settings} iconSize="2" title="Explicit Icon Size" subTitle="iconSize='2'" size="lg" />
<ListItem icon={Star} scaleIcon={1.5} title="Scaled Icon" subTitle="scaleIcon={1.5}" size="md" />
</>;

Variant

ListItem supports a variant prop for different visual styles:

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ListItem.tsx (registry item "listitem")
// npm dependencies: @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item
import { ListItem } from "../components/tamagui/ListItem";
export default () => <ListItem variant="outlined" title="Outlined Style" />;

Currently supports outlined (transparent background with border).

Apply (context)

ListItem.Apply is the styled context every part reads. Use it to pass color, size, and variant to a group of list items at once:

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ListItem.tsx (registry item "listitem")
// npm dependencies: @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item
import { YGroup } from 'tamagui';
import { ListItem } from "../components/tamagui/ListItem";
import { Trash } from './components/icons';
export default () => <YGroup>
<ListItem.Apply color="red-600">
<YGroup.Item>
<ListItem icon={Trash} title="Delete item" />
</YGroup.Item>
<YGroup.Item>
<ListItem icon={Trash} title="Remove all" />
</YGroup.Item>
</ListItem.Apply>
</YGroup>;

A ListItem republishes whichever of these it was passed directly, so its own icons and text pick them up without any wiring.

Build your own

@tamagui/list-item exports the behavior and the parts separately, so you can put your own skin on them. useListItem themes the icon props and assembles title, subtitle, and children into the frame’s child; everything else is yours. This is exactly how tamagui’s own ListItem is built:

import {
createStyledHOC,
ListItemContext,
ListItemFrame as ListItemBehaviorFrame,
type ListItemIconProps,
ListItemSubtitle,
ListItemText,
ListItemTitle,
styled,
useListItem,
withStaticProperties,
} from '@tamagui/ui'
import { useGetThemedIcon } from '@tamagui/helpers-tamagui'
const ListItemFrame = styled(ListItemBehaviorFrame, {
context: ListItemContext,
backgroundColor: 'background hover:background-hover',
borderColor: 'border-color',
color: 'color',
})
// the icon px is yours to decide; the behavior package only publishes color
const ListItemIcon = ({ children, size = 16 }: ListItemIconProps) => {
const { color } = ListItemContext.useStyledContext()
return useGetThemedIcon({ size, color })(children)
}
const ListItemComponent = createStyledHOC(ListItemFrame, function ListItem(props, ref) {
const { props: listItemProps } = useListItem(props)
return <ListItemFrame ref={ref} {...listItemProps} />
})
export const ListItem = withStaticProperties(ListItemComponent, {
Apply: ListItemContext.Provider,
Frame: ListItemFrame,
Icon: ListItemIcon,
Subtitle: ListItemSubtitle,
Text: ListItemText,
Title: ListItemTitle,
})

Because the frame declares ListItemContext, passing size, color, or variant to it is what publishes them to the text and icons below. There is no context to assemble by hand.

API reference

ListItem

ListItem extends View, inheriting all the Tamagui standard props, plus:

Props

  • title

    React.ReactNode

    Main text content of the list item. A string renders through `ListItem.Title`.

  • subTitle

    React.ReactNode

    Secondary text content, rendered below the title. A string renders through `ListItem.Subtitle`.

  • size

    'xs' | 'sm' | 'md' | 'lg' | 'xl'

    A named size ("xs" to "xl", default "md") is a rung in the sizing ladder: padding, font size, and icon size. Published to the parts through the ListItem context. Subtitles render one step below the title.

  • color

    ColorTokens | string

    Color token or CSS color inherited by `ListItem.Icon` children and the icon props.

  • variant

    'outlined'

    Visual variant style. Currently supports "outlined" (transparent background with border).

  • icon

    JSX.Element | React.ComponentType<{ size?: number; color?: any }>

    Icon element or component displayed before the main content. Receives `size` and `color` if a component.

  • iconAfter

    JSX.Element | React.ComponentType<{ size?: number; color?: any }>

    Icon element or component displayed after the main content. Receives `size` and `color` if a component.

  • iconSize

    number | string

    Explicitly set the icon size, either in pixels or as a sizing-ladder name, overriding the one derived from `size`.

  • scaleIcon

    number

    Scale factor for the icon (default: 1). Applied after `iconSize` or the default size calculation.

  • disabled

    boolean

    If true, reduces opacity and disables pointer events.

  • // Styling Text

    ---

    Text style props are not forwarded from the frame. Style `ListItem.Title`, `ListItem.Subtitle`, or `ListItem.Text` directly, or pass JSX to `title` and `subTitle`.

  • ListItem.Apply

    The styled context provider. Passes color, size, and variant to every ListItem and ListItem part beneath it.

    Props

  • color

    ColorTokens | string

    Color token to pass to icons in children ListItems.

  • size

    'xs' | 'sm' | 'md' | 'lg' | 'xl'

    Named size to apply to children ListItems.

  • variant

    'outlined'

    Variant to apply to children ListItems.

  • ListItem.Frame

    The base View for the ListItem. Renders as li on web with role="listitem".

    ListItem.Text

    Wraps children when title and subTitle are not used. Extends SizableText.

    ListItem.Title

    Renders a string title. Extends SizableText.

    ListItem.Subtitle

    Renders a string subTitle. Extends SizableText. Its font size is one step smaller than the ListItem’s size.

    ListItem.Icon

    Sizes and colors an icon from the surrounding ListItem context.

    Props

  • size

    'xs' | 'sm' | 'md' | 'lg' | 'xl' | number

    Overrides the size taken from context.

  • scaleIcon

    number

    Scale factor applied to the resolved size (default: 1).

  • useListItem

    Themes the icon and iconAfter props and assembles title, subTitle, and children into a single child. Returns the props to spread onto a frame, with the ones it consumed removed and everything else untouched.

    const { props } = useListItem(props)

    Source

    v2-look ListItem: theme palette, border, cursor, hover/press color styling, text color, the outlined/active appearance, and disabled dimming, over the unstyled @tamagui/ui ListItem behavior (which keeps structural layout + the size mechanism + the disabled pointer-event block). This is the exact source the registry ships — copy it into your app and the examples above import from your copy.

    components/tamagui/ListItem.tsx
    // Styled ListItem = @tamagui/ui's list-item behavior and parts + the default // v2-look skin (theme palette, border, cursor, hover/press color, the outlined // and active appearances, disabled dimming) and the sizing ladder. // resolveSizing derives every size value from the config. @tamagui/ui // ships no ListItem of its own: it exposes `useListItem` and the frame, text, // title, subtitle, and icon parts, and a skin decides the rest. This is the // single skin definition — the shadcn registry item is generated from this file. import { type ComponentSize, createStyledHOC, type GetProps, resolveSizing, styled, withStaticProperties, } from '@tamagui/core' import { useGetThemedIcon } from '@tamagui/helpers-tamagui' import { type ListItemBehaviorProps, ListItemContext, ListItemFrame as ListItemBehaviorFrame, type ListItemIconProps as ListItemBehaviorIconProps, ListItemSubtitle as ListItemBehaviorSubtitle, ListItemText as ListItemBehaviorText, ListItemTitle as ListItemBehaviorTitle, useListItem, } from '@tamagui/list-item' export type ListItemSize = ComponentSize | boolean const getListItemFrameSize = styled.dynamic<ListItemSize>((val, env) => { const sizing = resolveSizing(val, env) if (!sizing) return return { paddingInline: sizing.paddingInline, paddingBlock: sizing.paddingBlock, gap: sizing.gap, } }) const getListItemTextSize = styled.dynamic<ListItemSize>((val, env) => { const sizing = resolveSizing(val, env) if (!sizing) return return { fontSize: sizing.fontSize, lineHeight: sizing.lineHeight, } }) // the subtitle sits one step below the title: the previous rung's font key, // or the rung's own at the bottom of the ladder const getListItemSubtitleSize = styled.dynamic<ListItemSize>((val, env) => { const sizing = resolveSizing(val, env) if (!sizing) return const names = Object.keys(env.sizing.sizes) const prev = names[Math.max(0, names.indexOf(sizing.name) - 1)] const prevKey = prev != null ? env.sizing.sizes[prev]?.fontSize : undefined const key = prevKey ?? sizing.fontSize return { fontSize: key, lineHeight: key, } }) // icon px for an explicit size: numbers pass through, names read the ladder, // anything else is the default rung. undefined when nothing resolves, so the // icon falls back to its own default size. const listItemIconPx = (size: unknown, scaleIcon = 1): number | undefined => { if (typeof size === 'number') return size * scaleIcon const icon = resolveSizing(size as ListItemSize)?.icon ?? resolveSizing(undefined)?.icon return icon == null ? undefined : icon * scaleIcon } // the additive-border "outlined" appearance (formerly themeableVariantStyles.outlined // from ThemeableStack, now removed from the behavior package). const outlined = { backgroundColor: 'transparent hover:transparent press:transparent', borderWidth: 1, borderColor: 'border-color hover:border-color-hover press:border-color-press', } as const export const ListItemFrame = styled(ListItemBehaviorFrame, { context: ListItemContext, displayName: 'ListItem', backgroundColor: 'background hover:background-hover press:background-press', borderColor: 'border-color hover:border-color-hover press:border-color-press', color: 'color', cursor: 'default', variants: { size: getListItemFrameSize, variant: { outlined, }, active: { true: { backgroundColor: 'hover:background', }, }, disabled: { true: { opacity: 0.5, pointerEvents: 'none', }, }, } as const, defaultVariants: { size: 'md', }, }) export const ListItemText = styled(ListItemBehaviorText, { context: ListItemContext, displayName: 'ListItemText', variants: { size: getListItemTextSize, } as const, defaultVariants: { size: 'md', }, }) export const ListItemTitle = styled(ListItemBehaviorTitle, { context: ListItemContext, displayName: 'ListItemTitle', variants: { size: getListItemTextSize, } as const, defaultVariants: { size: 'md', }, }) export const ListItemSubtitle = styled(ListItemBehaviorSubtitle, { context: ListItemContext, displayName: 'ListItemSubtitle', variants: { size: getListItemSubtitleSize, } as const, defaultVariants: { size: 'md', }, }) export const ListItemIcon = ({ children, size, scaleIcon = 1, }: ListItemBehaviorIconProps) => { const context = ListItemContext.useStyledContext() const getThemedIcon = useGetThemedIcon({ size: listItemIconPx(size ?? context.size, scaleIcon), color: context.color, }) return getThemedIcon(children) } const ListItemComponent = createStyledHOC( ListItemFrame, function ListItem(props: ListItemBehaviorProps, ref) { // the `icon` prop is themed here, before the frame renders, so the skin // resolves its px up front instead of reading them back off the context const contextSize = ListItemContext.useStyledContext()?.size const size = props.size ?? contextSize ?? 'md' const scaleIcon = props.scaleIcon ?? 1 const iconSize = props.iconSize ?? listItemIconPx(size, scaleIcon) const { props: listItemProps } = useListItem({ ...props, iconSize, scaleIcon: props.iconSize == null ? 1 : scaleIcon, }) return <ListItemFrame ref={ref} {...listItemProps} /> } ) export const ListItem = withStaticProperties(ListItemComponent, { Apply: ListItemContext.Provider, Frame: ListItemFrame, Icon: ListItemIcon, Subtitle: ListItemSubtitle, Text: ListItemText, Title: ListItemTitle, }) export type ListItemProps = GetProps<typeof ListItemComponent>

    Dependencies

    yarn add @tamagui/core @tamagui/helpers-tamagui @tamagui/list-item

    Expects theme tokens: background, background-hover, background-press, border-color, border-color-hover, border-color-press, color.

    Need raw behavior without any skin? tamagui/unstyled re-exports the @tamagui/ui primitives (advanced).