Column Autosizing
LyteNyte Grid can automatically size columns to fit their content. Columns do not need to be visible to be autosized. The grid uses heuristic functions to determine optimal widths based on content.
Autosizing can consider header width, cell content width, or both. Each column can
define autosizeHeaderFn and autosizeCellFn to control header and cell
width calculations. If you omit these functions, LyteNyte Grid
uses a default text measurement function.
LyteNyte Grid encourages custom header and cell renderers. When using custom renderers, provide matching autosize functions to ensure accurate sizing.
Autosizing Cells
Use the api.autosizeColumns method to autosize all columns or
a selected subset. The demo below shows this behavior.
Clicking Autosize Cells triggers the autosizing logic.
Cell Autosize
"use client";import { Grid, measureText, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import type { Column } from "@1771technologies/lytenyte-pro/types";import { useId } from "react";import {ExchangeCell,GridButton,makePerfHeaderCell,NetworkCell,PercentCell,PercentCellPositiveNegative,SymbolCell,tw,} from "./components";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import { data } from "@1771technologies/grid-sample-data/dex-pairs-performance";const columns: Column<DEXPerformanceData>[] = [{id: "symbol",cellRenderer: SymbolCell,name: "Symbol",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(`${data.symbol.split("/")[0].trim()}${data.symbolTicker}`,p.grid.state.viewport.get(),).width;const iconWidth = 20;const gapWidth = 20;const padding = 24;return textWidth + iconWidth + gapWidth + padding;},},{id: "network",cellRenderer: NetworkCell,name: "Network",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.network, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "exchange",cellRenderer: ExchangeCell,name: "Exchange",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.exchange, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "change24h",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Change", "24h"),name: "Change % 24h",type: "number,",},{id: "perf1w",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1w"),name: "Perf % 1W",type: "number,",},{id: "perf1m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1m"),name: "Perf % 1M",type: "number,",},{id: "perf3m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "3m"),name: "Perf % 3M",type: "number,",},{id: "perf6m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "6m"),name: "Perf % 6M",type: "number,",},{id: "perfYtd",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "YTD"),name: "Perf % YTD",type: "number",},{ id: "volatility", cellRenderer: PercentCell, name: "Volatility", type: "number" },{id: "volatility1m",cellRenderer: PercentCell,headerRenderer: makePerfHeaderCell("Volatility", "1m"),name: "Volatility 1M",type: "number",},];export default function ColumnBase() {const ds = useClientRowDataSource({ data: data });const grid = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,});const view = grid.view.useValue();return (<div><div className="flex gap-2 border-b px-2 py-2"><GridButtononClick={() => {grid.api.columnAutosize({});}}>Autosize Cells</GridButton><GridButtononClick={() => {grid.state.columns.set(columns);}}>Reset Columns</GridButton></div><div className="lng-grid" style={{ height: 500 }}><Grid.Root grid={grid}><Grid.Viewport className="text-xs"><Grid.Header>{view.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className={tw("text-ln-gray-60! dark:text-ln-gray-70! flex h-full w-full items-center text-nowrap px-2 text-xs capitalize",c.column.type === "number" && "justify-end",)}/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{view.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="text-xs! flex h-full w-full items-center px-2"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div></div>);}
import type {CellRendererParams,HeaderCellRendererParams,} from "@1771technologies/lytenyte-pro/types";import type { ClassValue } from "clsx";import clsx from "clsx";import { twMerge } from "tailwind-merge";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import {exchanges,networks,symbols,} from "@1771technologies/grid-sample-data/dex-pairs-performance";import type { JSX } from "react";export function tw(...c: ClassValue[]) {return twMerge(clsx(...c));}export function SymbolCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const ticker = row.data.symbolTicker;const symbol = row.data.symbol;const image = symbols[row.data.symbolTicker];return (<div className="grid grid-cols-[20px_auto_auto] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for symbol ${symbol}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="bg-ln-gray-20 text-ln-gray-100 flex h-fit items-center justify-center rounded-lg px-2 py-px text-[10px]">{ticker}</div><div className="w-full overflow-hidden text-ellipsis">{symbol.split("/")[0]}</div></div>);}export function NetworkCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.network;const image = networks[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for network ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function ExchangeCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.exchange;const image = exchanges[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for exchange ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function PercentCellPositiveNegative({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return (<divclassName={tw("h-ful flex w-full items-center justify-end tabular-nums",field < 0 ? "text-red-600 dark:text-red-300" : "text-green-600 dark:text-green-300",)}>{value}</div>);}export function PercentCell({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return <div className="h-ful flex w-full items-center justify-end tabular-nums">{value}</div>;}export const makePerfHeaderCell = (name: string, subname: string) => {return (_: HeaderCellRendererParams<DEXPerformanceData>) => {return (<div className="flex h-full w-full flex-col items-end justify-center"><div>{name}</div><div className="font-mono uppercase">{subname}</div></div>);};};export function GridButton(props: JSX.IntrinsicElements["button"]) {return (<button{...props}className={tw("dark:border-ln-gray-30 border-ln-gray-60 hover:bg-ln-gray-20 text-ln-gray-80 flex h-10 cursor-pointer items-center gap-2 rounded-lg border px-2 text-sm font-semibold shadow-lg transition-colors","shadow-[inset_0_0_11px_0_hsla(176,55%,89%,0.12)] backdrop-blur-[10px]",props.className,)}></button>);}
The Autosize Cells button calls api.autosizeColumns to compute and apply
new widths. When you call this method without arguments,
the grid autosizes every column. Autosizing is a one off operation, if
cell data changes later, the grid does not autosize again.
Call api.autosizeColumns whenever updated content requires resizing.
<GridButtononClick={() => {grid.api.columnAutosize();}}/>
Autosize Header Text
The api.autosizeColumns method accepts an option that includes a column's header
content in the calculation. You can customize how header width
is measured using autosizeHeaderFn. This is useful when header text
is longer than any cell in the column.
In the demo below, the Crypto Currency Symbol, Ticker, and Name column has a long header, so autosizing expands the column to fit the header.
Autosize Header Text
"use client";import { Grid, measureText, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import type { Column } from "@1771technologies/lytenyte-pro/types";import { useId } from "react";import {ExchangeCell,GridButton,makePerfHeaderCell,NetworkCell,PercentCell,PercentCellPositiveNegative,SymbolCell,tw,} from "./components";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import { data } from "@1771technologies/grid-sample-data/dex-pairs-performance";const columns: Column<DEXPerformanceData>[] = [{id: "symbol",cellRenderer: SymbolCell,name: "Crypto Currency Symbol, Ticker, and Name",autosizeHeaderFn: (p) => {const textWidth = measureText(`${p.column.name ?? p.column.id}`,p.grid.state.viewport.get(),).width;const padding = 20;return textWidth + padding;},autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(`${data.symbol.split("/")[0].trim()}${data.symbolTicker}`,p.grid.state.viewport.get(),).width;const iconWidth = 20;const gapWidth = 20;const padding = 24;return textWidth + iconWidth + gapWidth + padding;},},{id: "network",cellRenderer: NetworkCell,name: "Network",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.network, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "exchange",cellRenderer: ExchangeCell,name: "Exchange",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.exchange, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "change24h",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Change", "24H"),name: "Change % 24h",type: "number,",},{id: "perf1w",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1W"),name: "Perf % 1W",type: "number,",},{id: "perf1m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1M"),name: "Perf % 1M",type: "number,",},{id: "perf3m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "3M"),name: "Perf % 3M",type: "number,",},{id: "perf6m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "6M"),name: "Perf % 6M",type: "number,",},{id: "perfYtd",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "YTD"),name: "Perf % YTD",type: "number",},{ id: "volatility", cellRenderer: PercentCell, name: "Volatility", type: "number" },{id: "volatility1m",cellRenderer: PercentCell,headerRenderer: makePerfHeaderCell("Volatility", "1M"),name: "Volatility 1M",type: "number",},];export default function ColumnBase() {const ds = useClientRowDataSource({ data: data });const grid = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,});const view = grid.view.useValue();return (<div><div className="flex gap-2 border-b px-2 py-2"><GridButtononClick={() => {grid.api.columnAutosize({ includeHeader: true });}}>Autosize Including Headers</GridButton><GridButtononClick={() => {grid.state.columns.set(columns);}}>Reset Columns</GridButton></div><div className="lng-grid" style={{ height: 500 }}><Grid.Root grid={grid}><Grid.Viewport className="text-xs"><Grid.Header>{view.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className={tw("text-ln-gray-60! dark:text-ln-gray-70! flex h-full w-full items-center text-nowrap px-2 text-xs",c.column.type === "number" && "justify-end",)}/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{view.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="text-xs! flex h-full w-full items-center px-2"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div></div>);}
import type {CellRendererParams,HeaderCellRendererParams,} from "@1771technologies/lytenyte-pro/types";import type { ClassValue } from "clsx";import clsx from "clsx";import { twMerge } from "tailwind-merge";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import {exchanges,networks,symbols,} from "@1771technologies/grid-sample-data/dex-pairs-performance";import type { JSX } from "react";export function tw(...c: ClassValue[]) {return twMerge(clsx(...c));}export function SymbolCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const ticker = row.data.symbolTicker;const symbol = row.data.symbol;const image = symbols[row.data.symbolTicker];return (<div className="grid grid-cols-[20px_auto_auto] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for symbol ${symbol}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="bg-ln-gray-20 text-ln-gray-100 flex h-fit items-center justify-center rounded-lg px-2 py-px text-[10px]">{ticker}</div><div className="w-full overflow-hidden text-ellipsis">{symbol.split("/")[0]}</div></div>);}export function NetworkCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.network;const image = networks[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for network ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function ExchangeCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.exchange;const image = exchanges[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for exchange ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function PercentCellPositiveNegative({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return (<divclassName={tw("h-ful flex w-full items-center justify-end tabular-nums",field < 0 ? "text-red-600 dark:text-red-300" : "text-green-600 dark:text-green-300",)}>{value}</div>);}export function PercentCell({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return <div className="h-ful flex w-full items-center justify-end tabular-nums">{value}</div>;}export const makePerfHeaderCell = (name: string, subname: string) => {return (_: HeaderCellRendererParams<DEXPerformanceData>) => {return (<div className="flex h-full w-full flex-col items-end justify-center"><div>{name}</div><div className="font-mono uppercase">{subname}</div></div>);};};export function GridButton(props: JSX.IntrinsicElements["button"]) {return (<button{...props}className={tw("dark:border-ln-gray-30 border-ln-gray-60 hover:bg-ln-gray-20 text-ln-gray-80 flex h-10 cursor-pointer items-center gap-2 rounded-lg border px-2 text-sm font-semibold shadow-lg transition-colors","shadow-[inset_0_0_11px_0_hsla(176,55%,89%,0.12)] backdrop-blur-[10px]",props.className,)}></button>);}
The Autosize Cells Including Header button
passes the includeHeader flag:
<GridButtononClick={() => {grid.api.columnAutosize({ includeHeader: true });}}/>
Autosize Calculators
Columns can define their own autosize calculation functions. When you
call api.autosizeColumns, the grid uses these functions to compute each width.
The example below shows the autosize functions used in this guide's demos:
const columns = [{id: "symbol",autosizeHeaderFn: (p) => {const textWidth = measureText(`${p.column.name ?? p.column.id}`,p.grid.state.viewport.get(),).width;const padding = 20;return textWidth + padding;},autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(`${data.symbol.split("/")[0].trim()}${data.symbolTicker}`,p.grid.state.viewport.get(),).width;const iconWidth = 20;const gapWidth = 20;const padding = 24;return textWidth + iconWidth + gapWidth + padding;},},];
These autosize functions mirror the structure of the cell renderer. They account for the logo icon,
ticker text, symbol name, and the spacing added by CSS.
The autosizeCellFn combines these measurements into a final width.
The autosizeHeaderFn and autosizeCellFn functions above use measureText, a utility exported
from the LyteNyte Grid package. The measureText function uses an offscreen
canvas to approximate text width based on the provided element, in this case the viewport.
Autosize Dry Run
The autosize method returns an AutosizeResult object mapping
column ids to their computed widths. It also supports a
dry-run mode that calculates widths without applying them:
const widths = grid.api.columnAutosize({ dryRun: true });
Autosizing Individual Columns
You can autosize only specific columns by passing their
identifiers to api.autosizeColumns. The demo below shows
Autosize Symbol and Autosize Network, which resize only those columns.
Autosize Individual Columns
"use client";import { Grid, measureText, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import type { Column } from "@1771technologies/lytenyte-pro/types";import { useId } from "react";import {ExchangeCell,GridButton,makePerfHeaderCell,NetworkCell,PercentCell,PercentCellPositiveNegative,SymbolCell,tw,} from "./components";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import { data } from "@1771technologies/grid-sample-data/dex-pairs-performance";const columns: Column<DEXPerformanceData>[] = [{id: "symbol",cellRenderer: SymbolCell,name: "Symbol",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(`${data.symbol.split("/")[0].trim()}${data.symbolTicker}`,p.grid.state.viewport.get(),).width;const iconWidth = 20;const gapWidth = 20;const padding = 24;return textWidth + iconWidth + gapWidth + padding;},},{id: "network",cellRenderer: NetworkCell,name: "Network",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.network, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "exchange",cellRenderer: ExchangeCell,name: "Exchange",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.exchange, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "change24h",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Change", "24h"),name: "Change % 24h",type: "number,",},{id: "perf1w",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1w"),name: "Perf % 1W",type: "number,",},{id: "perf1m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1m"),name: "Perf % 1M",type: "number,",},{id: "perf3m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "3m"),name: "Perf % 3M",type: "number,",},{id: "perf6m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "6m"),name: "Perf % 6M",type: "number,",},{id: "perfYtd",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "YTD"),name: "Perf % YTD",type: "number",},{ id: "volatility", cellRenderer: PercentCell, name: "Volatility", type: "number" },{id: "volatility1m",cellRenderer: PercentCell,headerRenderer: makePerfHeaderCell("Volatility", "1m"),name: "Volatility 1M",type: "number",},];export default function ColumnBase() {const ds = useClientRowDataSource({ data: data });const grid = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,});const view = grid.view.useValue();return (<div><div className="flex flex-wrap gap-2 border-b px-2 py-2"><GridButtononClick={() => {grid.api.columnAutosize({ columns: ["symbol"] });}}>Autosize Symbol</GridButton><GridButtononClick={() => {grid.api.columnAutosize({ columns: ["network"] });}}>Autosize Network</GridButton><GridButtononClick={() => {grid.api.columnAutosize({});}}>All Columns</GridButton><GridButtononClick={() => {grid.state.columns.set(columns);}}>Reset Columns</GridButton></div><div className="lng-grid" style={{ height: 500 }}><Grid.Root grid={grid}><Grid.Viewport className="text-xs"><Grid.Header>{view.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className={tw("text-ln-gray-60! dark:text-ln-gray-70! flex h-full w-full items-center text-nowrap px-2 text-xs capitalize",c.column.type === "number" && "justify-end",)}/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{view.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="text-xs! flex h-full w-full items-center px-2"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div></div>);}
import type {CellRendererParams,HeaderCellRendererParams,} from "@1771technologies/lytenyte-pro/types";import type { ClassValue } from "clsx";import clsx from "clsx";import { twMerge } from "tailwind-merge";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import {exchanges,networks,symbols,} from "@1771technologies/grid-sample-data/dex-pairs-performance";import type { JSX } from "react";export function tw(...c: ClassValue[]) {return twMerge(clsx(...c));}export function SymbolCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const ticker = row.data.symbolTicker;const symbol = row.data.symbol;const image = symbols[row.data.symbolTicker];return (<div className="grid grid-cols-[20px_auto_auto] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for symbol ${symbol}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="bg-ln-gray-20 text-ln-gray-100 flex h-fit items-center justify-center rounded-lg px-2 py-px text-[10px]">{ticker}</div><div className="w-full overflow-hidden text-ellipsis">{symbol.split("/")[0]}</div></div>);}export function NetworkCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.network;const image = networks[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for network ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function ExchangeCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.exchange;const image = exchanges[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for exchange ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function PercentCellPositiveNegative({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return (<divclassName={tw("h-ful flex w-full items-center justify-end tabular-nums",field < 0 ? "text-red-600 dark:text-red-300" : "text-green-600 dark:text-green-300",)}>{value}</div>);}export function PercentCell({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return <div className="h-ful flex w-full items-center justify-end tabular-nums">{value}</div>;}export const makePerfHeaderCell = (name: string, subname: string) => {return (_: HeaderCellRendererParams<DEXPerformanceData>) => {return (<div className="flex h-full w-full flex-col items-end justify-center"><div>{name}</div><div className="font-mono uppercase">{subname}</div></div>);};};export function GridButton(props: JSX.IntrinsicElements["button"]) {return (<button{...props}className={tw("dark:border-ln-gray-30 border-ln-gray-60 hover:bg-ln-gray-20 text-ln-gray-80 flex h-10 cursor-pointer items-center gap-2 rounded-lg border px-2 text-sm font-semibold shadow-lg transition-colors","shadow-[inset_0_0_11px_0_hsla(176,55%,89%,0.12)] backdrop-blur-[10px]",props.className,)}></button>);}
Specify the columns to autosize using any supported reference:
grid.api.columnAutosize({ columns: ["symbol"] });// Other ways to reference columns:grid.api.columnAutosize({ columns: [1] }); // By visible column indexgrid.api.columnAutosize({ columns: [{ id: "symbol" }] }); // By column object
Double Click To Resize
The columnDoubleClickToAutosize grid property controls whether LyteNyte
Grid autosizes a column when you double-click the column's right edge.
Try double-clicking the header edges in the demo below.
Double Click To Autosize
"use client";import { Grid, measureText, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import type { Column } from "@1771technologies/lytenyte-pro/types";import { useId } from "react";import {ExchangeCell,makePerfHeaderCell,NetworkCell,PercentCell,PercentCellPositiveNegative,SymbolCell,tw,} from "./components";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import { data } from "@1771technologies/grid-sample-data/dex-pairs-performance";const columns: Column<DEXPerformanceData>[] = [{id: "symbol",cellRenderer: SymbolCell,name: "Symbol",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(`${data.symbol.split("/")[0].trim()}${data.symbolTicker}`,p.grid.state.viewport.get(),).width;const iconWidth = 20;const gapWidth = 20;const padding = 24;return textWidth + iconWidth + gapWidth + padding;},},{id: "network",cellRenderer: NetworkCell,name: "Network",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.network, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "exchange",cellRenderer: ExchangeCell,name: "Exchange",autosizeCellFn: (p) => {if (p.row.kind !== "leaf" || !p.row.data) return null;const data = p.row.data;const textWidth = measureText(data.exchange, p.grid.state.viewport.get()).width;const iconWidth = 20;const gapWidth = 6;const padding = 20;return textWidth + iconWidth + gapWidth + padding;},},{id: "change24h",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Change", "24h"),name: "Change % 24h",type: "number,",},{id: "perf1w",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1w"),name: "Perf % 1W",type: "number,",},{id: "perf1m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "1m"),name: "Perf % 1M",type: "number,",},{id: "perf3m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "3m"),name: "Perf % 3M",type: "number,",},{id: "perf6m",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "6m"),name: "Perf % 6M",type: "number,",},{id: "perfYtd",cellRenderer: PercentCellPositiveNegative,headerRenderer: makePerfHeaderCell("Perf %", "YTD"),name: "Perf % YTD",type: "number",},{ id: "volatility", cellRenderer: PercentCell, name: "Volatility", type: "number" },{id: "volatility1m",cellRenderer: PercentCell,headerRenderer: makePerfHeaderCell("Volatility", "1m"),name: "Volatility 1M",type: "number",},];export default function ColumnBase() {const ds = useClientRowDataSource({ data: data });const grid = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,columnDoubleClickToAutosize: true,columnBase: {uiHints: {resizable: true,},},});const view = grid.view.useValue();return (<div className="lng-grid" style={{ height: 500 }}><Grid.Root grid={grid}><Grid.Viewport className="text-xs"><Grid.Header>{view.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className={tw("text-ln-gray-60! dark:text-ln-gray-70! flex h-full w-full items-center text-nowrap px-2 text-xs capitalize",c.column.type === "number" && "justify-end",)}/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{view.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="text-xs! flex h-full w-full items-center px-2"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div>);}
import type {CellRendererParams,HeaderCellRendererParams,} from "@1771technologies/lytenyte-pro/types";import type { ClassValue } from "clsx";import clsx from "clsx";import { twMerge } from "tailwind-merge";import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";import {exchanges,networks,symbols,} from "@1771technologies/grid-sample-data/dex-pairs-performance";import type { JSX } from "react";export function tw(...c: ClassValue[]) {return twMerge(clsx(...c));}export function SymbolCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const ticker = row.data.symbolTicker;const symbol = row.data.symbol;const image = symbols[row.data.symbolTicker];return (<div className="grid grid-cols-[20px_auto_auto] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for symbol ${symbol}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="bg-ln-gray-20 text-ln-gray-100 flex h-fit items-center justify-center rounded-lg px-2 py-px text-[10px]">{ticker}</div><div className="w-full overflow-hidden text-ellipsis">{symbol.split("/")[0]}</div></div>);}export function NetworkCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.network;const image = networks[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for network ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function ExchangeCell({ grid: { api }, row }: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const name = row.data.exchange;const image = exchanges[name];return (<div className="grid grid-cols-[20px_1fr] items-center gap-1.5"><div><imgsrc={image}alt={`Logo for exchange ${name}`}className="h-full w-full overflow-hidden rounded-full"/></div><div className="w-full overflow-hidden text-ellipsis">{name}</div></div>);}export function PercentCellPositiveNegative({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return (<divclassName={tw("h-ful flex w-full items-center justify-end tabular-nums",field < 0 ? "text-red-600 dark:text-red-300" : "text-green-600 dark:text-green-300",)}>{value}</div>);}export function PercentCell({grid: { api },column,row,}: CellRendererParams<DEXPerformanceData>) {if (!api.rowIsLeaf(row) || !row.data) return null;const field = api.columnField(column, row);if (typeof field !== "number") return "-";const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";return <div className="h-ful flex w-full items-center justify-end tabular-nums">{value}</div>;}export const makePerfHeaderCell = (name: string, subname: string) => {return (_: HeaderCellRendererParams<DEXPerformanceData>) => {return (<div className="flex h-full w-full flex-col items-end justify-center"><div>{name}</div><div className="font-mono uppercase">{subname}</div></div>);};};export function GridButton(props: JSX.IntrinsicElements["button"]) {return (<button{...props}className={tw("dark:border-ln-gray-30 border-ln-gray-60 hover:bg-ln-gray-20 text-ln-gray-80 flex h-10 cursor-pointer items-center gap-2 rounded-lg border px-2 text-sm font-semibold shadow-lg transition-colors","shadow-[inset_0_0_11px_0_hsla(176,55%,89%,0.12)] backdrop-blur-[10px]",props.className,)}></button>);}
For double-click autosizing to work, the column must be resizable.
Set the resizable property on the column's uiHints.
The example below enables double-click resizing for all columns:
const grid = Grid.useLyteNyte({// Other grid propscolumnDoubleClickToAutosize: true,columnBase: {uiHints: {resizable: true,},},});
Virtualization Considerations
LyteNyte Grid uses row virtualization for performance. Autosizing considers only visible rows. If the viewport has not initialized, the grid samples about 50 rows for calculations.
If a column has a known set of possible values, consider writing an autosize function that returns the maximum width required for those values, independent of viewport visibility.
To learn more about LyteNyte Grid's virtualization features and configuration options, see the Row & Column Virtualization guide.
LyteNyte Grid autosizes every column by default, even when virtualization keeps a column out of the DOM. If your grid defines many columns, consider autosizing only the visible ones to avoid unnecessary work.
Next Steps
- Column Resizing: Change column widths programmatically or through user interaction.
- Column ID & Name: Define user-friendly column names and ensure unique IDs.
- Column Field: Control how a column retrieves each cell's value.
- Row and Column Virtualization: Learn how LyteNyte Grid enables performance at scale with row and column virtualization.
Column Header Height
LyteNyte Grid lets you customize header height flexibly. The header's total height comes from the combined height of column group headers, column headers, and floating headers.
Column Moving
Columns in LyteNyte Grid may be reordered programmatically or through the grid's drag-and-drop header interactions. Column groups can also be reordered as a unit.