List
List Variant
- Basic List
- List With Actions
- List With Status
- List With Avatars
Basic List
The List-Basic component provides a clean and accessible way to display lists of content with consistent spacing. It supports both unordered lists (bullet points) and ordered lists (numbers), making it perfect for feature lists, instructions, or any content that needs to be organized in a list format.
- Preview
- Code
<ListBasic
items={undefined}
type="unordered"
spacing="md"
/>
Installation
- CLI
- Manual
ignix add component list-basic
import React from "react";
import { cn } from "../../../utils/cn";
export interface ListBasicProps {
items?: React.ReactNode[];
type?: 'unordered' | 'ordered';
spacing?: 'sm' | 'md' | 'lg';
className?: string;
children?: React.ReactNode;
}
const ListBasic: React.FC<ListBasicProps> = ({
items = [],
type = 'unordered',
spacing = 'md',
className,
children
}) => {
const spacingClasses = {
sm: 'space-y-2',
md: 'space-y-3',
lg: 'space-y-4',
};
const unorderedMarkerStyles = {
sm: 'list-disc list-outside ml-5',
md: 'list-disc list-outside ml-5',
lg: 'list-disc list-outside ml-6',
};
const orderedMarkerStyles = {
sm: 'list-decimal list-outside ml-5',
md: 'list-decimal list-outside ml-5',
lg: 'list-decimal list-outside ml-6',
};
const baseClasses = cn(
spacingClasses[spacing],
type === 'unordered' ? unorderedMarkerStyles[spacing] : orderedMarkerStyles[spacing],
'text-foreground',
className
);
const renderItems = () => {
if (!items || items.length === 0) return null;
return items.map((item, index) => (
<li
key={index}
className="leading-relaxed pl-1"
>
{item}
</li>
));
};
if (children) {
const ListTag = type === 'ordered' ? 'ol' : 'ul';
return (
<ListTag className={baseClasses}>
{children}
</ListTag>
);
}
const ListTag = type === 'ordered' ? 'ol' : 'ul';
return (
<ListTag className={baseClasses}>
{renderItems()}
</ListTag>
);
};
ListBasic.displayName = "ListBasic";
export { ListBasic };
Usage
Import the component:
import { ListBasic } from './components/ui/list-basic';
Basic Usage
The simplest way to use ListBasic is with the items prop:
function BasicList() {
return (
<ListBasic
items={['Item 1', 'Item 2', 'Item 3']}
/>
);
}
Ordered List
To create a numbered list, set the type prop to "ordered":
function OrderedList() {
return (
<ListBasic
items={['First step', 'Second step', 'Third step']}
type="ordered"
/>
);
}
Custom Spacing
Control the spacing between items using the spacing prop:
function CustomSpacing() {
return (
<ListBasic
items={['Item 1', 'Item 2', 'Item 3']}
spacing="lg" // Options: 'sm', 'md', 'lg'
/>
);
}
Using Children
For more complex content, you can use the children prop:
function ComplexList() {
return (
<ListBasic type="unordered" spacing="md">
<li>
<strong>Bold item</strong> with additional text
</li>
<li>
Item with <a href="#">a link</a>
</li>
<li>
<em>Italic item</em> for emphasis
</li>
</ListBasic>
);
}
Spacing Variants
The component supports three spacing options to control the vertical spacing between list items:
- Preview
- Code
Small Spacing
- Item one
- Item two
- Item three
Medium Spacing
- Item one
- Item two
- Item three
Large Spacing
- Item one
- Item two
- Item three
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 className="text-sm font-semibold mb-2">Small Spacing</h3>
<ListBasic
items={['Item one', 'Item two', 'Item three']}
type="unordered"
spacing="sm"
/>
</div>
<div>
<h3 className="text-sm font-semibold mb-2">Medium Spacing</h3>
<ListBasic
items={['Item one', 'Item two', 'Item three']}
type="unordered"
spacing="md"
/>
</div>
<div>
<h3 className="text-sm font-semibold mb-2">Large Spacing</h3>
<ListBasic
items={['Item one', 'Item two', 'Item three']}
type="unordered"
spacing="lg"
/>
</div>
</div>
List Types
Choose between unordered (bullet points) and ordered (numbers) lists:
- Preview
- Code
Unordered List
- Bullet point one
- Bullet point two
- Bullet point three
Ordered List
- Numbered item one
- Numbered item two
- Numbered item three
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<h3 className="text-sm font-semibold mb-2">Unordered List</h3>
<ListBasic
items={['Bullet point one', 'Bullet point two', 'Bullet point three']}
type="unordered"
spacing="md"
/>
</div>
<div>
<h3 className="text-sm font-semibold mb-2">Ordered List</h3>
<ListBasic
items={['Numbered item one', 'Numbered item two', 'Numbered item three']}
type="ordered"
spacing="md"
/>
</div>
</div>
Using Children
When you need more control over individual list items, use the children prop:
- Preview
- Code
- Bold item with additional text
- Item with a link
- Italic item for emphasis
- Item with
codeinline
<ListBasic type="unordered" spacing="md">
<li>
<strong>Bold item</strong> with additional text
</li>
<li>
Item with <a href="#" className="text-primary underline">a link</a>
</li>
<li>
<em>Italic item</em> for emphasis
</li>
<li>
Item with <code className="bg-muted px-1 py-0.5 rounded">code</code> inline
</li>
</ListBasic>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | React.ReactNode[] | [] | Array of items to display in the list. Can be strings or React nodes. |
type | 'unordered' | 'ordered' | 'unordered' | Type of list marker. 'unordered' for bullet points, 'ordered' for numbers. |
spacing | 'sm' | 'md' | 'lg' | 'md' | Spacing size between list items. 'sm' (8px), 'md' (12px), 'lg' (16px). |
className | string | undefined | Additional CSS classes to apply to the list container. |
children | React.ReactNode | undefined | Alternative way to pass list items as children. If provided, items prop will be ignored. |
Examples:
Feature List
A common use case for displaying product features:
<ListBasic
items={[
'Responsive design for all devices',
'Dark mode support',
'Accessible components',
'TypeScript support',
'Customizable themes'
]}
type="unordered"
spacing="md"
/>
Step-by-Step Instructions
Perfect for tutorials or guides:
<ListBasic
items={[
'Open the application',
'Navigate to Settings',
'Select your preferences',
'Click Save to apply changes'
]}
type="ordered"
spacing="md"
/>
Mixed Content List
Using children for complex content:
<ListBasic type="unordered" spacing="lg">
<li className="flex items-start gap-2">
<span className="text-primary">✓</span>
<span>Completed task with checkmark</span>
</li>
<li className="flex items-start gap-2">
<span className="text-warning">⚠</span>
<span>Warning item with icon</span>
</li>
<li className="flex items-start gap-2">
<span className="text-destructive">✗</span>
<span>Error item with icon</span>
</li>
</ListBasic>
Accessibility
The ListBasic component uses semantic HTML (<ul> and <ol>) which provides:
- Screen reader support: Screen readers can identify and navigate lists properly
- Keyboard navigation: Standard list navigation works as expected
- Semantic meaning: The list structure is clear to assistive technologies
Best Practices
- Use unordered lists for items that don't have a specific order (features, options, etc.)
- Use ordered lists for sequential steps, rankings, or numbered instructions
- Choose appropriate spacing based on your design needs:
sm: For compact layouts or when space is limitedmd: For standard content (default)lg: For better readability and visual separation
- Use children prop when you need to customize individual items with links, icons, or formatting
- Keep items concise for better readability and scanning
List With Actions
The List With Actions component extends the basic list functionality by adding contextual action buttons (like Edit, Delete, View) to each list item. Actions are intelligently revealed on hover or focus on desktop devices, while remaining always visible on mobile for better touch interaction.
- Preview
- Code
💡 Hover over items or use Tab to focus them to see the action buttons. On mobile, actions are always visible.
const actions = [
{ id: 'view', label: 'View', onClick: (index) => console.log('View', index) },
{ id: 'edit', label: 'Edit', onClick: (index) => console.log('Edit', index) },
{ id: 'delete', label: 'Delete', onClick: (index) => console.log('Delete', index) },
];
<ListWithActions
items={undefined}
actions={actions}
type="unordered"
spacing="md"
/>
Installation
- CLI
- Manual
ignix add component list-with-actions
import React, { useCallback, useMemo } from "react";
import { cn } from "../../../utils/cn";
import { ListBasic, type ListBasicProps } from "../list-basic";
export interface ListAction {
id: string;
label: React.ReactNode;
ariaLabel?: string;
onClick: (index: number) => void;
}
export interface ListWithActionsProps {
items: React.ReactNode[];
actions: ListAction[];
type?: ListBasicProps["type"];
spacing?: "sm" | "md" | "lg";
className?: string;
itemClassName?: string;
}
const ListWithActionsComponent: React.FC<ListWithActionsProps> = ({
items,
actions,
type,
spacing = "md",
className,
itemClassName,
}) => {
const listClassName = useMemo(
() => cn("w-full", "text-foreground", "list-none", className),
[className]
);
const rowClasses = useMemo(
() =>
cn(
"flex items-center justify-between gap-3 rounded-md border border-border/60 bg-background px-3 py-2",
"transition-colors",
"hover:bg-muted/60 focus-within:bg-muted/60",
"group",
itemClassName
),
[itemClassName]
);
const createActionClickHandler = useCallback(
(action: ListAction, index: number) => () => {
action.onClick(index);
},
[]
);
return (
<ListBasic type={type} spacing={spacing} className={listClassName}>
{items.map((item, index) => (
<li key={index} className={rowClasses} tabIndex={0}>
<div className={cn("flex-1 min-w-0", type && "flex items-center gap-2")}>
{type && (
<span className="w-6 shrink-0 text-xs font-medium text-muted-foreground text-right">
{type === "ordered" ? `${index + 1}.` : "•"}
</span>
)}
<div className="flex-1 min-w-0">{item}</div>
</div>
<div
className={cn(
"flex items-center gap-1",
"hidden group-hover:flex group-focus-within:flex",
"sm:flex"
)}
>
{actions.map((action) => (
<button
key={action.id}
type="button"
className="inline-flex items-center justify-center rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
aria-label={action.ariaLabel}
onClick={createActionClickHandler(action, index)}
>
{action.label}
</button>
))}
</div>
</li>
))}
</ListBasic>
);
};
export const ListWithActions = React.memo(ListWithActionsComponent);
Usage
Import the component:
import { ListWithActions } from './components/ui/list-with-actions';
Basic Usage
The simplest way to use ListWithActions is with the items and actions props:
function BasicList() {
const actions = [
{ id: 'view', label: 'View', onClick: (index) => console.log('View', index) },
{ id: 'edit', label: 'Edit', onClick: (index) => console.log('Edit', index) },
{ id: 'delete', label: 'Delete', onClick: (index) => console.log('Delete', index) },
];
return (
<ListWithActions
items={['Item 1', 'Item 2', 'Item 3']}
actions={actions}
/>
);
}
With Ordered List
To show numbered markers, pass the type prop:
function OrderedList() {
const actions = [
{ id: 'edit', label: 'Edit', onClick: (index) => {} },
{ id: 'delete', label: 'Delete', onClick: (index) => {} },
];
return (
<ListWithActions
type="ordered"
items={['Step 1', 'Step 2', 'Step 3']}
actions={actions}
/>
);
}
Without Markers
If you don't want bullets or numbers, simply omit the type prop:
function NoMarkers() {
return (
<ListWithActions
items={['Task 1', 'Task 2', 'Task 3']}
actions={actions}
/>
);
}
Custom Spacing
Control the spacing between items:
function CustomSpacing() {
return (
<ListWithActions
items={['Item 1', 'Item 2', 'Item 3']}
actions={actions}
spacing="lg" // Options: 'sm', 'md', 'lg'
/>
);
}
Rich Content Items
You can pass complex JSX as items:
function RichContent() {
const items = [
(
<div className="flex flex-col">
<span className="font-medium">Analytics Dashboard</span>
<span className="text-xs text-muted-foreground">Last updated 2 hours ago</span>
</div>
),
(
<div className="flex flex-col">
<span className="font-medium">Marketing Campaign</span>
<span className="text-xs text-muted-foreground">Running · Ends in 3 days</span>
</div>
),
];
return (
<ListWithActions
items={items}
actions={actions}
spacing="lg"
/>
);
}
Variants
Actions Behavior
Actions appear on hover/focus on desktop and are always visible on mobile:
- Preview
- Code
- Task 1
- Task 2
- Task 3
Hover over items or press Tab to focus them. Actions appear on hover/focus on desktop and are always visible on mobile.
<ListWithActions
items={['Item 1', 'Item 2', 'Item 3']}
actions={actions}
/>
Ordered List
Use numbered markers for sequential items:
- Preview
- Code
- 1.Step 1: Gather requirements
- 2.Step 2: Design the solution
- 3.Step 3: Implement features
<ListWithActions
type="ordered"
items={[
'Step 1: Gather requirements',
'Step 2: Design the solution',
'Step 3: Implement features',
]}
actions={actions}
/>
Rich Content
Display complex content in list items:
- Preview
- Code
- Analytics DashboardLast updated 2 hours ago
- Marketing CampaignRunning · Ends in 3 days
- Billing IntegrationIn review · 4 comments
const items = [
<div className="flex flex-col">
<span className="font-medium">Analytics Dashboard</span>
<span className="text-xs text-muted-foreground">Last updated 2 hours ago</span>
</div>,
// ... more items
];
<ListWithActions
items={items}
actions={actions}
spacing="lg"
/>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | React.ReactNode[] | required | Array of items to display in the list. Can be strings or React nodes. |
actions | ListAction[] | required | Array of action definitions. Each action must have id, label, and onClick. |
type | 'unordered' | 'ordered' | undefined | Type of list marker. When provided, shows bullets (unordered) or numbers (ordered). When omitted, no markers are shown. |
spacing | 'sm' | 'md' | 'lg' | 'md' | Spacing size between list items. 'sm' (8px), 'md' (12px), 'lg' (16px). |
className | string | undefined | Additional CSS classes to apply to the list container. |
itemClassName | string | undefined | Additional CSS classes to apply to each list row. |
ListAction Interface
| Property | Type | Required | Description |
|---|---|---|---|
id | string | required | Unique identifier for the action (used as React key). |
label | React.ReactNode | required | Visible label or icon for the action button. |
ariaLabel | string | optional | Accessible label announced to screen readers. |
onClick | (index: number) => void | required | Callback fired when the action is clicked. Receives the item's index. |
Accessibility
The ListWithActions component is designed with accessibility in mind:
- Keyboard navigation: Each row is focusable (
tabIndex={0}), allowing keyboard users to reveal actions by focusing on items - Screen reader support: Actions include
ariaLabelprops for better screen reader announcements - Focus management: Actions become visible when a row receives focus, ensuring keyboard users can access all functionality
- Semantic HTML: Uses proper list semantics (
<ul>or<ol>) inherited fromListBasic
Best Practices
-
Use memoized callbacks: To prevent unnecessary re-renders, memoize your action handlers with
useCallback:const handleEdit = useCallback((index: number) => {
editItem(index);
}, []);
const actions = [
{ id: 'edit', label: 'Edit', onClick: handleEdit },
]; -
Provide aria labels: Always include
ariaLabelfor icon-only actions:{ id: 'delete', label: <TrashIcon />, ariaLabel: 'Delete item', onClick: handleDelete } -
Keep actions concise: Limit the number of actions per item (3-4 max) to avoid cluttering the UI
-
Mobile-first: Remember that actions are always visible on mobile, so design them to be touch-friendly
-
Use appropriate spacing: Choose spacing based on content density:
sm: For compact lists or when space is limitedmd: For standard content (default)lg: For better readability with rich content
-
Consider type prop:
- Use
type="ordered"for sequential steps or ranked items - Use
type="unordered"for feature lists or options - Omit
typefor clean, marker-free lists
- Use
Performance
The component is optimized for performance:
- React.memo: Prevents re-renders when props haven't changed
- useMemo: Memoizes computed class names
- useCallback: Memoizes action click handlers
To maximize performance in your application, ensure your actions array and items array references remain stable between renders.
List With Status
The List With Status component extends the basic list functionality by adding status badges to each list item. Each item displays a status badge with appropriate colors and icons (success, error, warning, info, pending), making it perfect for displaying task lists, order statuses, notification lists, or any content that needs status indicators.
- Preview
- Code
💡 Click on items to see the click handler in action (check console).
const items = undefined;
<ListWithStatus
items={items}
type="unordered"
spacing="md"
/>
Installation
- CLI
- Manual
ignix add component list-with-status
import React, { useMemo, useCallback } from "react";
import { cn } from "../../../utils/cn";
import { ListBasic, type ListBasicProps } from "../list-basic";
import { CheckCircle2, XCircle, AlertCircle, Clock, Info } from "lucide-react";
export type StatusType = 'success' | 'error' | 'warning' | 'info' | 'pending';
export interface ListItemWithStatus {
id: string;
title: string;
description?: string;
status: StatusType;
statusLabel?: string;
statusIcon?: React.ReactNode;
customContent?: React.ReactNode;
}
export interface ListWithStatusProps {
items: ListItemWithStatus[];
type?: ListBasicProps["type"];
spacing?: "sm" | "md" | "lg";
className?: string;
itemClassName?: string;
onItemClick?: (item: ListItemWithStatus, index: number) => void;
}
const STATUS_LABELS: Record<StatusType, string> = {
success: 'Success',
error: 'Error',
warning: 'Warning',
info: 'Info',
pending: 'Pending',
} as const;
const STATUS_BADGE_CLASSES: Record<StatusType, string> = {
success: cn("bg-success/30 text-success border-success/20", "dark:bg-success/100 dark:text-success-foreground"),
error: cn("bg-destructive/30 text-destructive border-destructive/20", "dark:bg-destructive/50 dark:text-destructive-foreground"),
warning: cn("bg-warning/10 text-warning border-warning/20", "dark:bg-warning/50 dark:text-warning-foreground"),
info: cn("bg-primary/10 text-primary border-primary/20", "dark:bg-primary/30 dark:text-primary-foreground"),
pending: cn("bg-secondary/10 text-secondary-foreground border-primary/20", "dark:bg-secondary/20"),
} as const;
const STATUS_ICON_CLASSES: Record<StatusType, string> = {
success: "text-success",
error: "text-destructive",
warning: "text-warning",
info: "text-primary",
pending: "text-secondary-foreground",
} as const;
const STATUS_ICONS: Record<StatusType, React.ReactNode> = {
success: <CheckCircle2 className="h-3 w-3" />,
error: <XCircle className="h-3 w-3" />,
warning: <AlertCircle className="h-3 w-3" />,
info: <Info className="h-3 w-3" />,
pending: <Clock className="h-3 w-3" />,
} as const;
const ListWithStatusComponent: React.FC<ListWithStatusProps> = ({
items,
type,
spacing = "md",
className,
itemClassName,
onItemClick,
}) => {
const listClassName = useMemo(
() => cn("w-full", "text-foreground", "list-none", className),
[className]
);
const rowClasses = useMemo(
() =>
cn(
"flex items-center justify-between gap-3 pr-4",
"transition-colors",
onItemClick && "cursor-pointer rounded-md px-3 py-2 hover:bg-muted/60 focus-within:bg-muted/60 focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2",
itemClassName
),
[itemClassName, onItemClick]
);
const createItemClickHandler = useCallback(
(item: ListItemWithStatus, index: number) => () => {
if (onItemClick) {
onItemClick(item, index);
}
},
[onItemClick]
);
const getStatusBadgeConfig = useCallback((item: ListItemWithStatus) => {
return {
label: item.statusLabel || STATUS_LABELS[item.status],
icon: item.statusIcon || STATUS_ICONS[item.status],
iconClasses: STATUS_ICON_CLASSES[item.status],
badgeClasses: STATUS_BADGE_CLASSES[item.status],
};
}, []);
const renderItem = (item: ListItemWithStatus, index: number) => {
const badgeConfig = getStatusBadgeConfig(item);
const hasClickHandler = !!onItemClick;
const content = item.customContent || (
<div className="flex flex-col min-w-0 flex-1">
<span className="font-medium text-foreground truncate">{item.title}</span>
{item.description && (
<span className="text-sm text-muted-foreground truncate">
{item.description}
</span>
)}
</div>
);
const statusBadge = (
<div className="flex items-center gap-1.5 shrink-0">
{badgeConfig.icon && (
<span className={cn("flex-shrink-0", badgeConfig.iconClasses)} aria-hidden="true">
{badgeConfig.icon}
</span>
)}
<span
className={cn(
"inline-flex items-center px-2 py-1 rounded-md text-xs font-medium",
"border transition-colors",
badgeConfig.badgeClasses
)}
aria-label={\`Status: ${'${badgeConfig.label}'}\`}
>
{badgeConfig.label}
</span>
</div>
);
const itemContent = (
<div
className={rowClasses}
onClick={hasClickHandler ? createItemClickHandler(item, index) : undefined}
>
{type && (
<span className="w-6 shrink-0 text-xs font-medium text-muted-foreground text-right">
{type === "ordered" ? \`${'${index + 1}'}.\` : "•"}
</span>
)}
{content}
{statusBadge}
</div>
);
return (
<li key={item.id}>
{itemContent}
</li>
);
};
return (
<ListBasic type={type} spacing={spacing} className={listClassName}>
{items.map(renderItem)}
</ListBasic>
);
};
ListWithStatusComponent.displayName = "ListWithStatus";
export const ListWithStatus = React.memo(ListWithStatusComponent);
Usage
Import the component:
import { ListWithStatus } from './components/ui/list-with-status';
Basic Usage
The simplest way to use ListWithStatus is with the items prop:
function BasicListWithStatus() {
const items = [
{
id: '1',
title: 'Payment Processed',
description: 'Your payment has been successfully processed',
status: 'success',
},
{
id: '2',
title: 'Order Failed',
description: 'Unable to process your order',
status: 'error',
},
];
return (
<ListWithStatus items={items} />
);
}
With Ordered List
To show numbered markers, pass the type prop:
function OrderedListWithStatus() {
const items = [
{
id: '1',
title: 'Step 1: Gather requirements',
description: 'Collect all necessary information',
status: 'success',
},
{
id: '2',
title: 'Step 2: Design the solution',
description: 'Create a detailed design plan',
status: 'pending',
},
];
return (
<ListWithStatus
type="ordered"
items={items}
/>
);
}
With Click Handler
Add interactivity by providing an onItemClick callback:
function InteractiveListWithStatus() {
const items = [
{
id: '1',
title: 'Task 1',
description: 'Click to view details',
status: 'success',
},
{
id: '2',
title: 'Task 2',
description: 'Click to view details',
status: 'pending',
},
];
const handleItemClick = (item, index) => {
console.log('Clicked:', item.title);
// Navigate to detail page, open modal, etc.
};
return (
<ListWithStatus
items={items}
onItemClick={handleItemClick}
/>
);
}
Custom Status Labels and Icons
Override default status labels and icons:
function CustomStatusList() {
const items = [
{
id: '1',
title: 'Custom Status',
description: 'With custom label and icon',
status: 'success',
statusLabel: 'Completed',
statusIcon: <CheckIcon className="h-3 w-3" />,
},
];
return (
<ListWithStatus items={items} />
);
}
Custom Content
Use customContent for more complex item layouts:
function CustomContentList() {
const items = [
{
id: '1',
title: 'Analytics Dashboard',
status: 'success',
customContent: (
<div className="flex flex-col">
<span className="font-medium">Analytics Dashboard</span>
<span className="text-xs text-muted-foreground">
Last updated 2 hours ago
</span>
</div>
),
},
];
return (
<ListWithStatus items={items} spacing="lg" />
);
}
Variants
Status Variants
All available status types with their default colors and icons:
- Preview
- Code
- Success StatusOperation completed successfullySuccess
- Error StatusAn error occurred during processingError
- Warning StatusPlease review this itemWarning
- Info StatusAdditional information availableInfo
- Pending StatusWaiting for processingPending
const items = [
{
id: '1',
title: 'Success Status',
description: 'Operation completed successfully',
status: 'success',
},
{
id: '2',
title: 'Error Status',
description: 'An error occurred during processing',
status: 'error',
},
// ... more items
];
<ListWithStatus items={items} />
Ordered List
Use numbered markers for sequential items:
- Preview
- Code
- 1.Step 1: Gather requirementsCollect all necessary informationSuccess
- 2.Step 2: Design the solutionCreate a detailed design planPending
- 3.Step 3: Implement featuresBuild and test the solutionPending
<ListWithStatus
type="ordered"
items={items}
/>
Custom Content
Display complex content in list items:
- Preview
- Code
- Analytics DashboardLast updated 2 hours agoSuccess
- Marketing CampaignRunning · Ends in 3 daysInfo
- Billing IntegrationIn review · 4 commentsWarning
const items = [
{
id: '1',
title: 'Analytics Dashboard',
status: 'success',
customContent: (
<div className="flex flex-col">
<span className="font-medium">Analytics Dashboard</span>
<span className="text-xs text-muted-foreground">Last updated 2 hours ago</span>
</div>
),
},
// ... more items
];
<ListWithStatus items={items} spacing="lg" />
Spacing Variants
Different spacing options for list items:
- Preview
- Code
Small Spacing
- Item oneSuccess
- Item twoInfo
- Item threeWarning
Medium Spacing
- Item oneSuccess
- Item twoInfo
- Item threeWarning
Large Spacing
- Item oneSuccess
- Item twoInfo
- Item threeWarning
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 className="text-sm font-semibold mb-2">Small Spacing</h3>
<ListWithStatus items={items} spacing="sm" />
</div>
<div>
<h3 className="text-sm font-semibold mb-2">Medium Spacing</h3>
<ListWithStatus items={items} spacing="md" />
</div>
<div>
<h3 className="text-sm font-semibold mb-2">Large Spacing</h3>
<ListWithStatus items={items} spacing="lg" />
</div>
</div>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | ListItemWithStatus[] | required | Array of items to display in the list. Each item must have id, title, and status. |
type | 'unordered' | 'ordered' | undefined | Type of list marker. When provided, shows bullets (unordered) or numbers (ordered). When omitted, no markers are shown. |
spacing | 'sm' | 'md' | 'lg' | 'md' | Spacing size between list items. 'sm' (8px), 'md' (12px), 'lg' (16px). |
className | string | undefined | Additional CSS classes to apply to the list container. |
itemClassName | string | undefined | Additional CSS classes to apply to each list row. |
onItemClick | (item: ListItemWithStatus, index: number) => void | undefined | Callback fired when an item is clicked. |
ListItemWithStatus Interface
| Property | Type | Required | Description |
|---|---|---|---|
id | string | required | Unique identifier for the item (used as React key). |
title | string | required | Display title of the item. |
description | string | optional | Optional description or subtitle text displayed below the title. |
status | 'success' | 'error' | 'warning' | 'info' | 'pending' | required | Status type that determines badge color and icon. |
statusLabel | string | optional | Custom label for the status badge. If not provided, uses default label based on status type. |
statusIcon | React.ReactNode | optional | Custom icon for the status badge. If not provided, uses default icon based on status type. |
customContent | React.ReactNode | optional | Custom content to render instead of title/description. If provided, title and description are ignored. |
Examples:
Task List
Perfect for displaying tasks with their completion status:
const tasks = [
{
id: '1',
title: 'Design landing page',
description: 'Create mockups and wireframes',
status: 'success',
},
{
id: '2',
title: 'Implement authentication',
description: 'Set up user login flow',
status: 'pending',
},
{
id: '3',
title: 'Fix critical bug',
description: 'Resolve payment processing issue',
status: 'error',
},
];
<ListWithStatus items={tasks} />
Order Status
Display order or transaction statuses:
const orders = [
{
id: '1',
title: 'Order #12345',
description: 'Shipped · Expected delivery: Jan 30',
status: 'success',
},
{
id: '2',
title: 'Order #12346',
description: 'Processing · Will ship soon',
status: 'pending',
},
{
id: '3',
title: 'Order #12347',
description: 'Payment failed · Please retry',
status: 'error',
},
];
<ListWithStatus items={orders} spacing="lg" />
Notification List
Show notifications with different priority levels:
const notifications = [
{
id: '1',
title: 'New message received',
description: 'You have a new message from John',
status: 'info',
},
{
id: '2',
title: 'System maintenance scheduled',
description: 'Maintenance will occur tonight at 2 AM',
status: 'warning',
},
{
id: '3',
title: 'Profile updated successfully',
description: 'Your profile changes have been saved',
status: 'success',
},
];
<ListWithStatus items={notifications} />
Accessibility
The ListWithStatus component is designed with accessibility in mind:
- Status indicators: Status badges include
aria-labelattributes for screen readers - Semantic HTML: Uses proper list semantics (
<ul>or<ol>) inherited fromListBasic - Keyboard navigation: Items with
onItemClickare keyboard accessible and focusable - Color contrast: Status badges use appropriate color contrast ratios for readability
- Icon accessibility: Icons are marked with
aria-hidden="true"since status labels provide text alternatives
Best Practices
-
Use appropriate status types: Choose status types that clearly communicate the state:
success: Completed, approved, or successful operationserror: Failed operations or critical issueswarning: Requires attention or reviewinfo: Informational messages or updatespending: In progress or waiting states
-
Provide descriptions: Include
descriptiontext to provide context about the status -
Use custom labels when needed: Override default status labels when your domain uses different terminology
-
Consider spacing: Choose spacing based on content density:
sm: For compact lists or when space is limitedmd: For standard content (default)lg: For better readability with descriptions
-
Use click handlers for interactivity: Add
onItemClickwhen items should be clickable (e.g., to view details, navigate, or open modals) -
Keep item IDs unique: Ensure each item has a unique
idfor proper React key handling -
Memoize click handlers: Use
useCallbackto memoizeonItemClickhandlers to prevent unnecessary re-renders
Performance
The component is optimized for performance:
- React.memo: Prevents re-renders when props haven't changed
- useMemo: Memoizes computed class names
- useCallback: Memoizes click handlers and badge configuration functions
To maximize performance in your application, ensure your items array reference remains stable between renders.
The List With Avatars component extends the basic list functionality by displaying avatars on the left side of each item, with names and descriptions on the right. Each item can optionally link to a profile page, making it perfect for user directories, team listings, contact lists, and member directories.
List With Avatars
- Preview
- Code
Click on items to navigate to their profiles. Avatars are aligned on the left, with names and descriptions on the right.
const items = undefined;
<ListWithAvatars
items={items}
spacing="md"
avatarSize="md"
avatarShape="circle"
showProfileLinks={true}
/>
Installation
- CLI
- Manual
ignix add component list-with-avatars
import React, { useMemo, useCallback } from "react";
import { cn } from "../../../utils/cn";
import { ListBasic, type ListBasicProps } from "../list-basic";
import { Avatar, type AvatarProps } from "../avatar";
export interface ListItemWithAvatar {
id: string;
name: string;
description?: string;
profileLink?: string;
avatarSrc?: string;
avatarAlt?: string;
avatarLetters?: string;
avatarIcon?: React.ReactNode;
avatarSize?: AvatarProps["size"];
avatarShape?: AvatarProps["shape"];
customContent?: React.ReactNode;
}
export interface ListWithAvatarsProps {
items: ListItemWithAvatar[];
type?: ListBasicProps["type"];
spacing?: "sm" | "md" | "lg";
className?: string;
itemClassName?: string;
avatarSize?: AvatarProps["size"];
avatarShape?: AvatarProps["shape"];
onItemClick?: (item: ListItemWithAvatar, index: number) => void;
showProfileLinks?: boolean;
}
const ListWithAvatarsComponent: React.FC<ListWithAvatarsProps> = ({
items,
type,
spacing = "md",
className,
itemClassName,
avatarSize = "md",
avatarShape = "circle",
onItemClick,
showProfileLinks = true,
}) => {
const listClassName = useMemo(
() =>
cn(
"w-full",
"text-foreground",
"list-none",
className
),
[className]
);
const rowClasses = useMemo(
() =>
cn(
"flex items-center gap-3",
"transition-colors",
itemClassName
),
[itemClassName]
);
const clickableRowClasses = useMemo(
() =>
cn(
rowClasses,
showProfileLinks && "cursor-pointer rounded-md px-3 py-2 hover:bg-muted/60 focus-within:bg-muted/60 focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2"
),
[rowClasses, showProfileLinks]
);
const createItemClickHandler = useCallback(
(item: ListItemWithAvatar, index: number) => () => {
if (onItemClick) {
onItemClick(item, index);
}
},
[onItemClick, showProfileLinks]
);
const renderItem = (item: ListItemWithAvatar, index: number) => {
const isClickable = showProfileLinks && (item.profileLink || onItemClick);
const finalRowClasses = isClickable ? clickableRowClasses : rowClasses;
const finalAvatarSize = item.avatarSize || avatarSize;
const finalAvatarShape = item.avatarShape || avatarShape;
const content = item.customContent || (
<div className="flex flex-col min-w-0 flex-1">
<span className="font-medium text-foreground truncate">{item.name}</span>
{item.description && (
<span className="text-sm text-muted-foreground truncate">
{item.description}
</span>
)}
</div>
);
const avatar = (
<Avatar
src={item.avatarSrc}
alt={item.avatarAlt || item.name}
letters={item.avatarLetters}
icon={item.avatarIcon}
size={finalAvatarSize}
shape={finalAvatarShape}
className="shrink-0"
/>
);
const itemContent = (
<div className={finalRowClasses} onClick={isClickable ? createItemClickHandler(item, index) : undefined}>
{type && (
<span className="w-6 shrink-0 text-xs font-medium text-muted-foreground text-right">
{type === "ordered" ? `${index + 1}.` : "•"}
</span>
)}
{avatar}
{item.profileLink && showProfileLinks ? (
<a
href={item.profileLink}
className="flex flex-col min-w-0 flex-1 no-underline text-inherit hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded-sm"
onClick={(e) => {
if (onItemClick) {
e.preventDefault();
onItemClick(item, index);
}
}}
tabIndex={0}
>
{content}
</a>
) : (
content
)}
</div>
);
return (
<li key={item.id}>
{itemContent}
</li>
);
};
return (
<ListBasic type={type} spacing={spacing} className={listClassName}>
{items.map(renderItem)}
</ListBasic>
);
};
ListWithAvatarsComponent.displayName = "ListWithAvatars";
export const ListWithAvatars = React.memo(ListWithAvatarsComponent);
Usage
Import the component:
import { ListWithAvatars } from './components/ui/list-with-avatars';
Basic Usage
The simplest way to use ListWithAvatars is with the items prop:
function BasicList() {
const items = [
{
id: '1',
name: 'John Doe',
description: 'Software Engineer',
profileLink: '/profile/john-doe',
avatarSrc: '/avatars/john.jpg',
avatarAlt: 'John Doe',
},
{
id: '2',
name: 'Jane Smith',
description: 'Product Designer',
profileLink: '/profile/jane-smith',
avatarSrc: '/avatars/jane.jpg',
avatarAlt: 'Jane Smith',
},
];
return (
<ListWithAvatars
items={items}
/>
);
}
With Avatar Initials
When you don't have avatar images, use the avatarLetters prop:
function WithInitials() {
const items = [
{
id: '1',
name: 'John Doe',
description: 'Software Engineer',
profileLink: '/profile/john-doe',
avatarLetters: 'JD',
},
{
id: '2',
name: 'Jane Smith',
description: 'Product Designer',
profileLink: '/profile/jane-smith',
avatarLetters: 'JS',
},
];
return (
<ListWithAvatars
items={items}
/>
);
}
With Ordered List
To show numbered markers, pass the type prop:
function OrderedList() {
return (
<ListWithAvatars
type="ordered"
items={items}
/>
);
}
Custom Avatar Sizes
Control the avatar size globally or per item:
function CustomSizes() {
return (
<ListWithAvatars
items={items}
avatarSize="lg" // Options: 'sm', 'md', 'lg', 'xl', etc.
/>
);
}
Custom Avatar Shapes
Change the avatar shape:
function CustomShapes() {
return (
<ListWithAvatars
items={items}
avatarShape="square" // Options: 'circle', 'square', 'rounded'
/>
);
}
Without Profile Links
Disable profile links if you don't need navigation:
function WithoutLinks() {
return (
<ListWithAvatars
items={items}
showProfileLinks={false}
/>
);
}
With Custom Click Handler
Use a custom click handler instead of profile links:
function WithCustomHandler() {
const handleItemClick = (item, index) => {
console.log('Clicked:', item.name);
// Custom logic here
};
return (
<ListWithAvatars
items={items}
onItemClick={handleItemClick}
showProfileLinks={false}
/>
);
}
Variants:
Avatar Alignment
Avatars are consistently aligned on the left side:
- Preview
- Code
Avatars are consistently aligned on the left, ensuring a clean and organized appearance.
<ListWithAvatars
items={items}
spacing="md"
avatarSize="md"
/>
Profile Links
Profile links are functional and accessible:
- Preview
- Code
Click on any item to navigate to the profile. Links are fully functional and accessible.
const items = [
{
id: '1',
name: 'John Doe',
description: 'Software Engineer',
profileLink: '/profile/john-doe',
avatarSrc: '/avatars/john.jpg',
avatarAlt: 'John Doe',
},
// ... more items
];
<ListWithAvatars
items={items}
showProfileLinks={true}
/>
Avatar Variants
Different avatar sizes and shapes:
- Preview
- Code
Different Sizes
Different Shapes
<ListWithAvatars
items={items}
spacing="md"
/>
Text Readability
Names and descriptions are clearly readable:
- Preview
- Code
Names use a medium font weight for emphasis, while descriptions use smaller, muted text. Long text is automatically truncated.
<ListWithAvatars
items={items}
spacing="md"
/>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | ListItemWithAvatar[] | required | Array of items, each with avatar, name, and description. |
type | 'unordered' | 'ordered' | undefined | Type of list marker. When provided, shows bullets (unordered) or numbers (ordered). When omitted, no markers are shown. |
spacing | 'sm' | 'md' | 'lg' | 'md' | Spacing size between list items. 'sm' (8px), 'md' (12px), 'lg' (16px). |
className | string | undefined | Additional CSS classes to apply to the list container. |
itemClassName | string | undefined | Additional CSS classes to apply to each list row. |
avatarSize | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | ... | 'md' | Default size for all avatars. Can be overridden per item. |
avatarShape | 'circle' | 'square' | 'rounded' | ... | 'circle' | Default shape for all avatars. Can be overridden per item. |
onItemClick | (item: ListItemWithAvatar, index: number) => void | undefined | Optional callback fired when an item is clicked. |
showProfileLinks | boolean | true | Whether to make items clickable via profileLink. |
ListItemWithAvatar Interface
| Property | Type | Required | Description |
|---|---|---|---|
id | string | required | Unique identifier for the item (used as React key). |
name | string | required | Display name of the person/user. |
description | string | optional | Optional description or subtitle text. |
profileLink | string | optional | Optional URL to the profile page. If provided, the item becomes clickable. |
avatarSrc | string | optional | Optional image source URL for the avatar. |
avatarAlt | string | optional | Optional alt text for the avatar image. |
avatarLetters | string | optional | Optional letters to display in avatar (e.g., initials). |
avatarIcon | React.ReactNode | optional | Optional icon to display in avatar. |
avatarSize | AvatarProps['size'] | optional | Optional size of the avatar. Overrides default avatarSize prop. |
avatarShape | AvatarProps['shape'] | optional | Optional shape of the avatar. Overrides default avatarShape prop. |
customContent | React.ReactNode | optional | Optional custom content to render instead of name/description. |
Examples:
Team Directory
A common use case for displaying team members:
const teamMembers = [
{
id: '1',
name: 'Sarah Chen',
description: 'Engineering Manager',
profileLink: '/team/sarah-chen',
avatarSrc: '/avatars/sarah.jpg',
avatarAlt: 'Sarah Chen',
},
{
id: '2',
name: 'Michael Torres',
description: 'Senior Frontend Developer',
profileLink: '/team/michael-torres',
avatarSrc: '/avatars/michael.jpg',
avatarAlt: 'Michael Torres',
},
{
id: '3',
name: 'Emily Davis',
description: 'Product Manager',
profileLink: '/team/emily-davis',
avatarLetters: 'ED',
},
];
<ListWithAvatars
items={teamMembers}
spacing="md"
/>
User List with Mixed Avatars
Using different avatar sources (images, initials, icons):
const users = [
{
id: '1',
name: 'John Doe',
description: 'Software Engineer',
profileLink: '/users/john-doe',
avatarSrc: '/avatars/john.jpg',
avatarAlt: 'John Doe',
},
{
id: '2',
name: 'Jane Smith',
description: 'Product Designer',
profileLink: '/users/jane-smith',
avatarLetters: 'JS',
},
{
id: '3',
name: 'Bob Johnson',
description: 'UX Researcher',
profileLink: '/users/bob-johnson',
avatarIcon: <UserIcon />,
},
];
<ListWithAvatars
items={users}
avatarSize="lg"
spacing="md"
/>
Contact List
Displaying contacts with profile links:
const contacts = [
{
id: '1',
name: 'Alice Williams',
description: 'Frontend Developer',
profileLink: '/contacts/alice-williams',
avatarSrc: '/avatars/alice.jpg',
avatarAlt: 'Alice Williams',
},
{
id: '2',
name: 'David Kim',
description: 'Backend Developer',
profileLink: '/contacts/david-kim',
avatarLetters: 'DK',
},
];
<ListWithAvatars
items={contacts}
spacing="md"
showProfileLinks={true}
/>
Accessibility
The ListWithAvatars component is designed with accessibility in mind:
- Keyboard navigation: Profile links are fully keyboard accessible with proper focus states
- Screen reader support: Avatar images include alt text support, and names/descriptions provide context
- Semantic HTML: Uses proper list semantics (
<ul>or<ol>) inherited fromListBasic - Focus management: Links have visible focus indicators for keyboard users
- ARIA labels: Avatar images use proper alt text for screen readers
Best Practices
-
Provide avatar images or initials: Always provide either
avatarSrc,avatarLetters, oravatarIconfor better visual identification -
Use descriptive alt text: When using
avatarSrc, always provideavatarAltfor accessibility:
{
id: '1',
name: 'John Doe',
avatarSrc: '/avatars/john.jpg',
avatarAlt: 'John Doe', // Important for accessibility
}
-
Keep descriptions concise: Descriptions should be brief and informative. Long text will be truncated automatically.
-
Use appropriate spacing: Choose spacing based on content density:
sm: For compact lists or when space is limitedmd: For standard content (default)lg: For better readability with longer descriptions
- Consider avatar sizes:
smormd: For dense lists or sidebarsmdorlg: For main content areaslgorxl: For prominent displays or featured sections
-
Profile links: Always provide
profileLinkwhen items should be clickable. UseshowProfileLinks={false}only when navigation is not needed. -
Memoize callbacks: If using
onItemClick, memoize the handler withuseCallbackto prevent unnecessary re-renders:
const handleItemClick = useCallback((item, index) => {
navigateToProfile(item.profileLink);
}, []);
Performance
The component is optimized for performance:
- React.memo: Prevents re-renders when props haven't changed
- useMemo: Memoizes computed class names
- useCallback: Memoizes click handlers
To maximize performance in your application, ensure your items array reference remains stable between renders. Consider using useMemo for the items array if it's computed from other data.