RTL is used for displaying languages that go from Right to Left, eg Hebrew and Arabic. To get AG Grid to display in RTL format, set the property enableRtl=true.
RTL Example Copy Link
Below shows a grid in RTL mode. Use the language selector to switch between Arabic, Hebrew and English to see how the grid adapts to different RTL languages.
import {
AG_GRID_LOCALE_EG,
AG_GRID_LOCALE_IL,
} from "@ag-grid-community/locale";
import {
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
LocaleModule,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
NumberEditorModule,
TextEditorModule,
TextFilterModule,
NumberFilterModule,
ClientSideRowModelModule,
LocaleModule,
]);
interface LanguageConfig {
localeText: Record<string, string> | undefined;
enableRtl: boolean;
columnDefs: ColDef[];
rowData: Record<string, any>[];
}
const LANGUAGES: Record<string, LanguageConfig> = {
arabic: {
localeText: AG_GRID_LOCALE_EG,
enableRtl: true,
columnDefs: [
{ field: "city", headerName: "المدينة" },
{ field: "country", headerName: "البلد" },
{ field: "population", headerName: "عدد السكان" },
{ field: "area", headerName: "المساحة (كم²)" },
],
rowData: [
{ city: "القاهرة", country: "مصر", population: 21_323_000, area: 3_085 },
{
city: "الرياض",
country: "السعودية",
population: 7_677_000,
area: 1_798,
},
{ city: "دبي", country: "الإمارات", population: 3_564_000, area: 1_588 },
{
city: "الدار البيضاء",
country: "المغرب",
population: 3_752_000,
area: 384,
},
{ city: "بغداد", country: "العراق", population: 8_126_000, area: 673 },
{ city: "الجزائر", country: "الجزائر", population: 3_915_000, area: 363 },
{ city: "عمّان", country: "الأردن", population: 4_008_000, area: 1_680 },
{ city: "تونس", country: "تونس", population: 2_365_000, area: 212 },
{ city: "بيروت", country: "لبنان", population: 2_434_000, area: 67 },
{ city: "الكويت", country: "الكويت", population: 2_989_000, area: 200 },
],
},
hebrew: {
localeText: AG_GRID_LOCALE_IL,
enableRtl: true,
columnDefs: [
{ field: "city", headerName: "עיר" },
{ field: "country", headerName: "מדינה" },
{ field: "population", headerName: "אוכלוסייה" },
{ field: "area", headerName: "שטח (קמ״ר)" },
],
rowData: [
{ city: "ירושלים", country: "ישראל", population: 982_000, area: 126 },
{ city: "תל אביב", country: "ישראל", population: 467_000, area: 52 },
{ city: "חיפה", country: "ישראל", population: 285_000, area: 64 },
{ city: "ראשון לציון", country: "ישראל", population: 254_000, area: 59 },
{ city: "פתח תקווה", country: "ישראל", population: 247_000, area: 36 },
{ city: "אשדוד", country: "ישראל", population: 226_000, area: 47 },
{ city: "נתניה", country: "ישראל", population: 221_000, area: 29 },
{ city: "באר שבע", country: "ישראל", population: 210_000, area: 117 },
{ city: "חולון", country: "ישראל", population: 196_000, area: 19 },
{ city: "בני ברק", country: "ישראל", population: 204_000, area: 7 },
],
},
english: {
localeText: undefined,
enableRtl: false,
columnDefs: [
{ field: "city", headerName: "City" },
{ field: "country", headerName: "Country" },
{ field: "population", headerName: "Population" },
{ field: "area", headerName: "Area (km²)" },
],
rowData: [
{
city: "London",
country: "United Kingdom",
population: 9_541_000,
area: 1_572,
},
{
city: "New York",
country: "United States",
population: 8_336_000,
area: 783,
},
{
city: "Sydney",
country: "Australia",
population: 5_312_000,
area: 12_368,
},
{ city: "Toronto", country: "Canada", population: 2_794_000, area: 630 },
{ city: "Dublin", country: "Ireland", population: 1_263_000, area: 115 },
{
city: "Cape Town",
country: "South Africa",
population: 4_618_000,
area: 2_461,
},
{
city: "Singapore",
country: "Singapore",
population: 5_917_000,
area: 733,
},
{
city: "Auckland",
country: "New Zealand",
population: 1_657_000,
area: 1_086,
},
{ city: "Mumbai", country: "India", population: 21_297_000, area: 603 },
{ city: "Nairobi", country: "Kenya", population: 4_922_000, area: 696 },
],
},
};
let gridApi: GridApi;
function getGridOptions(language: string): GridOptions {
const config = LANGUAGES[language];
return {
columnDefs: config.columnDefs,
rowData: config.rowData,
enableRtl: config.enableRtl,
localeText: config.localeText,
defaultColDef: {
editable: true,
flex: 1,
minWidth: 100,
filter: true,
},
};
}
function onLanguageChange() {
const select = document.querySelector<HTMLSelectElement>("#language")!;
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi.destroy();
gridApi = createGrid(gridDiv, getGridOptions(select.value));
}
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, getGridOptions("arabic"));
if (typeof window !== "undefined") {
// Attach external event handlers to window so they can be called from index.html
(<any>window).onLanguageChange = onLanguageChange;
}
Complex Example Copy Link
Below shows a more complex example with the tool panel and pinned areas visible to demonstrate edge cases of RTL. This example uses AG Grid Enterprise, so the tool panel and context menus are active. Use the language selector to switch between RTL languages.
import { AgChartsEnterpriseModule } from "ag-charts-enterprise";
import {
CellClassParams,
CellStyle,
ColDef,
ColGroupDef,
DefaultMenuItem,
GetContextMenuItemsParams,
GridApi,
GridOptions,
ICellRendererParams,
LocaleModule,
MenuItemDef,
ModuleRegistry,
RowSelectedEvent,
SelectionChangedEvent,
ValueSetterParams,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import { AllEnterpriseModule } from "ag-grid-enterprise";
import { CountryCellRenderer } from "./country-renderer";
import {
COUNTRY_CODES,
LANGUAGES,
LanguageConfig,
createRowData,
} from "./data";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
AllEnterpriseModule.with(AgChartsEnterpriseModule),
LocaleModule,
]);
const dataSize: string = ".1x22";
let currentLang: LanguageConfig = LANGUAGES["arabic"];
let gridApi: GridApi;
function getAutoGroupColumnDef(): ColDef {
return {
headerName: currentLang.headers.group,
width: 200,
field: "name",
valueGetter: (params) => {
if (params.node && params.node.group) {
return params.node.key;
} else {
return params.data[params.colDef.field!];
}
},
cellRenderer: "agGroupCellRenderer",
};
}
function getGridOptions(language: string): GridOptions {
currentLang = LANGUAGES[language];
return {
columnDefs: createCols(),
rowData: createRowData(language),
context: { COUNTRY_CODES },
defaultColDef: {
editable: true,
minWidth: 100,
filter: true,
floatingFilter: true,
},
sideBar: true,
rowGroupPanelShow: "always",
pivotPanelShow: "always",
enableRtl: currentLang.enableRtl,
localeText: currentLang.localeText,
statusBar: {
statusPanels: [{ statusPanel: "agAggregationComponent" }],
},
rowSelection: {
mode: "multiRow",
groupSelects: "descendants",
selectAll: "filtered",
},
quickFilterText: undefined,
autoGroupColumnDef: getAutoGroupColumnDef(),
onRowSelected: rowSelected,
onSelectionChanged: selectionChanged,
getBusinessKeyForNode: (node) => {
if (node.data) {
return node.data.name;
} else {
return "";
}
},
getContextMenuItems: getContextMenuItems,
};
}
function getContextMenuItems(
params: GetContextMenuItemsParams,
): (DefaultMenuItem | MenuItemDef)[] {
const result: (DefaultMenuItem | MenuItemDef)[] =
params.defaultItems!.splice(0);
result.push({
name: currentLang.contextMenu.customMenuItem,
icon: '<img src="https://www.ag-grid.com/example-assets/lab.png" style="width: 14px;" />',
action: () => {
const value = params.value ? params.value : "<empty>";
console.log("You clicked a custom menu item on cell " + value);
},
});
return result;
}
function createDefaultCols(): (ColDef | ColGroupDef)[] {
const firstColumn: ColDef = {
headerName: currentLang.headers.name,
field: "name",
width: 200,
editable: true,
enableRowGroup: true,
icons: {
sortAscending: '<i class="fa fa-sort-alpha-up"/>',
sortDescending: '<i class="fa fa-sort-alpha-down"/>',
},
};
const cols: (ColDef | ColGroupDef)[] = [
{
headerName: currentLang.headers.participant,
children: [
firstColumn,
{
headerName: currentLang.headers.language,
field: "language",
width: 150,
editable: true,
filter: "agSetColumnFilter",
cellRenderer: languageCellRenderer,
cellEditor: "agSelectCellEditor",
enableRowGroup: true,
enablePivot: true,
cellEditorParams: {
values: currentLang.editorLanguages,
},
pinned: "right",
headerTooltip: currentLang.headers.languageTooltip,
},
{
headerName: currentLang.headers.country,
field: "country",
width: 150,
editable: true,
cellRenderer: CountryCellRenderer,
enableRowGroup: true,
enablePivot: true,
cellEditor: "agRichSelectCellEditor",
cellEditorParams: {
cellRenderer: CountryCellRenderer,
values: currentLang.editorCountries,
},
filterParams: {
cellRenderer: CountryCellRenderer,
},
},
],
},
{
headerName: currentLang.headers.gameOfChoice,
children: [
{
headerName: currentLang.headers.gameName,
field: "game.name",
width: 180,
editable: true,
filter: "agSetColumnFilter",
tooltip: true,
cellClass: () => {
return "alphabet";
},
enableRowGroup: true,
enablePivot: true,
pinned: "left",
icons: {
sortAscending: '<i class="fa fa-sort-alpha-up"/>',
sortDescending: '<i class="fa fa-sort-alpha-down"/>',
},
},
{
headerName: currentLang.headers.bought,
field: "game.bought",
filter: "agSetColumnFilter",
editable: true,
width: 100,
enableRowGroup: true,
enablePivot: true,
enableValue: true,
cellRenderer: booleanCellRenderer,
cellStyle: { "text-align": "center" },
comparator: booleanComparator,
filterParams: { cellRenderer: booleanFilterCellRenderer },
},
],
},
{
groupId: "performance",
children: [
{
headerName: currentLang.headers.bankBalance,
field: "bankBalance",
width: 150,
editable: true,
cellRenderer: currencyRenderer,
cellStyle: currencyCssFunc,
filter: "agNumberColumnFilter",
enableValue: true,
icons: {
sortAscending: '<i class="fa fa-sort-amount-up"/>',
sortDescending: '<i class="fa fa-sort-amount-down"/>',
},
},
{
headerName: currentLang.headers.extraInfo1,
columnGroupShow: "open",
width: 150,
editable: false,
sortable: false,
suppressHeaderMenuButton: true,
cellStyle: { "text-align": "right" },
cellRenderer: () => {
return currentLang.cellContent.abra;
},
},
{
headerName: currentLang.headers.extraInfo2,
columnGroupShow: "open",
width: 150,
editable: false,
sortable: false,
suppressHeaderMenuButton: true,
cellStyle: { "text-align": "left" },
cellRenderer: () => {
return currentLang.cellContent.cadabra;
},
},
],
},
{
headerName: currentLang.headers.rating,
field: "rating",
width: 100,
editable: true,
cellRenderer: ratingRenderer,
enableRowGroup: true,
enablePivot: true,
enableValue: true,
filterParams: { cellRenderer: ratingFilterRenderer },
},
{
headerName: currentLang.headers.totalWinnings,
field: "totalWinnings",
filter: "agNumberColumnFilter",
editable: true,
valueSetter: numberValueSetter,
width: 150,
enableValue: true,
cellRenderer: currencyRenderer,
cellStyle: currencyCssFunc,
icons: {
sortAscending: '<i class="fa fa-sort-amount-up"/>',
sortDescending: '<i class="fa fa-sort-amount-down"/>',
},
},
];
const monthGroup: ColGroupDef = {
headerName: currentLang.headers.monthlyBreakdown,
children: [],
};
cols.push(monthGroup);
for (let i = 0, len = currentLang.months.length; i < len; ++i) {
const month = currentLang.months[i];
const child: ColDef = {
headerName: month,
field: "month_" + i,
width: 100,
filter: "agNumberColumnFilter",
editable: true,
enableValue: true,
cellClassRules: {
"good-score": 'typeof x === "number" && x > 50000',
"bad-score": 'typeof x === "number" && x < 10000',
},
valueSetter: numberValueSetter,
cellRenderer: currencyRenderer,
cellStyle: { "text-align": "right" },
};
monthGroup.children.push(child);
}
return cols;
}
function getColCount() {
switch (dataSize) {
case "10x100":
return 100;
default:
return 22;
}
}
function createCols() {
const colCount = getColCount();
const defaultCols = createDefaultCols();
const columns = defaultCols.slice(0, colCount);
for (let col = 22; col < colCount; col++) {
const colName = currentLang.colNames[col % currentLang.colNames.length];
const colDef = {
headerName: colName,
field: "col" + col,
width: 200,
editable: true,
};
columns.push(colDef);
}
return columns;
}
function selectionChanged(event: SelectionChangedEvent) {
console.log(
"Callback selectionChanged: selection count = " +
event.selectedNodes?.length,
);
}
function rowSelected(event: RowSelectedEvent) {
// the number of rows selected could be huge, if the user is grouping and selects a group, so
// to stop the console from clogging up, we only print if in the first 10 (by chance we know
// the node id's are assigned from 0 upwards)
if (Number(event.node.id) < 10) {
const valueToPrint = event.node.group
? "group (" + event.node.key + ")"
: event.node.data.name;
console.log("Callback rowSelected: " + valueToPrint);
}
}
function numberValueSetter(params: ValueSetterParams) {
const newValue = params.newValue;
let valueAsNumber;
if (newValue === null || newValue === undefined || newValue === "") {
valueAsNumber = null;
} else {
valueAsNumber = parseFloat(params.newValue);
}
const field = params.colDef.field!;
const data = params.data;
data[field] = valueAsNumber;
return true;
}
function currencyCssFunc(params: CellClassParams): CellStyle {
if (params.value !== null && params.value !== undefined && params.value < 0) {
return { color: "red", "text-align": "right", "font-weight": "bold" };
} else {
return { "text-align": "right" };
}
}
function ratingFilterRenderer(params: ICellRendererParams) {
return ratingRendererGeneral(params.value, true);
}
function ratingRenderer(params: ICellRendererParams) {
return ratingRendererGeneral(params.value, false);
}
function ratingRendererGeneral(value: any, forFilter: boolean) {
if (value === "(Select All)") {
return value;
}
let result = "<span>";
for (let i = 0; i < 5; i++) {
if (value > i) {
result +=
'<img src="https://www.ag-grid.com/example-assets/gold-star.png" />';
}
}
if (forFilter && Number(value) === 0) {
result += currentLang.cellContent.noStars;
}
return result;
}
function currencyRenderer(params: ICellRendererParams) {
if (params.value === null || params.value === undefined) {
return null;
} else if (isNaN(params.value)) {
return "NaN";
} else {
if (params.node.group && params.column!.getAggFunc() === "count") {
return params.value;
} else {
return (
"£" +
Math.floor(params.value)
.toString()
.replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,")
);
}
}
}
function booleanComparator(value1: any, value2: any) {
const value1Cleaned = booleanCleaner(value1);
const value2Cleaned = booleanCleaner(value2);
const value1Ordinal =
value1Cleaned === true ? 0 : value1Cleaned === false ? 1 : 2;
const value2Ordinal =
value2Cleaned === true ? 0 : value2Cleaned === false ? 1 : 2;
return value1Ordinal - value2Ordinal;
}
let count = 0;
function booleanCellRenderer(params: ICellRendererParams) {
count++;
if (count <= 1) {
// params.api.onRowHeightChanged();
}
const valueCleaned = booleanCleaner(params.value);
if (valueCleaned === true) {
//this is the unicode for tick character
return "<span title='true'>✔</span>";
} else if (valueCleaned === false) {
//this is the unicode for cross character
return "<span title='false'>✖</span>";
} else if (params.value !== null && params.value !== undefined) {
return params.value.toString();
} else {
return null;
}
}
function booleanFilterCellRenderer(params: ICellRendererParams) {
const valueCleaned = booleanCleaner(params.value);
if (valueCleaned === true) {
//this is the unicode for tick character
return "✔";
} else if (valueCleaned === false) {
//this is the unicode for cross character
return "✖";
} else if (params.value === "(Select All)") {
return params.value;
} else {
return currentLang.cellContent.empty;
}
}
function booleanCleaner(value: any) {
if (value === "true" || value === true || value === 1) {
return true;
} else if (value === "false" || value === false || value === 0) {
return false;
} else {
return null;
}
}
function languageCellRenderer(params: ICellRendererParams) {
if (params.value !== null && params.value !== undefined) {
return params.value;
} else {
return null;
}
}
function onLanguageChange() {
const select = document.querySelector<HTMLSelectElement>("#language")!;
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi.destroy();
gridApi = createGrid(gridDiv, getGridOptions(select.value));
}
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, getGridOptions("arabic"));
if (typeof window !== "undefined") {
// Attach external event handlers to window so they can be called from index.html
(<any>window).onLanguageChange = onLanguageChange;
}
How it Works Copy Link
If you are creating your own theme, knowing how the RTL is implemented will be useful.
CSS Styling Copy Link
The following CSS classes are added to the grid when RTL is on and off:
- ag-rtl: Added when RTL is ON. It sets the style
'direction=rtl'. - ag-ltr: Added when RTL is OFF. It sets the style
'direction=ltr'.
You can see these classes by inspecting the DOM of AG Grid. A lot of the layout of the grid is reversed with this simple CSS class change.
Themes then also use these styles for adding different values based on whether RTL is used or NOT. For example, the following is used inside the provided themes:
// selection checkbox gets 4px padding to the RIGHT when LTR
.ag-ltr .ag-selection-checkbox {
padding-right 4px;
}
// selection checkbox gets 4px padding to the LEFT when RTL
.ag-rtl .ag-selection-checkbox {
padding-left 4px;
} Pinning and Scroll Bars Copy Link
Under normal operation, when columns are pinned to the right, the vertical scroll will appear alongside the right pinned panel. For RTL the scroll will appear on the left pinned panel when left pinning columns.
Layout of Columns Copy Link
The grid normally lays the columns out from left to right. When doing RTL the columns go from the right to the left. If the grid was using normal HTML layout, then the columns would all reverse by themselves, however the grid used Column Visualisation, so it needs to know exactly where each column is. Hence there is a lot of math logic inside AG Grid that is tied with the scrolling. Not only is the scrolling inverted, all the maths logic is inverted also. All of this is taken care of for you inside AG Grid. Once enableRtl=true is set, the grid will know to use the RTL variant of all the calculations.