<?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: Alex</title>
    <description>The latest articles on DEV Community by Alex (@dyingangel666).</description>
    <link>https://dev.to/dyingangel666</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909446%2Faf5553fe-42dc-4cfd-8ae3-dfe37c9a23b5.png</url>
      <title>DEV Community: Alex</title>
      <link>https://dev.to/dyingangel666</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dyingangel666"/>
    <language>en</language>
    <item>
      <title>A year of the EAA — does your component library's CI catch a11y violations yet?</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:51:14 +0000</pubDate>
      <link>https://dev.to/dyingangel666/a-year-of-the-eaa-does-your-component-librarys-ci-catch-a11y-violations-yet-34kb</link>
      <guid>https://dev.to/dyingangel666/a-year-of-the-eaa-does-your-component-librarys-ci-catch-a11y-violations-yet-34kb</guid>
      <description>&lt;p&gt;&lt;em&gt;The EAA is one year into enforcement, and most component libraries still ship without a single automated a11y check. Here is how I closed that gap in an Angular workspace, on two levels — live during development, and as a hard fail in CI.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A year in, and we are still flying blind
&lt;/h2&gt;

&lt;p&gt;This week marks one year since the European Accessibility Act became enforceable on 28 June 2025. Member states started accepting complaints. Market surveillance authorities started asking questions. And a lot of teams that had been treating "we'll do an a11y pass before launch" as a strategy noticed, quietly, that there was no launch — there was a constant trickle of releases, and the a11y pass had never actually happened.&lt;/p&gt;

&lt;p&gt;Component libraries are where this hits first. They ship dozens or hundreds of building blocks, every one of them potentially the thing that makes your product fail an audit. Ask yourself, honestly: how many components in your design system pass &lt;code&gt;axe-core&lt;/code&gt; against WCAG 2.2 AA today? You don't know. Your CI doesn't know either.&lt;/p&gt;

&lt;p&gt;This post is about how to fix that for an Angular library — on two layers, both wired into the same tool, and both runnable in under an hour of setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-layer model
&lt;/h2&gt;

&lt;p&gt;There are two distinct moments where a11y feedback should land for a component library:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dev-time.&lt;/strong&gt; When I'm building a component variant, I want to see a11y violations &lt;em&gt;as they happen&lt;/em&gt;, against the exact variant I'm rendering, without leaving my workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI-time.&lt;/strong&gt; When a PR opens, I want every variant of every component re-audited and the build to fail loudly if the library regresses past a threshold.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are not the same job. Dev-time is about catching cheaply. CI-time is about holding the line. You want both, and the tool I'll show — &lt;a href="https://github.com/dyingangel666/ng-prism" rel="noopener noreferrer"&gt;ng-prism&lt;/a&gt;, an Angular-native component showcase — wires both into the same workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Live a11y in the component lab
&lt;/h2&gt;

&lt;p&gt;ng-prism is a showcase tool similar in spirit to Storybook, but built around Angular's component model — I wrote about &lt;a href="https://dev.to/dyingangel666/why-i-built-ng-prism-an-angular-native-storybook-alternative-with-zero-story-files-1ip7"&gt;why I built it&lt;/a&gt; in an earlier post. You put &lt;code&gt;@Showcase&lt;/code&gt; on a component, ng-prism scans your library, and you get a styleguide app with controls, theming, panels — and an A11y panel built into the core.&lt;/p&gt;

&lt;p&gt;The panel runs &lt;code&gt;axe-core&lt;/code&gt; against the rendered variant every time the variant or any input changes. It has four tabs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Violations&lt;/strong&gt; — the live axe-core report, sorted by impact (critical, serious, moderate, minor), with a score ring that updates in real time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard&lt;/strong&gt; — tab-order overlay, focus trap detection, missing-&lt;code&gt;tabindex&lt;/code&gt; warnings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ARIA Tree&lt;/strong&gt; — the component's accessible tree as the browser sees it, with the computed accessible names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen Reader&lt;/strong&gt; — what a screen reader would announce, as a list or step-through player with a "screen-reader perspective" toggle that dims the canvas so you stop seeing what users don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't add a plugin to get this. It comes with &lt;code&gt;@ng-prism/core&lt;/code&gt;. The only thing you ship into your workspace is &lt;code&gt;axe-core&lt;/code&gt; as a peer dependency.&lt;/p&gt;

&lt;p&gt;When a component needs a per-component override — a decorative icon that should not be audited, or a rule you know is wrong for this widget — you tag the showcase config:&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;Showcase&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;a11y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rules&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;color-contrast&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="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="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;ToastComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the cheap layer. It catches the issues that would otherwise survive review because nobody pulled up DevTools and ran axe by hand. But the moment a contributor doesn't open the panel, the issue ships. That is what Layer 2 is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: a CI step that fails loud
&lt;/h2&gt;

&lt;p&gt;Here is the design call I want to spend a minute on: ng-prism does not ship a built-in audit CLI. I tried that, and pulled it out.&lt;/p&gt;

&lt;p&gt;Component libraries live in different stacks. Some teams already have Playwright. Some run &lt;code&gt;@axe-core/cli&lt;/code&gt; in a Docker step. Some have a custom headless harness for visual regression and want a11y bolted onto the same browser session. A built-in CLI would either lock teams into one of those, or balloon into a config surface that mirrors every option of all of them.&lt;/p&gt;

