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.

Top comments (0)