If you want real vertical text in CSS, set writing-mode: vertical-rl (or vertical-lr) on the element, and reach for text-orientation to control how the individual glyphs sit. That is the whole answer for the common cases: a sideways table header, a rotated sidebar label, a book-spine title, or genuine top-to-bottom CJK. The transform: rotate(90deg) trick that most people try first is not the same thing, and most of the time it is the wrong tool.
.vertical {
writing-mode: vertical-rl;
text-orientation: mixed;
}That is it. The text now flows top to bottom, columns advance right to left, and the element takes up a tall, narrow box instead of a wide one. The rest of this page is the detail: what each value does, why writing-mode reflows where rotate cannot, and the handful of cases where rotate is still the correct choice.
writing-mode: the three values you actually use
writing-mode changes the block-flow direction, which is the direction in which lines stack. There are several values in the spec, but in practice you use three:
.horizontal { writing-mode: horizontal-tb; } /* the default: lines stack top to bottom, text runs left to right */
.vertical-rtl { writing-mode: vertical-rl; } /* lines stack right to left, text runs top to bottom */
.vertical-ltr { writing-mode: vertical-lr; } /* lines stack left to right, text runs top to bottom */vertical-rl is the one you want for almost everything Western and for Japanese/Chinese vertical text. vertical-lr differs only in which side the next column lands on (left instead of right), which matters for Mongolian and for some chart-axis layouts where you want the first line on the left.
writing-mode is well supported. It and text-orientation have both been Baseline (available across Chrome, Edge, Firefox, and Safari) since September 2020, so there is no prefix dance and no fallback to maintain in 2026.
text-orientation: how each glyph sits
writing-mode decides the direction text flows. text-orientation decides how each character is rotated within that vertical flow. It only does anything when the element is in a vertical writing mode. The two values you reach for:
/* Latin letters lie on their side, rotated 90deg; CJK glyphs stay upright. */
.mixed { writing-mode: vertical-rl; text-orientation: mixed; }
/* Every glyph stands upright and the letters stack on top of each other. */
.upright { writing-mode: vertical-rl; text-orientation: upright; }mixed is the default and the right pick for a rotated label or a book spine in English: the Latin text reads as if you tilted your head to the right. upright gives you the stacked-letters look, each character standing the right way up with the next one below it, which is what you want for a narrow vertical UI label where rotated text would be hard to read. There is also text-orientation: sideways, which rotates the whole line 90deg clockwise including any CJK glyphs. Reach for it only when you specifically want everything laid sideways.
One footgun with upright: it forces the used direction to ltr for that element, so do not use it for right-to-left scripts.
Why this beats transform: rotate(90deg)
The reason rotate feels wrong the moment you try to build a real layout is that a transform is a paint-time effect, not a layout change. The element still occupies its original horizontal box. The browser lays out a wide element, then visually spins the pixels. So:
- The surrounding layout does not reflow around the rotated text. You end up nudging it back into place with negative margins,
position: absolute, or atransform-originyou have to eyeball. - The text does not wrap to the new orientation. A rotated paragraph wraps to the original width and then gets turned, so long content overflows sideways instead of breaking into vertical lines.
- Alignment is fragile. Centering rotated text inside a cell or button means fighting
transform-originand translate offsets.
writing-mode changes the actual layout. The element becomes tall and narrow, the parent reserves the right space, and the text wraps top-to-bottom the way a vertical column should. It is also the only correct option for CJK vertical typography, because rotating a line of Japanese is not the same as setting it vertically (the glyphs would all be on their sides instead of upright). For a sideways table header or a .book-spine title, writing-mode is simply less code and fewer surprises:
th.rotated {
writing-mode: vertical-rl;
text-orientation: mixed;
/* The cell now sizes to the vertical text. No translate hacks. */
white-space: nowrap;
}When rotate is still the right call
transform: rotate() is not dead. Use it when you want a purely visual rotation that should not affect layout, and when you need an arbitrary angle. A small "SALE" badge spun 45deg across a card corner, a decorative diagonal ribbon, an icon you tilt on hover, those are transform jobs. writing-mode only does the orthogonal vertical axes; if you need 30deg or a smoothly animated angle, that is transform: rotate(), and the fact that it ignores layout is exactly what you want there.
sideways-lr and sideways-rl: rotating the whole line
There is one layout writing-mode can do that the vertical-* values cannot, and it is the case people reach for most after vertical-rl: a Western label that reads bottom to top, rotated 90deg counter-clockwise. That is the classic left-hand y-axis label or a book-spine title you read with your head tilted left. vertical-rl plus text-orientation: mixed rotates Latin text clockwise (read it head-tilted-right), and there is no text-orientation value that flips that direction. The dedicated writing-mode values do:
/* Whole line rotated 90deg clockwise (reads top to bottom). */
.sideways-down { writing-mode: sideways-rl; }
/* Whole line rotated 90deg counter-clockwise (reads bottom to top). */
.sideways-up { writing-mode: sideways-lr; }sideways-rl and sideways-lr lay every glyph sideways the way text-orientation: sideways does, but they bake the rotation direction into the writing mode itself, so sideways-lr is the clean answer for that bottom-to-top axis label without a single transform.
The catch is recency. These two values are far newer than the rest of this page: Chrome shipped them in 132 (January 2025) and Safari in 18.4 (March 2025); Firefox had sideways-lr for years and filled in sideways-rl to match. They reached Baseline (across all four engines) only in 2025, so unlike vertical-rl, they are not safe to ship without a fallback if you still support browsers more than a release or two old. For a bottom-to-top label on older targets, fall back to writing-mode: vertical-rl with a transform: rotate(180deg), or accept the clockwise direction.
Aligning vertical text
Once an element is vertical, vertical-align behaves the way you expect for inline content, but it now controls alignment along what was the horizontal axis. The cleaner habit is to use logical properties so your CSS does not care which way the text flows:
.vertical-block {
writing-mode: vertical-rl;
text-orientation: mixed;
block-size: 12rem; /* the "height" of the column, in flow terms */
inline-size: auto; /* the "width", which is now horizontal */
padding-block: 0.5rem;
}block-size, inline-size, padding-block, and margin-inline follow the writing mode, so the same rule reads correctly whether the element is horizontal or vertical. That is the payoff of doing vertical text the layout-aware way: the box model rotates with the text instead of fighting it.
See also
- Truncate text with a CSS ellipsis, end, beginning, and multiline, for the other common "text does not fit the box" problem.
- CSS content and attr() for generated content, useful when your vertical label is generated from a data attribute.
- The CSS :has() parent selector, for styling a container based on what it holds, which pairs well with rotated headers.
Sources
Authoritative references this article was fact-checked against.
- writing-mode (MDN Web Docs)developer.mozilla.org
- text-orientation (MDN Web Docs)developer.mozilla.org
- CSS Writing Modes Level 4 (W3C)w3.org
- writing-mode sideways-lr / sideways-rl support (Can I use)caniuse.com