&lt;p&gt;So instead of a CLI, ng-prism exposes a small contract — call it the &lt;strong&gt;External Audit API&lt;/strong&gt; — and lets your team bring its own auditor. The contract has three anchors:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Anchor&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;window.__PRISM_MANIFEST__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A global with &lt;code&gt;{ components: [{ className, variants: [{ name, index }] }], pages: [...] }&lt;/code&gt; set by the running app.&lt;/td&gt;
&lt;td&gt;Discovery. The auditor needs to know what to audit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;?component=&amp;lt;className&amp;gt;&amp;amp;variant=&amp;lt;index&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL params the app reads on navigation.&lt;/td&gt;
&lt;td&gt;Drive the app to a specific variant from the outside.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;[data-prism-rendered="&amp;lt;className&amp;gt;:&amp;lt;index&amp;gt;"]&lt;/code&gt; on &lt;code&gt;.demo-wrap&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;An attribute on the renderer host.&lt;/td&gt;
&lt;td&gt;Render-completion marker — wait for this to flip before injecting axe.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A working auditor against this contract is small. Here is the core loop in Playwright (adapted from the real script I run against my own library, ~250 lines total including a tiny static server and threshold checks):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForFunction&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__PRISM_MANIFEST__&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__PRISM_MANIFEST__&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;comp&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&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="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;variant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.demo-wrap&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="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-prism-rendered&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="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addScriptTag&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;axeSource&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.demo-wrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;axe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;violations&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;The script writes an aggregated &lt;code&gt;a11y-report.json&lt;/code&gt; at the end. ng-prism reads that file at build time and embeds the totals into the runtime manifest, so a coloured pill (green/orange/red) appears in the styleguide header — visible to anyone reviewing a deployed PR preview.&lt;/p&gt;

&lt;p&gt;The thresholds live in your prism config:&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="c1"&gt;// ng-prism.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ng-prism/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;a11y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;reportPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a11y-report.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;serious&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;moderate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the report breaches any threshold, the &lt;strong&gt;build fails&lt;/strong&gt;. Not a warning, not a soft error — the pipeline goes red, the PR cannot merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it in CI
&lt;/h2&gt;

&lt;p&gt;The three steps for a GitHub Actions job (adapt to your runner of choice):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx nx run my-lib-prism:build&lt;/span&gt;       &lt;span class="c1"&gt;# 1. build the prism app&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx nx run my-lib:audit-a11y&lt;/span&gt;        &lt;span class="c1"&gt;# 2. your audit script writes a11y-report.json&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx nx run my-lib-prism:build&lt;/span&gt;       &lt;span class="c1"&gt;# 3. re-build to embed the report into the manifest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two builds, because the report is generated against the first build and embedded by the second. The header pill renders only after step 3 — useful if you deploy PR previews, because reviewers can see the a11y score next to the components without clicking through every panel.&lt;/p&gt;

&lt;p&gt;The audit step is your own script. If you don't want to write one, the Playwright snippet above plus argument parsing and threshold checks is roughly 200–250 lines and lives in &lt;code&gt;scripts/&lt;/code&gt;. It's yours, in your repo, in JS or TS you can edit when your CI changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this fits Angular libraries specifically
&lt;/h2&gt;

&lt;p&gt;A small note on tooling fit, because Angular libraries have one constraint that bites tooling all the time: dialogs and overlays. CDK Overlay, MatDialog, anything that portals to the body — they break in iframe-based component labs. ng-prism renders components directly into the host document via &lt;code&gt;ViewContainerRef.createComponent()&lt;/code&gt;, so overlays land where they should, and the a11y audit sees them as real DOM. That matters because most of the WCAG violations I catch are in dialogs and floating UI — exactly the surfaces other tools tend to miss.&lt;/p&gt;

&lt;p&gt;Beyond that: signal inputs and outputs work natively, the showcase config is just a decorator on the component (no separate story file), and a11y is in the core — not behind a plugin you have to remember to install.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add @ng-prism/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add &lt;code&gt;@Showcase&lt;/code&gt; to a component, run the prism builder, open the A11y panel, and watch the score ring move when you change a variant. From there, the External Audit API documented in &lt;a href="https://github.com/dyingangel666/ng-prism/blob/main/docs/guide/accessibility.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/guide/accessibility.md&lt;/code&gt;&lt;/a&gt; is enough to write the CI script in an afternoon.&lt;/p&gt;

&lt;p&gt;The repo: &lt;a href="https://github.com/dyingangel666/ng-prism" rel="noopener noreferrer"&gt;github.com/dyingangel666/ng-prism&lt;/a&gt;. If this post saved your team a CI compliance scramble, a star is appreciated — and issues, PRs, and "this works / this doesn't" reports are very welcome.&lt;/p&gt;

