Introduction
Components
- Accordion
- Action Sheet
- Alert
- Audio Player
- Audio Recorder
- Audio Waveform
- Avatar
- Badge
- BottomSheet
- Button
- Camera
- Camera Preview
- Card
- Carousel
- Checkbox
- Collapsible
- Color Picker
- Combobox
- Date Picker
- File Picker
- Gallery
- Hello Wave
- Icon
- Image
- Input
- Input OTP
- Link
- MediaPicker
- Mode Toggle
- Onboarding
- ParallaxScrollView
- Picker
- Popover
- Progress
- Radio
- ScrollView
- SearchBar
- Separator
- Share
- Sheet
- Skeleton
- Spinner
- Switch
- Table
- Tabs
- Text
- Toast
- Toggle
- Video
- View
Charts
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';
interface User {
id: number;
name: string;
email: string;
role: string;
status: 'Active' | 'Inactive';
}
const sampleData: User[] = [
{
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'Admin',
status: 'Active',
},
{
id: 2,
name: 'Jane Smith',
email: 'jane@example.com',
role: 'User',
status: 'Active',
},
{
id: 3,
name: 'Bob Johnson',
email: 'bob@example.com',
role: 'Manager',
status: 'Inactive',
},
{
id: 4,
name: 'Alice Brown',
email: 'alice@example.com',
role: 'User',
status: 'Active',
},
{
id: 5,
name: 'Charlie Wilson',
email: 'charlie@example.com',
role: 'Admin',
status: 'Active',
},
];
const columns: TableColumn<User>[] = [
{
id: 'name',
header: 'Name',
accessorKey: 'name',
sortable: true,
filterable: true,
},
{
id: 'email',
header: 'Email',
accessorKey: 'email',
sortable: true,
filterable: true,
},
{
id: 'role',
header: 'Role',
accessorKey: 'role',
sortable: true,
filterable: true,
},
{
id: 'status',
header: 'Status',
accessorKey: 'status',
sortable: true,
filterable: true,
},
];
export function TableDemo() {
return (
<Table
data={sampleData}
columns={columns}
pageSize={5}
searchPlaceholder='Search users...'
/>
);
}
Installation
pnpm dlx bna-ui add table
Usage
import { Table, TableColumn } from '@/components/ui/table';
const data = [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
];
const columns: TableColumn[] = [
{ id: 'name', header: 'Name', accessorKey: 'name', sortable: true },
{ id: 'email', header: 'Email', accessorKey: 'email', sortable: true },
{ id: 'role', header: 'Role', accessorKey: 'role', filterable: true },
];
<Table data={data} columns={columns} />;
Examples
Basic Table
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';
interface User {
id: number;
name: string;
email: string;
role: string;
status: 'Active' | 'Inactive';
}
const sampleData: User[] = [
{
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'Admin',
status: 'Active',
},
{
id: 2,
name: 'Jane Smith',
email: 'jane@example.com',
role: 'User',
status: 'Active',
},
{
id: 3,
name: 'Bob Johnson',
email: 'bob@example.com',
role: 'Manager',
status: 'Inactive',
},
{
id: 4,
name: 'Alice Brown',
email: 'alice@example.com',
role: 'User',
status: 'Active',
},
{
id: 5,
name: 'Charlie Wilson',
email: 'charlie@example.com',
role: 'Admin',
status: 'Active',
},
];
const columns: TableColumn<User>[] = [
{
id: 'name',
header: 'Name',
accessorKey: 'name',
sortable: true,
filterable: true,
},
{
id: 'email',
header: 'Email',
accessorKey: 'email',
sortable: true,
filterable: true,
},
{
id: 'role',
header: 'Role',
accessorKey: 'role',
sortable: true,
filterable: true,
},
{
id: 'status',
header: 'Status',
accessorKey: 'status',
sortable: true,
filterable: true,
},
];
export function TableDemo() {
return (
<Table
data={sampleData}
columns={columns}
pageSize={5}
searchPlaceholder='Search users...'
/>
);
}
Sortable Columns
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
rating: number;
}
const products: Product[] = [
{
id: 1,
name: 'Laptop Pro',
price: 1299.99,
category: 'Electronics',
inStock: true,
rating: 4.5,
},
{
id: 2,
name: 'Wireless Mouse',
price: 29.99,
category: 'Electronics',
inStock: true,
rating: 4.2,
},
{
id: 3,
name: 'Coffee Mug',
price: 12.99,
category: 'Kitchen',
inStock: false,
rating: 4.0,
},
{
id: 4,
name: 'Desk Chair',
price: 199.99,
category: 'Furniture',
inStock: true,
rating: 4.7,
},
{
id: 5,
name: 'Notebook',
price: 5.99,
category: 'Office',
inStock: true,
rating: 3.8,
},
{
id: 6,
name: 'Smartphone',
price: 699.99,
category: 'Electronics',
inStock: true,
rating: 4.3,
},
];
const columns: TableColumn<Product>[] = [
{
id: 'name',
header: 'Product Name',
accessorKey: 'name',
sortable: true,
filterable: true,
minWidth: 150,
},
{
id: 'price',
header: 'Price',
accessorKey: 'price',
sortable: true,
align: 'right',
cell: (value) => `$${value.toFixed(2)}`,
minWidth: 100,
},
{
id: 'category',
header: 'Category',
accessorKey: 'category',
sortable: true,
filterable: true,
minWidth: 120,
},
{
id: 'inStock',
header: 'In Stock',
accessorKey: 'inStock',
sortable: true,
align: 'center',
cell: (value) => (value ? '✅' : '❌'),
minWidth: 100,
},
{
id: 'rating',
header: 'Rating',
accessorKey: 'rating',
sortable: true,
align: 'center',
cell: (value) => `⭐ ${value}`,
minWidth: 100,
},
];
export function TableSortable() {
return (
<Table
data={products}
columns={columns}
pageSize={4}
searchPlaceholder='Search products...'
/>
);
}
Custom Cell Rendering
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Table, TableColumn } from '@/components/ui/table';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
interface Employee {
id: number;
name: string;
email: string;
department: string;
salary: number;
avatar?: string;
joinDate: string;
status: 'Active' | 'On Leave' | 'Terminated';
}
const employees: Employee[] = [
{
id: 1,
name: 'Sarah Johnson',
email: 'sarah.j@company.com',
department: 'Engineering',
salary: 95000,
avatar: 'https://avatars.githubusercontent.com/u/1?v=4',
joinDate: '2022-01-15',
status: 'Active',
},
{
id: 2,
name: 'Mike Chen',
email: 'mike.c@company.com',
department: 'Design',
salary: 78000,
joinDate: '2023-03-20',
status: 'Active',
},
{
id: 3,
name: 'Emma Davis',
email: 'emma.d@company.com',
department: 'Marketing',
salary: 65000,
avatar: 'https://avatars.githubusercontent.com/u/2?v=4',
joinDate: '2021-11-08',
status: 'On Leave',
},
{
id: 4,
name: 'James Wilson',
email: 'james.w@company.com',
department: 'Sales',
salary: 72000,
joinDate: '2020-09-12',
status: 'Terminated',
},
];
const columns: TableColumn<Employee>[] = [
{
id: 'employee',
header: 'Employee',
accessorKey: 'name',
sortable: true,
filterable: true,
minWidth: 200,
cell: (value, row) => (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
<Avatar size={32}>
{row.avatar && <AvatarImage source={{ uri: row.avatar }} />}
<AvatarFallback>
{row.name
.split(' ')
.map((n) => n[0])
.join('')}
</AvatarFallback>
</Avatar>
<View>
<Text variant='body' style={{ fontWeight: '600' }}>
{row.name}
</Text>
<Text variant='caption' style={{ opacity: 0.7 }}>
{row.email}
</Text>
</View>
</View>
),
},
{
id: 'department',
header: 'Department',
accessorKey: 'department',
sortable: true,
filterable: true,
minWidth: 120,
},
{
id: 'salary',
header: 'Salary',
accessorKey: 'salary',
sortable: true,
align: 'right',
minWidth: 120,
cell: (value) => (
<Text variant='body' style={{ fontWeight: '600' }}>
${value.toLocaleString()}
</Text>
),
},
{
id: 'joinDate',
header: 'Join Date',
accessorKey: 'joinDate',
sortable: true,
align: 'center',
minWidth: 120,
cell: (value) => new Date(value).toLocaleDateString(),
},
{
id: 'status',
header: 'Status',
accessorKey: 'status',
sortable: true,
filterable: true,
align: 'center',
minWidth: 120,
cell: (value) => (
<Badge
variant={
value === 'Active'
? 'default'
: value === 'On Leave'
? 'secondary'
: 'destructive'
}
>
{value}
</Badge>
),
},
];
export function TableCustomCells() {
return (
<Table
data={employees}
columns={columns}
pageSize={3}
searchPlaceholder='Search employees...'
/>
);
}
Pagination
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';
interface Order {
id: string;
customer: string;
product: string;
amount: number;
date: string;
status: 'Pending' | 'Completed' | 'Cancelled';
}
// Generate sample data
const generateOrders = (count: number): Order[] => {
const customers = [
'John Doe',
'Jane Smith',
'Bob Johnson',
'Alice Brown',
'Charlie Wilson',
'Diana Ross',
'Frank Miller',
'Grace Lee',
];
const products = [
'Laptop',
'Mouse',
'Keyboard',
'Monitor',
'Headphones',
'Webcam',
'Tablet',
'Phone',
];
const statuses: Order['status'][] = ['Pending', 'Completed', 'Cancelled'];
return Array.from({ length: count }, (_, i) => ({
id: `ORD-${String(i + 1).padStart(4, '0')}`,
customer: customers[i % customers.length],
product: products[i % products.length],
amount: Math.floor(Math.random() * 1000) + 50,
date: new Date(Date.now() - Math.random() * 90 * 24 * 60 * 60 * 1000)
.toISOString()
.split('T')[0],
status: statuses[Math.floor(Math.random() * statuses.length)],
}));
};
const orders = generateOrders(50);
const columns: TableColumn<Order>[] = [
{
id: 'id',
header: 'Order ID',
accessorKey: 'id',
sortable: true,
filterable: true,
minWidth: 120,
},
{
id: 'customer',
header: 'Customer',
accessorKey: 'customer',
sortable: true,
filterable: true,
minWidth: 150,
},
{
id: 'product',
header: 'Product',
accessorKey: 'product',
sortable: true,
filterable: true,
minWidth: 120,
},
{
id: 'amount',
header: 'Amount',
accessorKey: 'amount',
sortable: true,
align: 'right',
minWidth: 100,
cell: (value) => `$${value.toFixed(2)}`,
},
{
id: 'date',
header: 'Date',
accessorKey: 'date',
sortable: true,
align: 'center',
minWidth: 120,
},
{
id: 'status',
header: 'Status',
accessorKey: 'status',
sortable: true,
filterable: true,
align: 'center',
minWidth: 120,
},
];
export function TablePagination() {
return (
<Table
data={orders}
columns={columns}
pageSize={8}
searchPlaceholder='Search orders...'
pagination={true}
/>
);
}
Search and Filter
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';
interface Book {
id: number;
title: string;
author: string;
genre: string;
year: number;
isbn: string;
pages: number;
}
const books: Book[] = [
{
id: 1,
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald',
genre: 'Fiction',
year: 1925,
isbn: '978-0-7432-7356-5',
pages: 180,
},
{
id: 2,
title: 'To Kill a Mockingbird',
author: 'Harper Lee',
genre: 'Fiction',
year: 1960,
isbn: '978-0-06-112008-4',
pages: 281,
},
{
id: 3,
title: '1984',
author: 'George Orwell',
genre: 'Dystopian',
year: 1949,
isbn: '978-0-452-28423-4',
pages: 328,
},
{
id: 4,
title: 'Pride and Prejudice',
author: 'Jane Austen',
genre: 'Romance',
year: 1813,
isbn: '978-0-14-143951-8',
pages: 432,
},
{
id: 5,
title: 'The Catcher in the Rye',
author: 'J.D. Salinger',
genre: 'Fiction',
year: 1951,
isbn: '978-0-316-76948-0',
pages: 277,
},
{
id: 6,
title: 'Lord of the Flies',
author: 'William Golding',
genre: 'Fiction',
year: 1954,
isbn: '978-0-571-05686-2',
pages: 224,
},
{
id: 7,
title: 'The Hobbit',
author: 'J.R.R. Tolkien',
genre: 'Fantasy',
year: 1937,
isbn: '978-0-547-92822-7',
pages: 366,
},
{
id: 8,
title: "Harry Potter and the Sorcerer's Stone",
author: 'J.K. Rowling',
genre: 'Fantasy',
year: 1997,
isbn: '978-0-439-70818-8',
pages: 309,
},
{
id: 9,
title: 'The Da Vinci Code',
author: 'Dan Brown',
genre: 'Mystery',
year: 2003,
isbn: '978-0-307-47427-5',
pages: 689,
},
{
id: 10,
title: 'Brave New World',
author: 'Aldous Huxley',
genre: 'Science Fiction',
year: 1932,
isbn: '978-0-06-085052-4',
pages: 268,
},
];
const columns: TableColumn<Book>[] = [
{
id: 'title',
header: 'Title',
accessorKey: 'title',
sortable: true,
filterable: true,
minWidth: 200,
},
{
id: 'author',
header: 'Author',
accessorKey: 'author',
sortable: true,
filterable: true,
minWidth: 150,
},
{
id: 'genre',
header: 'Genre',
accessorKey: 'genre',
sortable: true,
filterable: true,
minWidth: 120,
},
{
id: 'year',
header: 'Year',
accessorKey: 'year',
sortable: true,
align: 'center',
minWidth: 80,
},
{
id: 'isbn',
header: 'ISBN',
accessorKey: 'isbn',
filterable: true,
minWidth: 150,
},
{
id: 'pages',
header: 'Pages',
accessorKey: 'pages',
sortable: true,
align: 'right',
minWidth: 80,
},
];
export function TableSearch() {
return (
<Table
data={books}
columns={columns}
pageSize={6}
searchPlaceholder='Search books by title, author, genre, or ISBN...'
searchable={true}
filterable={true}
/>
);
}
Loading State
import { Button } from '@/components/ui/button';
import { Table, TableColumn } from '@/components/ui/table';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
interface ApiData {
id: number;
name: string;
value: number;
category: string;
}
const mockData: ApiData[] = [
{ id: 1, name: 'Item A', value: 100, category: 'Type 1' },
{ id: 2, name: 'Item B', value: 250, category: 'Type 2' },
{ id: 3, name: 'Item C', value: 175, category: 'Type 1' },
{ id: 4, name: 'Item D', value: 320, category: 'Type 3' },
];
const columns: TableColumn<ApiData>[] = [
{
id: 'name',
header: 'Name',
accessorKey: 'name',
sortable: true,
filterable: true,
},
{
id: 'value',
header: 'Value',
accessorKey: 'value',
sortable: true,
align: 'right',
},
{
id: 'category',
header: 'Category',
accessorKey: 'category',
sortable: true,
filterable: true,
},
];
export function TableLoading() {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<ApiData[]>([]);
const simulateLoading = () => {
setLoading(true);
setData([]);
// Simulate API call
setTimeout(() => {
setData(mockData);
setLoading(false);
}, 2000);
};
const clearData = () => {
setData([]);
setLoading(false);
};
return (
<View style={{ gap: 16 }}>
<View style={{ flexDirection: 'row', gap: 12 }}>
<Button onPress={simulateLoading} disabled={loading}>
Load Data
</Button>
<Button variant='outline' onPress={clearData} disabled={loading}>
Clear Data
</Button>
</View>
<Table
data={data}
columns={columns}
loading={loading}
emptyMessage="Click 'Load Data' to fetch some data"
pageSize={5}
/>
</View>
);
}
API Reference
Table
The main table component that renders data in a structured format.
Prop | Type | Default | Description |
---|---|---|---|
data | T[] | - | Array of data objects to display in the table. |
columns | TableColumn<T>[] | - | Array of column definitions. |
pagination | boolean | true | Whether to enable pagination. |
pageSize | number | 10 | Number of rows per page. |
searchable | boolean | true | Whether to show the search bar. |
searchPlaceholder | string | 'Search...' | Placeholder text for the search input. |
loading | boolean | false | Whether to show loading state. |
emptyMessage | string | 'No data available' | Message to show when no data is available. |
style | ViewStyle | - | Additional styles for the table container. |
headerStyle | ViewStyle | - | Additional styles for the header row. |
rowStyle | ViewStyle | - | Additional styles for data rows. |
cellStyle | ViewStyle | - | Additional styles for table cells. |
onRowPress | (row: T, index: number) => void | - | Callback when a row is pressed. |
sortable | boolean | true | Whether to enable global sorting functionality. |
filterable | boolean | true | Whether to enable global filtering functionality. |
TableColumn
Configuration object for table columns.
Prop | Type | Default | Description |
---|---|---|---|
id | string | - | Unique identifier for the column. |
header | string | - | Text to display in the column header. |
accessorKey | string | - | Key to access data from the row object. |
sortable | boolean | false | Whether this column can be sorted. |
filterable | boolean | false | Whether this column is included in search filters. |
width | number | string | - | Fixed width for the column. |
minWidth | number | 100 | Minimum width for the column. |
cell | (value: any, row: T) => React.ReactNode | - | Custom cell renderer function. |
headerCell | () => React.ReactNode | - | Custom header cell renderer function. |
align | 'left' | 'center' | 'right' | 'left' | Text alignment for the column. |
Features
Sorting
- Click column headers to sort (supports ascending, descending, and no sort)
- Visual indicators show current sort state
- Multiple data types supported (string, number, date)
Filtering
- Global search across all filterable columns
- Case-insensitive search
- Real-time filtering as you type
Pagination
- Configurable page size
- Navigation controls (first, previous, next, last)
- Page information display
Responsive Design
- Horizontal scrolling for wide tables
- Flexible column widths
- Mobile-friendly touch interactions
Accessibility
The Table component is built with accessibility in mind:
- Proper semantic structure with TouchableOpacity for interactive elements
- Screen reader support for sort states and pagination
- High contrast colors for better visibility
- Keyboard navigation support where applicable
- Clear loading and empty states
Performance
For large datasets, consider:
- Implementing server-side pagination
- Using React.memo for custom cell components
- Virtualizing rows for very large datasets
- Debouncing search input for better performance