React adapter

@love-rox/tcy-react

A `<Tcy>` component for React 17+. Walks children recursively and wraps half-width alphanumeric runs in `<span class="tcy">`. Produces deterministic, SSR-safe output.

v0.3.0MITSSR-safe

Install

@love-rox/tcy-core comes along as a dependency — no separate install needed.

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 in <Tcy> inside an element with writing-mode: vertical-rl. Only string children are rewritten; element children are walked recursively without being touched.

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

export function Chapter() {
  return (
    <p style={{ writingMode: "vertical-rl" }}>
      <Tcy>第1章 2026年4月、Webの縦書きは進化した。</Tcy>
    </p>
  );
}

Options

<Tcy> accepts the options below. The shared options behave identically to the Vue, rehype, and Astro adapters.

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.
maxLengthnumberundefinedMaximum length for a tcy segment. Runs longer than this are demoted back to plain text — for example maxLength: 2 keeps single digits and pairs uprighted while leaving four-digit years like 2026 lying on their side.
excludeWordsstring[]undefinedExact words to exclude from tcy wrapping. Matched against the whole segment value (not a substring) — useful for keeping acronyms like URL / API or specific years out of the upright treatment.

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.
  • SSR-safe — server and client emit the same DOM, so there's no hydration mismatch.
  • Children that contain React elements are still walked recursively; text inside those elements is processed too.

Other adapters

Using Vue? The Vue adapter has the same API. On a Markdown / Astro pipeline? The rehype and Astro adapters do this at build time, with no React dependency in the bundle.