@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.
exportdefault()=><ListItemicon={<Avatarcircularsize="sm"><Avatar.Imagesrc="/placeholder.png"/><Avatar.FallbackbackgroundColor="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.
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:
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.
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`.
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>