&lt;p&gt;A year of the EAA is enough. Stop shipping component libraries that can't tell you their own a11y score.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
      <category>typescript</category>
      <category>testing</category>
    </item>
    <item>
      <title>Why I think Component-Driven Development needs a rethink in the Signal era</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Mon, 18 May 2026 13:23:59 +0000</pubDate>
      <link>https://dev.to/dyingangel666/why-i-think-component-driven-development-needs-a-rethink-in-the-signal-era-i91</link>
      <guid>https://dev.to/dyingangel666/why-i-think-component-driven-development-needs-a-rethink-in-the-signal-era-i91</guid>
      <description>&lt;p&gt;&lt;em&gt;Component-Driven Development assumed a render model that signal-based Angular has quietly left behind. The tooling has not caught up, and I am not sure simply patching it will be enough.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment that made me write this
&lt;/h2&gt;

&lt;p&gt;I was building &lt;a href="https://github.com/dyingangel666/ng-prism" rel="noopener noreferrer"&gt;ng-prism&lt;/a&gt;, an Angular-native component showcase tool I maintain, and I needed to update a component's inputs from a controls panel. The naive version looked roughly like this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vcr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeDetectorRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works on a pre-signal component. On a signal-based component it quietly breaks the component. &lt;code&gt;variant&lt;/code&gt; and &lt;code&gt;label&lt;/code&gt; are no longer plain properties. They are &lt;code&gt;InputSignal&lt;/code&gt; objects, callable as &lt;code&gt;variant()&lt;/code&gt;. Angular stores the &lt;code&gt;InputSignal&lt;/code&gt; as a plain class field; there is no &lt;code&gt;defineProperty&lt;/code&gt; setter or proxy to intercept the write. So the assignment just overwrites the field reference with a string, and Angular never finds out. The next render then blows up the first time the template evaluates &lt;code&gt;variant()&lt;/code&gt;, because &lt;code&gt;variant&lt;/code&gt; is no longer a function. I verified this against &lt;code&gt;@angular/core&lt;/code&gt; 21.2.0: &lt;code&gt;createInputSignal()&lt;/code&gt; returns a plain function with a &lt;code&gt;[SIGNAL]&lt;/code&gt; symbol attached, no setter trap in sight.&lt;/p&gt;

&lt;p&gt;The fix is one line:&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;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;variant&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;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the version that lives in ng-prism today, in the renderer effect that drives every showcase:&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="c1"&gt;// packages/ng-prism/src/app/renderer/prism-renderer.component.ts&lt;/span&gt;
&lt;span class="c1"&gt;// Simplified; real version also handles content projection and unknown-input warnings.&lt;/span&gt;
&lt;span class="nf"&gt;effect&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&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;rendererService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inputValues&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;    &lt;span class="o"&gt;=&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;componentRef&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prism:rerender:start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// setInput() already marks dirty and schedules CD. The explicit&lt;/span&gt;
  &lt;span class="c1"&gt;// detectChanges() here forces it to run synchronously so the&lt;/span&gt;
  &lt;span class="c1"&gt;// performance.mark below wraps the actual render, not just the&lt;/span&gt;
  &lt;span class="c1"&gt;// dirty-marking. Also keeps timings predictable under zoneless.&lt;/span&gt;
  &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeDetectorRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prism:rerender:end&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;This is a tiny detail, but it is the symptom of a much bigger thing. The way Component-Driven Development (CDD) thinks about a component (props in, render out, args table on the side) was modelled on a world where component inputs were plain properties. That world is gone in Angular. And once you stop assuming it, a lot of the tooling we have built over the last decade starts to look like it is solving the wrong problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The implicit model behind classic CDD
&lt;/h2&gt;

&lt;p&gt;Look at what almost every CDD tool, Storybook included, has agreed on for years:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A component is a pure-ish function of its inputs.&lt;/li&gt;
&lt;li&gt;Inputs are configured as a flat dictionary, the “args”.&lt;/li&gt;
&lt;li&gt;A “story” is a specific value of that dictionary.&lt;/li&gt;
&lt;li&gt;Changing args triggers a discrete re-render cycle.&lt;/li&gt;
&lt;li&gt;Addons (a11y, viewport, knobs, controls) hook into that cycle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the pre-Hooks era this was the right abstraction. React class components had &lt;code&gt;setState&lt;/code&gt;. Angular had &lt;code&gt;@Input()&lt;/code&gt; decorators and &lt;code&gt;ngOnChanges&lt;/code&gt;. Vue had options API. All of them had a clear, discrete moment where a property got assigned, the framework noticed, and the component re-rendered top-down. Args tables map onto that perfectly. Whatever knob you turn becomes a property assignment, which becomes an &lt;code&gt;ngOnChanges&lt;/code&gt; call, which becomes a render.&lt;/p&gt;

&lt;p&gt;This is also why Storybook’s args model felt so natural for so long. Args are properties. Properties are state. State drives render. Story = scenario = property snapshot.&lt;/p&gt;

&lt;p&gt;It was a beautiful, simple model. It is also, for Angular today, the wrong abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Signals actually changed
&lt;/h2&gt;

