Mastering CSS writing-mode in React: practical patterns for vertical text layouts
Learn how to use CSS writing-mode in React and Next.js projects. Real‑world examples, performance tips, and trade‑offs for vertical text in modern web apps.

Why writing-mode matters in production UI
When you need vertical text—think Japanese sidebars, decorative headings, or data tables with rotated labels—writing-mode is the cleanest solution. It avoids the transform: rotate() hacks that break accessibility and line‑height calculations. In a large Next.js codebase, a consistent approach saves you from layout bugs that surface only on high‑DPI screens.
Basic usage in a component
In React you can apply the property directly via style or a CSS module. The latter is preferred because it keeps the CSS static for the optimizer.
// components/VerticalLabel.tsx
import styles from './VerticalLabel.module.css';
export default function VerticalLabel({text}: {text: string}) {
return <span className={styles.vertical}>{text}</span>;
}/* VerticalLabel.module.css */
.vertical {
writing-mode: vertical-rl; /* right‑to‑left vertical */
text-orientation: mixed; /* keep latin upright */
padding: 0.5rem;
}This renders the string vertically while keeping Latin characters readable. The text-orientation switch is often overlooked; without it, letters appear rotated, which hurts readability.
Performance considerations
Browsers treat writing-mode as a layout property, so it triggers a re‑flow when toggled. In a table with hundreds of cells, switching modes on hover can cause jank. The safe pattern is to set the mode once at mount and avoid dynamic changes. If you need a hover effect, use transform: rotate() on a child element instead of flipping the whole block.
SEO and accessibility
Screen readers read vertical text in logical order, but you must still provide a proper lang attribute on the container. For mixed‑language content, wrap the vertical portion in a <span lang="ja"> so the voice engine switches correctly. Also, avoid using writing-mode for purely decorative text; aria-hidden="true" prevents unnecessary verbosity.
Common pitfalls and how to avoid them
- Overflow hidden: vertical blocks often exceed container height. Use
overflow-x:autoon the parent to enable scrolling. - Flexbox incompatibility: flex containers don’t respect the block‑direction of
writing-mode. Switch todisplay:inline-blockfor the vertical element or use CSS Grid. - Browser support quirks: Safari requires the
-webkit-prefix for older versions. Include it in your fallback CSS.
Putting it together in Next.js
Next.js static generation means the CSS is extracted at build time. By keeping writing-mode in a module, the CSS is deduped and cached. Here’s a minimal page that shows a vertical navigation on the left side.
// pages/vertical-nav.tsx
import VerticalLabel from '@/components/VerticalLabel';
export default function VerticalNav() {
return (
<nav style={{display:'flex'}}>
<VerticalLabel text="メニュー" />
<ul style={{marginLeft:'1rem'}}>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
);
}The vertical label stays crisp, the rest of the layout uses normal flexbox, and the page ships with a single CSS bundle. No runtime calculations, no layout thrash.