Fabric.js draws text in rows only. Fabric.js vertical text has been an open request since 2013 (fabric.js #511), and HTML canvas vertical text has no built-in writing mode either. VerticalText adds editable vertical text for Chinese, Japanese and Korean.
Try it
Loading the live demo. Its code is below.
"use client";
import {
type CombineUpright,
type TextOrientation,
VerticalText,
} from "fabricjs-document-engine/text";
import {
DownloadIcon,
PencilIcon,
RotateCwIcon,
SigmaIcon,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react";
import { DemoCanvas, DemoFrame, Status, Toolbar, ToolButton } from "./demo-ui";
import { PAGE, useFabricCanvas } from "./use-fabric-canvas";
const TEXT = "縦書きの文章、ABCも読める。\n令和8年10月2日\n한국어 세로쓰기";
export default function VerticalTextDemo() {
const t = useTranslations("demos");
const { elementRef, canvas } = useFabricCanvas();
const textRef = useRef<VerticalText | null>(null);
const [orientation, setOrientation] = useState<TextOrientation>("mixed");
const [combine, setCombine] = useState<CombineUpright>("digits2");
const [message, setMessage] = useState("");
useEffect(() => {
if (!canvas) return;
const text = new VerticalText(TEXT, {
left: PAGE.width - 40,
top: 20,
originX: "right",
originY: "top",
fontSize: 24,
lineHeight: 1.3,
fill: "#0d0d0d",
combineUpright: "digits2",
styles: {
0: {
0: { fill: "#6f00ff" },
1: { fill: "#6f00ff" },
2: { fill: "#6f00ff" },
},
},
});
textRef.current = text;
canvas.add(text);
canvas.setActiveObject(text);
canvas.requestRenderAll();
return () => {
canvas.remove(text);
};
}, [canvas]);
useEffect(() => {
const text = textRef.current;
if (!canvas || !text) return;
text.set({ textOrientation: orientation, combineUpright: combine });
text.setCoords();
canvas.requestRenderAll();
}, [canvas, orientation, combine]);
function edit() {
const text = textRef.current;
if (!canvas || !text) return;
canvas.setActiveObject(text);
text.enterEditing();
text.selectAll();
setMessage(t("vertical.editing"));
}
function downloadSvg() {
if (!canvas) return;
const url = URL.createObjectURL(
new Blob([canvas.toSVG()], { type: "image/svg+xml" })
);
const link = document.createElement("a");
link.href = url;
link.download = "vertical-text.svg";
link.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
setMessage(t("vertical.downloaded"));
}
return (
<DemoFrame>
<DemoCanvas elementRef={elementRef} label={t("canvas")} />
<Toolbar>
<ToolButton
icon={PencilIcon}
label={t("vertical.edit")}
primary
disabled={!canvas}
onClick={edit}
/>
<ToolButton
icon={RotateCwIcon}
label={
orientation === "mixed"
? t("vertical.mixed")
: t("vertical.upright")
}
onClick={() =>
setOrientation((value) => (value === "mixed" ? "upright" : "mixed"))
}
/>
<ToolButton
icon={SigmaIcon}
label={
combine === "digits2"
? t("vertical.combined")
: t("vertical.separate")
}
onClick={() =>
setCombine((value) => (value === "digits2" ? "none" : "digits2"))
}
/>
<ToolButton
icon={DownloadIcon}
label={t("vertical.svg")}
disabled={!canvas}
onClick={downloadSvg}
/>
</Toolbar>
<Status tone={message ? "ok" : "idle"}>
{message || t("vertical.hint")}
</Status>
</DemoFrame>
);
}Click Edit and type, or use an input method editor. The arrow keys move along the column and to the next one.
Add vertical text
import { VerticalText, textObjects } from "fabricjs-document-engine/text";
const engine = createDocumentEngine({ canvas, customObjects: textObjects });
canvas.add(
new VerticalText("縦書きの文章、ABCも読める。\n令和8年10月2日", {
left: 400,
top: 20,
fontSize: 28,
combineUpright: "digits2",
})
);- Each line becomes a column. Columns read top to bottom and are placed from right to left.
- Chinese, Japanese and Korean characters stand upright, each in its own square.
- Latin words, digits and brackets turn 90° and keep their own spacing, as in printed Japanese books.
- The ideographic comma and full stop sit in the top-right corner of their square.
combineUpright: "digits2"sets one or two digits side by side in one square, the tate-chū-yoko style used for dates.textOrientation: "upright"stands every character up, Latin letters too.
Which characters stand and which turn follows Unicode's table for vertical text (UAX #50). Styles, colors, underlines and text backgrounds work as in any IText. An underline runs on the right of the column.
Edit vertical text
| Key | Moves the cursor |
|---|---|
| ↓ and ↑ | To the next and previous character in the column |
| ← and → | To the next and previous column |
The cursor is a horizontal bar, selections cover whole column segments, and clicks find the character under the pointer. Input methods for Chinese and Japanese work as in a normal text box.
Export
const { blob } = await engine.export({ format: "svg" });SVG export draws each upright character and each turned run with its own position, instead of writing-mode, so the file looks the same in browsers, Illustrator and PDF. PDF export uses the same drawing; pass a font file in fonts for Chinese or Japanese text in PDF.
The options are listed in the Text API.