Skip to main content

Item

The Item component is an optimized list row with automatic borders, press animations, and support for leading/trailing elements.

Import

import { Item } from 'papillon-ui';

Basic Usage

import { Item, Typography } from 'papillon-ui';

<Item onPress={() => console.log('Pressed')}>
  <Typography variant="title">Basic Item</Typography>
  <Typography variant="caption" color="secondary">Subtitle text</Typography>
</Item>

Props

onPress
() => void
Callback fired when the item is pressed.
children
React.ReactNode
Content to display in the item. Automatically arranged based on Leading/Trailing components.
animate
boolean
Enables enter/exit animations for the item.
contentContainerStyle
StyleProp<ViewStyle>
Style applied to the content container (middle section).
style
StyleProp<ViewStyle>
Style applied to the entire item container.
isLast
boolean
default:false
Automatically set by List component. Removes bottom border when true.
disablePadding
boolean
default:false
Removes default padding from the item.

Leading and Trailing

Use Item.Leading and Item.Trailing for icons or badges:
import { Item, Typography } from 'papillon-ui';
import { User, ChevronRight, Bell } from 'lucide-react-native';

<Item onPress={() => {}}>
  <Item.Leading>
    <User size={24} />
  </Item.Leading>
  
  <Typography variant="title">Profile</Typography>
  <Typography variant="caption" color="secondary">
    View your profile settings
  </Typography>
  
  <Item.Trailing>
    <ChevronRight size={20} />
  </Item.Trailing>
</Item>

Content Arrangement

The Item component automatically arranges children:
  1. Leading elements appear first (left side)
  2. Content (other children) fills the center with flex
  3. Trailing elements appear last (right side)
<Item>
  <Item.Leading>         {/* Left */}
    <Icon />
  </Item.Leading>
  
  <Typography>         {/* Center (flex: 1) */}
    Main Content
  </Typography>
  
  <Item.Trailing>        {/* Right */}
    <Badge />
  </Item.Trailing>
</Item>

Press Animations

Item includes smooth press animations:
const [isPressed, setIsPressed] = useState(false);

<Item 
  onPress={() => {}}
  onPressIn={() => setIsPressed(true)}
  onPressOut={() => setIsPressed(false)}
>
  <Typography>Animated on press</Typography>
</Item>
Animation behavior:
  • Press In: Scales to 0.97 and reduces opacity to 0.7
  • Press Out: Springs back with smooth easing
  • Runs on UI thread via Reanimated worklets

Multiple Content Lines

Stack multiple elements vertically:
<Item onPress={() => {}}>
  <Typography variant="title">Primary Text</Typography>
  <Typography variant="body2" color="secondary">
    Secondary text
  </Typography>
  <Typography variant="caption" color="secondary">
    Additional info
  </Typography>
</Item>

With Badges

<Item onPress={() => {}}>
  <Item.Leading>
    <Bell size={24} />
  </Item.Leading>
  
  <Typography variant="title">Notifications</Typography>
  
  <Item.Trailing>
    <View style={{
      backgroundColor: '#FF3B30',
      borderRadius: 12,
      paddingHorizontal: 8,
      paddingVertical: 2
    }}>
      <Typography color="light" variant="caption">3</Typography>
    </View>
  </Item.Trailing>
</Item>

Nested Layout

Combine with other components:
import { Stack } from 'papillon-ui';

<Item onPress={() => {}}>
  <Item.Leading>
    <Avatar size={40} />
  </Item.Leading>
  
  <Stack gap={2}>
    <Typography variant="title">John Doe</Typography>
    <Typography variant="caption" color="secondary">
      [email protected]
    </Typography>
  </Stack>
  
  <Item.Trailing>
    <CheckIcon />
  </Item.Trailing>
</Item>

Custom Padding

<Item 
  disablePadding
  style={{ paddingVertical: 20, paddingHorizontal: 24 }}
>
  <Typography>Custom padded item</Typography>
</Item>

Without Press Effect

Omit onPress for non-interactive items:
<Item>
  <Typography variant="title">Non-interactive</Typography>
  <Typography variant="caption">No press animation</Typography>
</Item>

Performance

Extreme Optimization Techniques:
  • Memoized with custom areEqual function for minimal re-renders
  • Pre-computed style objects to avoid recreation
  • Shared values for animations (single animationValue instead of separate scale/opacity)
  • Border color caching with WeakMap
  • Early returns to skip rendering when unnecessary
  • Minimal allocations in children sorting
const MemoizedItem = React.memo(({ data }) => (
  <Item onPress={() => {}}>
    <Typography>{data.title}</Typography>
  </Item>
));

Theming

Item uses your React Navigation theme:
import { useTheme } from '@react-navigation/native';

function ThemedItem() {
  const { colors } = useTheme();
  
  // Item automatically uses:
  // - colors.text + "25" for border color
  
  return (
    <Item onPress={() => {}}>
      <Typography>Themed item</Typography>
    </Item>
  );
}

Best Practices

Items are designed to work inside List containers:
<List>
  <Item><Typography>Item 1</Typography></Item>
  <Item><Typography>Item 2</Typography></Item>
</List>
Always wrap icons in Leading/Trailing for proper alignment:
<Item>
  <Item.Leading><Icon /></Item.Leading>
  <Typography>Text</Typography>
  <Item.Trailing><Badge /></Item.Trailing>
</Item>
When rendering many items, wrap in React.memo:
const MemoItem = React.memo(ItemComponent);

Accessibility

<Item 
  onPress={() => {}}
  accessibilityRole="button"
  accessibilityLabel="View profile"
  accessibilityHint="Double tap to view your profile settings"
>
  <Typography>Profile</Typography>
</Item>

Full Example

import { List, Item, Typography, Stack } from 'papillon-ui';
import { User, Mail, Phone, MapPin, ChevronRight } from 'lucide-react-native';
import { useTheme } from '@react-navigation/native';

function ContactList({ contacts }) {
  const { colors } = useTheme();
  
  return (
    <List>
      {contacts.map(contact => (
        <Item 
          key={contact.id}
          onPress={() => navigate('ContactDetail', { id: contact.id })}
        >
          <Item.Leading>
            <View style={{
              width: 48,
              height: 48,
              borderRadius: 24,
              backgroundColor: contact.color + '30',
              alignItems: 'center',
              justifyContent: 'center'
            }}>
              <User size={24} color={contact.color} />
            </View>
          </Item.Leading>
          
          <Stack gap={4}>
            <Typography variant="title">{contact.name}</Typography>
            
            <Stack direction="horizontal" gap={12}>
              <Stack direction="horizontal" gap={4}>
                <Mail size={14} color={colors.text + '80'} />
                <Typography variant="caption" color="secondary">
                  {contact.email}
                </Typography>
              </Stack>
              
              <Stack direction="horizontal" gap={4}>
                <Phone size={14} color={colors.text + '80'} />
                <Typography variant="caption" color="secondary">
                  {contact.phone}
                </Typography>
              </Stack>
            </Stack>
          </Stack>
          
          <Item.Trailing>
            <ChevronRight size={20} color={colors.text + '60'} />
          </Item.Trailing>
        </Item>
      ))}
    </List>
  );
}

Build docs developers (and LLMs) love