TL;DR: 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.
The Problem Every Angular Team Knows
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 .stories.ts 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.
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.
I wanted something different. Something that feels like Angular because it is Angular.
Introducing ng-prism
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 @Showcase decorator, and ng-prism discovers it at build time via the TypeScript Compiler API.
No story files. No parallel file tree. The documentation lives where the code lives.
import { Showcase } from '@ng-prism/core';
import { Component, input, output } from '@angular/core';
@Showcase({
title: 'Button',
category: 'Atoms',
description: 'The primary action button.',
variants: [
{ name: 'Primary', inputs: { label: 'Save', variant: 'primary' } },
{ name: 'Danger', inputs: { label: 'Delete', variant: 'danger' } },
{ name: 'Ghost', inputs: { label: 'Cancel', variant: 'ghost' } }
],
})
@Component({
selector: 'lib-button',
standalone: true,
template: `<button [class]="variant()">{{ label() }}</button>`
})
export class ButtonComponent {
label = input.required<string>();
variant = input<'primary' | 'danger' | 'ghost'>('primary');
clicked = output<void>();
}
That's it. Run ng run my-lib:prism, 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.
How It Works Under the Hood
ng-prism doesn't guess your component's API. It reads it. At build time, a custom Angular Builder kicks off a pipeline:
-
TypeScript Compiler API scanner — Parses your library's entry point, walks the AST, and extracts every component annotated with
@Showcase. It readsinput()andoutput()signal declarations, infers types, detects defaults, and builds a complete component manifest. - Plugin hooks — Registered plugins can enrich the scanned data (e.g., extracting JSDoc comments, injecting Figma URLs from metadata).
- Runtime manifest generation — The pipeline produces a TypeScript file with real import statements pointing to your actual component classes. No JSON serialization, no runtime reflection.
-
Angular Dev Server — The builder delegates to
@angular-devkit/architect, so you get the Angular dev server you already know — HMR, source maps, the works.
The result: your styleguide is a regular Angular app. No iframe. Components render in the same document context. MatDialog? Works. CDK Overlay? Works. CSS custom properties from a parent theme? Inherited naturally.
## Signal-Native From Day One
ng-prism was built for Angular 21+ and the signal API. It understands input(), input.required(), and output() natively. The controls panel automatically generates the right control widget for each
input type:
-
string→ text field -
boolean→ toggle -
number→ number input - Union types like
'primary' | 'danger'→ dropdown - Complex types → JSON editor
When you change a value in the controls panel, it flows through Angular's signal system. There's no @Input() decorator support — and that's by design. If you're starting a new component library in 2026, you should be using signals.
## Built-In Accessibility Auditing
Accessibility isn't a plugin in ng-prism — it's a core feature. The built-in A11y panel provides four perspectives on every component:
- Violations — axe-core audit with a visual score ring, sorted by impact severity
- Keyboard Navigation — Tab order visualization with overlay indicators
- ARIA Tree — The accessibility tree as the browser sees it
- Screen Reader Simulation — Step through your component the way a screen reader would, with play/pause navigation
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.
## Plugin Architecture — Extend Everything
ng-prism follows a Vite-style plugin model. A plugin is a plain object with optional hooks for both build time and runtime:
import type { NgPrismPlugin } from '@ng-prism/core/plugin';
export function myPlugin(): NgPrismPlugin {
return {
name: 'my-plugin',
// Build-time: enrich scanned component data
onComponentScanned(component) {
component.meta = { ...component.meta, myData: extractSomething(component) };
},
// Runtime: add a panel to the UI
panels: [{
id: 'my-panel',
label: 'My Panel',
loadComponent: () => import('./my-panel.component.js').then(m => m.MyPanelComponent),
position: 'bottom',
placement: 'addon',
}],
};
}
Official Plugins
- @ng-prism/plugin-jsdoc — Extracts JSDoc comments at build time and generates structured API documentation, including parameter tables
- @ng-prism/plugin-figma — Embeds Figma designs as interactive iframes to enable direct visual comparison with components
- @ng-prism/plugin-box-model — Overlays CSS box model dimensions directly on rendered components for layout inspection
- @ng-prism/plugin-perf — Profiles initial render and re-render performance using the browser Performance API
- @ng-prism/plugin-coverage — Displays per-component test coverage based on Istanbul/V8 reports
Plugins lazy-load their components, so they don't bloat your initial bundle.
Setup in Two Minutes
ng add @ng-prism/core
The schematic asks which library you want to showcase, creates a prism app project, wires up the builder targets in angular.json, and generates a config file. Then:
ng run my-lib:prism
Your styleguide is running at localhost:4200. For the config, you get a typed prism.config.ts:
import { defineConfig } from '@ng-prism/core';
import { jsDocPlugin } from '@ng-prism/plugin-jsdoc';
import { figmaPlugin } from '@ng-prism/plugin-figma';
export default defineConfig({
plugins: [
jsDocPlugin(),
figmaPlugin()
],
theme: {
'--prism-primary': '#6366f1',
'--prism-primary-from': '#6366f1',
'--prism-primary-to': '#8b5cf6'
// ...
}
});
Watch Mode
The serve builder watches your library sources and config file. Change a component, add a @Showcase decorator, modify an input — the manifest regenerates, and the Angular dev server picks up the change. No restart needed.
Component Pages & Custom Pages
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.
ng-prism supports Component Pages — free-form Angular components registered alongside your showcased components. They're defined in your main.ts via providePrism():
import { PrismShellComponent, providePrism, componentPage } from '@ng-prism/core';
import { ButtonPatternsPageComponent } from './pages/button-patterns.page.js';
bootstrapApplication(PrismShellComponent, {
providers: [
providePrism(manifest, config, {
componentPages: [
componentPage({
title: 'Button Patterns',
category: 'Atoms',
component: ButtonPatternsPageComponent,
}),
],
}),
],
});
For static pages that don't need Angular components, use Custom Pages directly in prism.config.ts:
export default defineConfig({
pages: [
{ type: 'custom', title: 'Changelog', category: 'Meta', data: { version: '2.1.0' } },
],
});
Both appear in the sidebar navigation alongside regular components.
Content Projection & Directive Hosting
Real-world components use <ng-content>. ng-prism handles this with a content property on variants:
@Showcase({
title: 'Card',
variants: [{
name: 'With Header',
content: {
'[card-header]': '<h3>Title</h3>',
'default': '<p>Body content</p>'
}
}]
})
Need to showcase a directive instead of a component? Use the host property to specify what element it attaches to — either a plain HTML string or another Angular component.
Why Not Just Use Storybook?
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:
- Maintaining a parallel
.stories.tsfile tree that drifts out of sync - Fighting iframe restrictions when your component uses overlays or portals
- Configuring a separate build system (webpack/Vite) alongside the Angular CLI
- Wrapping Angular-specific patterns (dependency injection, signals) in framework-agnostic abstractions
…then ng-prism might be worth a look. It doesn't try to be framework-agnostic. It's Angular, all the way down.
The Road Ahead
ng-prism is open source under the MIT license and follows Angular's versioning: @ng-prism/core@21.x 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.
What's coming next:
- More official plugins
- CI integration — Manifest validation in your pipeline (e.g. ensuring every public component has a @Showcase decorator)
- Design token documentation — Automatic token overview extracted from SCSS / CSS custom properties
- Export — Share your component catalog as a PDF or static HTML page
Get Started
dyingangel666
/
ng-prism
Lightweight Angular-native component showcase — no story files needed.
ng-prism
Lightweight, Angular-native component showcase tool. Annotate components with @Showcase — no separate story files needed.
Features
- Zero-config discovery — TypeScript Compiler API scans your library at build time
-
Signal-native — works with
input()/output()signals - Directive support — showcase directives with configurable host elements
- Plugin architecture — JSDoc, A11y, Figma, Performance, Box Model, Coverage
- Live Controls — auto-generated input controls with type-aware editors
- Code Snippets — live-updating Angular template snippets per variant
- Component Pages — free-form demo pages for complex components
- Deep-linking — URL state sync for sharing specific component/variant/view
- Themeable — full CSS custom property system, replaceable UI sections
Quick Start
1. Install
npm install @ng-prism/core
2. Add @Showcase to a component
import { Component, input, output } from '@angular/core';
import { Showcase } from '@ng-prism/core';
@Showcase({
title: 'Button',
category: 'Atoms',
description…
</p>
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.
This article was originally published by DEV Community and written by Alex.
Read original article on DEV Community