&lt;p&gt;I do not want to retread the “signals are great” territory. What matters for CDD is more specific.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inputs are no longer properties.&lt;/strong&gt; An &lt;code&gt;InputSignal&lt;/code&gt; is a callable getter. From outside the component, the only correct way to push a value into it is &lt;code&gt;ComponentRef.setInput(name, value)&lt;/code&gt;. There is no property to assign anymore. Tools that still build their abstractions on “assign this prop” are not even wrong yet, they just produce broken components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Components are nodes in a reactive graph, not pure functions of inputs.&lt;/strong&gt; Half of a real-world signal-based component is &lt;code&gt;computed()&lt;/code&gt; derived state. Those derived signals are part of the component’s behaviour. They are not inputs, but they are also not opaque internal state. An args table cannot represent them. A story shapes inputs, not derived state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lifecycle work is increasingly split between explicit hooks and reactive subscriptions.&lt;/strong&gt; &lt;code&gt;ngOnInit&lt;/code&gt; and &lt;code&gt;ngOnDestroy&lt;/code&gt; still exist and are still idiomatic for a lot of cases (subscriptions, teardown, one-shot setup). What has changed is that &lt;code&gt;ngOnChanges&lt;/code&gt; is largely irrelevant for signal inputs, and a growing share of the work that used to live in lifecycle hooks now lives inside &lt;code&gt;effect()&lt;/code&gt; or &lt;code&gt;computed()&lt;/code&gt; that is read by the template. The places where work happens have multiplied, and a chunk of it fires on a much finer-grained schedule than &lt;code&gt;mount → change → destroy&lt;/code&gt;. CDD tools that visualise lifecycle as those three states are visualising a real but shrinking slice of what the component actually does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Re-renders are continuous, not cycle-based.&lt;/strong&gt; Zoneless Angular still has &lt;code&gt;ApplicationRef.tick()&lt;/code&gt; and a &lt;code&gt;ChangeDetectionScheduler&lt;/code&gt;. What is gone is the &lt;em&gt;implicit&lt;/em&gt; tick driven by zone.js intercepting every async operation. In a zoneless app, change detection runs because the scheduler decided to run it, which in practice is because a signal flagged a view dirty. From the outside that looks less like a single render cycle and more like a graph of signals notifying their dependents at fine granularity. An addon that says “re-run my check on each render cycle” has no clean event to listen to, because there is no single render cycle visible at the public surface of the component.&lt;/p&gt;

&lt;p&gt;The pre-signal mental model is not catastrophically wrong. It is just lossy. And every layer of CDD tooling pays the price of that loss somewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the tooling lags, concretely
&lt;/h2&gt;

&lt;p&gt;Three examples I have hit while building ng-prism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Controls and args still treat inputs as a flat property bag.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Storybook &lt;code&gt;args&lt;/code&gt; object and ng-prism’s own variant config both look like this:&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="nl"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is fine as a starting state. It is wrong as a model of how the component actually consumes its inputs. A signal-based input has a default expression, can be required, can be tied through &lt;code&gt;computed()&lt;/code&gt; into other state, and can be read multiple times per render. None of that is in the args object. The args object is a snapshot of a tree it cannot see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A11y, perf, and visual-diff addons assume “render happened, now check”.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;axe-core, layout measurements, screenshot capture: they all rely on a moment where the DOM has settled and you can inspect it. Angular does give you some primitives here. &lt;code&gt;afterNextRender()&lt;/code&gt; fires once after the next render. &lt;code&gt;afterRender()&lt;/code&gt; fires after every render. &lt;code&gt;ApplicationRef.isStable&lt;/code&gt; exposes a stable-state observable. None of those quite match what an a11y or visual-diff addon actually wants, which is closer to “the reactive graph has been quiet for N milliseconds across multiple microtasks, and I am now safe to walk the DOM”. That concept does not exist as a framework primitive. So you debounce, you wait for animation frames, you hope. In ng-prism the built-in a11y audit runs after a 500ms debounce on signal changes (&lt;code&gt;A11yAuditService.scheduleAudit&lt;/code&gt;, default &lt;code&gt;debounceMs = 500&lt;/code&gt;), which works in practice but feels like a workaround. I am not sure a true “graph is quiescent” signal even makes sense in a system designed to update continuously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The “scenario” unit is too coarse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Storybook story is one set of args. A ng-prism variant is one set of input values. Neither can express “this component embedded in a parent that emits a signal stream over time”. The interesting bug in a signal-based component is rarely the static case. It is the transition. It is the moment when an upstream &lt;code&gt;computed()&lt;/code&gt; updates twice in the same microtask and your effect runs once instead of twice. You cannot represent that with &lt;code&gt;{ variant: 'primary' }&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tried in ng-prism, honestly
&lt;/h2&gt;

