Column ID & Name
Every column in LyteNyte Grid must have a unique ID. LyteNyte Grid uses this ID to manage column related state. When the ID is not human readable, you can provide a display name.
Unique Column IDs
LyteNyte Grid uses the id of a column to uniquely identify and track the column.
The id is used for retrieval, detecting movement, and computing cell values.
Column IDs are immutable. If you change a column’s id value, LyteNyte Grid
treats the column as a new column, even if the rest of the object is unchanged.
A column id can be any string that fits your data model.
Many developers set the id to a key on the row data. This is convenient
since LyteNyte Grid also uses the id as a fallback when a column
does not define a field. See the Column Field guide for details.
Column Display Name
By default, LyteNyte Grid uses the column’s id as its display name.
The display name appears in the column header and in components that manage
columns, such as the Column Manager.
If the id is not suitable for display, set the name property to a
human-readable string. In the example below, several columns specify
a name to improve their header text:
1const columns: Grid.Column[] = [2 { id: "id", name: "ID" },3 { id: "product" },4 { id: "price" },5 { id: "customer" },6 { id: "purchaseDate", name: "Purchase Date" },7 { id: "paymentMethod", name: "Payment Method" },8 { id: "email" },9];Human Readable Names
14 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";14import { ViewportShadows } from "@1771technologies/lytenyte-pro/components";15
16export interface GridSpec {17 readonly data: OrderData;18}19
20const columns: Grid.Column<GridSpec>[] = [21 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },22 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },23 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },24 { id: "customer", cellRenderer: AvatarCell, width: 180, name: "Customer" },25 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130 },26 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },27 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email" },28];29
30export default function ColumnDemo() {31 const ds = useClientDataSource({ data: data });32
33 return (34 <div className="ln-grid" style={{ height: 500 }}>35 <Grid rowHeight={50} columns={columns} rowSource={ds} slotShadows={ViewportShadows} />36 </div>37 );38}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);In this example, purchaseDate, paymentMethod, and id use name to
provide readable labels, while the remaining IDs are already clear. Developers often
set name when the id is written in snake case or camel case.
Updating the Display Name
A column’s id is immutable, but its name can be updated. To change the display
name, update the column specification. LyteNyte Grid provides the columnUpdate API
for updating individual columns.
For example, you may want users to rename columns. In the demo below, users can double-click a header to edit the display name. Pressing Enter or clicking away saves the new value.
Renaming Column Headers
1import "@1771technologies/lytenyte-pro/light-dark.css";2import {3 Grid,4 useClientDataSource,5 usePiece,6 type PieceWritable,7} from "@1771technologies/lytenyte-pro";8import type { OrderData } from "@1771technologies/grid-sample-data/orders";9import { data } from "@1771technologies/grid-sample-data/orders";10import {11 AvatarCell,12 EmailCell,13 HeaderCell,14 IdCell,15 PaymentMethodCell,16 PriceCell,17 ProductCell,18 PurchaseDateCell,19} from "./components.jsx";20import { useMemo, useState } from "react";21
22export interface GridSpec {23 readonly data: OrderData;24 readonly api: { editing: PieceWritable<string | null>; updateHeaderName: (s: string, id: string) => void };25}26
27const base: Grid.ColumnBase<GridSpec> = { headerRenderer: HeaderCell };28
29export default function ColumnDemo() {30 const ds = useClientDataSource({ data: data });31
32 const [columns, setColumns] = useState<Grid.Column<GridSpec>[]>([33 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },34 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },35 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },36 { id: "customer", cellRenderer: AvatarCell, width: 180, name: "Customer" },37 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 120 },38 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },39 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email" },40 ]);41
42 const [editing, setEditing] = useState<string | null>(null);43 const editingPiece = usePiece(editing, setEditing);44
45 const api = useMemo<(api: Grid.API<GridSpec>) => GridSpec["api"]>(() => {46 return (api) => ({47 editing: editingPiece,48 updateHeaderName: (newName, id) => {49 api.columnUpdate({ [id]: { name: newName } });50 setEditing(null);51 },52 });53 }, [editingPiece]);54
55 return (56 <div className="ln-grid ln-header:px-0" style={{ height: 500 }}>57 <Grid58 events={useMemo<Grid.Events<GridSpec>>(59 () => ({60 headerCell: {61 keyDown: ({ column, event: ev }) => {62 if (ev.key !== "Enter") return;63
64 setEditing(column.id);65 },66 },67 }),68 [],69 )}70 apiExtension={api}71 rowSource={ds}72 columnBase={base}73 columns={columns}74 rowHeight={50}75 onColumnsChange={setColumns}76 />77 </div>78 );79}1import type { Grid } from "@1771technologies/lytenyte-pro";2import type { ClassValue } from "clsx";3import clsx from "clsx";4import { twMerge } from "tailwind-merge";5import { format } from "date-fns";6import { useState, type JSX, type ReactNode } from "react";7import type { GridSpec } from "./demo";8
9function tw(...c: ClassValue[]) {10 return twMerge(clsx(...c));11}12
13export function HeaderCell({ column, api }: Grid.T.HeaderParams<GridSpec>) {14 const isUpdating = api.editing.useValue((x) => x === column.id);15
16 return (17 <div18 className={tw(19 "flex h-full w-full items-center",20 column.type === "number" && "justify-end",21 !isUpdating && "px-2",22 )}23 onDoubleClick={() => {24 if (isUpdating) return;25 api.editing.set(column.id);26 }}27 >28 {isUpdating && (29 <HeaderCellInput30 name={column.name ?? column.id}31 onAccept={(name) => api.updateHeaderName(name, column.id)}32 />33 )}34 {!isUpdating && <>{column.name ?? column.id}</>}35 </div>36 );37}38
39function HeaderCellInput({40 name,41 onAccept: onUpdate,42 ...props43}: JSX.IntrinsicElements["input"] & { name: string; onAccept: (newName: string) => void }) {44 const [value, setValue] = useState(name);45
46 return (47 <input48 className="bg-ln-gray-10 h-full w-full px-2 outline-none"49 value={value}50 onChange={(e) => setValue(e.target.value)}51 autoFocus52 onKeyDown={(e) => {53 if (e.key === "Escape") {54 // Try focus the header cell. This assumes only two levels of nesting.55 e.currentTarget.parentElement?.parentElement?.focus();56 onUpdate(name);57 }58 if (e.key === "Enter") {59 e.stopPropagation();60 e.currentTarget.parentElement?.parentElement?.focus();61 onUpdate(value);62 }63 }}64 onBlur={() => onUpdate(value)}65 {...props}66 />67 );68}69
70export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {71 if (!api.rowIsLeaf(row) || !row.data) return;72
73 const url = row.data?.productThumbnail;74 const title = row.data.product;75 const desc = row.data.productDescription;76
77 return (78 <div className="flex h-full w-full items-center gap-2">79 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />80 <div className="text-ln-text-dark flex flex-col gap-0.5">81 <div className="font-semibold">{title}</div>82 <div className="text-ln-text-light text-xs">{desc}</div>83 </div>84 </div>85 );86}87
88export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {89 if (!api.rowIsLeaf(row) || !row.data) return;90
91 const url = row.data?.customerAvatar;92
93 const name = row.data.customer;94
95 return (96 <div className="flex h-full w-full items-center gap-2">97 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />98 <div className="text-ln-text-dark flex flex-col gap-0.5">99 <div>{name}</div>100 </div>101 </div>102 );103}104
105const formatter = new Intl.NumberFormat("en-Us", {106 minimumFractionDigits: 2,107 maximumFractionDigits: 2,108});109export function PriceCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {110 if (!api.rowIsLeaf(row) || !row.data) return;111
112 const price = formatter.format(row.data.price);113 const [dollars, cents] = price.split(".");114
115 return (116 <div className="flex h-full w-full items-center justify-end">117 <div className="flex items-baseline tabular-nums">118 <span className="text-ln-text font-semibold">${dollars}</span>.119 <span className="relative text-xs">{cents}</span>120 </div>121 </div>122 );123}124
125export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {126 if (!api.rowIsLeaf(row) || !row.data) return;127
128 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");129
130 return <div className="flex h-full w-full items-center">{formattedDate}</div>;131}132export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {133 if (!api.rowIsLeaf(row) || !row.data) return;134
135 return <div className="text-xs tabular-nums">{row.data.id}</div>;136}137
138export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {139 if (!api.rowIsLeaf(row) || !row.data) return;140
141 const cardNumber = row.data.cardNumber;142 const provider = row.data.paymentMethod;143
144 let Logo: ReactNode = null;145 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;146 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;147
148 return (149 <div className="flex h-full w-full items-center gap-2">150 <div className="flex w-7 items-center justify-center">{Logo}</div>151 <div className="flex items-center gap-px">152 <div className="bg-ln-gray-40 size-2 rounded-full"></div>153 <div className="bg-ln-gray-40 size-2 rounded-full"></div>154 <div className="bg-ln-gray-40 size-2 rounded-full"></div>155 <div className="bg-ln-gray-40 size-2 rounded-full"></div>156 </div>157 <div className="tabular-nums">{cardNumber}</div>158 </div>159 );160}161
162export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {163 if (!api.rowIsLeaf(row) || !row.data) return;164
165 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;166}167
168const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (169 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>170 <path171 fill="#1434cb"172 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"173 />174 </svg>175);176
177const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (178 <svg179 xmlns="http://www.w3.org/2000/svg"180 width={2500}181 height={1524}182 viewBox="55.2 38.3 464.5 287.8"183 {...props}184 >185 <path186 fill="#f79f1a"187 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"188 />189 <path190 fill="#ea001b"191 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"192 />193 <path194 fill="#ff5f01"195 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"196 />197 </svg>198);To support editing header names, this example uses a custom header cell and extends the grid API to enable it. For more guidance on extending the grid API, see the API Extension guide.
Next Steps
- Column Base: Learn how to specify default configuration options for columns.
- Column Resizing: Change column widths programmatically or via user interaction.
- Column Moving: Reorder columns programmatically or through drag-and-drop.
- Column Field: Control how a column retrieves its value for each cell.
