StockFind Brand

Tables

Data tables for screener results, watchlists, and portfolio holdings

Overview

Tables use react-aria-components (HeroUI v3 does not include Table). They are not wrapped in a Card — they render directly in a plain <div>.

Standard Table

import {
  Cell, Column, Row, Table, TableBody, TableHeader,
} from "react-aria-components";

<div>
  <div className="overflow-x-auto">
    <Table aria-label="Stock screener results" className="w-full min-w-max text-sm">
      <TableHeader className="bg-accent text-accent-foreground">
        <Column className="p-4 font-medium outline-none">
          <button
            type="button"
            className="inline-flex items-center gap-2 text-accent-foreground"
          >
            Symbol
            <ArrowUpDown className="size-3.5 text-accent-foreground" />
          </button>
        </Column>
        <Column className="p-4 font-medium text-right outline-none">
          <button
            type="button"
            className="inline-flex items-center gap-2 text-accent-foreground"
          >
            Price
          </button>
        </Column>
      </TableHeader>
      <TableBody>
        <Row className="group border-separator border-b outline-none last:border-b-0 hover:bg-accent/5">
          <Cell className="p-4 outline-none">AAPL</Cell>
          <Cell className="p-4 text-right tabular-nums outline-none">$175.25</Cell>
        </Row>
      </TableBody>
    </Table>
  </div>
</div>

Sticky Symbol Column

For wide tables with horizontal scroll, the symbol column stays pinned to the left.

{/* Header cell */}
<Column className="sticky left-0 z-10 border-separator border-r bg-accent p-4 font-medium outline-none">
  Symbol
</Column>

{/* Body cell */}
<Cell className="sticky left-0 z-10 border-separator border-r bg-surface p-4 outline-none group-hover:bg-accent/5">
  <Link href="/dashboard/stocks/aapl" className="flex items-center gap-2 hover:underline">
    <CompanyLogo symbol="AAPL" size="sm" />
    <div className="flex flex-col gap-2">
      <span className="font-semibold text-accent">AAPL</span>
      <span className="max-w-36 truncate text-muted text-xs">Apple Inc.</span>
    </div>
  </Link>
</Cell>

Sort Indicators

Sort icons always use text-accent-foreground — never muted.

function SortIndicator({ column, currentSort, direction }) {
  if (currentSort !== column)
    return <ArrowUpDown className="size-3.5 text-accent-foreground" />;
  return direction === "asc" ? (
    <ArrowUp className="size-3.5" />
  ) : (
    <ArrowDown className="size-3.5" />
  );
}

Design Rules

ElementClassesNotes
Headerbg-accent text-accent-foregroundAccent background, all text accent-foreground
Header texttext-accent-foregroundNever muted — full opacity on all labels and sort icons
Rowgroup border-separator border-b hover:bg-accent/5group enables sticky cell hover
Sticky header cellbg-accentMatches header background
Sticky body cellbg-surface group-hover:bg-accent/5White base, accent tint on hover
Bordersborder-separatorNever border-default or border-divider
Financial valuestabular-numsEnsures proper column alignment
WrapperPlain <div>No <Card> wrapper around tables
Scrolloverflow-x-auto + min-w-maxHorizontal scroll for wide tables

Expandable Rows

For tables with row details (e.g., watchlist), use onAction on the Row and render a details row below.

<Row
  className="group cursor-pointer border-separator border-b outline-none last:border-b-0 hover:bg-accent/5"
  onAction={() => toggleExpanded(item.id)}
>
  {/* ... cells ... */}
</Row>

{isExpanded && (
  <Row className="outline-none">
    <Cell className="col-span-full p-0 outline-none" colSpan={columns.length}>
      <RowDetails item={item} />
    </Cell>
  </Row>
)}

On this page