&lt;p&gt;ng-prism started life with the same implicit model as Storybook. Components have inputs. Inputs have values. Variants are named tuples of input values. The decorator looks similar:&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;Showcase&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;variants&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Danger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;danger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What ng-prism does that I think is on the right track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;setInput()&lt;/code&gt; for every input push, never property assignment. So at least the signal contract is respected. This is the absolute floor and a surprising number of Angular-adjacent tools still get it wrong.&lt;/li&gt;
&lt;li&gt;Drives the rendering loop with &lt;code&gt;effect()&lt;/code&gt; on a &lt;code&gt;signal&lt;/code&gt; of input values, not with a render-cycle event. The renderer reacts to whatever upstream signal happens to change.&lt;/li&gt;
&lt;li&gt;Treats the scanner as a build-time concern. Signal inputs are recognised via the TypeScript Compiler API at build time, so the runtime never has to read decorator metadata. The decorator itself is literally a no-op, just a marker.&lt;/li&gt;
&lt;li&gt;Supports zoneless via an opt-in flag in the &lt;code&gt;ng add&lt;/code&gt; schematic (&lt;code&gt;--zoneless&lt;/code&gt;), which wires up &lt;code&gt;provideZonelessChangeDetection()&lt;/code&gt; and drops &lt;code&gt;zone.js&lt;/code&gt; from polyfills. The default still ships with zone.js, but nothing in the renderer depends on an implicit zone tick; change detection runs because something signal-shaped told it to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What ng-prism does &lt;strong&gt;not&lt;/strong&gt; solve yet, and where I think the whole category is still stuck:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The variant model is still a flat input snapshot. I cannot describe a component as “embedded in a parent that pushes this signal stream over 2 seconds”.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;computed()&lt;/code&gt; derived state is invisible in the UI. You see the inputs you set. You do not see the graph that hangs off them. For some components, that graph is the interesting part.&lt;/li&gt;
&lt;li&gt;The a11y, perf, and box-model panels all hook into renderer output the way an old addon would. They debounce. They do not subscribe to the actual reactive graph the component is part of.&lt;/li&gt;
&lt;li&gt;The “code snippet” feature generates a template string from input values. It cannot show how the component would behave in a parent where one of those inputs is itself a signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am being deliberate about this in the docs. I do not want ng-prism to claim a level of insight into signal-based components that it does not yet deliver.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I think the next generation should look like
&lt;/h2&gt;

&lt;p&gt;This is the speculative part, so take it as opinion.&lt;/p&gt;

&lt;p&gt;I think the unit of CDD will stop being “the component with these props” and start being “the component embedded in a reactive context”. A scenario will look more like a tiny fixture: this component, mounted under this parent, with these signal sources feeding it, over time. Closer to a Playwright scenario than a Storybook story.&lt;/p&gt;

&lt;p&gt;I think tooling will need to render the reactive graph, not just the visual output. For a serious component, the dependency graph between inputs, &lt;code&gt;computed()&lt;/code&gt;, and &lt;code&gt;effect()&lt;/code&gt; is the documentation. We render the box and the controls. We should also be able to render the graph.&lt;/p&gt;

&lt;p&gt;I think the addon model needs to flip. Instead of hooking into a render lifecycle that no longer exists, addons should subscribe to specific signals. Visual diff subscribes to the renderedElement signal. A11y subscribes to the same. Perf subscribes to a “graph quiescent” signal that the framework would have to expose. None of this lives in Storybook’s current architecture, and bolting it on is not obviously the right answer.&lt;/p&gt;

&lt;p&gt;And I think “args” as a UI metaphor has run its course. A signal-based component is not configured by setting properties. It is wired up. The control surface should reflect that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I am not arguing that Storybook is bad, or that the people maintaining it have missed something obvious. They built the right tool for the framework world of 2018, and that tool became a standard. Standards lag by definition. That is fine.&lt;/p&gt;

&lt;p&gt;What I am arguing is that the abstraction is starting to leak in ways that matter. Signal-based Angular is a different kind of component runtime, not a faster version of the old one, and the surface area that CDD tooling was designed against has changed underneath it.&lt;/p&gt;

&lt;p&gt;I am building ng-prism partly to test that hypothesis in code, not just in prose. I am also fully prepared to find out that I am wrong, that an args table plus &lt;code&gt;setInput()&lt;/code&gt; plus a debounce is good enough for 95% of cases, and that the rest is academic. Possible. But I do not think it is, and I would rather find out by building than by predicting.&lt;/p&gt;

&lt;p&gt;If you have hit the same kind of friction, or you have a counter-example where the args model still maps cleanly onto a signal-heavy component, I would genuinely like to hear it. That is more useful than another “signals are great” thread.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;ng-prism is open source on &lt;a href="https://github.com/dyingangel666/ng-prism" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Feedback and counter-examples especially welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>storybook</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I Built ng-prism — An Angular-Native Storybook Alternative With Zero Story Files</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Sat, 02 May 2026 19:03:21 +0000</pubDate>
      <link>https://dev.to/dyingangel666/why-i-built-ng-prism-an-angular-native-storybook-alternative-with-zero-story-files-1ip7</link>
      <guid>https://dev.to/dyingangel666/why-i-built-ng-prism-an-angular-native-storybook-alternative-with-zero-story-files-1ip7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; ng-prism lets you showcase Angular components by adding a single decorator to the component class itself. No story files, no parallel file tree, no framework mismatch. Just Angular.&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%2Fhbxo8mppys9eyqg24mr6.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%2Fhbxo8mppys9eyqg24mr6.png" alt="Example Screenshot of NgPrism" width="800" height="640"&gt;&lt;/a&gt;           &lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Every Angular Team Knows
&lt;/h2&gt;

&lt;p&gt;If you've ever maintained a Storybook setup for an Angular component library, you know the drill: for every component you write, you also write a &lt;code&gt;.stories.ts&lt;/code&gt; file. Then you keep both in sync. Then someone renames an input, the stories break silently, and nobody notices until the designer opens Storybook two sprints later.&lt;/p&gt;

