Problem 
The GoJS library offers some solutions to enhance accessibility, but they are limited and fairly straightforward. In Synergy, we have implemented additional solutions that can further improve accessibility.
One of the missing aspects is accessibility support for the palette component. The default GoJS palette does not have built-in keyboard support which means we neither can navigate through palette elements only with arrow keys nor place them on the diagram without using mouse. These are very important aspects of web accessibility since there are users that rely only on the keyboards since they cannot use mouse because of different disabilities.
Thanks to the solution below, we will be able to add nodes using only the keyboard. Additionally, we will leverage an external library to create the palette using HTML, which will enhance both the UI and UX of the palette items. The HTML palette has many advantages over the canvas one. Not only from accessibility aspects but it gives much more customizations options.
This example uses dnd-kit
, a React library that helps handle drag-and-drop functionality and adds keyboard support by default. Therefore, the example is written in React. However, most frontend frameworks/libraries have their own drag-and-drop solutions that can be used in a similar way.
Solution 
Introduction
Below, I will present only the most essential parts required for implementing the drag-and-drop functionality. Some parts of the solution might be omitted in this explanation, but the complete implementation will be linked at the end via StackBlitz, where you can see how it works.
We will use the following libraries:
Implementation
First, we need to wrap our Diagram
and Palette
components with the DndContext
from @dnd-kit/core
:
// App.tsx
import { DndContext } from '@dnd-kit/core';
...
<DndContext onDragStart={handleOnDragStart} onDragEnd={handleOnDragEnd}>
<div className="wrapper">
<Diagram />
<Palette activeItemId={activeItemId} />
</div>
</DndContext>
In the example above, we render the Diagram
and Palette
components within the DndContext
. We pass activeItemId
to the palette, representing the ID of the currently dragged item. This will allow us to show the dragged element moving with the cursor.
Below are the implementations of handleOnDragStart
and handleOnDragEnd
:
// App.tsx
import { DragEndEvent, DragStartEvent } from "@dnd-kit/core";
...
// Set activeItemId on drag start
const handleOnDragStart = (event: DragStartEvent) =>
setActiveItemId(event.active.id as string);
const handleOnDragEnd = ({
over,
active,
activatorEvent,
delta,
}: DragEndEvent) => {
setActiveItemId(null);
if (over?.id !== "diagram" || !diagram?.div || !active.data) {
return;
}
let startX = 0;
let startY = 0;
// The data below is passed by the palette item, providing the starting position when dragging begins.
const data = active.data.current as {
x: number;
y: number;
width: number;
height: number;
};
// Determine startX and startY depending on whether dragging was initiated via mouse or keyboard.
if (
activatorEvent instanceof PointerEvent ||
activatorEvent instanceof MouseEvent
) {
startX = activatorEvent.clientX;
startY = activatorEvent.clientY;
} else {
startX = data.x;
startY = data.y;
}
const diagramRect = diagram.div.getBoundingClientRect();
const x = startX + delta.x - diagramRect.x - (data.width * diagram.scale) / 2;
const y =
startY + delta.y - diagramRect.y - (data.height * diagram.scale) / 2;
diagram.commit(() => {
// Add node with calculated position
diagram.model.addNodeData({
location: go.Point.stringify(
diagram.transformViewToDoc(new go.Point(x, y))
),
text: `Node ${activeItemId}`,
});
});
};
Next, we need to make the Diagram
a droppable area so that users can drop nodes onto it:
// Diagram.tsx
import { useDroppable } from '@dnd-kit/core';
...
const diagramRef = useRef<HTMLDivElement>(null);
const { setNodeRef: setDroppableRef } = useDroppable({
id: "diagram",
});
...
setDroppableRef(diagramRef.current);
...
<div className="diagram" ref={diagramRef} />
The final step is to prepare the Palette and PaletteItem components to enable dragging from the palette:
// PaletteItem.tsx
import { useEffect, useRef } from "react";
import { useDraggable } from "@dnd-kit/core";
export function PaletteItem({ id }: { id: string }) {
const ref = useRef<HTMLDivElement | null>(null);
const rect = ref.current?.getBoundingClientRect();
const { attributes, listeners, setNodeRef } = useDraggable({
id,
data: rect
? {
x: rect.x + 40,
y: rect.y + 20,
width: rect.width,
height: rect.height,
}
: undefined,
});
useEffect(() => {
if (ref.current) {
setNodeRef(ref.current);
}
}, [ref, setNodeRef]);
return (
<div ref={ref} {...attributes} {...listeners}>
Node {id}
</div>
);
}
The code above is a complete example of the PaletteItem
component, which renders a simple palette item. We call useDraggable
to make the element draggable, passing its position and size as data. The x
and y
values represent the center point, since GoJS nodes have their locationSpot
set to the center. This data is used inside the handleOnDragStart
function in App.tsx
.
// Palette.tsx
import { DragOverlay } from '@dnd-kit/core';
...
<DragOverlay dropAnimation={null}>
{activeItemId ? <PaletteItem id={activeItemId} /> : null}
</DragOverlay>
Inside the Palette
component, we render a PaletteItem
for each item. Additionally, we implement the DragOverlay
using the same PaletteItem
component. This ensures that the dragged element looks exactly like the one inside the palette.
Summary
The above code sections are sufficient to enable keyboard support for the palette, which is provided by default by @dnd-kit/core. The key is to wrap everything properly so that it integrates well with GoJS.
The complete solution can be found here: StackBlitz