<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wojciech Krzesaj</title>
    <description>The latest articles on DEV Community by Wojciech Krzesaj (@wojciechkrzesaj).</description>
    <link>https://dev.to/wojciechkrzesaj</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3871656%2Fc6f235c6-472c-40e2-af49-d448a6a1bf35.jpg</url>
      <title>DEV Community: Wojciech Krzesaj</title>
      <link>https://dev.to/wojciechkrzesaj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wojciechkrzesaj"/>
    <language>en</language>
    <item>
      <title>Why your accessibility tools can't see your diagram</title>
      <dc:creator>Wojciech Krzesaj</dc:creator>
      <pubDate>Wed, 20 May 2026 08:30:04 +0000</pubDate>
      <link>https://dev.to/wojciechkrzesaj/why-your-accessibility-tools-cant-see-your-diagram-5f3</link>
      <guid>https://dev.to/wojciechkrzesaj/why-your-accessibility-tools-cant-see-your-diagram-5f3</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lighthouse scored my Org Chart 98%. A keyboard user could not reach a single node.&lt;/li&gt;
&lt;li&gt;I audited from three angles: automated tools, manual pass, LLM review.&lt;/li&gt;
&lt;li&gt;Fixes: landmarks, headings, focus styles, &lt;code&gt;role="tree"&lt;/code&gt; + custom keyboard shortcuts, skip-link as documentation.&lt;/li&gt;
&lt;li&gt;Tools score documents, not diagrams. They check what they can check.&lt;/li&gt;
&lt;li&gt;The biggest takeaway: try the app with the keyboard before the first commit.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Accessibility is getting harder to ignore, and that is mostly a good thing. The same markup that helps a screen reader user also lets web crawlers and LLM agents work from structure instead of screenshots. The catch is that the apps where the standard tools and articles say the least - diagrams, dashboards, data-heavy interfaces - are the ones where teams will increasingly need answers. The complexity is real, but it is not an excuse.&lt;/p&gt;

&lt;p&gt;I built an Org Chart with ngDiagram a while back, and I want accessibility to be part of how I build rather than something I bolt on later. To audit it I picked the tools I had not used before on a real app: Lighthouse, WAVE, an accessibility linter, and an LLM-based review. What follows is a walkthrough of that audit rather than a guide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs10ee20sg2hdr92qx7yt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs10ee20sg2hdr92qx7yt.png" alt="Org chart app with a node focused, showing a visible focus ring" width="800" height="450"&gt;&lt;/a&gt;The org chart after the audit - a node has keyboard focus, with a visible focus ring.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit
&lt;/h2&gt;

&lt;p&gt;The plan was to look at the app from three angles: automated tools, a manual pass, and an LLM review of the code itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the automated tools said
&lt;/h3&gt;

&lt;p&gt;Lighthouse went first. 98 out of 100, with one fix (a missing main landmark) and a manual checklist worth running through.&lt;/p&gt;

&lt;p&gt;WAVE was more useful. Zero errors, but the warnings were the interesting part: node descriptions rendered below comfortable reading size and no heading structure on the page at all. The order tab was the only thing nagging me - something about the labels on the diagram's tab stops did not quite line up with what I expected, and that was enough to push me into trying the app manually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wu4ihgwfnap91127tcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wu4ihgwfnap91127tcg.png" alt="WAVE order tab overlay numbering focusable elements 1, 2, 3... across the toolbar before the canvas" width="800" height="450"&gt;&lt;/a&gt;WAVE's order tab numbering the focusable elements. The canvas is buried far down the sequence.&lt;/p&gt;

