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 <span className="iconify ph--gender-male-bold size-4" />40 </div>41 Male42 </div>43 );44
45 if (field === "Female")46 return (47 <div className="flex h-full w-full items-center gap-2">48 <div className="flex size-6 items-center justify-center rounded-full bg-pink-500/50">49 <span className="iconify ph--gender-female-bold size-4" />50 </div>51 Female52 </div>53 );54
55 return "-";56}57
58function tw(...c: ClassValue[]) {59 return twMerge(clsx(...c));60}61
62const formatter = new Intl.NumberFormat("en-US", {63 maximumFractionDigits: 2,64 minimumFractionDigits: 0,65});66export function ProfitCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {67 const field = api.columnField(column, row);68
69 if (typeof field !== "number") return "-";70
71 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);72
73 return (74 <div75 className={tw(76 "flex h-full w-full items-center justify-end tabular-nums",77 field < 0 && "text-red-600 dark:text-red-300",78 field > 0 && "text-green-600 dark:text-green-300",79 )}80 >81 {formatted}82 </div>83 );84}85
86export function NumberCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {87 const field = api.columnField(column, row);88
89 if (typeof field !== "number") return "-";90
91 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);92
93 return <div className={"flex h-full w-full items-center justify-end tabular-nums"}>{formatted}</div>;94}95
96export function CostCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {97 const field = api.columnField(column, row);98
99 if (typeof field !== "number") return "-";100
101 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);102
103 return (104 <div105 className={tw(106 "flex h-full w-full items-center justify-end tabular-nums text-red-600 dark:text-red-300",107 )}108 >109 {formatted}110 </div>111 );112}113
114export function CountryCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {115 const field = api.columnField(column, row);116
117 const flag = countryFlags[field as keyof typeof countryFlags];118 if (!flag) return "-";119
120 return (121 <div className="flex h-full w-full items-center gap-2">122 <img className="size-4" src={flag} alt={`country flag of ${field}`} />123 <span>{String(field ?? "-")}</span>124 </div>125 );126}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 <span className="iconify ph--gender-male-bold size-4" />40 </div>41 Male42 </div>43 );44
45 if (field === "Female")46 return (47 <div className="flex h-full w-full items-center gap-2">48 <div className="flex size-6 items-center justify-center rounded-full bg-pink-500/50">49 <span className="iconify ph--gender-female-bold size-4" />50 </div>51 Female52 </div>53 );54
55 return "-";56}57
58function tw(...c: ClassValue[]) {59 return twMerge(clsx(...c));60}61
62const formatter = new Intl.NumberFormat("en-US", {63 maximumFractionDigits: 2,64 minimumFractionDigits: 0,65});66export function ProfitCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {67 const field = api.columnField(column, row);68
69 if (typeof field !== "number") return "-";70
71 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);72
73 return (74 <div75 className={tw(76 "flex h-full w-full items-center justify-end tabular-nums",77 field < 0 && "text-red-600 dark:text-red-300",78 field > 0 && "text-green-600 dark:text-green-300",79 )}80 >81 {formatted}82 </div>83 );84}85
86export function NumberCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {87 const field = api.columnField(column, row);88
89 if (typeof field !== "number") return "-";90
91 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);92
93 return <div className={"flex h-full w-full items-center justify-end tabular-nums"}>{formatted}</div>;94}95
96export function CostCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {97 const field = api.columnField(column, row);98
99 if (typeof field !== "number") return "-";100
101 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);102
103 return (104 <div105 className={tw(106 "flex h-full w-full items-center justify-end tabular-nums text-red-600 dark:text-red-300",107 )}108 >109 {formatted}110 </div>111 );112}113
114export function CountryCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {115 const field = api.columnField(column, row);116
117 const flag = countryFlags[field as keyof typeof countryFlags];118 if (!flag) return "-";119
120 return (121 <div className="flex h-full w-full items-center gap-2">122 <img className="size-4" src={flag} alt={`country flag of ${field}`} />123 <span>{String(field ?? "-")}</span>124 </div>125 );126}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 <span className="iconify ph--gender-male-bold size-4" />40 </div>41 Male42 </div>43 );44
45 if (field === "Female")46 return (47 <div className="flex h-full w-full items-center gap-2">48 <div className="flex size-6 items-center justify-center rounded-full bg-pink-500/50">49 <span className="iconify ph--gender-female-bold size-4" />50 </div>51 Female52 </div>53 );54
55 return "-";56}57
58function tw(...c: ClassValue[]) {59 return twMerge(clsx(...c));60}61
62const formatter = new Intl.NumberFormat("en-US", {63 maximumFractionDigits: 2,64 minimumFractionDigits: 0,65});66export function ProfitCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {67 const field = api.columnField(column, row);68
69 if (typeof field !== "number") return "-";70
71 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);72
73 return (74 <div75 className={tw(76 "flex h-full w-full items-center justify-end tabular-nums",77 field < 0 && "text-red-600 dark:text-red-300",78 field > 0 && "text-green-600 dark:text-green-300",79 )}80 >81 {formatted}82 </div>83 );84}85
86export function NumberCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {87 const field = api.columnField(column, row);88
89 if (typeof field !== "number") return "-";90
91 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);92
93 return <div className={"flex h-full w-full items-center justify-end tabular-nums"}>{formatted}</div>;94}95
96export function CostCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {97 const field = api.columnField(column, row);98
99 if (typeof field !== "number") return "-";100
101 const formatted = field < 0 ? `-$${formatter.format(Math.abs(field))}` : "$" + formatter.format(field);102
103 return (104 <div105 className={tw(106 "flex h-full w-full items-center justify-end tabular-nums text-red-600 dark:text-red-300",107 )}108 >109 {formatted}110 </div>111 );112}113
114export function CountryCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {115 const field = api.columnField(column, row);116
117 const flag = countryFlags[field as keyof typeof countryFlags];118 if (!flag) return "-";119
120 return (121 <div className="flex h-full w-full items-center gap-2">122 <img className="size-4" src={flag} alt={`country flag of ${field}`} />123 <span>{String(field ?? "-")}</span>124 </div>125 );126}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.
