Selecting Elements
Selection is the backbone of an editor: click a node to select it, see it highlight, act on it. In JointJS+ this is a built-in feature, so you don't wire pointer events or hold selection in React state yourself.
import { Diagram, Paper, PaperScroller, Selection, HTMLHost, useIsCellSelected, useSelection, useCells, linkRoutingOrthogonal, type ElementPort, } from '@joint/react-plus'; import '@joint/react-plus/styles.css'; import './example.css'; type NodeData = { label: string; description: string; icon: string; color: string }; const ports: Record<string, ElementPort> = { in: { cx: 0, cy: '50%', width: 10, height: 10, passive: true }, out: { cx: '100%', cy: '50%', width: 10, height: 10 }, }; const node = (id: string, x: number, y: number, data: NodeData) => ({ id, type: 'element' as const, position: { x, y }, size: { width: 160, height: 52 }, portMap: ports, data, }); const initialCells = [ node('a', 60, 50, { label: 'New Ticket', description: 'Trigger', icon: '⚡', color: '#f59e0b' }), node('b', 320, 50, { label: 'Is Urgent?', description: 'Condition', icon: '◆', color: '#a855f7' }), node('c', 320, 180, { label: 'Send Reply', description: 'Action', icon: '▶', color: '#22c55e' }), { id: 'a→b', type: 'link', source: { id: 'a', port: 'out' }, target: { id: 'b', port: 'in' }, style: { color: '#94a3b8', width: 2 } }, { id: 'b→c', type: 'link', source: { id: 'b', port: 'out' }, target: { id: 'c', port: 'in' }, style: { color: '#94a3b8', width: 2 } }, ]; // The element reads its own selected state — no context, no props threaded down. function NodeCard({ label, description, icon, color }: NodeData) { const isSelected = useIsCellSelected(); const className = isSelected ? 'node node--selected' : 'node'; return ( <HTMLHost useModelGeometry className={className}> <span className="node__icon" style={{ color }}> {icon} </span> <div> <div className="node__label">{label}</div> <div className="node__desc">{description}</div> </div> </HTMLHost> ); } // The footer reads the selection collection reactively. function Footer() { const { collection } = useSelection(); const selectedIds = useCells(collection, (cells) => (cells ?? []).map((c) => String(c.id))); return ( <footer className="footer"> {selectedIds.length ? ( <>Selected <code>{selectedIds.join(', ')}</code> · drag to move · click blank to clear</> ) : ( 'Click a node to select it · drag any node to move it' )} </footer> ); } export default function App() { // <Diagram>'s built-in interactions wire selection for you: click selects, // Cmd/Ctrl+click toggles, blank-click clears. The <Selection> feature turns // it on for this Paper — no custom context or pointer-event handlers needed. return ( <Diagram initialCells={initialCells}> <div className="stage"> <PaperScroller mode="infinite" className="canvas"> <Paper gridSize={10} renderElement={NodeCard} linkRouting={linkRoutingOrthogonal()}> <Selection wrapper={{ handles: [] }} /> </Paper> </PaperScroller> <Footer /> </div> </Diagram> ); }
Click an element to select it (the ring appears and the footer updates). Cmd/Ctrl+click to toggle, Shift+drag to rubber-band a region, click empty canvas to clear. Drag any element to move it.
How it works
<Diagram> ships built-in interactions (on by default), so dropping the <Selection> feature into the Paper makes click select, Cmd/Ctrl+click toggle, region select, and blank-click clear work automatically:
function NodeCard() {
const isSelected = useIsCellSelected(); // this element's selected state
const className = isSelected ? 'node node--selected' : 'node';
return <HTMLHost useModelGeometry className={className}>…</HTMLHost>;
}
// <Selection> turns selection on for this Paper — no events to wire.
// wrapper={{ handles: [] }} keeps just the outline (no resize/rotate handles).
<Paper renderElement={NodeCard}>
<Selection wrapper={{ handles: [] }} />
</Paper>
// Read the live selection from anywhere inside <Diagram>
const { collection } = useSelection();
const selectedIds = useCells(collection, (cells) => (cells ?? []).map((c) => String(c.id)));
Two hooks connect selection to your React UI:
useIsCellSelected(): insiderenderElement, subscribes an element to just its own selected state, so only the elements whose selection changed re-render.useSelection(): the<Selection>feature's companion — from anywhere inside<Diagram>, exposes the shared selectioncollection, aselectCellssetter, and the selection view itself (region gestures, select-all). Read the collection reactively withuseCells(collection, …).
The <Selection> feature also brings a selection box, resize and rotate handles, and rubber-band region selection for free. This demo passes wrapper={{ handles: [] }} to keep just the selection outline; drop that to get the full handle set. Full options in Selection.
@joint/reactThe community edition has no built-in selection. You wire it yourself: hold the selected id in React state (or a small Context), set it from the Paper pointer events shown in Interactivity & Events, and read it back in each element. It's a few lines, but JointJS+ gives you the whole interaction, plus the box and handles, out of the box.
Next
You have a selectable diagram. Next, add the JointJS+ editor building blocks around it, starting with an element palette.