&lt;p&gt;Storybook is a fantastic tool — but it was born in the React ecosystem. Angular support has always been a second-class citizen. The CSF format doesn't feel natural in Angular. The iframe rendering breaks MatDialog, CDK overlays, and portals. The webpack/Vite configuration is yet another build system you have to understand alongside the Angular CLI.&lt;/p&gt;

&lt;p&gt;I wanted something different. Something that feels like Angular because &lt;em&gt;it is&lt;/em&gt; Angular.                                                                                                                       &lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing ng-prism
&lt;/h2&gt;

&lt;p&gt;ng-prism is a lightweight component showcase tool built from the ground up for Angular. The core idea is radical in its simplicity: you annotate your component with a &lt;code&gt;@Showcase&lt;/code&gt; decorator, and ng-prism discovers it at build time via the TypeScript Compiler API.&lt;/p&gt;

&lt;p&gt;No story files. No parallel file tree. The documentation lives where the code lives.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Showcase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ng-prism/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                                                                                                                                                   
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&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="nd"&gt;Showcase&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                             
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Atoms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                         
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The primary action button.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;variants&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Danger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;danger&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ghost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cancel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ghost&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="p"&gt;})&lt;/span&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;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                    
    &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                            
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;button [class]="variant()"&amp;gt;{{ label() }}&amp;lt;/button&amp;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;ButtonComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                                                                                                                                               
    &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                                                                                                                                                                            
    &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;danger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ghost&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;clicked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;That's it. Run &lt;code&gt;ng run my-lib:prism&lt;/code&gt;, and you get a fully interactive styleguide with variant tabs, a live controls panel, event logging, and code snippets — all extracted from your actual component at build time.                                                                                                                                                                                                       &lt;/p&gt;
&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;ng-prism doesn't guess your component's API. It reads it. At build time, a custom Angular Builder kicks off a pipeline:                                                                                        &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript Compiler API scanner&lt;/strong&gt; — Parses your library's entry point, walks the AST, and extracts every component annotated with &lt;code&gt;@Showcase&lt;/code&gt;. It reads &lt;code&gt;input()&lt;/code&gt; and &lt;code&gt;output()&lt;/code&gt; signal declarations,
infers types, detects defaults, and builds a complete component manifest.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin hooks&lt;/strong&gt; — Registered plugins can enrich the scanned data (e.g., extracting JSDoc comments, injecting Figma URLs from metadata).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime manifest generation&lt;/strong&gt; — The pipeline produces a TypeScript file with real import statements pointing to your actual component classes. No JSON serialization, no runtime reflection.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular Dev Server&lt;/strong&gt; — The builder delegates to &lt;code&gt;@angular-devkit/architect&lt;/code&gt;, so you get the Angular dev server you already know — HMR, source maps, the works.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: your styleguide is a regular Angular app. No iframe. Components render in the same document context. &lt;code&gt;MatDialog&lt;/code&gt;? Works. CDK Overlay? Works. CSS custom properties from a parent theme? Inherited naturally.                                                                                                                                                                                                     &lt;/p&gt;
&lt;h2&gt;
  
  
  Signal-Native From Day One
&lt;/h2&gt;

&lt;p&gt;ng-prism was built for Angular 21+ and the signal API. It understands &lt;code&gt;input()&lt;/code&gt;, &lt;code&gt;input.required()&lt;/code&gt;, and &lt;code&gt;output()&lt;/code&gt; natively. The controls panel automatically generates the right control widget for each&lt;br&gt;&lt;br&gt;
  input type:                                                                                                                                                                                                  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;string&lt;/code&gt; → text field
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;boolean&lt;/code&gt; → toggle&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;number&lt;/code&gt; → number input&lt;/li&gt;
&lt;li&gt;Union types like &lt;code&gt;'primary' | 'danger'&lt;/code&gt; → dropdown
&lt;/li&gt;
&lt;li&gt;Complex types → JSON editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you change a value in the controls panel, it flows through Angular's signal system. There's no &lt;code&gt;@Input()&lt;/code&gt; decorator support — and that's by design. If you're starting a new component library in 2026, you should be using signals.                                                                                                                                                                                   &lt;/p&gt;
&lt;h2&gt;
  
  
  Built-In Accessibility Auditing
&lt;/h2&gt;

&lt;p&gt;Accessibility isn't a plugin in ng-prism — it's a core feature. The built-in A11y panel provides four perspectives on every component:                                                                         &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Violations&lt;/strong&gt; — axe-core audit with a visual score ring, sorted by impact severity
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard Navigation&lt;/strong&gt; — Tab order visualization with overlay indicators
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ARIA Tree&lt;/strong&gt; — The accessibility tree as the browser sees it
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen Reader Simulation&lt;/strong&gt; — Step through your component the way a screen reader would, with play/pause navigation
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switch to "Screen Reader" perspective in the toolbar, and the canvas dims while SR annotations overlay your component. It's a first-class development tool, not an afterthought.                               &lt;/p&gt;
&lt;h2&gt;
  
  
  Plugin Architecture — Extend Everything
&lt;/h2&gt;

