---
title: "RTL - Right To Left"
framework: javascript
version: "36.1.0"
---

# RTL - Right To Left

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

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.

#### RTL Simple

```ts
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";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  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;
}
```

[Live example: RTL Simple](https://www.ag-grid.com/examples/rtl/rtl-simple/typescript)

## Complex Example

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.

#### RTL Complex

```ts
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";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  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",
          tooltipField: "game.name",
          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 (
        "&pound;" +
        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'>&#10004;</span>";
  } else if (valueCleaned === false) {
    //this is the unicode for cross character
    return "<span title='false'>&#10006;</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 "&#10004;";
  } else if (valueCleaned === false) {
    //this is the unicode for cross character
    return "&#10006;";
  } 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;
}
```

[Live example: RTL Complex](https://www.ag-grid.com/examples/rtl/rtl-complex/typescript)

## How it Works

If you are creating your own theme, knowing how the RTL is implemented will be useful.

### CSS Styling

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:

```css
// 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

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

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.
