Home/Appearance & Platform/RTL Support
Appearance & Platform

RTL Support

One property mirrors the whole grid. The two examples below share a columns array, a config object and a data source — they differ by dir and by nothing else, so everything you can see change is what dir changes.

Left to right — the baseline

The default. The pinned # column is frozen at the left edge and Status at the right, the vertical scrollbar sits on the right, and the columns run in array order. Scroll sideways and watch which two stay put.

#
Name
Role
Department
Office
Email
Salary
Status
1
Nadia Haddad
Staff Engineer
Engineering
Amman
nadia.haddad@example.com
142000
Active
2
Ben Okafor
Product Designer
Design
Lagos
ben.okafor@example.com
98000
Active
3
Yara Nasser
Engineering Manager
Engineering
Dubai
yara.nasser@example.com
161000
Active
4
Karim Farouk
Data Engineer
Data
Cairo
karim.farouk@example.com
118000
On leave
5
Elena Novak
Support Specialist
Support
Prague
elena.novak@example.com
67000
Active
6
Walid Basha
Security Analyst
Security
Riyadh
walid.basha@example.com
124000
Active
7
Alice Smith
Frontend Engineer
Engineering
Manchester
alice.smith@example.com
104000
Active
8
Petra Lange
Finance Controller
Finance
Berlin
petra.lange@example.com
131000
Active
9
Idris Salem
Platform Engineer
Engineering
Tunis
idris.salem@example.com
127000
On leave
10
Tara Aziz
Content Strategist
Marketing
Doha
tara.aziz@example.com
86000
Active
11
Carla Ferrari
QA Engineer
Engineering
Milan
carla.ferrari@example.com
93000
Active
12
Omar Zubair
Solutions Architect
Engineering
Kuwait City
omar.zubair@example.com
149000
Active
Rows per page:
1–12 of 24

Nothing here is RTL-specific, which is why it is on the page: it is the grid a reader already has. Eight columns add up to more than the card is wide, so the body scrolls sideways — and two of them do not move with it. # is frozen at the left edge and Status at the right, because that is what pin: 'left' and pin: 'right' mean here.

Note which edge the vertical scrollbar is drawn on, and where the pager's next button sits. Both move in the example below, and neither is configured anywhere.

Right to left — the same grid, mirrored

dir: 'rtl' and nothing else. Every column keeps its declaration and its place in the array; the axis they are laid out along is what flipped — pins, scrollbar, pager arrows and the arrow keys with it.

#
Name
Role
Department
Office
Email
Salary
Status
1
Nadia Haddad
Staff Engineer
Engineering
Amman
nadia.haddad@example.com
142000
Active
2
Ben Okafor
Product Designer
Design
Lagos
ben.okafor@example.com
98000
Active
3
Yara Nasser
Engineering Manager
Engineering
Dubai
yara.nasser@example.com
161000
Active
4
Karim Farouk
Data Engineer
Data
Cairo
karim.farouk@example.com
118000
On leave
5
Elena Novak
Support Specialist
Support
Prague
elena.novak@example.com
67000
Active
6
Walid Basha
Security Analyst
Security
Riyadh
walid.basha@example.com
124000
Active
7
Alice Smith
Frontend Engineer
Engineering
Manchester
alice.smith@example.com
104000
Active
8
Petra Lange
Finance Controller
Finance
Berlin
petra.lange@example.com
131000
Active
9
Idris Salem
Platform Engineer
Engineering
Tunis
idris.salem@example.com
127000
On leave
10
Tara Aziz
Content Strategist
Marketing
Doha
tara.aziz@example.com
86000
Active
11
Carla Ferrari
QA Engineer
Engineering
Milan
carla.ferrari@example.com
93000
Active
12
Omar Zubair
Solutions Architect
Engineering
Kuwait City
omar.zubair@example.com
149000
Active
Rows per page:
1–12 of 24

The columns array is the one above, unchanged — same order, same widths, same pin: 'left' on #. Open both directory.component.ts tabs and the only line that differs is this one:

dir: 'rtl',

# is now frozen at the right edge and Status at the left. The pin did not change side — the sides changed places. 'left' and 'right' name the start and end of the reading direction, so one column definition works in both without a branch.

