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.
npx shadcn@latest add https://webberui.com/r/cjk-vertical-nav.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<CjkVerticalNav />
Installation
npx shadcn@latest add https://webberui.com/r/cjk-vertical-nav.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: "序章" },
{ id: "history", label: "茶史" },
{ id: "tasting", label: "品茗之道" },
]}
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 | "章節導覽" | 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 (React)
A React date picker that toggles between the ROC (Minguo) calendar and the Gregorian one — a grid calendar with a "民國 115 年" (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.