Skip to content

Formatting a Right-to-Left Table of Contents

9 min read · updated August 11, 2026

You set the document direction to right-to-left, the body text is perfect, and the table of contents is still wrong: numbers on the wrong side, dots running the wrong way, nested entries indented from the wrong edge. The body text obeyed the direction setting because it is text. A table of contents is mostly not text.

Four things wrong at once

Name them separately, because they have four separate fixes and people usually apply one and conclude the direction setting is broken.

  • Entry titles right-align but page numbers stay left, or the reverse. The row is a two-item layout and only one item followed the direction.
  • The dot leader runs from the wrong end, or does not reach the number, or overlaps it.
  • Nested levels indent from the left edge. The indentation is a physical margin-left or an absolute tab stop.
  • Multi-part numbers fragmentA-3, 12–15, xii — the same neutral-and-weak-class problem as everywhere else in this cluster.

A leader is not text

The dots between a chapter title and its page number are not characters in the document. In a word processor they are a property of a tab stop: the tab advances to a defined position and fills the gap it crossed with a repeated character. In CSS they are usually a repeating background or a border on a flexible element. In a PDF produced by a typesetting engine they are drawn glyphs computed at layout time.

That is why direction does not fix them. The bidi algorithm reorders characters within a line; the leader is not a character, so there is nothing for it to reorder. The leader is positioned by the layout engine, and the layout engine mirrors only if the layout was expressed in terms that can mirror. A tab stop at “16 centimetres from the left margin” means the same thing in both directions and is therefore wrong in one of them.

The same distinction explains why the entry title behaves and the row does not. The title is text and obeys the paragraph direction. The row is a container, and a container obeys whatever properties it was given.

The CSS leader, mirrored

The robust pattern is a flex row with a growing middle element carrying the dots. Written with logical properties it mirrors with no direction-specific rules at all.

.toc          { direction: rtl; }        /* or inherit from html */

.toc-entry {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

/* the growing element that carries the dots */
.toc-entry .leader {
  flex: 1 1 auto;
  border-block-end: 1px dotted currentColor;
  transform: translateY(-0.25em);
  min-width: 2em;
}

.toc-entry .page {
  flex: 0 0 auto;
  direction: ltr;
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
}

/* nesting: logical, so it indents from the reading edge */
.toc-entry.level-2 { padding-inline-start: 1.5em; }
.toc-entry.level-3 { padding-inline-start: 3em; }

Flex source order mirrors under direction: rtl, so the title drawn first in the markup appears at the right, the leader fills leftward, and the page number sits at the left edge — correct for a right-to-left document, with the identical stylesheet producing the mirror image for a left-to-right one. The two properties doing the work are border-block-end rather than border-bottom and padding-inline-start rather than padding-left.

Two details that look cosmetic and are not. min-width on the leader stops the dots disappearing entirely when a title is long enough to fill the row, which otherwise makes an entry look broken. And tabular-nums on the page number makes a column of numbers align on the digit, which is the whole visual point of a table of contents.

Page numbers, ranges and Roman numerals

Bare Arabic numerals are class EN and are raised to an even level by rule I2, so they lay out left-to-right inside a right-to-left line without any help. Anything more complicated than a bare number is where this breaks, and a table of contents is full of such things.

  • Ranges. 12–15 with an en dash: the dash is a neutral between two EN runs, so N1 joins them and it holds together. With an ASCII hyphen, class ES, rule W4 absorbs a single separator between two numbers and it also holds — until W2 has converted the digits to AN because the last strong character was Arabic, at which point it does not. This is the date-reversal mechanism from why numbers look reversed appearing in a page range.
  • Roman numerals. xii is Latin letters, class L, not numbers. In a right-to-left line it is a strong left-to-right run and behaves like an English word, which is fine on its own and not fine next to a neutral.
  • Prefixed numbers. A-3, 3.2.1, Appendix B. Mixed classes with separators, the most fragile case of all.

Isolating the page number element, as in the CSS above, handles all three at once and costs nothing. Do it unconditionally rather than case by case — the entry that fragments will be the one added a year later by somebody who has not read this page.

Tab stops in word processors and PDF

Documents generated as DOCX or built from an existing template have a different mechanism and a specific trap. A word processor’s table of contents is built from paragraphs with tab stops, and a tab stop has a type and a position. In a right-to-left paragraph, the paragraph direction is a property of the paragraph, and the tab stop positions are measured from the paragraph’s start edge — which flips with direction — but only if the paragraph is genuinely marked right-to-left rather than merely right-aligned.

That distinction is the trap. Right-aligning an LTR paragraph makes it look right-to-left and leaves every tab stop measured from the left margin, so the page numbers land wherever the old stops were — typically somewhere in the middle of the page, with a leader trailing off in front of them. A template copied from an English document and right-aligned exhibits exactly this. The paragraph must carry the right-to-left mark in its properties, and the tab stops must be redefined, not merely inherited.

For PDF output the safest route is the same as for any other RTL document: generate HTML, get it right in a browser where the bidi algorithm and the shaper both run, and print from there rather than positioning text with a direct PDF writer. The reasoning is in handling RTL text in an AI-generated PDF. If the table of contents is generated from Markdown headings, the list and link constructs bring their own problems, covered in why Markdown breaks in RTL output.

One last thing to check that nothing above catches: the anchors. A table of contents whose entries link to headings depends on the anchor identifiers, and Arabic or Hebrew heading text produces either percent-encoded fragments or transliterated ones depending on the generator. Both work; they must simply be consistent between the heading and the link, and a pipeline that normalises one side and not the other produces a contents page where every entry is a dead link.