SortHeader

Sortable table column header with direction indicator and hover affordance.

import { SortHeader }from "@/components/ui"

Specification

Renders a sortable table column header (a <th>) with a direction indicator and a hover affordance.

Props (5)
Every prop this component accepts, its type, default, and how to use it.
PropTypeDefaultDescription
label*string—The column heading text.
state*"asc" | "desc" | "none"—Current sort direction for this column.
onClick*() => void—Called when the header is clicked to cycle the sort.
styleCSSProperties—Inline styles applied to the <th> (e.g. a fixed width).
classNamestring—Extra classes merged onto the <th>.

Sortable columns

Full source
The complete component, read from src/components/ui/sort-header.tsx.
sort-header.tsx
"use client";

import { type CSSProperties } from "react";
import { ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";

type SortState = "asc" | "desc" | "none";

interface SortHeaderProps {
  label: string;
  state: SortState;
  onClick: () => void;
  style?: CSSProperties;
  className?: string;
}

export function SortHeader({ label, state, onClick, style, className }: SortHeaderProps) {
  const active = state === "asc" || state === "desc";

  return (
    <th
      scope="col"
      aria-sort={state === "asc" ? "ascending" : state === "desc" ? "descending" : "none"}
      style={style}
      className={cn(
        "px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wider",
        active ? "text-text" : "text-text-secondary",
        className
      )}
    >
      <button
        type="button"
        onClick={onClick}
        className="group inline-flex items-center gap-1.5 rounded uppercase tracking-wider text-inherit focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
      >
        {label}
        {state === "asc" ? (
          <ChevronUp size={13} strokeWidth={2.5} aria-hidden />
        ) : state === "desc" ? (
          <ChevronDown size={13} strokeWidth={2.5} aria-hidden />
        ) : (
          <ChevronsUpDown
            size={13}
            aria-hidden
            className="text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100"
          />
        )}
      </button>
    </th>
  );
}