Marker Column
The marker column is an auxiliary column created and managed by LyteNyte Grid. It is always pinned to the start of the viewport.
Use the marker column for information that does not come from the row data or for interactive actions that do not fit cleanly into other columns. Common use cases include:
- Displaying the current row index.
- Rendering a checkbox for row selection.
Enabling Marker Column
To enable the marker column, add a columnMarker definition to the grid
and set its on property to true.
1const marker: Grid.ColumnMarker<GridSpec> = {2 on: true,3
4 // Other marker column properties5};The demo below shows the marker column displaying row indices.
The displayed index starts at 1, while actual row indices in
LyteNyte Grid start at 0.
Row Index Marker Column
13 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import type { OrderData } from "@1771technologies/grid-sample-data/orders";3import { data } from "@1771technologies/grid-sample-data/orders";4import {5 AvatarCell,6 EmailCell,7 IdCell,8 PaymentMethodCell,9 PriceCell,10 ProductCell,11 PurchaseDateCell,12} from "./components.jsx";13import { useClientDataSource, Grid } from "@1771technologies/lytenyte-pro";14
15export interface GridSpec {16 readonly data: OrderData;17}18
19const columns: Grid.Column<GridSpec>[] = [20 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },21 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },22 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },23 { id: "customer", cellRenderer: AvatarCell, width: 180, name: "Customer" },24 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130 },25 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },26 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email" },27];28
29const marker: Grid.ColumnMarker<GridSpec> = {30 on: true,31 cellRenderer: (p) => {32 return <div className="flex h-full w-full items-center justify-center text-xs">{p.rowIndex + 1}</div>;33 },34};35
36export default function ColumnDemo() {37 const ds = useClientDataSource({ data: data });38
39 return (40 <div41 className="ln-grid ln-cell-marker:border-e ln-cell-marker:border-e-ln-border-strong"42 style={{ height: 500 }}43 >44 <Grid rowHeight={50} columns={columns} rowSource={ds} columnMarker={marker} />45 </div>46 );47}1import { format } from "date-fns";2import { type JSX, type ReactNode } from "react";3import type { Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5
6export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {7 if (!api.rowIsLeaf(row) || !row.data) return;8
9 const url = row.data?.productThumbnail;10 const title = row.data.product;11 const desc = row.data.productDescription;12
13 return (14 <div className="flex h-full w-full items-center gap-2">15 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />16 <div className="text-ln-text-dark flex flex-col gap-0.5">17 <div className="font-semibold">{title}</div>18 <div className="text-ln-text-light text-xs">{desc}</div>19 </div>20 </div>21 );22}23
24export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {25 if (!api.rowIsLeaf(row) || !row.data) return;26
27 const url = row.data?.customerAvatar;28
29 const name = row.data.customer;30
31 return (32 <div className="flex h-full w-full items-center gap-2">33 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />34 <div className="text-ln-text-dark flex flex-col gap-0.5">35 <div>{name}</div>36 </div>37 </div>38 );39}40
41const formatter = new Intl.NumberFormat("en-Us", {42 minimumFractionDigits: 2,43 maximumFractionDigits: 2,44});45export function PriceCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {46 if (!api.rowIsLeaf(row) || !row.data) return;47
48 const price = formatter.format(row.data.price);49 const [dollars, cents] = price.split(".");50
51 return (52 <div className="flex h-full w-full items-center justify-end">53 <div className="flex items-baseline tabular-nums">54 <span className="text-ln-text font-semibold">${dollars}</span>.55 <span className="relative text-xs">{cents}</span>56 </div>57 </div>58 );59}60
61export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {62 if (!api.rowIsLeaf(row) || !row.data) return;63
64 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");65
66 return <div className="flex h-full w-full items-center">{formattedDate}</div>;67}68export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {69 if (!api.rowIsLeaf(row) || !row.data) return;70
71 return <div className="text-xs tabular-nums">{row.data.id}</div>;72}73
74export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {75 if (!api.rowIsLeaf(row) || !row.data) return;76
77 const cardNumber = row.data.cardNumber;78 const provider = row.data.paymentMethod;79
80 let Logo: ReactNode = null;81 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;82 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;83
84 return (85 <div className="flex h-full w-full items-center gap-2">86 <div className="flex w-7 items-center justify-center">{Logo}</div>87 <div className="flex items-center gap-px">88 <div className="bg-ln-gray-40 size-2 rounded-full"></div>89 <div className="bg-ln-gray-40 size-2 rounded-full"></div>90 <div className="bg-ln-gray-40 size-2 rounded-full"></div>91 <div className="bg-ln-gray-40 size-2 rounded-full"></div>92 </div>93 <div className="tabular-nums">{cardNumber}</div>94 </div>95 );96}97
98export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {99 if (!api.rowIsLeaf(row) || !row.data) return;100
101 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;102}103
104const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (105 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>106 <path107 fill="#1434cb"108 d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707 25.178 117.653h-67.454z"109 />110 </svg>111);112
113const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (114 <svg115 xmlns="http://www.w3.org/2000/svg"116 width={2500}117 height={1524}118 viewBox="55.2 38.3 464.5 287.8"119 {...props}120 >121 <path122 fill="#f79f1a"123 d="M519.7 182.2c0 79.5-64.3 143.9-143.6 143.9s-143.6-64.4-143.6-143.9S296.7 38.3 376 38.3s143.7 64.4 143.7 143.9z"124 />125 <path126 fill="#ea001b"127 d="M342.4 182.2c0 79.5-64.3 143.9-143.6 143.9S55.2 261.7 55.2 182.2 119.5 38.3 198.8 38.3s143.6 64.4 143.6 143.9z"128 />129 <path130 fill="#ff5f01"131 d="M287.4 68.9c-33.5 26.3-55 67.3-55 113.3s21.5 87 55 113.3c33.5-26.3 55-67.3 55-113.3s-21.5-86.9-55-113.3z"132 />133 </svg>134);The marker column differs from standard columns and has important restrictions:
- It is always pinned to the start of the viewport.
- It cannot be moved or reordered.
- It is not part of the
columnsstate but does affect column display order. When enabled, the marker column will always appear first, and display index0always refers to the marker column.
Selection Marker Column
A common use case for the marker column is row selection.
The marker column can render a checkbox for each row. Because
this is so common, LyteNyte Grid provides the api.rowHandleSelect
method to simplify selection logic, including shift-select behavior.
Marker Column Selection
30 collapsed lines
1import "@1771technologies/lytenyte-pro/components.css";2import "@1771technologies/lytenyte-pro/light-dark.css";3import type { OrderData } from "@1771technologies/grid-sample-data/orders";4import { data } from "@1771technologies/grid-sample-data/orders";5import {6 AvatarCell,7 EmailCell,8 IdCell,9 PaymentMethodCell,10 PriceCell,11 ProductCell,12 PurchaseDateCell,13} from "./components.jsx";14import { useClientDataSource, Grid } from "@1771technologies/lytenyte-pro";15import { Checkbox, SelectAll } from "@1771technologies/lytenyte-pro/components";16
17export interface GridSpec {18 readonly data: OrderData;19}20
21const columns: Grid.Column<GridSpec>[] = [22 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },23 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },24 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },25 { id: "customer", cellRenderer: AvatarCell, width: 180, name: "Customer" },26 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130 },27 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },28 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email" },29];30
31
32const marker: Grid.ColumnMarker<GridSpec> = {33 on: true,34 cellRenderer: MarkerCell,35 headerRenderer: MarkerHeader,36};37
38export default function ColumnDemo() {39 const ds = useClientDataSource({ data: data });40
41 return (42 <div43 className="ln-grid ln-cell-marker:border-e ln-cell-marker:border-e-ln-border-strong"44 style={{ height: 500 }}45 >46 <Grid47 rowHeight={50}48 columns={columns}49 rowSource={ds}50 columnMarker={marker}51 rowSelectionMode="multiple"52 />53 </div>54 );55}56
57function MarkerHeader(params: Grid.T.HeaderParams<GridSpec>) {58 return (59 <div className="flex h-full w-full items-center justify-center">60 <SelectAll61 {...params}62 slot={({ indeterminate, selected, toggle }) => {63 return (64 <Checkbox65 checked={selected}66 indeterminate={indeterminate}67 onClick={(ev) => {68 ev.preventDefault();69 toggle();70 }}71 onKeyDown={(ev) => {72 if (ev.key === "Enter" || ev.key === " ") toggle();73 }}74 />75 );76 }}77 />78 </div>79 );80}81
82function MarkerCell({ api, selected }: Grid.T.CellRendererParams<GridSpec>) {83 return (84 <div className="flex h-full w-full items-center justify-center">85 <Checkbox86 checked={selected}87 onClick={(ev) => {88 ev.stopPropagation();89 api.rowHandleSelect({ shiftKey: ev.shiftKey, target: ev.target });90 }}91 onKeyDown={(ev) => {92 if (ev.key === "Enter" || ev.key === " ")93 api.rowHandleSelect({ shiftKey: ev.shiftKey, target: ev.target });94 }}95 />96 </div>97 );98}1import { format } from "date-fns";2import { type JSX, type ReactNode } from "react";3import type { Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5import { clsx, type ClassValue } from "clsx";6import { twMerge } from "tailwind-merge";7
8export function tw(...c: ClassValue[]) {9 return twMerge(clsx(...c));10}11
12export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {13 if (!api.rowIsLeaf(row) || !row.data) return;14
15 const url = row.data?.productThumbnail;16 const title = row.data.product;17 const desc = row.data.productDescription;18
19 return (20 <div className="flex h-full w-full items-center gap-2">21 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />22 <div className="text-ln-text-dark flex flex-col gap-0.5">23 <div className="font-semibold">{title}</div>24 <div className="text-ln-text-light text-xs">{desc}</div>25 </div>26 </div>27 );28}29
30export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {31 if (!api.rowIsLeaf(row) || !row.data) return;32
33 const url = row.data?.customerAvatar;34
35 const name = row.data.customer;36
37 return (38 <div className="flex h-full w-full items-center gap-2">39 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />40 <div className="text-ln-text-dark flex flex-col gap-0.5">41 <div>{name}</div>42 </div>43 </div>44 );45}46
47const formatter = new Intl.NumberFormat("en-Us", {48 minimumFractionDigits: 2,49 maximumFractionDigits: 2,50});51export function PriceCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {52 if (!api.rowIsLeaf(row) || !row.data) return;53
54 const price = formatter.format(row.data.price);55 const [dollars, cents] = price.split(".");56
57 return (58 <div className="flex h-full w-full items-center justify-end">59 <div className="flex items-baseline tabular-nums">60 <span className="text-ln-text font-semibold">${dollars}</span>.61 <span className="relative text-xs">{cents}</span>62 </div>63 </div>64 );65}66
67export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {68 if (!api.rowIsLeaf(row) || !row.data) return;69
70 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");71
72 return <div className="flex h-full w-full items-center">{formattedDate}</div>;73}74export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {75 if (!api.rowIsLeaf(row) || !row.data) return;76
77 return <div className="text-xs tabular-nums">{row.data.id}</div>;78}79
80export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {81 if (!api.rowIsLeaf(row) || !row.data) return;82
83 const cardNumber = row.data.cardNumber;84 const provider = row.data.paymentMethod;85
86 let Logo: ReactNode = null;87 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;88 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;89
90 return (91 <div className="flex h-full w-full items-center gap-2">92 <div className="flex w-7 items-center justify-center">{Logo}</div>93 <div className="flex items-center gap-px">94 <div className="bg-ln-gray-40 size-2 rounded-full"></div>95 <div className="bg-ln-gray-40 size-2 rounded-full"></div>96 <div className="bg-ln-gray-40 size-2 rounded-full"></div>97 <div className="bg-ln-gray-40 size-2 rounded-full"></div>98 </div>99 <div className="tabular-nums">{cardNumber}</div>100 </div>101 );102}103
104export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {105 if (!api.rowIsLeaf(row) || !row.data) return;106
107 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;108}109
110const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (111 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>112 <path113 fill="#1434cb"114 d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707 25.178 117.653h-67.454z"115 />116 </svg>117);118
119const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (120 <svg121 xmlns="http://www.w3.org/2000/svg"122 width={2500}123 height={1524}124 viewBox="55.2 38.3 464.5 287.8"125 {...props}126 >127 <path128 fill="#f79f1a"129 d="M519.7 182.2c0 79.5-64.3 143.9-143.6 143.9s-143.6-64.4-143.6-143.9S296.7 38.3 376 38.3s143.7 64.4 143.7 143.9z"130 />131 <path132 fill="#ea001b"133 d="M342.4 182.2c0 79.5-64.3 143.9-143.6 143.9S55.2 261.7 55.2 182.2 119.5 38.3 198.8 38.3s143.6 64.4 143.6 143.9z"134 />135 <path136 fill="#ff5f01"137 d="M287.4 68.9c-33.5 26.3-55 67.3-55 113.3s21.5 87 55 113.3c33.5-26.3 55-67.3 55-113.3s-21.5-86.9-55-113.3z"138 />139 </svg>140);The demo uses api.rowHandleSelect to manage checkbox interactions:
1<GridCheckbox2 checked={selected}3 onClick={(ev) => {4 ev.stopPropagation();5 api.rowHandleSelect({ shiftKey: ev.shiftKey, target: ev.target });6 }}7 onKeyDown={(ev) => {8 if (ev.key === "Enter" || ev.key === " ")9 api.rowHandleSelect({ shiftKey: ev.shiftKey, target: ev.target });10 }}11/>Next Steps
- Column Resizing: Change column widths programmatically or via user interaction.
- Column Moving: Reorder columns programmatically or through drag-and-drop.
- Column Groups: Create a column header hierarchy and associate columns with column groups.
- Column Field: Control how a column retrieves its value for each cell.