&lt;p&gt;ng-prism follows a Vite-style plugin model. A plugin is a plain object with optional hooks for both build time and runtime:&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="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NgPrismPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ng-prism/core/plugin&lt;/span&gt;&lt;span class="dl"&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;function&lt;/span&gt; &lt;span class="nf"&gt;myPlugin&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;NgPrismPlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                                                                                                                                                                     
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                       

      &lt;span class="c1"&gt;// Build-time: enrich scanned component data&lt;/span&gt;
      &lt;span class="nf"&gt;onComponentScanned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                                                                                                                                            
        &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;extractSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&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="c1"&gt;// Runtime: add a panel to the UI                                                                                                                                                                          &lt;/span&gt;
      &lt;span class="na"&gt;panels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;                                                                                                                                                                                               
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-panel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Panel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                       
        &lt;span class="na"&gt;loadComponent&lt;/span&gt;&lt;span class="p"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-panel.component.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MyPanelComponent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                      
        &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;addon&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Official Plugins
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;@ng-prism/plugin-jsdoc&lt;/strong&gt; — Extracts JSDoc comments at build time and generates structured API documentation, including parameter tables
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@ng-prism/plugin-figma&lt;/strong&gt; — Embeds Figma designs as interactive iframes to enable direct visual comparison with components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@ng-prism/plugin-box-model&lt;/strong&gt; — Overlays CSS box model dimensions directly on rendered components for layout inspection
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@ng-prism/plugin-perf&lt;/strong&gt; — Profiles initial render and re-render performance using the browser Performance API
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@ng-prism/plugin-coverage&lt;/strong&gt; — Displays per-component test coverage based on Istanbul/V8 reports
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plugins lazy-load their components, so they don't bloat your initial bundle.                                                                                                                                   &lt;/p&gt;
&lt;h2&gt;
  
  
  Setup in Two Minutes
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  ng add @ng-prism/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The schematic asks which library you want to showcase, creates a prism app project, wires up the builder targets in &lt;code&gt;angular.json&lt;/code&gt;, and generates a config file. Then:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  ng run my-lib:prism                                                                                                                                                                                          
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your styleguide is running at &lt;code&gt;localhost:4200&lt;/code&gt;. For the config, you get a typed &lt;code&gt;prism.config.ts&lt;/code&gt;:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ng-prism/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                                                                                                                                               
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jsDocPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ng-prism/plugin-jsdoc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;figmaPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ng-prism/plugin-figma&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;                                                                                                                                                                                                   
      &lt;span class="nf"&gt;jsDocPlugin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;                                                                                                                                                                                           
      &lt;span class="nf"&gt;figmaPlugin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;theme&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;--prism-primary&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;#6366f1&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;--prism-primary-from&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;#6366f1&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;--prism-primary-to&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;#8b5cf6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;                                                                                                                                                                            
      &lt;span class="c1"&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;h2&gt;
  
  
  Watch Mode
&lt;/h2&gt;

&lt;p&gt;The serve builder watches your library sources and config file. Change a component, add a &lt;code&gt;@Showcase&lt;/code&gt; decorator, modify an input — the manifest regenerates, and the Angular dev server picks up the change. No restart needed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Component Pages &amp;amp; Custom Pages
&lt;/h2&gt;

&lt;p&gt;Not everything fits neatly into a per-component showcase. Sometimes you need a "Patterns" page showing how multiple components compose, or a color token overview.                                             &lt;/p&gt;

&lt;p&gt;ng-prism supports &lt;strong&gt;Component Pages&lt;/strong&gt; — free-form Angular components registered alongside your showcased components. They're defined in your &lt;code&gt;main.ts&lt;/code&gt; via &lt;code&gt;providePrism()&lt;/code&gt;:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismShellComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;providePrism&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;componentPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ng-prism/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                                                                                                           
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ButtonPatternsPageComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/button-patterns.page.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                                                                                                                 

  &lt;span class="nf"&gt;bootstrapApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PrismShellComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                                                                                                                                    
    &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;                                                                                                                                                                                                 
      &lt;span class="nf"&gt;providePrism&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                                                                                                                                         
        &lt;span class="na"&gt;componentPages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nf"&gt;componentPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button Patterns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Atoms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                   
            &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonPatternsPageComponent&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="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;For static pages that don't need Angular components, use &lt;strong&gt;Custom Pages&lt;/strong&gt; directly in &lt;code&gt;prism.config.ts&lt;/code&gt;:&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;                                                                                                                                                                                
    &lt;span class="na"&gt;pages&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Changelog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Meta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.1.0&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="p"&gt;});&lt;/span&gt;                                                                                                                                                                                                          
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Both appear in the sidebar navigation alongside regular components.&lt;/p&gt;
&lt;h2&gt;
  
  
  Content Projection &amp;amp; Directive Hosting
&lt;/h2&gt;

&lt;p&gt;Real-world components use &lt;code&gt;&amp;lt;ng-content&amp;gt;&lt;/code&gt;. ng-prism handles this with a &lt;code&gt;content&lt;/code&gt; property on variants:&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;Showcase&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;                                                                                                                                                                                                  
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                               
    &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;With Header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                                                       
      &lt;span class="na"&gt;content&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;[card-header]&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;&amp;lt;h3&amp;gt;Title&amp;lt;/h3&amp;gt;&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;default&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;&amp;lt;p&amp;gt;Body content&amp;lt;/p&amp;gt;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Need to showcase a directive instead of a component? Use the &lt;code&gt;host&lt;/code&gt; property to specify what element it attaches to — either a plain HTML string or another Angular component.                                 &lt;/p&gt;