&lt;p&gt;The ESLint accessibility rules (&lt;a href="https://github.com/angular-eslint/angular-eslint" rel="noopener noreferrer"&gt;angular-eslint's templateAccessibility ruleset&lt;/a&gt;) came in later as a habit rather than an audit. They do not catch the big things - those live in interactions, not markup. But they raise a hand when a label is missing or an aria attribute is wrong, every time you save. Next step is wiring them into CI so a missing label is a build failure rather than a reminder I might ignore.&lt;/p&gt;

&lt;h3&gt;
  
  
  What clicking and tabbing told me
&lt;/h3&gt;

&lt;p&gt;I set the mouse aside and opened the browser's developer tools. The accessibility panel has an option to show the page as a full accessibility tree - a nice discovery on its own. The zoom level was rendered as a bare number, which a screen reader would read as "100" with no idea what it was 100 of. The minimap was technically hidden when collapsed, but the underlying image was still in the tree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xd7fjtcw64z1hedji3m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xd7fjtcw64z1hedji3m.png" alt="Browser DevTools accessibility tree panel showing a text node containing only the number 57, with no surrounding label" width="800" height="400"&gt;&lt;/a&gt;The accessibility tree as a screen reader sees it: "57" with no context.&lt;/p&gt;

&lt;p&gt;The keyboard pass was where the audit stopped feeling routine. Tabbing worked fine until I reached the diagram, and at first I thought it worked there too. What I was actually reaching were the expand/collapse buttons on each node. The nodes themselves had no focus, and the plus-buttons for adding new ones only appeared on mouse hover - which meant they did not exist for anyone not using one, and they did not exist for the audit tools either, because there is nothing in the DOM until the pointer event fires. Drag-and-drop reordering was the same story. Two of the most important things in the app were entirely mouse-only, and the previous tools had not flagged any of it. They were not wrong. They were checking what they can check, and they cannot check interactions that only exist while you hover.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the LLM caught
&lt;/h3&gt;

&lt;p&gt;For the third pass I used Claude with a custom skill based on &lt;a href="https://mcpmarket.com/tools/skills/accessibility-review-1" rel="noopener noreferrer"&gt;the accessibility-review skill on MCPMarket&lt;/a&gt;. It agreed with the static tools on the obvious things. What it added was findings you can only get by reading the code: the specific input that auto-focused when the properties sidebar opened, the exact &lt;code&gt;pointerdown&lt;/code&gt; handler that locked add-buttons to a mouse, and the missing keyboard entry on the diagram as a top finding rather than a footnote. Running it twice gave overlapping but not identical lists. Not every point was worth fixing, and saying no to a few of them ended up being part of the audit too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the diagram keyboard-reachable
&lt;/h2&gt;

&lt;p&gt;The first decision was what kind of thing the diagram actually is. An org chart is a tree, so the canvas got &lt;code&gt;role="tree"&lt;/code&gt; and each node got &lt;code&gt;role="treeitem"&lt;/code&gt;. That changes how a screen reader narrates the structure: instead of "graphic, region" it reads as a hierarchical tree, which matches what the user is actually looking at. Picking the role is the application's job, not the library's - ngDiagram does not know whether you are rendering an org chart, a state machine, or a dependency graph, and each of those wants a different role.&lt;/p&gt;

&lt;p&gt;The canvas gets the tree role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"diagram-instructions"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"tree"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Organization tree"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ng-diagram&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each node component declares itself a &lt;code&gt;treeitem&lt;/code&gt; through its host bindings, alongside the aria state it needs to expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.role]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"treeitem"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.tabindex]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isFocusable() ? 0 : -1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-selected]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node().selected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-expanded]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ariaExpanded()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-label]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ariaLabel()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NodeComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;isFocusable()&lt;/code&gt; is a roving tabindex: only one node is in the tab order at a time, so Tab moves in and out of the canvas in one step rather than walking every node.&lt;/p&gt;

&lt;p&gt;The interaction model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
Shift + arrows&lt;/td&gt;
&lt;td&gt;Move focus between nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrows (when focused)&lt;/td&gt;
&lt;td&gt;Move the node on the canvas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Space&lt;/td&gt;
&lt;td&gt;Expand / collapse children&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
Enter / Esc
&lt;/td&gt;
&lt;td&gt;Open / close properties panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
Alt + arrows&lt;/td&gt;
&lt;td&gt;Add a new node next to the focused one (child or sibling depending on layout direction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;td&gt;Remove the focused node&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8numcfnrhkmf74ygt26a.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8numcfnrhkmf74ygt26a.gif" alt="Animated demo of moving focus between org chart nodes with Shift+arrow keys, expanding a node with Space, and adding a sibling with Alt+arrow" width="800" height="450"&gt;&lt;/a&gt;Keyboard navigation across the tree: focus moves with Shift+arrows, Space expands, Alt+arrow adds a new node.&lt;/p&gt;

&lt;p&gt;Adding all of this raised a question I had not been thinking about: how does the user know any of it exists? Implementing keyboard support is one job. Making sure people can find out it is there is another - a shortcut nobody is told about is a secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skip-link as documentation
&lt;/h2&gt;

&lt;p&gt;The answer ended up sitting next to the skip-link. The skip-link itself jumps focus to the diagram; right after it, a short paragraph lists every shortcut. The diagram's &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; references that paragraph via &lt;code&gt;aria-describedby&lt;/code&gt;, so the instructions get announced when focus enters the canvas. The paragraph stays visible on the page too - sighted keyboard users need to know the shortcuts as much as screen reader users do, and the audit tools cannot tell them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmsnam1d4eggoh5thf5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmsnam1d4eggoh5thf5k.png" alt="Top of the page with the focused 'Skip to diagram' link followed by a paragraph listing all keyboard shortcuts" width="800" height="450"&gt;&lt;/a&gt;The skip-link with the shortcut list immediately next to it - visible to sighted users, announced to screen readers.&lt;/p&gt;

&lt;p&gt;The side effect was the thing I did not see coming. Once there was a place to tell the user about a shortcut, the answer to "the plus-buttons only show on hover" stopped being "show them on focus too" and became "a custom shortcut, written down right next to the skip-link." Having somewhere to document a shortcut is what made them worth building at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smaller fixes
&lt;/h2&gt;

&lt;p&gt;The static-tool findings mostly translated to small fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page got a &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; landmark and a visually-hidden &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Styled spans pretending to be headings became real heading tags.&lt;/li&gt;
&lt;li&gt;Icon-only buttons (add, toggle, caret, sidebar, theme, zoom) picked up &lt;code&gt;aria-label&lt;/code&gt;s; the toggle-expand button got &lt;code&gt;aria-expanded&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;"100" in the zoom widget became "Zoom level 100" - the kind of fix where a lot of accessibility work hides: rewriting a number to say what the number is of.&lt;/li&gt;
&lt;li&gt;Blanket &lt;code&gt;outline: none&lt;/code&gt; came out of the form styles; &lt;code&gt;:focus-visible&lt;/code&gt; rings went in.&lt;/li&gt;
&lt;li&gt;The minimap, which duplicates content the keyboard user already navigates on the main canvas, got &lt;code&gt;aria-hidden="true"&lt;/code&gt; - along with the button that toggles it, since a control that opens a panel the user cannot perceive is no use to them either.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I left alone (on purpose)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drag-and-drop hierarchy changes are still mouse-only.&lt;/strong&gt; A real gap; a keyboard equivalent (cut/paste-style reparenting) was bigger than this round could absorb.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No help dialog or shortcut overlay yet.&lt;/strong&gt; The description next to the skip-link is enough to make the keyboard support discoverable for this pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The properties sidebar is deliberately non-modal.&lt;/strong&gt; No focus trap, no focus return on close, because a user editing a node's properties should still be able to move around the diagram underneath. That one is a feature, not a deferred fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this leaves me
&lt;/h2&gt;

&lt;p&gt;The audit changed the app. What I did not expect was that it would also change how I look at apps. If I had spent any real time using my own work with the keyboard only, before I started building it, I would have built it differently from day one. That is the thing I am taking forward: not a list of fixes, but the habit of trying the app the way the user will, before the first commit.&lt;/p&gt;

&lt;p&gt;Tools like Lighthouse, WAVE, and the LLM pass are useful starting points. They tell you where to look. They cannot tell you whether the app is actually usable. That part still needs a person, a keyboard, and a willingness to be honest with yourself about what you find.&lt;/p&gt;

&lt;p&gt;Diagrams and other visual tools have a lot going on - many features, many interactions, many edge cases - and making them work for every user is harder than making a form work for every user. Most of the existing material does not help with the difference. If you are curious about how those problems play out in practice (and where some of them are still unsolved), come talk to us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to look next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.synergycodes.com/gaad-accessibility-awareness-day" rel="noopener noreferrer"&gt;Building Accessible Whiteboards and Diagrams&lt;/a&gt; - the GAAD 2026 webinar we are hosting&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/synergycodes/ng-diagram-orgchart" rel="noopener noreferrer"&gt;ngDiagram Org Chart&lt;/a&gt; - the app the audit was on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.ngdiagram.dev" rel="noopener noreferrer"&gt;ngDiagram&lt;/a&gt; - the library the app is built on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/angular-eslint/angular-eslint" rel="noopener noreferrer"&gt;angular-eslint&lt;/a&gt; - the ESLint plugin with the templateAccessibility ruleset&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mcpmarket.com/tools/skills/accessibility-review-1" rel="noopener noreferrer"&gt;accessibility-review skill&lt;/a&gt; - the basis for the LLM audit skill I used&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;GAAD 2026 webinar - Thursday, 21 May, 14:00 CET&lt;/strong&gt;&lt;br&gt;
Anna Dulny-Leszczyńska and I are hosting &lt;a href="https://www.synergycodes.com/gaad-accessibility-awareness-day" rel="noopener noreferrer"&gt;Building Accessible Whiteboards and Diagrams&lt;/a&gt;: a free, 45-minute talk with real examples instead of theory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://luma.com/ps1l5vu9" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Save your seat&lt;/a&gt;
&lt;/p&gt;




</description>
      <category>a11y</category>
      <category>angular</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I've Used GoJS for Years. Here's What Happened When I Built an Org Chart with ngDiagram</title>
      <dc:creator>Wojciech Krzesaj</dc:creator>
      <pubDate>Tue, 14 Apr 2026 07:59:11 +0000</pubDate>
      <link>https://dev.to/ngdiagram-dev/ive-used-gojs-for-years-heres-what-happened-when-i-built-an-org-chart-with-ngdiagram-1gkn</link>
      <guid>https://dev.to/ngdiagram-dev/ive-used-gojs-for-years-heres-what-happened-when-i-built-an-org-chart-with-ngdiagram-1gkn</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I've spent 5+ years building diagram-heavy apps with GoJS. Recently I built an org chart starter kit with ngDiagram - an Angular-native, open-source diagramming library. The big difference: nodes are just Angular components, styling is just CSS, and the MCP server makes AI assistance actually useful during development. The trade-off is real though - you build more yourself (layouts or expand/collapse), but you get full control and zero context-switching between "diagram code" and "Angular code." If you're an Angular dev evaluating diagramming options, this is an honest breakdown of both. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I work at Synergy Codes, the company behind ngDiagram. This is my honest experience - including the rough edges. &lt;/p&gt;

&lt;p&gt;Picking a diagramming library for your project is a bigger decision than it looks. It's not just "can it render nodes and edges" - it's how it fits into your stack, how it scales with your requirements, and how productive you are day to day while working with it. The right choice helps you develop and introduce new features faster. &lt;/p&gt;

&lt;h2&gt;
  
  
  I've Used GoJS for Five Years. Three Weeks with ngDiagram Changed My Perspective
&lt;/h2&gt;

&lt;p&gt;I work as a Software Developer at &lt;a href="https://synergycodes.com/" rel="noopener noreferrer"&gt;Synergy Codes&lt;/a&gt; (the company behind ngDiagram), where I've been building diagram-heavy applications for over 5 years. Most of my experience is with GoJS, but I've worked with other diagramming libraries too. The projects spanned industries from automotive manufacturing and AI-powered tools to engineering platforms and enterprise analytics.  &lt;/p&gt;

&lt;p&gt;GoJS is powerful, no question. But it always felt like bolting a separate application onto your project. Recently, I built an org chart starter kit with &lt;a href="https://ngdiagram.dev/" rel="noopener noreferrer"&gt;ngDiagram&lt;/a&gt;, an Angular-native library that rethinks the whole approach. With all that background, I had a lot of context for what usually goes wrong and what usually works.  &lt;/p&gt;

&lt;p&gt;Here's what I found after about 3 weeks of building. &lt;br&gt;
 &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37in7s7glmza1bnsg1p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37in7s7glmza1bnsg1p4.png" width="800" height="450"&gt;&lt;/a&gt;Bird's-eye view of the org chart starter kit built with ngDiagram&lt;/p&gt;
&lt;h2&gt;
  
  
  The Brief: What This Org Chart Had to Do
&lt;/h2&gt;

&lt;p&gt;The goal: build an interactive org chart to showcase ngDiagram's capabilities. Not a static tree, but a fully interactive product. &lt;/p&gt;

&lt;p&gt;It needed to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display employees in a hierarchy with editable properties. &lt;/li&gt;
&lt;li&gt;Support drag &amp;amp; drop to restructure reporting lines. &lt;/li&gt;
&lt;li&gt;Render in both horizontal and vertical tree layouts. &lt;/li&gt;
&lt;li&gt;Show different node variants depending on zoom level - full details when zoomed in, compact view when zoomed out. &lt;/li&gt;
&lt;li&gt;Switch nodes between occupied and vacant dynamically. Clear someone's name in the sidebar, and the node becomes a placeholder. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sidebar form was wired to the diagram in real-time, including a dropdown that actually re-parented nodes in the graph. The whole tree supported expand/collapse of child nodes. &lt;/p&gt;

&lt;p&gt;I've built similar projects in GoJS before. This time I was genuinely curious how the whole experience would turn out with ngDiagram. &lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started: Less to Learn Than I Expected
&lt;/h2&gt;

&lt;p&gt;The first thing that struck me was how little I had to relearn. If you know Angular, you already know most of what you need to get started with ngDiagram. Node templates are just Angular components. Styling is just CSS. The diagram integrates into your project like any other piece of your UI. &lt;/p&gt;

&lt;p&gt;That's a real contrast to GoJS, where the diagram code tends to live in its own world. You build UI using &lt;code&gt;GraphObject.make&lt;/code&gt; calls, nesting panels inside panels to construct your nodes. It's a completely different way of building UI than what you're used to in Angular. You have to switch into that mindset every time you touch the diagram. With ngDiagram, I was moving between diagram components and the rest of the app without any context switching. It's all just Angular. &lt;/p&gt;

&lt;p&gt;Within the first few hours, I had a basic diagram rendering with custom, complex nodes. I couldn't say the same about my first encounter with GoJS years ago. &lt;/p&gt;
&lt;h2&gt;
  
  
  The MCP Advantage: AI That Actually Understands the Library
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't expect to write about in a diagramming library review: AI made a huge difference. &lt;/p&gt;

&lt;p&gt;ngDiagram ships with an MCP server, which means AI assistants understand the library's API and patterns. I used AI throughout the entire project. I was designing architecture, making technical decisions, and guiding the AI on how things should work. What I didn't do was dive deep into ngDiagram-specific API details - I relied on the AI's knowledge of the library for that, and the results were solid. &lt;/p&gt;

&lt;p&gt;I'd describe the behavior I wanted, explain the technical approach, sometimes show mockups - and get working code back in a few iterations. Node templates in particular were written almost entirely by AI. I reviewed them, fixed minor bugs, and moved on. &lt;/p&gt;

&lt;p&gt;This works so well because ngDiagram's building blocks are standard Angular. The AI isn't wrestling with some custom API - it's generating components and CSS. Things it already excels at. &lt;/p&gt;
&lt;h2&gt;
  
  
  Building the Org Chart with ngDiagram
&lt;/h2&gt;

&lt;p&gt;The org chart touched several areas of ngDiagram's API - from node templates and layout integration to drag &amp;amp; drop, expand/collapse, and edge connections. Here's how each one played out - and where it felt different from what I was used to in GoJS. &lt;/p&gt;
&lt;h3&gt;
  
  
  Node Templates: Three States, One Component
&lt;/h3&gt;

&lt;p&gt;The org chart needed three visual states for each node. A full view of all employee details. A compact view at lower zoom levels. A vacant state for unassigned positions. Some elements were independent of the visual state - the expand/collapse button shows when a node has children, and the add-node buttons appear on hover. Their logic lives outside the variant switching entirely. &lt;br&gt;
 &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftds31l3tb9bymwa0q7jl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftds31l3tb9bymwa0q7jl.png" alt="Screenshot of the Org chart app - 3 states of node" width="800" height="200"&gt;&lt;/a&gt;Full, compact, and vacant - the three visual states of an org chart node built with ngDiagram &lt;/p&gt;

&lt;p&gt;In ngDiagram, this was one Angular component with a &lt;code&gt;@switch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@switch (variant()) {
  @case ('vacant') {
    &lt;span class="nt"&gt;&amp;lt;app-vacant-node&lt;/span&gt; &lt;span class="na"&gt;[role]=&lt;/span&gt;&lt;span class="s"&gt;"node().data.role"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  }
  @case ('compact') {
    @let data = occupiedData();
    &lt;span class="nt"&gt;&amp;lt;app-compact-node&lt;/span&gt;
      &lt;span class="na"&gt;[fullName]=&lt;/span&gt;&lt;span class="s"&gt;"data?.fullName"&lt;/span&gt;
      &lt;span class="na"&gt;[role]=&lt;/span&gt;&lt;span class="s"&gt;"data?.role"&lt;/span&gt;
      &lt;span class="na"&gt;[color]=&lt;/span&gt;&lt;span class="s"&gt;"color()"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  }
  @case ('full') {
    @let data = occupiedData();
    &lt;span class="nt"&gt;&amp;lt;app-full-node&lt;/span&gt;
      &lt;span class="na"&gt;[fullName]=&lt;/span&gt;&lt;span class="s"&gt;"data?.fullName"&lt;/span&gt;
      &lt;span class="na"&gt;[role]=&lt;/span&gt;&lt;span class="s"&gt;"data?.role"&lt;/span&gt;
      &lt;span class="na"&gt;[color]=&lt;/span&gt;&lt;span class="s"&gt;"color()"&lt;/span&gt;
      &lt;span class="na"&gt;[reports]=&lt;/span&gt;&lt;span class="s"&gt;"data?.reports"&lt;/span&gt;
      &lt;span class="na"&gt;[span]=&lt;/span&gt;&lt;span class="s"&gt;"data?.span"&lt;/span&gt;
      &lt;span class="na"&gt;[shiftCapacity]=&lt;/span&gt;&lt;span class="s"&gt;"data?.shiftCapacity"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The zoom level comes from a diagram service as a signal. One &lt;code&gt;computed()&lt;/code&gt; drives the whole switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NodeVariant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isVacantNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vacant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewportService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared elements - expand/collapse, add buttons - just live outside the &lt;code&gt;@switch&lt;/code&gt;. Their own conditions determine when they show, completely independent of which variant is active. &lt;/p&gt;

&lt;p&gt;Nodes can also switch between occupied and vacant dynamically. Clear an employee's name in the sidebar form and the node converts to vacant live. Same node, different identity - no re-creation needed. &lt;/p&gt;

&lt;p&gt;In GoJS, you'd typically handle this with category switching. That means managing separate templates for each visual state - the behavior spreads across multiple definitions instead of being visible in one place. Or you could use visibility bindings on nested panels, which gets cumbersome when you have many dependent elements. Those shared elements? You can reuse them, but you still have to include them in every template. &lt;/p&gt;

&lt;h3&gt;
  
  
  Layout: Plugging In ELK.js
&lt;/h3&gt;

&lt;p&gt;For the tree layout, I used ELK.js with its &lt;code&gt;mrtree&lt;/code&gt; algorithm. The integration was straightforward - compute positions with ELK, assign them back to ngDiagram nodes. Both horizontal and vertical orientations worked. Coordinating ngDiagram transactions with ELK's async layout took some effort - but it was a solvable problem. &lt;br&gt;
 &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljohyvtzmdpznlxbha07.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljohyvtzmdpznlxbha07.gif" alt="Switching between horizontal and vertical tree layouts GIF" width="800" height="450"&gt;&lt;/a&gt;Switching between horizontal and vertical tree layouts - powered by ELK.js mrtree algorithm&lt;/p&gt;

&lt;h3&gt;
  
  
  Drag &amp;amp; Drop: Custom Implementation, Solid Building Blocks
&lt;/h3&gt;

&lt;p&gt;Drag &amp;amp; drop was more interesting. Beyond simple repositioning, I needed drops near existing nodes to trigger hierarchy changes - reassigning an employee's reporting line. This required a fully custom implementation, but ngDiagram provided useful building blocks - API methods and events - that helped put it together. The building blocks were there - I just had to put them together. &lt;br&gt;
 &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn3llxguyqhbgoswg9ao.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn3llxguyqhbgoswg9ao.gif" alt="Restructuring the hierarchy with drag &amp;amp; drop GIF" width="800" height="450"&gt;&lt;/a&gt;Restructuring the hierarchy with drag &amp;amp; drop - a node moved to a new position&lt;/p&gt;

&lt;h3&gt;
  
  
  Expand / Collapse: Not Built In - But Not Hard Either
&lt;/h3&gt;

&lt;p&gt;Expand/collapse was where the difference in library maturity showed most clearly. In GoJS, tree expand/collapse is built in - you get it essentially for free. In ngDiagram, you manage node visibility yourself by setting &lt;code&gt;isHidden&lt;/code&gt; on individual nodes, which means writing your own tree traversal logic to hide or show entire subtrees. It's not hard, but it's work that GoJS handles out of the box. &lt;br&gt;
 &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f9ohi24aezjiqp43q2x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f9ohi24aezjiqp43q2x.gif" alt="Expanding and collapsing child nodes GIF" width="800" height="450"&gt;&lt;/a&gt;Expanding and collapsing child nodes in the ngDiagram org chart&lt;/p&gt;

&lt;h3&gt;
  
  
  Ports: Explicit by Design
&lt;/h3&gt;

&lt;p&gt;In GoJS, any object on a node can be a port. In ngDiagram, ports are explicit components - you place &lt;code&gt;&amp;lt;ng-diagram-port&amp;gt;&lt;/code&gt; elements in your template and configure their position and type. In this project, I used two: top for incoming edges, bottom for outgoing. &lt;/p&gt;

&lt;p&gt;It's a more structured approach. Different from what I knew, but it made the connection points very clear in the template. &lt;/p&gt;

&lt;h3&gt;
  
  
  Theming: Light, Dark, and Zero Extra Wiring
&lt;/h3&gt;

&lt;p&gt;I used CSS custom properties as design tokens throughout the entire application - colors, spacing, borders, shadows, everything. One token file with definitions for light and dark themes. A toggle switches &lt;code&gt;data-theme&lt;/code&gt; on the document root, and the whole app - diagram nodes, sidebar, buttons, backgrounds - updates instantly. &lt;/p&gt;

&lt;p&gt;Because ngDiagram nodes are just Angular components with regular CSS, they pick up the tokens automatically. No special integration, no theme service wiring into the diagram. The same tokens that style a sidebar button also style a node's header. Once the token system was in place, adding the dark theme was trivial - just a new set of values. &lt;br&gt;
 &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bl7k363hg1r5n9bsbfg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bl7k363hg1r5n9bsbfg.gif" alt="Switching between light and dark mode in the org chart GIF" width="800" height="450"&gt;&lt;/a&gt;Switching between light and dark mode in the ngDiagram org chart&lt;/p&gt;

&lt;h2&gt;
  
  
  GoJS vs. ngDiagram: An Honest Comparison
&lt;/h2&gt;

&lt;p&gt;After three weeks of building with ngDiagram and years of building with GoJS, here's where I think each one stands. &lt;/p&gt;

&lt;h3&gt;
  
  
  Where GoJS Excels
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Feature Set and Maturity
&lt;/h4&gt;

&lt;p&gt;GoJS has years of development behind it. The feature set is massive - built-in tools for most common diagramming needs and countless edge cases already handled. Documentation is extensive, and there's an answer to almost every question. If you need a complex, battle-tested solution for a large enterprise project, GoJS delivers. &lt;/p&gt;

&lt;h4&gt;
  
  
  Considerations
&lt;/h4&gt;

&lt;p&gt;That said, GoJS can feel like an oversized tool for some projects. You carry a lot of weight you may never use. If you're new to the library, the learning curve is steep - it takes time before you can use it to its full potential. Licensing is worth mentioning too: GoJS requires a paid commercial license. It's justified for large projects, but it's a real consideration. &lt;/p&gt;

&lt;h3&gt;
  
  
  Where ngDiagram Excels
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Feature Set and Core
&lt;/h4&gt;

&lt;p&gt;ngDiagram gives you a solid foundation - node and edge rendering with full Angular templates, edge drawing between ports, selection, a minimap with zoom controls, viewport management, and a signals-based reactive model wrapped in transactions. That's the core, and it works well out of the box. &lt;/p&gt;

&lt;h4&gt;
  
  
  Developer Experience
&lt;/h4&gt;

&lt;p&gt;On the developer experience side, ngDiagram has a lot going for it. If you're an Angular developer, the onboarding is remarkably smooth. You're writing components, using HTML, working with signals or connecting to your state management.  &lt;/p&gt;

&lt;p&gt;The styling story is strong too - achieving polished visual results feels natural with CSS. Theming is a perfect example: I built a full light/dark theme using CSS design tokens, and the same tokens style both the diagram nodes and the rest of the app. One toggle, everything updates. In GoJS, theming was recently introduced, but it uses a separate object-based system rather than CSS - which makes it harder to keep the diagram visually consistent with the rest of your Angular application.  &lt;/p&gt;

&lt;p&gt;AI assistance via the MCP server also deserves special mention - it works exceptionally well, and the MCP integration makes AI genuinely useful throughout development. &lt;/p&gt;

&lt;h4&gt;
  
  
  Extensibility and Integration
&lt;/h4&gt;

&lt;p&gt;ngDiagram is also genuinely extensible. The API provides building blocks - methods, events, services - that let you build custom functionality on top without fighting the library. It's not just for small projects. It handles large numbers of nodes with strong performance and can power complex applications even without the years of maturity that GoJS has. &lt;/p&gt;

&lt;p&gt;There's also the integration story. In GoJS projects, the diagram always felt like a separate part of the application. The way you use it is so different from the rest of your code that it's natural to think of it as its own thing. ngDiagram dissolves that boundary. It's Angular all the way down. &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;And it's open source&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-off: What ngDiagram Asks of You
&lt;/h2&gt;

&lt;p&gt;ngDiagram's philosophy isn't to ship every feature built in. It gives you building blocks and a clean API to extend them. The tree layout, for example, isn't part of ngDiagram itself - you bring your own layout engine. I used &lt;a href="https://github.com/kieler/elkjs" rel="noopener noreferrer"&gt;ELK.js&lt;/a&gt; with the &lt;code&gt;mrtree&lt;/code&gt; algorithm, and ngDiagram provides &lt;a href="https://www.ngdiagram.dev/docs/examples/layout-integration/" rel="noopener noreferrer"&gt;a documented example of this integration&lt;/a&gt;, so I wasn't starting from scratch.  &lt;/p&gt;

&lt;p&gt;Things you build on top of what the library gives you: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expand/collapse (you manage node visibility via &lt;code&gt;isHidden&lt;/code&gt;) &lt;/li&gt;
&lt;li&gt;Zoom-responsive node variants &lt;/li&gt;
&lt;li&gt;Hierarchy validation &lt;/li&gt;
&lt;li&gt;Re-parenting logic &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That approach means more upfront work compared to something like GoJS, where tree layouts and expand/collapse come ready-made. But it also means you're never fighting someone else's assumptions about how your feature should work. The custom drag &amp;amp; drop with drop indicators, the anti-cycle validation, the way nodes switch between occupied and vacant - all of these fit naturally because I built them with standard Angular patterns on top of ngDiagram's API, not around a pre-built behavior. &lt;/p&gt;

&lt;h3&gt;
  
  
  There were a couple of rough edges along the way
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Expand/collapse positioning:&lt;/strong&gt; When expanding a collapsed subtree, the child nodes would reappear at their last known positions - scattered across the canvas - instead of emerging from the parent. The fix was to set all children's positions to the parent's coordinates before triggering the re-layout. That way they animate out from where you'd expect. A small thing, but the kind of detail that matters for a polished result. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watermark positioning:&lt;/strong&gt; I also ran into a watermark positioning issue where the default placement overlapped with my sidebar UI. The workaround was a &lt;code&gt;::ng-deep&lt;/code&gt; override - not ideal, but functional. The ngDiagram team already has an open GitHub issue for a proper positioning API, so this one is temporary. &lt;/p&gt;

&lt;p&gt;None of these were blockers. The API always gave me enough to solve the problem. But if you're coming from a batteries-included library like GoJS, it's worth knowing that ngDiagram asks you to bring more of your own implementation - and in return, you get full control over how everything works. &lt;/p&gt;

&lt;p&gt;As for delivery speed - it's hard to compare directly. I had more GoJS experience, but less practice using AI with it. With ngDiagram, it was the opposite: less library knowledge, but excellent AI support. It roughly evened out. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;Working with ngDiagram felt natural. The AI support through MCP was outstanding, and the fact that I could lean on Angular patterns I already knew made the whole experience smooth. After a while, things clicked and I could move fast. By the end of the project, ngDiagram had earned a permanent spot in my toolbox. &lt;/p&gt;

&lt;p&gt;If you're building in Angular, ngDiagram is a natural fit. The onboarding is fast. The MCP server makes AI assistance genuinely useful. The extensible APIs let you build what you need. CSS-based styling lets you achieve polished results naturally. It's a strong fit when you want full control over your diagramming features, and you're comfortable building on top of a solid core. &lt;/p&gt;

&lt;p&gt;GoJS is a strong fit for different reasons. If your project needs a wide range of built-in interactions, layout algorithms, and tools ready to go from day one - or if you're working across multiple frameworks - GoJS delivers that. The documentation is deep, the community is established, and for large enterprise projects with complex requirements, that maturity is genuinely valuable. &lt;/p&gt;

&lt;p&gt;The question is whether you'd rather build on a flexible core with Angular-native DX, or start with a comprehensive toolkit and adapt it to your stack. &lt;/p&gt;

&lt;p&gt;ngDiagram's architecture is solid, and the library is still growing. The potential is there. &lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Documentation: &lt;a href="https://www.ngdiagram.dev/docs" rel="noopener noreferrer"&gt;https://www.ngdiagram.dev/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/synergycodes/ng-diagram" rel="noopener noreferrer"&gt;https://github.com/synergycodes/ng-diagram&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NPM: &lt;a href="https://www.npmjs.com/package/ng-diagram" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ng-diagram&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Website: &lt;a href="https://www.ngdiagram.dev" rel="noopener noreferrer"&gt;https://www.ngdiagram.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
