CJK Vertical Nav
A side navigation bar in vertical Chinese writing — writing-mode vertical typesetting, with an ink line marking the current item.
A side navigation bar in vertical Chinese writing (writing-mode: vertical-rl), with the columns running right to left like the table of contents of a classical book. The current item is marked by an ink-brush vertical line on its right, which glides between sections on a spring animation via layoutId; on hover the letter spacing widens slightly and the text color deepens. Pair it with an IntersectionObserver to track the article's scroll position.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add cjk-vertical-nav.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<CjkVerticalNav/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/cjk-vertical-nav.en.jsonOr, once registries are configured in components.json, install it as @webberui/cjk-vertical-nav.
Usage
Uncontrolled usage: it maintains the current item itself and switches on click.
import { CjkVerticalNav } from "@/components/ui/cjk-vertical-nav";
<CjkVerticalNav
items={[
{ id: "prologue", label: "Prologue" },
{ id: "history", label: "Tea History" },
{ id: "tasting", label: "The Way of Tea Tasting" },
]}
defaultActiveId="prologue"
/>Controlled usage: pair it with an IntersectionObserver to track the section being scrolled through and write the result back to activeId, and the ink line follows along automatically.
const [activeId, setActiveId] = React.useState("prologue");
React.useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) setActiveId(entry.target.id);
}
},
{ rootMargin: "-40% 0px -55% 0px" },
);
document
.querySelectorAll("section[id]")
.forEach((el) => observer.observe(el));
return () => observer.disconnect();
}, []);
<CjkVerticalNav
items={items}
activeId={activeId}
onSelect={(id) => {
setActiveId(id);
document.getElementById(id)?.scrollIntoView({ behavior: "smooth" });
}}
/>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | CjkVerticalNavItem[] | — | The navigation item list (id and label), arranged right to left in vertical reading order |
activeId | string | — | Controlled mode: the id of the currently active item |
defaultActiveId | string | first item | Initial active item id in uncontrolled mode |
onSelect | (id: string) => void | — | Callback when an item is selected |
ariaLabel | string | "Chapter Guide" | Accessible name for the nav landmark |
className | string | — | Forwarded to the outermost nav |
Accessibility
- The outermost element is a
navlandmark with anaria-label, so screen readers can jump straight to the navigation region - The current item is marked
aria-current="true", so assistive technology knows which section you are in - Items are native
buttons supporting Tab focus and Enter/Space activation, with a clearfocus-visibleoutline - When the user has "reduce motion" enabled at the system level, the ink line changes position instantly with no spring movement animation
Chinese Zodiac Wheel
A twelve-animal zodiac ring — rotate to select, and the chosen sign expands into a card of years and traits.
ROC (Minguo) Date Picker
A React date picker that toggles between the ROC (Minguo) calendar and the Gregorian one — a grid calendar with a "ROC (Minguo) 115 Month" (Minguo year) dropdown, typed input like "115/08/18" or "2026-08-18" parsed with an IME guard, and ISO output that also carries the ROC and Gregorian forms.