Reference
Architecture
Three packages sharing one engine: @macrulez/visual-linker-core does the actual work with zero framework dependency — a single createVisualLinker(container, config) factory that measures blocks/ports via getBoundingClientRect(), batches recalculation through a ResizeObserver-driven watcher plus requestAnimationFrame, and renders paths into a plain SVG layer it creates and owns inside container. @macrulez/visual-linker-vue is a thin adapter: the <VisualLinker> component wraps your own markup, discovers blocks and ports marked in it (the v-vl-block/v-vl-port directives, data-vl-* attributes, or the blocks prop) with a MutationObserver-driven scan, runs the engine inside a drawing layer of its own — in the component's box, or a position: fixed layer teleported to <body> with scope="page" — and forwards the engine's layout event into three optional HTML overlay slots (#connection-label/#port/#marker); useVisualLinker() skips discovery entirely and just wires the engine to a container and blocks/connections you already own. The Vue package re-exports the full core surface, so installing @macrulez/visual-linker-vue alone is enough — no separate core install needed. @macrulez/visual-linker-nuxt adds nothing beyond auto-imports, a universal plugin registering the two directives, and the app-wide shared configuration, taken from nuxt.config.ts.
SSR compatibility
<VisualLinker> and useVisualLinker() are both SSR-safe — the engine is only ever created client-side, after mount, and <VisualLinker>'s drawing layer isn't rendered until then either, so the server output and the hydration pass stay identical. The v-vl-block/v-vl-port directives emit their data-vl-* attributes during server rendering too. No <ClientOnly> wrapper is needed anywhere, including with the Nuxt module.
Accessibility
By default the connections are a purely visual SVG overlay: every line carries role="img" and an aria-label (the connection's ariaLabel, or "Connection: a → b"), and its invisible hit area is hidden from assistive technology. Block content is your own markup and keeps whatever accessibility semantics you already gave it; marking an element as a block or port only adds data-vl-* attributes to it. If a connection conveys information a screen reader user needs (e.g. "step 2 depends on step 1"), express that relationship in the surrounding text or block content as well, rather than relying on the drawn line alone.
With the interaction.selectable option each connection becomes a focusable button that can be selected from the keyboard — see Selection & Accessibility. The animated flow is switched off for users who prefer reduced motion.
Bundle size & peer dependencies
@macrulez/visual-linker-core has no peer dependencies at all — usable standalone in any environment. @macrulez/visual-linker-vue depends on @macrulez/visual-linker-core (workspace:*) and peers on vue: ^3.3.0. @macrulez/visual-linker-nuxt depends on @macrulez/visual-linker-vue and @nuxt/kit, and peers on nuxt: ^3.9.0 || ^4.0.0. All three ship sideEffects: false.
Development
A pnpm workspace (packages/core, packages/vue, packages/nuxt, plus a Vue playground).
pnpm install
pnpm build # builds every package
pnpm test # runs every package's tests
pnpm typecheck
pnpm dev # runs the Vue playground
pnpm lint # eslint across the whole monorepo
pnpm format # prettier --write across the whole monorepoLicense
MIT.