react-native-custom-card
v1.1.4
Published
A comprehensive, customizable card component for React Native with shimmer loading, animations, and flexible layouts
Maintainers
Readme
React Native Custom Card
A comprehensive, customizable card component for React Native with shimmer loading, animations, and flexible layouts.
✨ Features
- 📦 Modular Structure - Header, Body, Footer with left/right item slots
- ⏳ Shimmer Loading - Built-in adaptive loading state with animated shimmer
- 🎬 Animations - Fade, scale, and slide transitions
- 🎨 Gradient Backgrounds - Support via
expo-linear-gradientorreact-native-linear-gradient - 📖 Expandable Text - Built-in "View more/less" toggle for long content
- 📱 Responsive Sizing - Adapts to screen size with configurable breakpoints
- 🔀 Flexible Layouts - Horizontal and vertical orientations
- ➖ Configurable Dividers - Horizontal and vertical orientations
- ✨ Fully Customizable - Colors, fonts, spacing, and more
- 📜 TypeScript - Full type definitions included
- 📋 FlatList Ready - Optimized for list rendering
- 🌐 Cross-Platform - Works on Android, iOS, and Expo
📦 Installation
npm install react-native-custom-card
# or
yarn add react-native-custom-card🚀 Quick Start
Simple Card Example
import { CustomCard } from 'react-native-custom-card';
const MyCard = () => (
<CustomCard
header={{
title: "Welcome!",
subtitle: "Getting started is easy"
}}
body={{
children: <Text>Your content here</Text>
}}
/>
);FlatList Example
const data = [
{
id: '1',
title: 'Card 1',
subtitle: 'Subtitle 1',
description:
'This card highlights an overview of the main features included in the project. It provides a clear summary of the purpose, core functionality, and the value it brings to the user. The idea is to help users quickly understand what this item represents.',
imageUrl: 'https://picsum.photos/200/300?random=1'
},
{
id: '2',
title: 'Card 2',
subtitle: 'Subtitle 2',
description:
'This section gives a detailed explanation of how the feature works behind the scenes. From performance improvements to user experience enhancements, everything is designed carefully to ensure smooth and reliable functionality in real-world usage.',
imageUrl: 'https://picsum.photos/200/300?random=2'
},
{
id: '3',
title: 'Card 3',
subtitle: 'Subtitle 3',
description:
'Here, users get a deeper insight into the benefits, use cases, and scenarios where this feature becomes valuable. It aims to provide clarity and motivation, showing how effectively it can be utilized during everyday interactions within the app.',
imageUrl: 'https://picsum.photos/200/300?random=3'
},
];
Horizontal Card Example
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<CustomCard
orientation="horizontal" // <--- Important
// Image on the left
leftItem={
<Image
source={{ uri: item.imageUrl }}
style={{ height: 80, width: 80, borderRadius: 8 }}
/>
}
// Content in the middle
body={{
title: item.title,
subtitle: item.subtitle,
description: item.description,
}}
// Optional: Icon on the right
rightItem={
<View style={{ justifyContent: 'center' }}>
<Text style={{ fontSize: 20, color: '#aaa' }}>›</Text>
</View>
}
onPress={() => console.log('Pressed:', item.id)}
/>
)}
/>
Vertical Card Example using FlatList
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<CustomCard
header={{
title: item.title,
subtitle: item.subtitle
}}
body={{
description: item.description, // <--- Add this line
children: (
<Image
source={{ uri: item.imageUrl }}
style={{ height: 100, width: 100, borderRadius: 8 }}
/>
)
}}
onPress={() => console.log('Pressed:', item.id)}
/>
)}
/>Horizontal custom card with shimmer, animation, gradient
<CustomCard
orientation="horizontal"
isLoading={false}
animated
animationType='scale'
animationDuration={10000}
gradient={{ enabled: true, from: '#908190ff', to: '#c6c5aaff', direction: 'to-top' }}
GradientComponent={LinearGradient}
leftItemShimmerShape='circle'
leftItemShimmerHeight={60}
leftItemShimmerWidth={60}
rightItemShimmerShape='circle'
rightItemShimmerHeight={50}
rightItemShimmerWidth={50}
bodyTextShimmerItems={[
{ width: '20%', height: 14, marginBottom: 8 },
{ width: '30%', height: 16, marginBottom: 8 },
{ width: '90%', height: 14, marginBottom: 6 },
{ width: '70%', height: 14, marginBottom: 8 },
]} // Image on the left
leftItem={
<Image
source={{ uri: item.imageUrl }}
style={{ height: 80, width: 80, borderRadius: 40 }}
/>
}
// Content in the middle
body={{
title: item.title,
titleStyle: { color: '#a96c5aff', fontWeight: 'bold', },
subtitle: item.subtitle,
subtitleStyle: { color: '#a96c5aff', fontWeight: 'bold', },
description:item.description , // (Optional) Custom text for button
descriptionStyle: { color: '#a96c5aff', fontWeight: 'bold', },
maxDescriptionLength:50,
collapseText:"collapse it",
expandText:"expand it",
descriptionToggleStyle:{color:"black"}
}}
// Optional: Icon on the right
rightItem={
<View style={{ justifyContent: 'center' }}>
<Text style={{ fontSize: 20, color: '#aaa' }}>›</Text>
</View>
}
onPress={() => console.log('Pressed:', item.id)}
/>📖 Props Reference
CustomCard Props
| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| header | CardHeaderProps | - | No | Header configuration |
| body | CardBodyProps | - | No | Body content configuration |
| footer | CardFooterProps | - | No | Footer configuration |
| isLoading | boolean | false | No | Show shimmer loading state |
| animated | boolean | false | No | Enable animations |
| animationType | 'fade' \| 'scale' \| 'slide' \| 'none' | 'fade' | No | Animation type |
| animationDuration | number | 300 | No | Animation duration (ms) |
| orientation | 'horizontal' \| 'vertical' | 'vertical' | No | Card layout |
| showHeaderDivider | boolean | false | No | Show divider below header |
| showFooterDivider | boolean | false | No | Show divider above footer |
| dividerProps | DividerProps | - | No | Divider customization |
| backgroundColor | string | '#FFFFFF' | No | Card background color |
| borderRadius | number | 12 | No | Card border radius |
| elevation | number | 3 | No | Shadow elevation (Android) |
| padding | number | 16 | No | Inner padding |
| margin | number | - | No | Outer margin |
| onPress | () => void | - | No | Press handler |
| testID | string | - | No | Test identifier |
TypeScript: Orientation-Based Props
CustomCardProps uses discriminated unions based on orientation. TypeScript will suggest different props depending on your card orientation:
Vertical Card (Default)
// When orientation is 'vertical' or omitted, you get these props:
<CustomCard
header={{ ... }}
body={{ ... }} // ✓ Available
footer={{ ... }} // ✓ Available
showHeaderDivider // ✓ Available
showFooterDivider // ✓ Available
headerLeftItemShimmerShape="circle" // ✓ Available
/>Horizontal Card
// When orientation="horizontal", you get these props:
<CustomCard
orientation="horizontal"
leftItem={<Image />} // ✓ Available
body={{ ... }} // ✓ Available
rightItem={<Icon />} // ✓ Available
leftItemShimmerShape="circle" // ✓ Available
rightItemShimmerWidth={24} // ✓ Available
// header, footer → Not suggested (vertical only)
/>Available Type Exports
import type {
CustomCardProps, // Discriminated union (use this for component props)
CustomCardBaseProps, // Shared props between orientations
HorizontalCardProps, // Horizontal-specific props
VerticalCardProps, // Vertical-specific props
CustomCardPropsInternal, // All props merged (for internal/advanced use)
} from 'react-native-custom-card';CardHeaderProps
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| title | string | No | Main title text |
| subtitle | string | No | Subtitle text |
| leftItem | ReactNode | No | Left component (avatar, icon) |
| showLeftDivider | boolean | No | Show vertical divider after leftItem |
| leftDividerProps | DividerProps | No | Customize the left divider |
| rightItem | ReactNode | No | Right component (menu, action) |
| style | ViewStyle | No | Container style |
| titleStyle | TextStyle | No | Title text style |
| subtitleStyle | TextStyle | No | Subtitle text style |
CardBodyProps
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| children | ReactNode | No | Body content |
| description | string \| DescriptionConfig | No | Description text or config object |
| maxDescriptionLength | number | No | Max chars before "View more" (default: 150) |
| expandText | string | No | Custom expand text (default: "View more") |
| collapseText | string | No | Custom collapse text (default: "View less") |
| descriptionPosition | 'top' \| 'bottom' | No | Position relative to children |
| leftItem | ReactNode | No | Left component |
| rightItem | ReactNode | No | Right component |
| borderRadius | number | No | Border radius for body container |
| backgroundColor | string | No | Background color for body |
| overlayItems | OverlayItemConfig[] | No | Items to overlay on content |
| overlayOnChildrenOnly | boolean | No | If true, overlays position relative to children only (e.g., image), not entire body |
| style | ViewStyle | No | Container style |
DescriptionConfig (Object format)
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| text | string | Yes | Description text |
| maxLength | number | No | Max chars before truncation |
| expandText | string | No | Custom expand button text |
| collapseText | string | No | Custom collapse button text |
OverlayItemConfig
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| content | ReactNode | Yes | Content to overlay (icon, badge) |
| position | 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' \| 'center' | No | Position (default: 'top-right') |
| offsetX | number | No | Horizontal offset from edge |
| offsetY | number | No | Vertical offset from edge |
| style | ViewStyle | No | Custom container style |
CardFooterProps
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| children | ReactNode | No | Footer content (buttons, etc.) |
| leftItem | ReactNode | No | Left component |
| rightItem | ReactNode | No | Right component |
| style | ViewStyle | No | Container style |
DividerProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| orientation | 'horizontal' \| 'vertical' | 'horizontal' | Divider direction |
| color | string | '#E5E7EB' | Divider color |
| thickness | number | 1 | Line thickness |
| marginHorizontal | number | 0 | Horizontal margin |
| marginVertical | number | 12 | Vertical margin |
📚 Examples
Basic Card
import { CustomCard } from 'react-native-custom-card';
<CustomCard
header={{
title: "Product Name",
subtitle: "$29.99"
}}
body={{
children: <Text>Product description goes here.</Text>
}}
footer={{
children: <Button title="Add to Cart" onPress={() => {}} />
}}
/>Card with Avatar and Actions
import { CustomCard } from 'react-native-custom-card';
import { Image, TouchableOpacity } from 'react-native';
<CustomCard
header={{
title: "John Doe",
subtitle: "Posted 2 hours ago",
leftItem: (
<Image
source={{ uri: 'https://example.com/avatar.jpg' }}
style={{ width: 40, height: 40, borderRadius: 20 }}
/>
),
rightItem: (
<TouchableOpacity>
<Text>⋮</Text>
</TouchableOpacity>
)
}}
body={{
children: (
<Image
source={{ uri: 'https://example.com/post.jpg' }}
style={{ width: '100%', height: 200, borderRadius: 8 }}
/>
)
}}
showHeaderDivider
/>Header with Divider
// Show a vertical divider between avatar and title
<CustomCard
header={{
leftItem: <Avatar source={...} />,
showLeftDivider: true, // 👈 Enable divider
leftDividerProps: { color: '#3B82F6', thickness: 2 }, // Optional customization
title: "User Profile",
subtitle: "Online"
}}
/>Description as Object
// Use object format for full control over expandable text
<CustomCard
body={{
description: {
text: "This is a very long product description that explains all the amazing features...",
maxLength: 100,
expandText: "Read more →",
collapseText: "← Show less"
},
children: <Image source={...} />
}}
/>Body Styling
// Add borderRadius and backgroundColor to body
<CustomCard
body={{
borderRadius: 16,
backgroundColor: '#F3F4F6',
children: <Text>Styled body content</Text>
}}
/>Multiple Children Components
You can pass an array of components to children in body or footer without needing a Fragment wrapper.
<CustomCard
body={{
children: [
<Image
key="image"
source={{ uri: 'https://picsum.photos/300/200' }}
style={{ width: '100%', height: 200, borderRadius: 8 }}
/>,
<View key="text-container" style={{ marginTop: 12 }}>
<Text>First line of text</Text>
<Text style={{ color: 'gray' }}>Second line of text</Text>
</View>
]
}}
footer={{
children: [
<Button key="btn1" title="Accept" onPress={() => {}} />,
<Button key="btn2" title="Decline" onPress={() => {}} />
]
}}
/>Overlay Items (Badges, Icons)
Overlay items work in both vertical and horizontal orientations. Use them to add badges, icons, or any content on top of your card body.
Vertical Card with Overlays
import { Ionicons } from '@expo/vector-icons';
// Overlay favorite icon and discount badge on product image (vertical layout)
<CustomCard
header={{ title: "Product", subtitle: "$29.99" }}
body={{
children: (
<Image
source={{ uri: 'https://picsum.photos/300/200' }}
style={{ width: '100%', height: 200 }}
/>
),
overlayItems: [
{
content: (
<TouchableOpacity onPress={() => console.log('Favorite!')}>
<Ionicons name="heart" size={24} color="#EF4444" />
</TouchableOpacity>
),
position: 'top-right',
offsetX: -8,
offsetY: 8
},
{
content: (
<View style={styles.badge}>
<Text style={styles.badgeText}>-20%</Text>
</View>
),
position: 'top-left',
offsetX: 8,
offsetY: 8
}
]
}}
/>Horizontal Card with Overlays
// Overlay items on horizontal layout - great for list items with status badges
<CustomCard
orientation="horizontal"
body={{
leftItem: (
<Image
source={{ uri: 'https://picsum.photos/100/100' }}
style={{ width: 80, height: 80, borderRadius: 8 }}
/>
),
children: (
<View>
<Text style={{ fontWeight: 'bold' }}>Product Name</Text>
<Text style={{ color: '#6B7280' }}>Description here</Text>
</View>
),
overlayItems: [
{
content: (
<View style={{ backgroundColor: '#10B981', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4 }}>
<Text style={{ color: '#FFF', fontSize: 10 }}>NEW</Text>
</View>
),
position: 'top-left',
offsetX: 4,
offsetY: 4
},
{
content: <Ionicons name="heart-outline" size={20} color="#9CA3AF" />,
position: 'top-right',
offsetX: -4,
offsetY: 4
}
]
}}
onPress={() => console.log('Card pressed')}
/>Overlay on Image Only
When you have both a description and an image, use overlayOnChildrenOnly to position overlays relative to the image only:
// Without overlayOnChildrenOnly: overlays position relative to entire body (image + description)
// With overlayOnChildrenOnly: overlays stay within the image bounds
<CustomCard
body={{
children: (
<Image
source={{ uri: 'https://picsum.photos/300/200' }}
style={{ width: '100%', height: 200 }}
/>
),
overlayItems: [
{
content: <TouchableOpacity onPress={() => console.log('Heart pressed!')}>
<Ionicons name="heart" size={24} color="#EF4444" />
</TouchableOpacity>
position: 'top-right',
offsetX: -8,
offsetY: 8
},
{
content: (
<View style={styles.badge}>
<Text style={styles.badgeText}>-20%</Text>
</View>
),
position: 'bottom-left',
offsetX: 8,
offsetY: -8
}
],
overlayOnChildrenOnly: true, // 👈 Key prop!
description: "Product description that appears below the image",
descriptionPosition: 'bottom'
}}
/>Loading State with Shimmer
The shimmer loading state works in both vertical and horizontal orientations. It automatically adapts to your card structure.
Vertical Card with Shimmer
import { CustomCard } from 'react-native-custom-card';
const [isLoading, setIsLoading] = useState(true);
// Basic shimmer
<CustomCard
isLoading={isLoading}
header={{ title: "Title", subtitle: "Subtitle" }}
body={{ children: <Text>Content</Text> }}
/>
// Shimmer with avatar and content type
<CustomCard
isLoading={isLoading}
header={{
title: "John Doe",
subtitle: "2 minutes ago",
leftItem: <Avatar source={...} />,
rightItem: <MenuIcon />
}}
body={{
children: <Image source={...} style={{ width: '100%', height: 200 }} />,
contentType: 'image', // 'text' | 'image' | 'mixed'
description: "Post caption here"
}}
footer={{
children: <ActionButtons />
}}
/>Horizontal Card with Shimmer
// Shimmer adapts to horizontal layout automatically
<CustomCard
orientation="horizontal"
isLoading={isLoading}
body={{
leftItem: (
<Image source={...} style={{ width: 80, height: 80, borderRadius: 8 }} />
),
children: (
<View>
<Text style={{ fontWeight: 'bold' }}>Product Name</Text>
<Text>Description text</Text>
<Text>$29.99</Text>
</View>
),
contentType: 'mixed'
}}
/>Content Type Options
The contentType prop controls how the shimmer appears for body content:
| contentType | Shimmer Appearance |
|-------------|-------------------|
| text (default) | Multiple text line placeholders |
| image | Large rectangular image placeholder |
| mixed | Image placeholder + text lines below |
Standalone Shimmer Component
Use the Shimmer component directly for custom loading states:
import { Shimmer } from 'react-native-custom-card';
// Different shapes
<View style={{ flexDirection: 'row', gap: 16 }}>
<Shimmer width={50} height={50} contentShape="circle" />
<Shimmer width={100} height={20} contentShape="rounded" />
<Shimmer width={80} height={30} contentShape="rectangle" />
</View>
// Custom styling
<Shimmer
width="100%"
height={200}
borderRadius={12}
backgroundColor="#E5E7EB"
highlightColor="#F3F4F6"
duration={1200}
/>| Shape | Description |
|-------|-------------|
| circle | Circular shimmer (avatars, icons) |
| rounded | Rounded rectangle (buttons, tags) |
| rectangle | Sharp corners (images, containers) |
Shimmer Direction
Control the direction of the shimmer animation effect:
import { Shimmer } from 'react-native-custom-card';
// Left to right (default)
<Shimmer width="100%" height={20} direction="left-to-right" />
// Right to left
<Shimmer width="100%" height={20} direction="right-to-left" />
// Top to bottom
<Shimmer width={100} height={100} direction="top-to-bottom" />
// Bottom to top
<Shimmer width={100} height={100} direction="bottom-to-top" />| Direction | Effect |
|-----------|--------|
| left-to-right | Shimmer moves from left to right (default) |
| right-to-left | Shimmer moves from right to left |
| top-to-bottom | Shimmer moves from top to bottom |
| bottom-to-top | Shimmer moves from bottom to top |
Example: Different Shimmer Directions
import { Shimmer } from 'react-native-custom-card';
// Product card with different shimmer directions
<View style={{ gap: 12 }}>
{/* Title - left to right (reading direction) */}
<Shimmer width="70%" height={24} direction="left-to-right" />
{/* Image - top to bottom (loading feel) */}
<Shimmer
width="100%"
height={200}
contentShape="rounded"
direction="top-to-bottom"
/>
{/* Price - right aligned, right to left */}
<View style={{ alignItems: 'flex-end' }}>
<Shimmer width={80} height={20} direction="right-to-left" />
</View>
</View>Using Shimmer Direction in CustomCard
Apply shimmer direction to all shimmer placeholders within a CustomCard:
import { CustomCard } from 'react-native-custom-card';
// Vertical card with top-to-bottom shimmer (loading feel)
<CustomCard
isLoading={true}
shimmerDirection="top-to-bottom"
header={{ title: "Card Title", subtitle: "Subtitle" }}
body={{
children: <Image source={{ uri: '...' }} style={{ width: '100%', height: 200 }} />
}}
/>
// Horizontal card with bottom-to-top shimmer
<CustomCard
orientation="horizontal"
isLoading={true}
shimmerDirection="bottom-to-top"
leftItem={<Avatar size={60} />}
body={{ title: "Product", subtitle: "Price" }}
/>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| shimmerDirection | 'left-to-right' \| 'right-to-left' \| 'top-to-bottom' \| 'bottom-to-top' | 'left-to-right' | Direction of shimmer animation for all shimmer elements |
Advanced Shimmer Customization
Take full control over shimmer placeholders with custom sizing for headers, body, footer, and description. Define multiple shimmer items with exact dimensions or auto-calculate from text content.
ShimmerItemConfig
Use ShimmerItemConfig to define custom shimmer items:
| Prop | Type | Description |
|------|------|-------------|
| width | number \| string | Width in pixels or percentage (e.g., 100 or '80%') |
| height | number | Height in pixels |
| shape | 'circle' \| 'rounded' \| 'rectangle' | Shimmer shape |
| marginBottom | number | Spacing below the shimmer |
| text | string | Auto-calculate width based on text content |
| fontSize | number | Font size for text calculation (default: 14) |
| maxWidth | number | Maximum width cap for long text |
Text-Based Auto-Sizing
Automatically size shimmer placeholders based on actual text content:
import { CustomCard } from 'react-native-custom-card';
<CustomCard
isLoading={true}
// Description shimmer auto-sized to match text
descriptionShimmerItems={[
{ text: 'This is a description', fontSize: 14, marginBottom: 6 },
{ text: 'Second line of text', fontSize: 14 }
]}
// Footer shimmer with text-based sizing
footerShimmerItems={[
{ text: 'Footer content', fontSize: 14 },
{ text: 'Button', fontSize: 14, shape: 'rounded' }
]}
body={{
description: "This is a description\nSecond line of text"
}}
footer={{
children: <Text>Footer content</Text>
}}
/>Note: Long text automatically caps at 100% width to prevent overflow. Use
maxWidthto set a custom cap.
Header Shimmer Customization (Vertical Cards)
Customize header avatar, title, subtitle, and right item shimmers:
<CustomCard
isLoading={true}
// Header shimmer sizing
headerLeftItemShimmerWidth={40}
headerLeftItemShimmerHeight={40}
headerLeftItemShimmerShape="circle"
headerRightItemShimmerWidth={24}
headerRightItemShimmerHeight={24}
headerRightItemShimmerShape="rounded"
headerTitleShimmerWidth={120} // or '70%'
headerSubtitleShimmerWidth={80} // or '40%'
header={{
title: "Card Title",
subtitle: "Subtitle",
leftItem: <Avatar size={40} />,
rightItem: <MenuIcon size={24} />
}}
/>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| headerLeftItemShimmerWidth | number | 44 | Width of header left item shimmer |
| headerLeftItemShimmerHeight | number | 44 | Height of header left item shimmer |
| headerLeftItemShimmerShape | ShimmerShape | 'circle' | Shape of header left item shimmer |
| headerRightItemShimmerWidth | number | 24 | Width of header right item shimmer |
| headerRightItemShimmerHeight | number | 24 | Height of header right item shimmer |
| headerRightItemShimmerShape | ShimmerShape | 'rounded' | Shape of header right item shimmer |
| headerTitleShimmerWidth | number \| string | '70%' | Width of header title shimmer |
| headerSubtitleShimmerWidth | number \| string | '40%' | Width of header subtitle shimmer |
Horizontal Card Shimmer Customization
Customize left item, right item, and body shimmers for horizontal cards:
<CustomCard
orientation="horizontal"
isLoading={true}
// Left/right item shimmer sizing
leftItemShimmerWidth={60}
leftItemShimmerHeight={60}
leftItemShimmerShape="circle"
rightItemShimmerWidth={32}
rightItemShimmerHeight={32}
rightItemShimmerShape="rounded"
// Body text shimmer widths
bodyTitleShimmerWidth="80%"
bodySubtitleShimmerWidth="60%"
bodyDescriptionShimmerWidth="90%"
leftItem={<Avatar size={60} />}
rightItem={<ChevronIcon size={32} />}
body={{
title: "Product Name",
subtitle: "Category",
description: "Product description"
}}
/>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| leftItemShimmerWidth | number | 80 | Width of left item shimmer |
| leftItemShimmerHeight | number | 80 | Height of left item shimmer |
| leftItemShimmerShape | ShimmerShape | 'rounded' | Shape of left item shimmer |
| rightItemShimmerWidth | number | 24 | Width of right item shimmer |
| rightItemShimmerHeight | number | 24 | Height of right item shimmer |
| rightItemShimmerShape | ShimmerShape | 'rounded' | Shape of right item shimmer |
| bodyTitleShimmerWidth | number \| string | '70%' | Width of body title shimmer |
| bodySubtitleShimmerWidth | number \| string | '50%' | Width of body subtitle shimmer |
| bodyDescriptionShimmerWidth | number \| string | '90%' | Width of body description shimmer |
| bodyTextShimmerItems | ShimmerItemConfig[] | - | Custom shimmer items for body text (overrides title/subtitle/description) |
Custom Body Text Shimmers (Horizontal)
Use bodyTextShimmerItems to define a custom number of body text shimmer lines with individual widths:
// Show exactly 3 shimmer lines with custom widths
<CustomCard
orientation="horizontal"
isLoading={true}
bodyTextShimmerItems={[
{ width: '60%', height: 16, marginBottom: 8 }, // Title line
{ width: '40%', height: 14, marginBottom: 6 }, // Subtitle line
{ width: '80%', height: 14 }, // Description line
]}
leftItemShimmerShape="circle"
leftItemShimmerWidth={50}
leftItemShimmerHeight={50}
leftItem={<Image source={{ uri: '...' }} style={{ width: 50, height: 50, borderRadius: 25 }} />}
body={{
title: "Product Name",
subtitle: "Category",
description: "Product description text here"
}}
/>
// Show 4 shimmer lines with children
<CustomCard
orientation="horizontal"
isLoading={true}
leftItemShimmerShape="circle"
rightItemShimmerShape="rectangle"
bodyTextShimmerItems={[
{ width: '60%', height: 16, marginBottom: 8 },
{ width: '40%', height: 14, marginBottom: 6 },
{ width: '80%', height: 14, marginBottom: 8 },
{ width: '20%', height: 14 },
]}
leftItem={
<Image
source={{ uri: 'https://picsum.photos/80/80' }}
style={{ width: 80, height: 80, borderRadius: 40 }}
/>
}
body={{
title: "Title",
subtitle: "Sub",
description: "Desc",
children: <Text>Child content</Text>
}}
rightItem={<Ionicons name="chevron-forward" size={24} color="#8eaa93" />}
/>Note: When
bodyTextShimmerItemsis provided, it overrides the automatic title/subtitle/description shimmers.
Body and Footer Shimmer Items
Define multiple custom shimmer items for body children and footer:
<CustomCard
isLoading={true}
// Multiple body shimmers (image + text lines)
bodyShimmerItems={[
{ width: '100%', height: 200, shape: 'rounded', marginBottom: 12 }, // Image
{ text: 'Product title here', fontSize: 16, marginBottom: 8 }, // Title
{ width: '70%', height: 14 } // Subtitle
]}
// Multiple footer shimmers
footerShimmerItems={[
{ width: 24, height: 24, shape: 'circle' }, // Icon
{ text: 'Add to Cart', fontSize: 14, shape: 'rounded' }, // Button
{ width: 24, height: 24, shape: 'circle' } // Icon
]}
body={{
children: <Image style={{ width: '100%', height: 200 }} />,
description: "Product description"
}}
footer={{
leftItem: <HeartIcon />,
children: <Button title="Add to Cart" />,
rightItem: <ShareIcon />
}}
/>| Prop | Type | Description |
|------|------|-------------|
| bodyShimmerItems | ShimmerItemConfig[] | Custom shimmer items for body children |
| footerShimmerItems | ShimmerItemConfig[] | Custom shimmer items for footer |
| descriptionShimmerItems | ShimmerItemConfig[] | Custom shimmer items for description text |
Complete Example
Full shimmer customization matching actual card content:
import { CustomCard } from 'react-native-custom-card';
import { Ionicons } from '@expo/vector-icons';
<CustomCard
isLoading={isLoading}
// Header shimmer
headerLeftItemShimmerWidth={24}
headerLeftItemShimmerHeight={24}
headerLeftItemShimmerShape="rounded"
headerTitleShimmerWidth={100}
headerSubtitleShimmerWidth={60}
// Body shimmer items
bodyShimmerItems={[
{ width: '100%', height: 200, shape: 'rounded', marginBottom: 12 }
]}
// Description shimmer (auto-sized from text)
descriptionShimmerItems={[
{ text: 'This is a product description', fontSize: 14 }
]}
// Footer shimmer items
footerShimmerItems={[
{ width: 24, height: 24, shape: 'rounded' },
{ text: 'Footer content', fontSize: 14 },
{ width: 24, height: 24, shape: 'rounded' }
]}
showHeaderDivider
showFooterDivider
header={{
title: "Vertical Card",
subtitle: "Subtitle",
leftItem: <Ionicons name="heart" size={24} color="#8eaa93" />
}}
body={{
children: (
<Image
source={{ uri: 'https://picsum.photos/300/200' }}
style={{ width: '100%', height: 200 }}
/>
),
description: "This is a product description",
descriptionPosition: "bottom"
}}
footer={{
children: <Text>Footer content</Text>,
leftItem: <Ionicons name="heart" size={24} />,
rightItem: <Ionicons name="share" size={24} />
}}
/>
### Granular Shimmer Configuration
For maximum control, use the `headerShimmerItem`, `bodyShimmerItem`, and `footerShimmerItem` props. These allow you to define precise shapes, dimensions, and margins for every element in the shimmer layout.
#### ShimmerElementConfig
Each element in the granular config uses the `ShimmerElementConfig` interface:
| Prop | Type | Description |
|------|------|-------------|
| `shape` | `'circle' \| 'rounded' \| 'rectangle'` | Shape of the shimmer element |
| `width` | `number \| string` | Width in pixels or percentage |
| `height` | `number` | Height in pixels |
| `marginBottom` | `number` | Spacing below |
| `marginTop` | `number` | Spacing above |
| `marginLeft` | `number` | Spacing to the left |
| `marginRight` | `number` | Spacing to the right |
| `marginVertical` | `number` | Vertical spacing |
| `marginHorizontal` | `number` | Horizontal spacing |
| `borderRadius` | `number` | Custom border radius (overrides shape default) |
| `style` | `ViewStyle` | Custom style object for padding, etc. |
#### Example: Precise Layout Mathcing
Match your shimmer exactly to your content layout:
```tsx
<CustomCard
isLoading={true}
// Header: Circle avatar + Title lines
headerShimmerItem={{
leftItem: { shape: 'circle', width: 40, height: 40, borderRadius: 20 },
title: { width: '60%', height: 16, marginBottom: 4 },
subtitle: { width: '30%', height: 12 },
rightItem: { shape: 'circle', width: 20, height: 20 }
}}
// Body: Multiple images with spacing + Description
bodyShimmerItem={{
children: [
// Image 1
{ shape: 'rounded', width: '100%', height: 200, borderRadius: 8, marginBottom: 12 },
// Image 2
{ shape: 'rounded', width: '100%', height: 200, borderRadius: 8, marginBottom: 22 }
],
// Description positioned at bottom with top margin
description: { shape: 'rectangle', width: '100%', height: 16, marginTop: 22 }
}}
// Footer: Left tag + Right icon + Center content
footerShimmerItem={{
leftItem: { shape: 'rounded', width: 40, height: 20 },
children: [
{ shape: 'rectangle', width: 60, height: 16 }
],
rightItem: { shape: 'circle', width: 20, height: 20 }
}}
/>Animated Card
Animations work in both vertical and horizontal orientations. Available types: fade, scale, slide.
Vertical Card with Animation
// Fade animation (default)
<CustomCard
animated
animationType="fade"
header={{ title: "Fade In Card" }}
body={{ children: <Text>This card fades in!</Text> }}
/>
// Scale animation
<CustomCard
animated
animationType="scale"
animationDuration={400}
header={{ title: "Scale Card" }}
body={{ children: <Text>This card scales in!</Text> }}
/>Horizontal Card with Animation
// Slide animation on horizontal layout - perfect for list items
<CustomCard
orientation="horizontal"
animated
animationType="slide"
animationDuration={300}
body={{
leftItem: (
<Image
source={{ uri: 'https://example.com/avatar.jpg' }}
style={{ width: 60, height: 60, borderRadius: 30 }}
/>
),
children: (
<View>
<Text style={{ fontWeight: 'bold' }}>Notification</Text>
<Text>You have a new message</Text>
</View>
)
}}
onPress={() => console.log('Notification pressed')}
/>| Animation Type | Effect |
|----------------|--------|
| fade | Opacity from 0 to 1 |
| scale | Scales from small to full size |
| slide | Slides in from the side |
Card Orientation
Cards support two orientations: vertical (default) and horizontal.
Vertical Layout (Default)
The default vertical layout stacks header, body, and footer from top to bottom:
// orientation="vertical" is the default, no need to specify
<CustomCard
header={{
title: "Vertical Card",
subtitle: "Default layout"
}}
body={{
children: (
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={{ width: '100%', height: 200 }}
/>
),
description: "Content stacks vertically below the header"
}}
footer={{
children: <Button title="Action" onPress={() => {}} />
}}
/>Horizontal Layout
Use orientation="horizontal" for side-by-side layouts, ideal for list items, product cards, or media rows:
<CustomCard
orientation="horizontal"
header={{ title: "Horizontal Card" }}
body={{
leftItem: (
<Image
source={{ uri: 'https://example.com/thumbnail.jpg' }}
style={{ width: 80, height: 80, borderRadius: 8 }}
/>
),
children: (
<View>
<Text style={{ fontWeight: 'bold' }}>Product Name</Text>
<Text>Content appears beside the image</Text>
<Text style={{ color: '#10B981' }}>$29.99</Text>
</View>
)
}}
onPress={() => console.log('Card pressed')}
/>| Orientation | Use Case |
|-------------|----------|
| vertical (default) | Blog posts, product details, social media cards, full-width content |
| horizontal | List items, compact product rows, media lists, notifications |
Using with FlatList
import { FlatList } from 'react-native';
import { CustomCard } from 'react-native-custom-card';
const data = [
{ id: '1', title: 'Card 1', description: 'Description 1' },
{ id: '2', title: 'Card 2', description: 'Description 2' },
{ id: '3', title: 'Card 3', description: 'Description 3' },
];
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<CustomCard
header={{ title: item.title }}
body={{ children: <Text>{item.description}</Text> }}
onPress={() => console.log('Pressed:', item.id)}
/>
)}
/>Custom Styling
<CustomCard
backgroundColor="#1F2937"
borderRadius={20}
elevation={5}
padding={24}
header={{
title: "Dark Card",
titleStyle: { color: '#FFFFFF' },
subtitle: "Custom styled",
subtitleStyle: { color: '#9CA3AF' }
}}
body={{
children: <Text style={{ color: '#E5E7EB' }}>Dark theme content</Text>
}}
/>Using Individual Components
import { CardHeader, CardBody, CardFooter, Divider } from 'react-native-custom-card';
import { View } from 'react-native';
<View style={{ backgroundColor: '#FFF', borderRadius: 12, padding: 16 }}>
<CardHeader title="Custom Layout" subtitle="Build your own" />
<Divider />
<CardBody>
<Text>Custom body content</Text>
</CardBody>
<Divider />
<CardFooter>
<Button title="Action" />
</CardFooter>
</View>🎨 Customizing Default Styles
Access the default styles to extend or override:
import { defaultStyles, colors, spacing } from 'react-native-custom-card';
// Use default colors
const myBackgroundColor = colors.background; // '#FFFFFF'
const myTextColor = colors.text; // '#1A1A1A'
// Use default spacing
const myPadding = spacing.lg; // 16📱 Platform Compatibility
This package works on all React Native platforms:
| Platform | Gradient Library |
|----------|------------------|
| Expo | expo-linear-gradient |
| Native Android/iOS | react-native-linear-gradient |
Expo Projects
npx expo install react-native-custom-card expo-linear-gradientimport { LinearGradient } from 'expo-linear-gradient';
import { CustomCard } from 'react-native-custom-card';
<CustomCard
gradient={{ enabled: true, from: '#667eea', to: '#764ba2' }}
GradientComponent={LinearGradient}
...
/>Native React Native (Android/iOS without Expo)
npm install react-native-custom-card react-native-linear-gradient
# iOS only - install pods
cd ios && pod install && cd ..import LinearGradient from 'react-native-linear-gradient';
import { CustomCard } from 'react-native-custom-card';
<CustomCard
header={{ title: 'Gradient Card', titleStyle: { color: '#FFF' } }}
body={{ children: <Text style={{ color: '#FFF' }}>Beautiful gradient!</Text> }}
gradient={{
enabled: true,
from: '#667eea',
to: '#764ba2',
direction: 'diagonal', // 'to-right' | 'to-left' | 'to-top' | 'to-bottom' | 'diagonal'
}}
GradientComponent={LinearGradient}
/>Note: The core package has zero native dependencies - only gradients require an external library.
Gradient with Orientation
Gradients work beautifully with both vertical and horizontal card orientations.
Vertical Card with Gradient
import { LinearGradient } from 'expo-linear-gradient';
<CustomCard
header={{
title: 'Premium Feature',
subtitle: 'Unlock now',
titleStyle: { color: '#FFF' },
subtitleStyle: { color: 'rgba(255,255,255,0.8)' }
}}
body={{
children: <Text style={{ color: '#FFF' }}>Stunning vertical gradient card!</Text>
}}
gradient={{
enabled: true,
from: '#667eea',
to: '#764ba2',
direction: 'diagonal'
}}
GradientComponent={LinearGradient}
/>Horizontal Card with Gradient
// Gradient on horizontal layout - perfect for featured list items
<CustomCard
orientation="horizontal"
header={{ title: 'Featured', titleStyle: { color: '#FFF' } }}
body={{
leftItem: (
<View style={{ width: 50, height: 50, backgroundColor: 'rgba(255,255,255,0.2)', borderRadius: 25, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 24 }}>⭐</Text>
</View>
),
children: (
<View>
<Text style={{ color: '#FFF', fontWeight: 'bold' }}>Premium Item</Text>
<Text style={{ color: 'rgba(255,255,255,0.8)' }}>Special gradient highlight</Text>
</View>
)
}}
gradient={{
enabled: true,
from: '#f093fb',
to: '#f5576c',
direction: 'to-right'
}}
GradientComponent={LinearGradient}
onPress={() => console.log('Featured item pressed')}
/>| Direction | Effect |
|-----------|--------|
| to-right | Left to right gradient |
| to-left | Right to left gradient |
| to-top | Bottom to top gradient |
| to-bottom | Top to bottom gradient |
| diagonal | Corner to corner gradient |
📏 Responsive Sizing
Enable responsive sizing for cards that adapt to screen size:
<CustomCard
header={{ title: 'Responsive Card' }}
body={{ children: <Text>Adapts to screen size</Text> }}
responsiveSize={{
enabled: true,
maxWidth: 500, // Max width in pixels
tabletBreakpoint: 768 // Screen width considered tablet
}}
/>📖 Expandable Description
CardBody supports expandable descriptions for long content:
<CustomCard
header={{ title: 'Article' }}
body={{
description: 'Very long description text that will be truncated...',
maxDescriptionLength: 100,
expandText: 'Read more',
collapseText: 'Show less',
descriptionPosition: 'bottom', // 'top' | 'bottom'
}}
/>Or use the standalone ExpandableText component:
import { ExpandableText } from 'react-native-custom-card';
<ExpandableText
text="Long text content here..."
maxLength={100}
expandText="Show more"
collapseText="Show less"
onToggle={(expanded) => console.log('Expanded:', expanded)}
/>➖ Divider Component
Use dividers in horizontal or vertical orientation:
import { Divider } from 'react-native-custom-card';
// Horizontal (default)
<Divider color="#E5E7EB" thickness={1} marginVertical={12} />
// Vertical (great for separating inline items)
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text>Left</Text>
<Divider orientation="vertical" length={20} marginHorizontal={16} />
<Text>Right</Text>
</View>🧪 Testing
Running Unit Tests
# Install dependencies
npm install
# Run tests
npm testTesting on Devices
Expo Go (Quickest)
cd your-expo-app
npx expo start
# Scan QR code with Expo Go app on iOS/AndroidiOS Simulator
npx expo start --iosAndroid Emulator
npx expo start --android🛠️ Development Setup
Setting Up Local Development
Clone the repository:
git clone https://github.com/Grishj/react-native-custom-card.git cd react-native-custom-cardInstall dependencies:
npm installBuild the package:
npm run buildTest with the included test app:
cd test-app npm install npx expo start
Linking Local Package for Testing
In your test app's metro.config.js:
const path = require('path');
module.exports = {
resolver: {
nodeModulesPaths: [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, '../node_modules'),
],
},
watchFolders: [
path.resolve(__dirname, '..'),
],
};❓ Troubleshooting
Common Issues
Ensure the package is installed and your Metro bundler is restarted:
npm install react-native-custom-card
npx expo start -c # Clear cache and restartMake sure you:
- Install a gradient library:
npx expo install expo-linear-gradientornpm install react-native-linear-gradient - Pass the component to
GradientComponentprop - Set
gradient.enabled: true
import { LinearGradient } from 'expo-linear-gradient';
<CustomCard
gradient={{ enabled: true, from: '#667eea', to: '#764ba2', direction: 'to-right' }} // 'to-right' | 'to-left' | 'to-top' | 'to-bottom' | 'diagonal'
GradientComponent={LinearGradient} // Don't forget this!
...
/>This package automatically enables LayoutAnimation on Android. If you're having issues, ensure you're using React Native 0.60+. For bare React Native projects, you may need to enable it manually:
import { UIManager, Platform } from 'react-native';
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}If you see type errors when passing LinearGradient, ensure you're using the latest version:
npm update react-native-custom-cardThe package's GradientComponentProps is designed to be compatible with both expo-linear-gradient and react-native-linear-gradient.
The shimmer uses useNativeDriver: true for optimal performance. If you experience issues:
- Ensure you're not running in Debug mode (use Release for performance testing)
- Reduce the number of simultaneous shimmer components if rendering many cards
Platform-Specific Notes
| Platform | Notes |
|----------|-------|
| Android | Uses elevation for shadows. LayoutAnimation auto-enabled. |
| iOS | Uses shadowColor/shadowOffset/shadowOpacity/shadowRadius for shadows. |
| Expo | Fully compatible. Use expo-linear-gradient for gradients. |
| Web | Basic support via React Native Web (shadows may differ). |
📄 License
MIT © Grish Joshi
