react-xarrows: drawing arrows between React components without a layout library
Draw arrows (or lines) between components in React!
At a glance
- What is it?
- react-xarrows renders arrows and lines between React elements by id or ref, with a v2 rendering model built around Xwrapper and the useXarrow hook so only the affected arrows repaint. It ships ESM, CommonJS and UMD builds with no runtime dependencies beyond React itself.
- Who is it for?
- Adopt react-xarrows when your React application needs explanatory arrows between components in a diagram, tutorial or flow view, and you want zero runtime dependencies beyond React and typed builds. Skip it if you need a general diagram library with shapes, connectors and export, since this draws arrows only.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An arrow is drawn by pointing at two DOM nodes
react-xarrows exists to draw arrows, or lines, between components in a React application, and the API reflects that narrow job. The core claim in the feature list is that you connect arrows or lines between components just by passing an id or a ref, and the simple example in the README shows exactly that.
import React, {useRef} from "react";
import Xarrow from "react-xarrows";
const boxStyle = {border: "grey solid 2px", borderRadius: "10px", padding: "5px"};
function SimpleExample() {
const box1Ref = useRef(null);
return (
<div style={{display: "flex", justifyContent: "space-evenly", width: "100%"}}>
<div ref={box1Ref} style={boxStyle}>hey</div>
<p id="elem2" style={boxStyle}>hey2</p>
<Xarrow
start={box1Ref} //can be react ref
end="elem2" //or an id
/>
</div>
);
}
export default SimpleExample;The start prop accepts a React ref and the end prop accepts a DOM id string, and the component works out where to put the arrow from that. There is no coordinate to compute and no container to register, which is what makes the library easy to adopt into an existing page. The companion features are the reason it is worth choosing over a canvas approach, namely a simple API that is still fully customizable, what the project calls a smart and intuitive look and behavior, and cached parsed props for efficiency.
Xwrapper and useXarrow are what changed in version 2
The v2 line changed how updates work, and the README says so explicitly: to invoke updates on xarrows you wrap your arrows and connected elements with Xwrapper, and you consume useXarrow on connected elements. The usage section repeats it, that react-xarrows will smartly trigger updates on relevant elements, and that you use the Xwrapper component and the useXarrow hook to achieve selective rendering. The consequence is that a naive use of the library, an arrow with a start and an end id and nothing else, is no longer the recommended shape. Connected elements call the hook to receive an update function.
import {useXarrow} from "react-xarrow"
const YourComponent = ({id, ...props}) => {
const updateXarrow = useXarrow()
//...
return (
<div id={id}>
...
</div>
)
}Receiving updateXarrow is optional, and the README says to use that function only if you want to trigger a render in a different phase from the render itself. The wrapper then groups the arrows with the elements they touch, and the effect is that each time a component calling useXarrow renders, the xarrows inside the wrapping Xwrapper also render, while the rest of the page does not. For a diagram with a dozen arrows that is the difference between a smooth interaction and a full subtree repaint, and it is the main reason to move to v2 rather than stay on an older line.
Dragging a box is the case the v2 example is built around
The v2 example pairs the hook with react-draggable, which shows the intended usage pattern for anything that moves. Each draggable box calls useXarrow and passes the returned function to both the onDrag and onStop handlers of the draggable wrapper, so the arrow redraws continuously while the pointer is down and once more when it settles. The example wires two boxes and a single arrow between them.
const DraggableBox = ({id}) => {
const updateXarrow = useXarrow();
return (
<Draggable onDrag={updateXarrow} onStop={updateXarrow}>
<div id={id} style={boxStyle}>
{id}
</div>
</Draggable>
);
};The TypeScript example that follows extends the same idea with an absolute positioned canvas, several boxes at fixed coordinates, and multiple arrows between the same pair of elements, one of them setting endAnchor to top and another setting startAnchor to bottom. That repetition is deliberate, since the anchor props control which point of the element the arrow attaches to, and showing three arrows between two boxes demonstrates the anchor options more clearly than one arrow can. The example also shows two separate Xwrapper blocks in the same tree, which is the way you scope repaints to a region when your page contains several independent diagrams.
Zero runtime dependencies and three module formats
The package metadata is the clearest evidence for the zero dependencies claim, since the peer dependency block lists exactly one entry, react at 16.8.0 or newer, and nothing else appears there. The README phrases it as zero runtime dependencies, with React as the only peer dependency, and separately notes that the library ships ESM, CommonJS and UMD, with types resolving correctly under each. The exports map in package.json is what makes that concrete. The main entry offers an import condition pointing at an ESM file with matching .d.mts types, a require condition pointing at a CommonJS file with .d.cts types, and a default that falls back to the ESM build, plus a subpath export for package.json. Alongside those, the top level fields point at the CommonJS build for main, the ESM build for module, a declaration file for types, and the UMD build for both unpkg and jsdelivr, which is how the live demo and the component gallery can load the library from a CDN with no bundler. The peer dependency floor of React 16.8.0 is also the practical floor for the library's own API, since hooks did not exist before that version, so useXarrow and the rest require a React release with hooks support.
The v2 line rebuilt the build and the CI
The project status section is short and unusually direct. It says the project is actively maintained again after a long gap, that the v2 line is where the work is happening, that open bugs are being fixed, that the build and CI were rebuilt, and that releases are published from CI with npm provenance. That last point matters more than it sounds. npm provenance is the mechanism that ties a published tarball to the commit and the workflow that produced it, so a consumer can verify that the version on the registry came from this repository rather than from someone's laptop. The release history supports the claim. The tags are v2.2.1 on 2026-10-11, v2.2.0 on 2026-08-28 and v2.1.0 on 2026-08-08, and the last push was on 2026-10-11, the same day as the v2.2.1 tag. So the cadence is roughly monthly across the v2 line and the repository is not archived. The build itself runs through vite, with a build script that cleans the lib directory first, a separate watch mode, a type-check script running tsc with no emit, lint and format scripts wired to eslint and prettier, and a test script running vitest.
Testing and the story-per-bug examples directory
The repository layout shows a testing setup that goes beyond a single test file. There is an __test__ directory at the top level, a type-tests directory with its own package, an examples directory with its own package.json, vite config, storybook configuration and its own tests directory, plus separate vitest and vite configuration files, a pnpm workspace file and a pnpm lock file. The scripts confirm the split, with a test script running vitest, a coverage variant, a separate test:types script that filters the workspace to the react-xarrows-type-tests package, and demo and storybook scripts that filter to the react-xarrows-examples package. The README also points at a specific convention for contributions, saying the examples directory has a story per fixed bug that you can copy to build one, and that issues with a reproduction get looked at first. That is a useful signal about how the project is run, since each regression becomes a runnable example rather than only a test assertion. The repository also carries a CHANGELOG.md, a TODO.md, a netlify.toml, a Gitpod configuration and an eslint config module, so the deployment of the documentation site and the contributor workflow are both handled in the tree.
Where react-xarrows is the wrong tool
The boundaries are clear once you see what the library is. It draws arrows and lines between components and nothing else, so if your requirement includes shapes, boxes with text, grouped nodes, a canvas you can pan and zoom, or export to SVG or PNG, you need a diagram library rather than this one. The anchor props let you choose where an arrow attaches, but there is no routing engine, so an arrow between two distant nodes will cross whatever sits between them. The second limitation is update behaviour, and it is the one to test before you commit. The v2 model requires wrapping arrows and their connected elements in Xwrapper and calling useXarrow on anything that moves, and the examples show this with an explicit drag handler. If a container resizes, or a font loads late and the layout shifts, the arrow positions have to be recomputed, and the README does not document a resize observer or an automatic reflow on layout change, so that is a behaviour you need to confirm for your own layout rather than assume. A third practical point is that arrows are rendered as SVG in the page, so very large numbers of them, or frequent re-renders of a complex diagram, are the case where a canvas based renderer would be the better engineering choice.
A hand drawn SVG overlay instead of a library
The alternative is to draw the arrows yourself, and for a single static diagram that is often less work than adding a dependency. You already have both endpoints in the DOM, so you can read their positions with getBoundingClientRect, compute the two anchor points, and render an SVG line or path yourself, updating it in a layout effect or on a scroll and resize listener. The advantages are real. There is no dependency, no version constraint, no peer requirement on a React release with hooks, and the markup is entirely yours, so you can style it however your design system wants and avoid whatever default stroke the library applies. The costs are equally real. You reimplement anchor selection, curvature, label placement, hit testing, and the update propagation that keeps arrows aligned when elements move, and every one of those is a piece of geometry that looks trivial until it is not. react-xarrows has already solved that set of problems and, in v2, solved the performance version of them, with selective repainting through Xwrapper and useXarrow and cached parsed props. The library also removes the need to keep your own position state in sync with the DOM, which is where a hand rolled version usually starts to drift.
Editorial conclusion
Adopt react-xarrows when your React application needs explanatory arrows between components in a diagram, tutorial or flow view, and you want zero runtime dependencies beyond React and typed builds. Skip it if you need a general diagram library with shapes, connectors and export, since this draws arrows only. Verify first that your React version satisfies the peer dependency of 16.8.0 or newer, that you understand the v2 requirement to wrap arrows and connected elements in Xwrapper, and that the arrow updates when a container resizes rather than assuming a live drag.
Frequently asked questions
How do I install react-xarrows and draw an arrow between two components?
Install it with npm install react-xarrows, or yarn add react-xarrows, then render the Xarrow component with a start prop that is either a React ref or an id string and an end prop that is an id string. The arrow is positioned from those two DOM elements.
What are Xwrapper and useXarrow for in react-xarrows?
In the v2 line you wrap your arrows and connected elements in Xwrapper and call useXarrow on connected elements. Each time a component using the hook renders, the arrows inside that wrapper render too, which gives selective repainting instead of redrawing every arrow on the page.
Does react-xarrows work without a bundler and does it need React hooks?
The package ships ESM, CommonJS and UMD builds, with the UMD build exposed on unpkg and jsdelivr for CDN use, and the types resolve under each format. React is the only peer dependency and the declared range is 16.8.0 or newer, which is the version where hooks were introduced.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/eliav2-react-xarrows)Was this useful?