npm package

tate-chu-yoko

A small library that automatically wraps half-width alphanumerics inside vertical Japanese text so CSS `text-combine-upright` can compose them as tate-chu-yoko (縦中横). Authors write plain text; typesetting is handled for them.

v0.1.2MITSSR-safe

120264月、Webの縦書きは進化した。

What it solves

Japanese vertical writing (writing-mode: vertical-rl) leaves half-width Latin letters and digits lying on their side. The fix — text-combine-upright: all — exists, but the developer still has to decide *which* runs to apply it to, usually by wrapping them in <span class="tcy"> by hand. That chore leaks into manuscripts, CMSes, and Markdown. tate-chu-yoko does the wrapping for you, so authors can keep writing plain text.

Three packages

Install only the adapter you need. The core tokenizer is pulled in automatically as a dependency.

@love-rox/tcy-core

Framework-agnostic tokenizer

Scans a string and returns a structured token stream identifying the runs that should be uprighted. Use it directly in a custom pipeline or build-time transform.

@love-rox/tcy-react

React component

A <Tcy> component that walks children recursively and wraps eligible runs in <span class="tcy">. Works with React 17+ and produces deterministic output on the server and the client.

@love-rox/tcy-vue

Vue 3 component

The same <Tcy> API for Vue 3 apps. Identical options and behavior as the React version.

Install

Pick the adapter (React or Vue) for your stack — @love-rox/tcy-core will be installed as a dependency.

bun

bun add @love-rox/tcy-react

pnpm

pnpm add @love-rox/tcy-react

npm

npm i @love-rox/tcy-react

yarn

yarn add @love-rox/tcy-react

Usage

Wrap the vertical content with <Tcy> inside an element that has writing-mode: vertical-rl.

Reacttsx
import { Tcy } from "@love-rox/tcy-react";

export function Chapter() {
  return (
    <p style={{ writingMode: "vertical-rl" }}>
      <Tcy>第1章 2026年4月、Webの縦書きは進化した。</Tcy>
    </p>
  );
}
Vue 3vue
<script setup lang="ts">
import { Tcy } from "@love-rox/tcy-vue";
</script>

<template>
  <p style="writing-mode: vertical-rl">
    <Tcy>第1章 2026年4月、Webの縦書きは進化した。</Tcy>
  </p>
</template>
Core (string tokenizer)ts
import { tokenize } from "@love-rox/tcy-core";

tokenize("第1章 2026年4月");
// [
//   { type: "text", value: "第" },
//   { type: "tcy",  value: "1" },
//   { type: "text", value: "章 " },
//   { type: "tcy",  value: "2026" },
//   { type: "text", value: "年" },
//   { type: "tcy",  value: "4" },
//   { type: "text", value: "月" },
// ]

Recommended CSS

Apply the following to the generated <span class="tcy"> elements.

CSScss
.tcy {
  -webkit-text-combine: horizontal;
  text-combine-upright: all;
}

Options

These options are shared by the React / Vue <Tcy> components and by tokenize().

Shared options

OptionTypeDefaultDescription
target'alphanumeric' | 'alpha' | 'digit' | 'ascii' | RegExp'alphanumeric'What counts as a tate-chu-yoko target. alphanumeric matches [0-9A-Za-z]; ascii covers printable ASCII. A custom RegExp is accepted.
combinebooleantrueMerge consecutive target characters into a single span. Set to false to wrap each character individually.
includestring | string[]undefinedExtra characters to treat as targets regardless of target.
excludestring | string[]undefinedCharacters to exclude. Takes precedence over include.

Component-only props

OptionTypeDefaultDescription
classNamestring'tcy'Class applied to each generated span.
askeyof JSX.IntrinsicElements'span'Tag name used for wrapping.

Behavior notes

  • Runs are not joined across element boundaries — <em>12</em>34 produces two separate spans.
  • Full-width alphanumerics (A–Z / 0–9) are left alone because they already render upright in vertical text.
  • Both React and Vue adapters are SSR-safe: the DOM emitted on the server matches the client.

Browser support

writing-mode: vertical-rl and text-combine-upright: all are supported in all modern browsers. For Safari it's still worth including -webkit-text-combine: horizontal alongside the standard property.

License

MIT License. Free to use in personal and commercial projects.

See it in action

The interactive demo lets you tweak options and compare the raw text with the <Tcy> output side by side.

Open the demo