Keyboard & Accessibility
RTL Support
Enable right-to-left rendering with a single configuration option.
Enable Right-to-Left (RTL) rendering by setting the rtl property directly on the grid component.
This explicit flag is required because the grid does not detect RTL based
on the CSS direction property of the viewport or parent elements.
Right-to-Left Mode
33 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 SwitchToggle,13} from "./components.jsx";14import { useClientDataSource, Grid, type Piece, usePiece } from "@1771technologies/lytenyte-pro";15import { useState } from "react";16import { ViewportShadows } from "@1771technologies/lytenyte-pro/components";17
18export interface GridSpec {19 readonly data: OrderData;20 readonly api: {21 rtl$: Piece<boolean>;22 };23}24
25const columns: Grid.Column<GridSpec>[] = [26 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },27 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },28 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },29 { id: "customer", cellRenderer: AvatarCell, width: 180, name: "Customer" },30 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130 },31 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },32 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email" },33];34
35export default function Demo() {36 const [rtl, setRtl] = useState(true);37 const ds = useClientDataSource<GridSpec>({38 data,39 });40
41 return (42 <>43 <div className="border-ln-border flex w-full border-b px-2 py-2">44 <SwitchToggle45 label="RTL"46 checked={rtl}47 onChange={() => {48 setRtl((prev) => !prev);49 }}50 />51 </div>52
53 <div className="ln-grid" style={{ height: 500 }}>54 <Grid55 apiExtension={{ rtl$: usePiece(rtl) }}56 rowHeight={50}57 columns={columns}58 rowSource={ds}59 slotShadows={ViewportShadows}60 rtl={rtl}61 />62 </div>63 </>64 );65}1import { format } from "date-fns";2import { useId, type CSSProperties, type JSX, type ReactNode } from "react";3import type { Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5import { Switch } from "radix-ui";6
7export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {8 if (!api.rowIsLeaf(row) || !row.data) return;9
10 const url = row.data?.productThumbnail;11 const title = row.data.product;12 const desc = row.data.productDescription;13
14 return (15 <div className="flex h-full w-full items-center gap-2">16 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />17 <div className="text-ln-text-dark flex flex-col gap-0.5">18 <div className="font-semibold">{title}</div>19 <div className="text-ln-text-light text-xs">{desc}</div>20 </div>21 </div>22 );23}24
25export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {26 if (!api.rowIsLeaf(row) || !row.data) return;27
28 const url = row.data?.customerAvatar;29
30 const name = row.data.customer;31
32 return (33 <div className="flex h-full w-full items-center gap-2">34 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />35 <div className="text-ln-text-dark flex flex-col gap-0.5">36 <div>{name}</div>37 </div>38 </div>39 );40}41
42const formatter = new Intl.NumberFormat("en-Us", {43 minimumFractionDigits: 2,44 maximumFractionDigits: 2,45});46export function PriceCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {47 const rtl = api.rtl$.useValue();48 if (!api.rowIsLeaf(row) || !row.data) return;49
50 const price = formatter.format(row.data.price);51 const [dollars, cents] = price.split(".");52
53 return (54 <div className="flex h-full w-full items-center justify-end">55 <div className="flex items-baseline tabular-nums">56 {!rtl && (57 <>58 <span className="text-ln-text font-semibold">${dollars}</span>.59 </>60 )}61 <span className="relative text-xs">{cents}</span>62 {rtl && (63 <>64 .<span className="text-ln-text font-semibold">${dollars}</span>65 </>66 )}67 </div>68 </div>69 );70}71
72export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {73 const rtl = api.rtl$.useValue();74
75 if (!api.rowIsLeaf(row) || !row.data) return;76
77 const formattedDate = format(row.data.purchaseDate, rtl ? "yyyy ,dd MMM" : "MMM dd, yyyy");78
79 return <div className="flex h-full w-full items-center">{formattedDate}</div>;80}81export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {82 if (!api.rowIsLeaf(row) || !row.data) return;83
84 return <div className="text-xs tabular-nums">{row.data.id}</div>;85}86
87export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {88 if (!api.rowIsLeaf(row) || !row.data) return;89
90 const cardNumber = row.data.cardNumber;91 const provider = row.data.paymentMethod;92
93 let Logo: ReactNode = null;94 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;95 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;96
97 return (98 <div className="flex h-full w-full items-center gap-2">99 <div className="flex w-7 items-center justify-center">{Logo}</div>100 <div className="flex items-center gap-px">101 <div className="bg-ln-gray-40 size-2 rounded-full"></div>102 <div className="bg-ln-gray-40 size-2 rounded-full"></div>103 <div className="bg-ln-gray-40 size-2 rounded-full"></div>104 <div className="bg-ln-gray-40 size-2 rounded-full"></div>105 </div>106 <div className="tabular-nums">{cardNumber}</div>107 </div>108 );109}110
111export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {112 if (!api.rowIsLeaf(row) || !row.data) return;113
114 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;115}116
117const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (118 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>119 <path120 fill="#1434cb"121 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"122 />123 </svg>124);125
126const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (127 <svg128 xmlns="http://www.w3.org/2000/svg"129 width={2500}130 height={1524}131 viewBox="55.2 38.3 464.5 287.8"132 {...props}133 >134 <path135 fill="#f79f1a"136 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"137 />138 <path139 fill="#ea001b"140 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"141 />142 <path143 fill="#ff5f01"144 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"145 />146 </svg>147);148
149export function SwitchToggle(props: { label: string; checked: boolean; onChange: (b: boolean) => void }) {150 const id = useId();151 return (152 <div className="flex items-center gap-2">153 <label className="text-ln-text-dark text-sm leading-none" htmlFor={id}>154 {props.label}155 </label>156 <Switch.Root157 className="bg-ln-gray-10 data-[state=checked]:bg-ln-primary-50 h-5.5 w-9.5 border-ln-border-strong relative cursor-pointer rounded-full border outline-none"158 id={id}159 checked={props.checked}160 onCheckedChange={(c) => props.onChange(c)}161 style={{ "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0)" } as CSSProperties}162 >163 <Switch.Thumb className="size-4.5 block translate-x-0.5 rounded-full bg-white/95 shadow transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-4 data-[state=checked]:bg-white" />164 </Switch.Root>165 </div>166 );167}Note
LyteNyte Grid uses logical CSS properties
throughout its codebase, so RTL works without extra styling changes.
Set the rtl prop to enable RTL; no other configuration is required.
Next Steps
- Accessibility: Review accessibility measures and ARIA compliance.
- Getting Started: Get started with LyteNyte Grid, a modern React data grid designed for enterprise-scale data challenges.
- Installation: Install LyteNyte Grid with a package manager or import its assets from a CDN.
