Stock Table
A stock Table featuring sticky headers, sorting, multiple selection, and column resizing, styled with Tailwind CSS.
Example#
Symbol | Name | Market Cap | Sector | Industry |
---|---|---|---|---|
$ATNX | Athenex, Inc. | $767.4M | n/a | n/a |
$AUY | Yamana Gold Inc. | $2.32B | Basic Industries | Precious Metals |
$BIOC | Biocept, Inc. | $32.98M | Health Care | Medical Specialities |
$DCM | NTT DOCOMO, Inc | $96.67B | Technology | Radio And Television Broadcasting And Communications Equipment |
$DNI | Dividend and Income Fund | $130.45M | n/a | n/a |
$DPG | Duff & Phelps Global Utility Income Fund Inc. | $626.98M | n/a | n/a |
$EFSC | Enterprise Financial Services Corporation | $965.1M | Finance | Major Banks |
$EPE | EP Energy Corporation | $1.02B | Energy | Oil & Gas Production |
$EQC | Equity Commonwealth | $3.93B | Consumer Services | Real Estate Investment Trusts |
$ETH | Ethan Allen Interiors Inc. | $822.58M | Consumer Durables | Home Furnishings |
$FENX | Fenix Parts, Inc. | $29.61M | Consumer Services | Motor Vehicles |
$FTRPR | Frontier Communications Corporation | n/a | Public Utilities | Telecommunications Equipment |
$GNW | Genworth Financial Inc | $1.82B | Finance | Life Insurance |
$GPIAU | GP Investments Acquisition Corp. | n/a | Consumer Durables | Home Furnishings |
$HIIQ | Health Insurance Innovations, Inc. | $392.38M | Finance | Specialty Insurers |
$JNP | Juniper Pharmaceuticals, Inc. | $55.3M | Health Care | Major Pharmaceuticals |
$KAP | KCAP Financial, Inc. | n/a | n/a | n/a |
$KNL | Knoll, Inc. | $1.04B | Consumer Durables | Office Equipment / Supplies / Services |
$KOP | Koppers Holdings Inc. | $716.78M | Basic Industries | Forest Products |
$KRA | Kraton Corporation | $979.78M | Basic Industries | Major Chemicals |
$MANH | Manhattan Associates, Inc. | $3.27B | Technology | Computer Software: Prepackaged Software |
$MARK | Remark Holdings, Inc. | $57.31M | Consumer Services | Telecommunications Equipment |
$MERC | Mercer International Inc. | $769.94M | Basic Industries | Paper |
$MOFG | MidWestOne Financial Group, Inc. | $437.4M | Finance | Major Banks |
$NFJ | AllianzGI NFJ Dividend, Interest & Premium Strategy Fund | $1.24B | Finance | Finance: Consumer Services |
$NMK^B | Niagara Mohawk Holdings, Inc. | n/a | Public Utilities | Power Generation |
$NNN | National Retail Properties | $5.87B | Consumer Services | Real Estate Investment Trusts |
$NVTR | Nuvectra Corporation | $132.49M | Health Care | Medical/Dental Instruments |
$PAACR | Pacific Special Acquisition Corp. | n/a | Finance | Business Services |
$PAH | Platform Specialty Products Corporation | $3.52B | Basic Industries | Major Chemicals |
$PBI | Pitney Bowes Inc. | $2.84B | Miscellaneous | Office Equipment / Supplies / Services |
$PNF | PIMCO New York Municipal Income Fund | $99.42M | n/a | n/a |
$PSA^Y | Public Storage | n/a | n/a | n/a |
$RFEU | First Trust RiverFront Dynamic Europe ETF | $52.66M | n/a | n/a |
$RGLS | Regulus Therapeutics Inc. | $50.52M | Health Care | Major Pharmaceuticals |
$SAB | Saratoga Investment Corp | n/a | n/a | n/a |
$SODA | SodaStream International Ltd. | $1.13B | Consumer Durables | Consumer Electronics/Appliances |
$SSB | South State Corporation | $2.55B | Finance | Major Banks |
$TBPH | Theravance Biopharma, Inc. | $1.97B | Health Care | Major Pharmaceuticals |
$TEO | Telecom Argentina Stet - France Telecom S.A. | $4.83B | Public Utilities | Telecommunications Equipment |
$THQ | Tekla Healthcare Opportunies Fund | $772.41M | n/a | n/a |
$TNP^C | Tsakos Energy Navigation Ltd | n/a | n/a | n/a |
$TWNK | Hostess Brands, Inc. | $2.09B | Consumer Non-Durables | Packaged Foods |
$ULBI | Ultralife Corporation | $102.3M | Miscellaneous | Industrial Machinery/Components |
$USDP | USD Partners LP | $300.48M | Transportation | Railroads |
$VNQI | Vanguard Global ex-U.S. Real Estate ETF | $4.39B | n/a | n/a |
$VRTS | Virtus Investment Partners, Inc. | $785.49M | Finance | Investment Managers |
$WF | Woori Bank | $10.29B | Finance | Commercial Banks |
$WING | Wingstop Inc. | $875.69M | Consumer Services | Restaurants |
$ZF | Virtus Total Return Fund Inc. | $277.82M | n/a | n/a |
import {Cell, Column, ColumnResizer, Group, ResizableTableContainer, Row, Table, TableBody, TableHeader} from 'react-aria-components';
import type {CellProps, ColumnProps, RowProps, SortDescriptor} from 'react-aria-components';
import ArrowUpIcon from '@spectrum-icons/ui/ArrowUpSmall';
import {useMemo, useState} from 'react';
function StockTableExample() {
let [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: 'symbol',
direction: 'ascending'
});
let sortedItems = useMemo(() => {
return stocks.sort((a, b) => {
let first = a[sortDescriptor.column];
let second = b[sortDescriptor.column];
let cmp = first.localeCompare(second);
if (sortDescriptor.direction === 'descending') {
cmp *= -1;
}
return cmp;
});
}, [sortDescriptor]);
return (
<div className="bg-gradient-to-r from-indigo-500 to-violet-500 p-8 rounded-lg flex items-center justify-center md:col-span-2">
<ResizableTableContainer className="max-h-[280px] w-full overflow-auto scroll-pt-[2.321rem] relative bg-white rounded-lg shadow text-gray-600">
<Table
aria-label="Stocks"
selectionMode="multiple"
selectionBehavior="replace"
sortDescriptor={sortDescriptor}
onSortChange={setSortDescriptor}
className="border-separate border-spacing-0"
>
<TableHeader>
<StockColumn id="symbol" allowsSorting>Symbol</StockColumn>
<StockColumn id="name" isRowHeader allowsSorting defaultWidth="3fr">
Name
</StockColumn>
<StockColumn id="marketCap" allowsSorting>Market Cap</StockColumn>
<StockColumn id="sector" allowsSorting>Sector</StockColumn>
<StockColumn id="industry" allowsSorting defaultWidth="2fr">
Industry
</StockColumn>
</TableHeader>
<TableBody items={sortedItems}>
{(item) => (
<StockRow>
<StockCell>
<span className="font-mono bg-slate-100 border border-slate-200 rounded px-1 group-selected:bg-slate-700 group-selected:border-slate-800">
${item.symbol}
</span>
</StockCell>
<StockCell className="font-semibold">{item.name}</StockCell>
<StockCell>{item.marketCap}</StockCell>
<StockCell>{item.sector}</StockCell>
<StockCell>{item.industry}</StockCell>
</StockRow>
)}
</TableBody>
</Table>
</ResizableTableContainer>
</div>
);
}
function StockColumn(props: ColumnProps & { children: React.ReactNode }) {
return (
<Column
{...props}
className="sticky top-0 p-0 border-0 border-b border-solid border-slate-300 bg-slate-200 font-bold text-left cursor-default first:rounded-tl-lg last:rounded-tr-lg whitespace-nowrap outline-none"
>
{({ allowsSorting, sortDirection }) => (
<div className="flex items-center pl-4 py-1">
<Group
role="presentation"
tabIndex={-1}
className="flex flex-1 items-center overflow-hidden outline-none rounded focus-visible:ring-2 ring-slate-600"
>
<span className="flex-1 truncate">{props.children}</span>
{allowsSorting && (
<span
className={`ml-1 w-4 h-4 flex items-center justify-center transition `}
>
{sortDirection && <ArrowUpIcon width={8} height={10} />}
</span>
)}
</Group>
<ColumnResizer className="w-px px-[8px] py-1 h-5 bg-clip-content bg-slate-400 cursor-col-resize rounded resizing:bg-slate-800 resizing:w-[2px] resizing:pl-[7px] focus-visible:ring-2 ring-slate-600 ring-inset" />
</div>
)}
</Column>
);
}
function StockRow<T extends object>(props: RowProps<T>) {
return (
<Row
{...props}
className="even:bg-slate-100 selected:bg-slate-600 selected:text-white cursor-default group outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-600 focus-visible:-outline-offset-4 selected:focus-visible:outline-white"
/>
);
}
function StockCell(props: CellProps) {
return (
<Cell
{...props}
className={`px-4 py-2 truncate focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-600 focus-visible:-outline-offset-4 group-selected:focus-visible:outline-white`}
/>
);
}
import {
Cell,
Column,
ColumnResizer,
Group,
ResizableTableContainer,
Row,
Table,
TableBody,
TableHeader
} from 'react-aria-components';
import type {
CellProps,
ColumnProps,
RowProps,
SortDescriptor
} from 'react-aria-components';
import ArrowUpIcon from '@spectrum-icons/ui/ArrowUpSmall';
import {useMemo, useState} from 'react';
function StockTableExample() {
let [sortDescriptor, setSortDescriptor] = useState<
SortDescriptor
>({ column: 'symbol', direction: 'ascending' });
let sortedItems = useMemo(() => {
return stocks.sort((a, b) => {
let first = a[sortDescriptor.column];
let second = b[sortDescriptor.column];
let cmp = first.localeCompare(second);
if (sortDescriptor.direction === 'descending') {
cmp *= -1;
}
return cmp;
});
}, [sortDescriptor]);
return (
<div className="bg-gradient-to-r from-indigo-500 to-violet-500 p-8 rounded-lg flex items-center justify-center md:col-span-2">
<ResizableTableContainer className="max-h-[280px] w-full overflow-auto scroll-pt-[2.321rem] relative bg-white rounded-lg shadow text-gray-600">
<Table
aria-label="Stocks"
selectionMode="multiple"
selectionBehavior="replace"
sortDescriptor={sortDescriptor}
onSortChange={setSortDescriptor}
className="border-separate border-spacing-0"
>
<TableHeader>
<StockColumn id="symbol" allowsSorting>
Symbol
</StockColumn>
<StockColumn
id="name"
isRowHeader
allowsSorting
defaultWidth="3fr"
>
Name
</StockColumn>
<StockColumn id="marketCap" allowsSorting>
Market Cap
</StockColumn>
<StockColumn id="sector" allowsSorting>
Sector
</StockColumn>
<StockColumn
id="industry"
allowsSorting
defaultWidth="2fr"
>
Industry
</StockColumn>
</TableHeader>
<TableBody items={sortedItems}>
{(item) => (
<StockRow>
<StockCell>
<span className="font-mono bg-slate-100 border border-slate-200 rounded px-1 group-selected:bg-slate-700 group-selected:border-slate-800">
${item.symbol}
</span>
</StockCell>
<StockCell className="font-semibold">
{item.name}
</StockCell>
<StockCell>{item.marketCap}</StockCell>
<StockCell>{item.sector}</StockCell>
<StockCell>{item.industry}</StockCell>
</StockRow>
)}
</TableBody>
</Table>
</ResizableTableContainer>
</div>
);
}
function StockColumn(
props: ColumnProps & { children: React.ReactNode }
) {
return (
<Column
{...props}
className="sticky top-0 p-0 border-0 border-b border-solid border-slate-300 bg-slate-200 font-bold text-left cursor-default first:rounded-tl-lg last:rounded-tr-lg whitespace-nowrap outline-none"
>
{({ allowsSorting, sortDirection }) => (
<div className="flex items-center pl-4 py-1">
<Group
role="presentation"
tabIndex={-1}
className="flex flex-1 items-center overflow-hidden outline-none rounded focus-visible:ring-2 ring-slate-600"
>
<span className="flex-1 truncate">
{props.children}
</span>
{allowsSorting && (
<span
className={`ml-1 w-4 h-4 flex items-center justify-center transition `}
>
{sortDirection && (
<ArrowUpIcon width={8} height={10} />
)}
</span>
)}
</Group>
<ColumnResizer className="w-px px-[8px] py-1 h-5 bg-clip-content bg-slate-400 cursor-col-resize rounded resizing:bg-slate-800 resizing:w-[2px] resizing:pl-[7px] focus-visible:ring-2 ring-slate-600 ring-inset" />
</div>
)}
</Column>
);
}
function StockRow<T extends object>(props: RowProps<T>) {
return (
<Row
{...props}
className="even:bg-slate-100 selected:bg-slate-600 selected:text-white cursor-default group outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-600 focus-visible:-outline-offset-4 selected:focus-visible:outline-white"
/>
);
}
function StockCell(props: CellProps) {
return (
<Cell
{...props}
className={`px-4 py-2 truncate focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-600 focus-visible:-outline-offset-4 group-selected:focus-visible:outline-white`}
/>
);
}
import {
Cell,
Column,
ColumnResizer,
Group,
ResizableTableContainer,
Row,
Table,
TableBody,
TableHeader
} from 'react-aria-components';
import type {
CellProps,
ColumnProps,
RowProps,
SortDescriptor
} from 'react-aria-components';
import ArrowUpIcon from '@spectrum-icons/ui/ArrowUpSmall';
import {
useMemo,
useState
} from 'react';
function StockTableExample() {
let [
sortDescriptor,
setSortDescriptor
] = useState<
SortDescriptor
>({
column: 'symbol',
direction:
'ascending'
});
let sortedItems =
useMemo(() => {
return stocks.sort(
(a, b) => {
let first =
a[
sortDescriptor
.column
];
let second =
b[
sortDescriptor
.column
];
let cmp = first
.localeCompare(
second
);
if (
sortDescriptor
.direction ===
'descending'
) {
cmp *= -1;
}
return cmp;
}
);
}, [sortDescriptor]);
return (
<div className="bg-gradient-to-r from-indigo-500 to-violet-500 p-8 rounded-lg flex items-center justify-center md:col-span-2">
<ResizableTableContainer className="max-h-[280px] w-full overflow-auto scroll-pt-[2.321rem] relative bg-white rounded-lg shadow text-gray-600">
<Table
aria-label="Stocks"
selectionMode="multiple"
selectionBehavior="replace"
sortDescriptor={sortDescriptor}
onSortChange={setSortDescriptor}
className="border-separate border-spacing-0"
>
<TableHeader>
<StockColumn
id="symbol"
allowsSorting
>
Symbol
</StockColumn>
<StockColumn
id="name"
isRowHeader
allowsSorting
defaultWidth="3fr"
>
Name
</StockColumn>
<StockColumn
id="marketCap"
allowsSorting
>
Market Cap
</StockColumn>
<StockColumn
id="sector"
allowsSorting
>
Sector
</StockColumn>
<StockColumn
id="industry"
allowsSorting
defaultWidth="2fr"
>
Industry
</StockColumn>
</TableHeader>
<TableBody
items={sortedItems}
>
{(item) => (
<StockRow>
<StockCell>
<span className="font-mono bg-slate-100 border border-slate-200 rounded px-1 group-selected:bg-slate-700 group-selected:border-slate-800">
${item
.symbol}
</span>
</StockCell>
<StockCell className="font-semibold">
{item
.name}
</StockCell>
<StockCell>
{item
.marketCap}
</StockCell>
<StockCell>
{item
.sector}
</StockCell>
<StockCell>
{item
.industry}
</StockCell>
</StockRow>
)}
</TableBody>
</Table>
</ResizableTableContainer>
</div>
);
}
function StockColumn(
props: ColumnProps & {
children:
React.ReactNode;
}
) {
return (
<Column
{...props}
className="sticky top-0 p-0 border-0 border-b border-solid border-slate-300 bg-slate-200 font-bold text-left cursor-default first:rounded-tl-lg last:rounded-tr-lg whitespace-nowrap outline-none"
>
{(
{
allowsSorting,
sortDirection
}
) => (
<div className="flex items-center pl-4 py-1">
<Group
role="presentation"
tabIndex={-1}
className="flex flex-1 items-center overflow-hidden outline-none rounded focus-visible:ring-2 ring-slate-600"
>
<span className="flex-1 truncate">
{props
.children}
</span>
{allowsSorting &&
(
<span
className={`ml-1 w-4 h-4 flex items-center justify-center transition `}
>
{sortDirection &&
(
<ArrowUpIcon
width={8}
height={10}
/>
)}
</span>
)}
</Group>
<ColumnResizer className="w-px px-[8px] py-1 h-5 bg-clip-content bg-slate-400 cursor-col-resize rounded resizing:bg-slate-800 resizing:w-[2px] resizing:pl-[7px] focus-visible:ring-2 ring-slate-600 ring-inset" />
</div>
)}
</Column>
);
}
function StockRow<
T extends object
>(props: RowProps<T>) {
return (
<Row
{...props}
className="even:bg-slate-100 selected:bg-slate-600 selected:text-white cursor-default group outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-600 focus-visible:-outline-offset-4 selected:focus-visible:outline-white"
/>
);
}
function StockCell(
props: CellProps
) {
return (
<Cell
{...props}
className={`px-4 py-2 truncate focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-600 focus-visible:-outline-offset-4 group-selected:focus-visible:outline-white`}
/>
);
}
Tailwind config#
This example uses the tailwindcss-react-aria-components plugin. Add it to your tailwind.config.js
:
module.exports = {
// ...
plugins: [
require('tailwindcss-react-aria-components')
]
};
module.exports = {
// ...
plugins: [
require('tailwindcss-react-aria-components')
]
};
module.exports = {
// ...
plugins: [
require(
'tailwindcss-react-aria-components'
)
]
};
Components#
Table
A table displays data in rows and columns, with row selection and sorting.