Skip to content

feat(data-grid): first-class column alignment API #121

Description

@focus0802

Problem

Data Grid has no first-class way to align a column. Today consumers hand-roll it with class names:

{
  accessorKey: "amount",
  meta: {
    cellClassName: "text-end",
    // Reaches into the component's internal DOM: the header content wrapper
    // is a private flex div, and headerClassName only lands on the <th>.
    headerClassName: "text-end [&>div]:w-full [&>div]:justify-end",
  },
}

Three problems with this:

  1. The header content layout has no public handle. meta.headerClassName is applied to the <th>, but the header content is wrapped in an internal flex div (-ms-2 flex h-full items-center justify-between gap-1.5 in data-grid-column-header.tsx). Positioning the trigger inside the cell forces consumers to select internal DOM with [&>div] variants.
  2. It couples consumer styles to internal structure. The [&>div] idiom recently caused a real bug: on resizable grids the same selector matches the column resize handle (also a direct div child of the <th>), and the compiled .\[\&\>div\]\:w-full>div (0,1,1) out-specifies the handle's .w-5 (0,1,0) — stretching the absolute handle across the whole header cell and swallowing every click. See fix(data-grid): prevent resize handle from covering header menu on end-aligned columns #120.
  3. The recipe must be repeated per column (start / center / end each need their own class string), and skeletons need separate handling again.

Proposal

Add a semantic alignment field to the column meta contract, e.g.:

{
  accessorKey: "amount",
  meta: {
    align: "end", // "start" | "center" | "end"
  },
}

The component then owns the mapping:

  • <td> / <th> get the matching text-* class;
  • the header content wrapper gets the matching w-full + justify-* internally (no consumer selector needed);
  • meta.skeleton alignment follows the same edge.

Open questions

  1. One knob or two? A single align covers the common case (numeric columns right-align both header and cell). Separate headerAlign / cellAlign would allow e.g. a start-aligned header over end-aligned cells. Which is the right surface?
  2. Interaction with existing headerClassName / cellClassName. Keep both as an escape hatch; explicit class names should still win over align-generated ones (or the contract should state the merge order).
  3. Naming. align on meta vs. a top-level column field — meta seems right since TanStack keeps presentation data there, but worth confirming.
  4. Scope. Both base and radix registries, docs, and an example (the c-* examples probably already carry the [&>div] idiom and could be migrated).

References

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions