Responsive Container
Learn how to create a responsive container for LyteNyte Grid using various approaches, from fixed sized containers to grid and flex based containers.
LyteNyte Grid lacks inherent dimensions. Since the viewport fills its parent container, the parent must have a defined size for the grid to render correctly. Although it doesn’t have a defined size, LyteNyte Grid remains fully responsive. To ensure responsiveness, make its parent element responsive.
Warning
Place LyteNyte Grid as the sole child of its container. Do not include sibling elements.
Pixel Value
Fixed pixel values for height and width provide the simplest sizing option but
limit flexibility. In most cases, set a fixed height and allow the
width to adjust based on the parent container. Set a fixed width only when necessary.
Pixel Height and Width
1import "@1771technologies/lytenyte-pro/components.css";2import "@1771technologies/lytenyte-pro/light-dark.css";3import { salesData, type SaleDataItem } from "@1771technologies/grid-sample-data/sales-data";4import { Grid, useClientDataSource } from "@1771technologies/lytenyte-pro";5import {6 AgeGroup,7 CostCell,8 CountryCell,9 DateCell,10 GenderCell,11 NumberCell,12 ProfitCell,13} from "./components.js";14
15export interface GridSpec {16 readonly data: SaleDataItem;17}18
19export const columns: Grid.Column<GridSpec>[] = [20 { id: "date", name: "Date", cellRenderer: DateCell, width: 110 },21 { id: "age", name: "Age", type: "number", width: 80 },22 { id: "ageGroup", name: "Age Group", cellRenderer: AgeGroup, width: 160 },23 { id: "customerGender", name: "Gender", cellRenderer: GenderCell, width: 100 },24 { id: "country", name: "Country", cellRenderer: CountryCell, width: 150 },25 { id: "orderQuantity", name: "Quantity", type: "number", width: 60 },26 { id: "unitPrice", name: "Price", type: "number", width: 80, cellRenderer: NumberCell },27 { id: "cost", name: "Cost", width: 80, type: "number", cellRenderer: CostCell },28 { id: "revenue", name: "Revenue", width: 80, type: "number", cellRenderer: ProfitCell },29 { id: "profit", name: "Profit", width: 80, type: "number", cellRenderer: ProfitCell },30 { id: "state", name: "State", width: 150 },31 { id: "product", name: "Product", width: 160 },32 { id: "productCategory", name: "Category", width: 120 },33 { id: "subCategory", name: "Sub-Category", width: 160 },34];35
36const base: Grid.ColumnBase<GridSpec> = { width: 120 };37
38export default function GridDemo() {39 const ds = useClientDataSource<GridSpec>({40 data: salesData,41 });42
43 return (44 <div className="ln-grid" style={{ height: 500 }}>45 <Grid columns={columns} columnBase={base} rowSource={ds} />46 </div>47 );48}1import type { Grid } from "@1771technologies/lytenyte-pro";2import type { GridSpec } from "./demo";3import { format, isValid, parse } from "date-fns";4import { countryFlags } from "@1771technologies/grid-sample-data/sales-data";5import { clsx, type ClassValue } from "clsx";6import { twMerge } from "tailwind-merge";7
8export function DateCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {9 const field = api.columnField(column, row);10
11 if (typeof field !== "string") return "-";12
13 const dateField = parse(field as string, "MM/dd/yyyy", new Date());14
15 if (!isValid(dateField)) return "-";16
17 const niceDate = format(dateField, "yyyy MMM dd");18 return <div className="flex h-full w-full items-center tabular-nums">{niceDate}</div>;19}20
21export function AgeGroup({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {22 const field = api.columnField(column, row);23
24 if (field === "Youth (<25)") return <div className="text-[#944cec] dark:text-[#B181EB]">{field}</div>;25 if (field === "Young Adults (25-34)")26 return <div className="text-[#aa6c1a] dark:text-[#E5B474]">{field}</div>;27 if (field === "Adults (35-64)") return <div className="text-[#0f7d4c] dark:text-[#52B086]">{field}</div>;28
29 return "-";30}31
32export function GenderCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {33 const field = api.columnField(column, row);34
35 if (field === "Male")36 return (37 <div className="flex h-full w-full items-center gap-2">38 <div className="flex size-6 items-center justify-center rounded-full bg-blue-500/50">39 <svg width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">40 <path d="M216,28H168a12,12,0,0,0,0,24h19L154.28,84.74a84,84,0,1,0,17,17L204,69V88a12,12,0,0,0,24,0V40A12,12,0,0,0,216,28ZM146.41,194.46a60,60,0,1,1,0-84.87A60.1,60.1,0,0,1,146.41,194.46Z"></path>41 </svg>42 </div>43 Male44 </div>45 );46
47 if (field === "Female")48 return (49 <div className="flex h-full w-full items-center gap-2">50 <div className="flex size-6 items-center justify-center rounded-full bg-pink-500/50">51 <svg width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">52 <path d="M212,96a84,84,0,1,0-96,83.13V196H88a12,12,0,0,0,0,24h28v20a12,12,0,0,0,24,0V220h28a12,12,0,0,0,0-24H140V179.13A84.12,84.12,0,0,0,212,96ZM68,96a60,60,0,1,1,60,60A60.07,60.07,0,0,1,68,96Z"></path>53 </svg>54 </div>55 Female56 </div>57 );58
59 return "-";60}61
62function tw(...c: ClassValue[]) {63 return twMerge(clsx(...c));64}65
66const formatter = new Intl.NumberFormat("en-US", {67 maximumFractionDigits: 2,68 minimumFractionDigits: 0,69});70export function ProfitCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {71 const field = api.columnField(column, row);72
73 if (typeof field !== "number") return "-";74
75 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);76
77 return (78 <div79 className={tw(80 "flex h-full w-full items-center justify-end tabular-nums",81 field < 0 && "text-red-600 dark:text-red-300",82 field > 0 && "text-green-600 dark:text-green-300",83 )}84 >85 {formatted}86 </div>87 );88}89
90export function NumberCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {91 const field = api.columnField(column, row);92
93 if (typeof field !== "number") return "-";94
95 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);96
97 return <div className={"flex h-full w-full items-center justify-end tabular-nums"}>{formatted}</div>;98}99
100export function CostCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {101 const field = api.columnField(column, row);102
103 if (typeof field !== "number") return "-";104
105 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);106
107 return (108 <div109 className={tw(110 "flex h-full w-full items-center justify-end tabular-nums text-red-600 dark:text-red-300",111 )}112 >113 {formatted}114 </div>115 );116}117
118export function CountryCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {119 const field = api.columnField(column, row);120
121 const flag = countryFlags[field as keyof typeof countryFlags];122 if (!flag) return "-";123
124 return (125 <div className="flex h-full w-full items-center gap-2">126 <img className="size-4" src={flag} alt={`country flag of ${field}`} />127 <span>{String(field ?? "-")}</span>128 </div>129 );130}The height of the grid viewport comes from the div that contains it, which uses
the following code:
1<div style={{ height: 500 }}>{/* Grid components */}</div>Flex Height Containers
Flexbox layouts distribute space dynamically. When you assign flex: 1 to a Flexbox child,
it expands to fill the available space. Since Flexbox containers grow with their content,
wrap LyteNyte Grid in an inner div with position: absolute, width: 100%,
and height: 100% to prevent the grid from increasing the container’s height.
The grid then fills the space without overflow.
Flex Height Container
1import "@1771technologies/lytenyte-pro/components.css";2import "@1771technologies/lytenyte-pro/light-dark.css";3import { salesData, type SaleDataItem } from "@1771technologies/grid-sample-data/sales-data";4import { Grid, useClientDataSource } from "@1771technologies/lytenyte-pro";5import {6 AgeGroup,7 CostCell,8 CountryCell,9 DateCell,10 GenderCell,11 NumberCell,12 ProfitCell,13} from "./components.js";14
15export interface GridSpec {16 readonly data: SaleDataItem;17}18
19export const columns: Grid.Column<GridSpec>[] = [20 { id: "date", name: "Date", cellRenderer: DateCell, width: 110 },21 { id: "age", name: "Age", type: "number", width: 80 },22 { id: "ageGroup", name: "Age Group", cellRenderer: AgeGroup, width: 160 },23 { id: "customerGender", name: "Gender", cellRenderer: GenderCell, width: 100 },24 { id: "country", name: "Country", cellRenderer: CountryCell, width: 150 },25 { id: "orderQuantity", name: "Quantity", type: "number", width: 60 },26 { id: "unitPrice", name: "Price", type: "number", width: 80, cellRenderer: NumberCell },27 { id: "cost", name: "Cost", width: 80, type: "number", cellRenderer: CostCell },28 { id: "revenue", name: "Revenue", width: 80, type: "number", cellRenderer: ProfitCell },29 { id: "profit", name: "Profit", width: 80, type: "number", cellRenderer: ProfitCell },30 { id: "state", name: "State", width: 150 },31 { id: "product", name: "Product", width: 160 },32 { id: "productCategory", name: "Category", width: 120 },33 { id: "subCategory", name: "Sub-Category", width: 160 },34];35
36const base: Grid.ColumnBase<GridSpec> = { width: 120 };37
38export default function GridDemo() {39 const ds = useClientDataSource<GridSpec>({40 data: salesData,41 });42
43 return (44 <div className="ln-grid" style={{ height: 500, display: "flex", flexDirection: "column" }}>45 <div style={{ flex: "1", position: "relative" }}>46 <div style={{ position: "absolute", width: "100%", height: "100%" }}>47 <Grid columns={columns} columnBase={base} rowSource={ds} />48 </div>49 </div>50 </div>51 );52}1import type { Grid } from "@1771technologies/lytenyte-pro";2import type { GridSpec } from "./demo";3import { format, isValid, parse } from "date-fns";4import { countryFlags } from "@1771technologies/grid-sample-data/sales-data";5import { clsx, type ClassValue } from "clsx";6import { twMerge } from "tailwind-merge";7
8export function DateCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {9 const field = api.columnField(column, row);10
11 if (typeof field !== "string") return "-";12
13 const dateField = parse(field as string, "MM/dd/yyyy", new Date());14
15 if (!isValid(dateField)) return "-";16
17 const niceDate = format(dateField, "yyyy MMM dd");18 return <div className="flex h-full w-full items-center tabular-nums">{niceDate}</div>;19}20
21export function AgeGroup({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {22 const field = api.columnField(column, row);23
24 if (field === "Youth (<25)") return <div className="text-[#944cec] dark:text-[#B181EB]">{field}</div>;25 if (field === "Young Adults (25-34)")26 return <div className="text-[#aa6c1a] dark:text-[#E5B474]">{field}</div>;27 if (field === "Adults (35-64)") return <div className="text-[#0f7d4c] dark:text-[#52B086]">{field}</div>;28
29 return "-";30}31
32export function GenderCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {33 const field = api.columnField(column, row);34
35 if (field === "Male")36 return (37 <div className="flex h-full w-full items-center gap-2">38 <div className="flex size-6 items-center justify-center rounded-full bg-blue-500/50">39 <svg width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">40 <path d="M216,28H168a12,12,0,0,0,0,24h19L154.28,84.74a84,84,0,1,0,17,17L204,69V88a12,12,0,0,0,24,0V40A12,12,0,0,0,216,28ZM146.41,194.46a60,60,0,1,1,0-84.87A60.1,60.1,0,0,1,146.41,194.46Z"></path>41 </svg>42 </div>43 Male44 </div>45 );46
47 if (field === "Female")48 return (49 <div className="flex h-full w-full items-center gap-2">50 <div className="flex size-6 items-center justify-center rounded-full bg-pink-500/50">51 <svg width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">52 <path d="M212,96a84,84,0,1,0-96,83.13V196H88a12,12,0,0,0,0,24h28v20a12,12,0,0,0,24,0V220h28a12,12,0,0,0,0-24H140V179.13A84.12,84.12,0,0,0,212,96ZM68,96a60,60,0,1,1,60,60A60.07,60.07,0,0,1,68,96Z"></path>53 </svg>54 </div>55 Female56 </div>57 );58
59 return "-";60}61
62function tw(...c: ClassValue[]) {63 return twMerge(clsx(...c));64}65
66const formatter = new Intl.NumberFormat("en-US", {67 maximumFractionDigits: 2,68 minimumFractionDigits: 0,69});70export function ProfitCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {71 const field = api.columnField(column, row);72
73 if (typeof field !== "number") return "-";74
75 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);76
77 return (78 <div79 className={tw(80 "flex h-full w-full items-center justify-end tabular-nums",81 field < 0 && "text-red-600 dark:text-red-300",82 field > 0 && "text-green-600 dark:text-green-300",83 )}84 >85 {formatted}86 </div>87 );88}89
90export function NumberCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {91 const field = api.columnField(column, row);92
93 if (typeof field !== "number") return "-";94
95 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);96
97 return <div className={"flex h-full w-full items-center justify-end tabular-nums"}>{formatted}</div>;98}99
100export function CostCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {101 const field = api.columnField(column, row);102
103 if (typeof field !== "number") return "-";104
105 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);106
107 return (108 <div109 className={tw(110 "flex h-full w-full items-center justify-end tabular-nums text-red-600 dark:text-red-300",111 )}112 >113 {formatted}114 </div>115 );116}117
118export function CountryCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {119 const field = api.columnField(column, row);120
121 const flag = countryFlags[field as keyof typeof countryFlags];122 if (!flag) return "-";123
124 return (125 <div className="flex h-full w-full items-center gap-2">126 <img className="size-4" src={flag} alt={`country flag of ${field}`} />127 <span>{String(field ?? "-")}</span>128 </div>129 );130}The flex height container uses a series of nested divs, as shown below:
1<div style={{ height: 500, display: "flex", flexDirection: "column" }}>2 <div style={{ flex: "1", position: "relative" }}>3 <div style={{ position: "absolute", width: "100%", height: "100%" }}>{/* Grid components */}</div>4 </div>5</div>Grid Height Containers
CSS Grid (not to be confused with LyteNyte Grid) provides a flexible layout system. Within a grid container, LyteNyte Grid expands to fill its assigned area while remaining properly contained.
Grid Height Container
1import "@1771technologies/lytenyte-pro/components.css";2import "@1771technologies/lytenyte-pro/light-dark.css";3import { salesData, type SaleDataItem } from "@1771technologies/grid-sample-data/sales-data";4import { Grid, useClientDataSource } from "@1771technologies/lytenyte-pro";5import {6 AgeGroup,7 CostCell,8 CountryCell,9 DateCell,10 GenderCell,11 NumberCell,12 ProfitCell,13} from "./components.js";14
15export interface GridSpec {16 readonly data: SaleDataItem;17}18
19export const columns: Grid.Column<GridSpec>[] = [20 { id: "date", name: "Date", cellRenderer: DateCell, width: 110 },21 { id: "age", name: "Age", type: "number", width: 80 },22 { id: "ageGroup", name: "Age Group", cellRenderer: AgeGroup, width: 160 },23 { id: "customerGender", name: "Gender", cellRenderer: GenderCell, width: 100 },24 { id: "country", name: "Country", cellRenderer: CountryCell, width: 150 },25 { id: "orderQuantity", name: "Quantity", type: "number", width: 60 },26 { id: "unitPrice", name: "Price", type: "number", width: 80, cellRenderer: NumberCell },27 { id: "cost", name: "Cost", width: 80, type: "number", cellRenderer: CostCell },28 { id: "revenue", name: "Revenue", width: 80, type: "number", cellRenderer: ProfitCell },29 { id: "profit", name: "Profit", width: 80, type: "number", cellRenderer: ProfitCell },30 { id: "state", name: "State", width: 150 },31 { id: "product", name: "Product", width: 160 },32 { id: "productCategory", name: "Category", width: 120 },33 { id: "subCategory", name: "Sub-Category", width: 160 },34];35
36const base: Grid.ColumnBase<GridSpec> = { width: 120 };37
38export default function GridDemo() {39 const ds = useClientDataSource<GridSpec>({40 data: salesData,41 });42
43 return (44 <div className="ln-grid" style={{ display: "grid", gridTemplateRows: "500px" }}>45 <Grid columns={columns} columnBase={base} rowSource={ds} />46 </div>47 );48}1import type { Grid } from "@1771technologies/lytenyte-pro";2import type { GridSpec } from "./demo";3import { format, isValid, parse } from "date-fns";4import { countryFlags } from "@1771technologies/grid-sample-data/sales-data";5import { clsx, type ClassValue } from "clsx";6import { twMerge } from "tailwind-merge";7
8export function DateCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {9 const field = api.columnField(column, row);10
11 if (typeof field !== "string") return "-";12
13 const dateField = parse(field as string, "MM/dd/yyyy", new Date());14
15 if (!isValid(dateField)) return "-";16
17 const niceDate = format(dateField, "yyyy MMM dd");18 return <div className="flex h-full w-full items-center tabular-nums">{niceDate}</div>;19}20
21export function AgeGroup({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {22 const field = api.columnField(column, row);23
24 if (field === "Youth (<25)") return <div className="text-[#944cec] dark:text-[#B181EB]">{field}</div>;25 if (field === "Young Adults (25-34)")26 return <div className="text-[#aa6c1a] dark:text-[#E5B474]">{field}</div>;27 if (field === "Adults (35-64)") return <div className="text-[#0f7d4c] dark:text-[#52B086]">{field}</div>;28
29 return "-";30}31
32export function GenderCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {33 const field = api.columnField(column, row);34
35 if (field === "Male")36 return (37 <div className="flex h-full w-full items-center gap-2">38 <div className="flex size-6 items-center justify-center rounded-full bg-blue-500/50">39 <svg width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">40 <path d="M216,28H168a12,12,0,0,0,0,24h19L154.28,84.74a84,84,0,1,0,17,17L204,69V88a12,12,0,0,0,24,0V40A12,12,0,0,0,216,28ZM146.41,194.46a60,60,0,1,1,0-84.87A60.1,60.1,0,0,1,146.41,194.46Z"></path>41 </svg>42 </div>43 Male44 </div>45 );46
47 if (field === "Female")48 return (49 <div className="flex h-full w-full items-center gap-2">50 <div className="flex size-6 items-center justify-center rounded-full bg-pink-500/50">51 <svg width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">52 <path d="M212,96a84,84,0,1,0-96,83.13V196H88a12,12,0,0,0,0,24h28v20a12,12,0,0,0,24,0V220h28a12,12,0,0,0,0-24H140V179.13A84.12,84.12,0,0,0,212,96ZM68,96a60,60,0,1,1,60,60A60.07,60.07,0,0,1,68,96Z"></path>53 </svg>54 </div>55 Female56 </div>57 );58
59 return "-";60}61
62function tw(...c: ClassValue[]) {63 return twMerge(clsx(...c));64}65
66const formatter = new Intl.NumberFormat("en-US", {67 maximumFractionDigits: 2,68 minimumFractionDigits: 0,69});70export function ProfitCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {71 const field = api.columnField(column, row);72
73 if (typeof field !== "number") return "-";74
75 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);76
77 return (78 <div79 className={tw(80 "flex h-full w-full items-center justify-end tabular-nums",81 field < 0 && "text-red-600 dark:text-red-300",82 field > 0 && "text-green-600 dark:text-green-300",83 )}84 >85 {formatted}86 </div>87 );88}89
90export function NumberCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {91 const field = api.columnField(column, row);92
93 if (typeof field !== "number") return "-";94
95 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);96
97 return <div className={"flex h-full w-full items-center justify-end tabular-nums"}>{formatted}</div>;98}99
100export function CostCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {101 const field = api.columnField(column, row);102
103 if (typeof field !== "number") return "-";104
105 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);106
107 return (108 <div109 className={tw(110 "flex h-full w-full items-center justify-end tabular-nums text-red-600 dark:text-red-300",111 )}112 >113 {formatted}114 </div>115 );116}117
118export function CountryCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {119 const field = api.columnField(column, row);120
121 const flag = countryFlags[field as keyof typeof countryFlags];122 if (!flag) return "-";123
124 return (125 <div className="flex h-full w-full items-center gap-2">126 <img className="size-4" src={flag} alt={`country flag of ${field}`} />127 <span>{String(field ?? "-")}</span>128 </div>129 );130}The grid height container uses the grid-template-rows property, as shown below:
1<div style={{ display: "grid", gridTemplateRows: "500px" }}>{/* Grid components */}</div>Other CSS sizing techniques also work well with LyteNyte Grid. Choose the approach that best fits your layout requirements.
Next Steps
- Headless Component Parts: Learn about the component parts that make up LyteNyte Grid.
- Grid Reactivity: Learn how LyteNyte Grid enables declarative reactivity.
- Row & Column Virtualization: Learn how LyteNyte Grid enables performance at scale with row and column virtualization.