&lt;h2&gt;
  
  
  Why Not Just Use Storybook?
&lt;/h2&gt;

&lt;p&gt;Storybook is mature, battle-tested, and has a massive ecosystem. If it works for your team, keep using it. But if you've felt the friction of:                                                                 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintaining a parallel &lt;code&gt;.stories.ts&lt;/code&gt; file tree that drifts out of sync
&lt;/li&gt;
&lt;li&gt;Fighting iframe restrictions when your component uses overlays or portals
&lt;/li&gt;
&lt;li&gt;Configuring a separate build system (webpack/Vite) alongside the Angular CLI
&lt;/li&gt;
&lt;li&gt;Wrapping Angular-specific patterns (dependency injection, signals) in framework-agnostic abstractions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…then ng-prism might be worth a look. It doesn't try to be framework-agnostic. It's Angular, all the way down.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Road Ahead
&lt;/h2&gt;

&lt;p&gt;ng-prism is open source under the MIT license and follows Angular's versioning: &lt;code&gt;@ng-prism/core@21.x&lt;/code&gt; targets Angular 21. The current release is v21.6.1. Why already v21.6.1? Because we already use it in my company for our component library, so it's already battle-tested.                                                                                                                                            &lt;/p&gt;

&lt;p&gt;What's coming next:                                                                                                                                                                                            &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More official plugins&lt;/li&gt;
&lt;li&gt;CI integration — Manifest validation in your pipeline (e.g. ensuring every public component has a @Showcase decorator)
&lt;/li&gt;
&lt;li&gt;Design token documentation — Automatic token overview extracted from SCSS / CSS custom properties
&lt;/li&gt;
&lt;li&gt;Export — Share your component catalog as a PDF or static HTML page
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;a href="https://dyingangel666.github.io/ng-prism/demo/" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://dyingangel666.github.io/ng-prism/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;code&gt;npm install @ng-prism/core&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dyingangel666" rel="noopener noreferrer"&gt;
        dyingangel666
      &lt;/a&gt; / &lt;a href="https://github.com/dyingangel666/ng-prism" rel="noopener noreferrer"&gt;
        ng-prism
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lightweight Angular-native component showcase — no story files needed.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/dyingangel666/ng-prism/docs/prism_cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdyingangel666%2Fng-prism%2FHEAD%2Fdocs%2Fprism_cover.png" alt="ng-prism — Angular components, refracted" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ng-prism&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Lightweight, Angular-native component showcase tool. Annotate components with &lt;code&gt;@Showcase&lt;/code&gt; — no separate story files needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://angular.dev" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d4ae39495ce9b7cbb3269939e19aa43ea39ec648d3d4587c0ee0c32c7fff1924/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f416e67756c61722d32312b2d646430303331" alt="Angular"&gt;&lt;/a&gt;
&lt;a href="https://www.typescriptlang.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e81b2c93b039aa4f8f88871be055cc5342af7f71ccbbdf6d68e3f19fea31a985/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d352e352b2d333137386336" alt="TypeScript"&gt;&lt;/a&gt;
&lt;a href="https://github.com/dyingangel666/ng-prism/./LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dyingangel666.github.io/ng-prism/demo/" rel="nofollow noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; · &lt;strong&gt;&lt;a href="https://dyingangel666.github.io/ng-prism/" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-config discovery&lt;/strong&gt; — TypeScript Compiler API scans your library at build time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal-native&lt;/strong&gt; — works with &lt;code&gt;input()&lt;/code&gt; / &lt;code&gt;output()&lt;/code&gt; signals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directive support&lt;/strong&gt; — showcase directives with configurable host elements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin architecture&lt;/strong&gt; — JSDoc, A11y, Figma, Performance, Box Model, Coverage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Controls&lt;/strong&gt; — auto-generated input controls with type-aware editors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Snippets&lt;/strong&gt; — live-updating Angular template snippets per variant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component Pages&lt;/strong&gt; — free-form demo pages for complex components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep-linking&lt;/strong&gt; — URL state sync for sharing specific component/variant/view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Themeable&lt;/strong&gt; — full CSS custom property system, replaceable UI sections&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Install&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install @ng-prism/core&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;2. Add &lt;code&gt;@Showcase&lt;/code&gt; to a component&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-ts notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-v"&gt;Component&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;input&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;output&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;'@angular/core'&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-v"&gt;Showcase&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;'@ng-prism/core'&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
@&lt;span class="pl-v"&gt;Showcase&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;title&lt;/span&gt;: &lt;span class="pl-s"&gt;'Button'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;category&lt;/span&gt;: &lt;span class="pl-s"&gt;'Atoms'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;description&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dyingangel666/ng-prism" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                                                                                                                                                        &amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If you're building an Angular component library and want your showcase to feel like Angular — give ng-prism a try. Star the repo if you find it useful, and feel free to open issues or contribute plugins.&lt;/p&gt;

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