The text is still English on purpose. dir moves the layout and translates nothing; the strings are a separate switch, and the two are shown together on Localisation.

What dir moves

config.dir: 'rtl' puts dir="rtl" on the grid's root element. Everything below follows from that one attribute — there is no second set of RTL styles to opt into, and not one column definition changes:

  • Column order on screen. The first column in your array is painted at the right and the last at the left. The array is untouched, so columns[0] is still #.
  • Text alignment. Cell and header content is right-aligned by inheritance rather than per cell — so a cellTemplate of your own is aligned too, with no work.
  • The vertical scrollbar moves to the left edge of the body, and the horizontal one starts at the right, because that is where the content begins.
  • Pinned columns swap edges — see below.
  • Horizontal icons. The pager's first/previous/next/last arrows flip, and so do the row-expand caret and the group-toggle chevron. The sort chevron does not: it points up or down, and up is up in every direction.
  • The arrow keys. ← moves to the next column, which is the one on your left. Keyboard navigation follows what is on screen, not the array index.

pin: 'left' and pin: 'right' are logical sides

They name the start and end of the reading direction, not the physical edges of the screen. Under dir: 'rtl' a pin: 'left' column freezes at the right-hand edge and a pin: 'right' column at the left-hand one — which is what the two examples above show, from one array. The 1px seam at each frozen edge moves with them — and so does the optional pin shadow, if you have turned one on.

This is the reason not to branch: an app that swaps 'left' for 'right' when the language changes ends up pinning the wrong column, because the grid has already mirrored them.

Direction is not translation

dir and messages are separate options, deliberately: plenty of products need a right-to-left layout with English strings in it, and plenty of translations read left to right. The second example above is Arabic-free for that reason — everything you can see change came out of dir. Pair the two on Localisation, where the same grid is shown with a complete Arabic message set beside its French and English siblings.

Switching the whole app at runtime

config.dir is per grid. For an app that flips direction when the reader changes language, set it once instead and every grid follows — none of them is re-created:

import { GridDirectionService, RTL_LOCALES } from '@gridcraft/angular';

const locale = inject(GridDirectionService);

locale.setDir(RTL_LOCALES.has(lang) ? 'rtl' : 'ltr');

RTL_LOCALES ships with the library and holds 'ar', 'fa' and 'ur', so the switch does not need its own list. Three levels are read, and the highest one present wins:

  • config.dir on the grid — an explicit per-grid answer.
  • GridDirectionService.setDir() — the app-wide direction, at runtime.
  • provideGridDefaults({ dir }) — the app-wide starting value.

Both examples above declare config.dir, which is why the left-to-right one stays left to right even when this whole site is switched to RTL. A grid that should follow the app simply leaves dir out.

The page around the grid is yours

Setting config.dir does not touch <html> or <body> — this page reads left to right while the second grid on it does not. That keeps a single right-to-left grid embeddable in an otherwise left-to-right screen, and it leaves the document's own dir your decision rather than the grid's.

Writing your own cellTemplate? Use CSS logical properties — margin-inline-start, padding-inline-end, border-inline-start — and it mirrors with the grid instead of needing a second rule.

Direction options

PropertyTypeDefaultDescription
config.dir'ltr' | 'rtl''ltr'Per-grid layout direction. Sets dir on the grid root; columns, pins, scrollbar, icons and arrow keys all follow. Beats the app-wide value.
column.pin'left' | 'right'unpinnedFreezes a column at the START ('left') or END ('right') of the reading direction — so one array pins correctly in both. See Column Pinning.
GridDirectionService.setDir()(dir: 'ltr' | 'rtl') => void—App-wide direction at runtime. Every grid without an explicit config.dir follows immediately; nothing is re-created.
GridDirectionService.dir()Signal<'ltr' | 'rtl'>'ltr'The current app-wide direction, as a read-only signal.
provideGridDefaults({ dir })'ltr' | 'rtl'—The app-wide starting direction, set once in your application config. setDir() and a per-grid config.dir both override it.
RTL_LOCALESSet<string>{ 'ar', 'fa', 'ur' }The bundled languages that read right to left — pair it with setDir() so a language switch does not keep its own list.
config.messagesPartial<GridMessages>EnglishThe grid's strings — a separate switch from dir. See Localisation.