JSON-driven Angular UI component library for building modern data-driven applications.
xiri-ng is a configuration-driven Angular component library where the backend controls the UI via JSON structures. Instead of writing Angular templates for every page, you define your UI as JSON on the server, and xiri-ng renders it automatically. This enables a clean separation: the backend decides what to show, and xiri-ng decides how to show it.
Perfect companion to xiri-go: The Go backend framework that generates type-safe JSON structures for xiri-ng. Together, they form the Xiri Framework - a complete stack for building enterprise applications with Go backends and Angular frontends.
- 40+ standalone Angular components ready for production use
- Configuration-driven forms with 20+ field types, validation, and server-side search
- Feature-rich tables with sorting, filtering, pagination, inline editing, and footer aggregations
- Modern UI components: Page headers, sections, toolbars, breadcrumbs, timelines, stats, description lists, skeletons, empty states
- Dynamic page rendering: URL → API call → fully rendered UI
- Responsive grid system (
xcol-md-{1-12}) - Material Design 3 theming with light/dark/auto mode
- Built on Angular 21 and Angular Material 21 with standalone components
- Tree-shakeable: Only import what you use
- Extensible via
xiri-dyncomponenttemplate overrides - Type-safe when paired with xiri-go
npm install @xiriframework/xiri-ngAngular 22, Angular Material 22, date-fns 4, RxJS 7, material-symbols. See package.json for the full list. Icon setup: see the library README.
Configure the library in your application bootstrap:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { provideXiriServices } from '@xiriframework/xiri-ng';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
provideXiriServices({ api: '/api/' }),
]
});Use xiri-dyncomponent to render JSON-driven UI:
import { Component } from '@angular/core';
import { XiriDynComponentComponent, XiriDynData } from '@xiriframework/xiri-ng';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [XiriDynComponentComponent],
template: `<xiri-dyncomponent [data]="components" />`
})
export class DashboardComponent {
components: XiriDynData[] = [
{
type: 'card',
display: 'xcol-md-6',
data: {
header: 'Users',
headerIcon: 'people',
data: { count: 42 },
fields: [{ id: 'count', name: 'Total', format: 'number' }]
}
},
{
type: 'table',
data: {
url: '/api/users',
fields: [
{ id: 'name', name: 'Name', format: 'text', sort: true },
{ id: 'email', name: 'Email', format: 'text', sort: true }
]
}
}
];
}A Xiri app is two halves: a xiri-go backend that emits JSON, and this xiri-ng frontend that renders it. The whole frontend is usually a single "DynPage" route — every URL fetches the matching API endpoint and renders the response. Three wiring steps:
1. Providers (main.ts) — services + a wildcard route to one DynPage component:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter, withRouterConfig } from '@angular/router';
import { provideXiriServices } from '@xiriframework/xiri-ng';
import { AppComponent } from './app/app.component';
import { DynpageComponent } from './app/dynpage.component'; // see "Dynamic Pages" below
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
provideXiriServices({ api: '/api/' }),
// onSameUrlNavigation:'reload' makes refresh:"page" / navigation reload the DynPage
provideRouter([ { path: '**', component: DynpageComponent } ],
withRouterConfig({ onSameUrlNavigation: 'reload' })),
]
});2. Proxy (proxy.conf.json, referenced from angular.json serve.options.proxyConfig) — forward /api to your xiri-go server:
{ "/api": { "target": "http://localhost:8080", "secure": false } }3. DynPage component — copy the one from Dynamic Pages below. That's the entire frontend; new screens are added purely on the backend.
The fastest way to start (without reading every API) is to install both bundled Claude Code skills, then describe the screen you want:
xiri-ng-expert— bundled in this repo (install). The Angular side: providers,xiri-dyncomponent, tables, forms, theming.xiri-go-expert— bundled in xiri-go. The Go side: component/table/form/dialog builders, responses, routing.
With both active, Claude scaffolds the backend handlers and the matching frontend from one prompt.
| Component | Type | Description |
|---|---|---|
| Form | form |
Dynamic form with field generation from JSON configuration |
| Text, Email, Number, Password, Textarea | Field types | Standard input fields with validation |
| Select / Multi-Select | select, multiselect |
Dropdowns with built-in search (ngx-mat-select-search) |
| Tree Select | treeselect |
Hierarchical tree selection |
| Date / DateTime | date, datetime |
Date and datetime pickers (date-fns) |
| Date Range / DateTime Range | daterange, datetimerange |
Range pickers for start/end dates |
| File Upload | file |
File upload with drag-and-drop, accepts filters |
| Volume | volume |
Specialized volume/capacity input |
| Time Limit | timelimit |
Duration input (days/hours/minutes) |
| Component | Type | Description |
|---|---|---|
| Table | table |
Data table with server-side sorting, filtering, pagination, inline editing, footer aggregations |
| Raw Table | - | Simple table for basic data display |
| Card | card |
Structured data display with header, icon, and action buttons |
| Card Link | cardlink |
Clickable navigation card |
| List | list |
List display |
| Stat | stat |
Single statistic/KPI display with value, label, icon, trend, and color theming |
| Stat Grid | stat-grid |
Grid layout for multiple statistics |
| Description List | description-list |
Key-value pairs display (like a definition list) |
| Timeline | timeline |
Vertical timeline for events/activities |
| Info Point | infopoint |
Information tooltip |
| Image Text | imagetext |
Image with text content |
| Links | links |
Link list display |
| Component | Type | Description |
|---|---|---|
| Page Header | page-header |
Modern page header with title, subtitle, icon, and color theming |
| Section | section |
Content section with optional title, subtitle, icon, and divider |
| Toolbar | toolbar |
Action toolbar with title and buttons |
| Header | header |
Simple text header with color + size (e.g. for inline section titles) |
| Breadcrumb | breadcrumb |
Breadcrumb navigation trail |
| Sidenav | - | Side navigation |
| Tabs | tabs |
Tabbed content |
| Expansion | expansion |
Expandable panels |
| Container | container |
Nested component grouping |
| Spacer | spacer |
Layout spacing |
| Divider | divider |
Visual content divider with optional text |
| Button Line | buttonline |
Row of action buttons |
| Button | - | Styled button (raised, flat, stroked, icon, fab) |
| Search | - | Search component |
| Component | Type | Description |
|---|---|---|
| Alert | - | Alert messages |
| Dialog | - | Modal dialogs |
| Done | - | Success/completion display |
| Error | - | Error display |
| Multi Progress | multiprogress |
Multiple progress indicators |
| Skeleton | - | Loading skeleton placeholders |
| Empty State | - | Empty state display |
| Snackbar Service | - | Toast notifications (success, error, info, warning) |
| Component | Type | Description |
|---|---|---|
| DynComponent | - | Renders XiriDynData[] arrays into UI |
| Stepper | stepper |
Multi-step workflows |
| Query | query |
Search/filter interface with dynamic result rendering |
| Export | Description |
|---|---|
SafehtmlPipe |
Pipe for rendering trusted HTML |
ColorType |
Theme color type definitions |
| Service | Description |
|---|---|
XiriDataService |
Central HTTP service. Prepends the configured api base URL to all requests. Methods: get(), post(), postFile(), postFileResponse() |
XiriDateService |
Date manipulation utilities wrapping date-fns with timezone support |
XiriNumberService |
Number formatting and validation |
ThemeService |
Material Design 3 theme management. Supports light, dark, and auto modes. Persists preference to localStorage |
XiriFormService |
Form state management across components |
XiriLocalStorageService |
Type-safe localStorage wrapper |
XiriSessionStorageService |
Type-safe sessionStorage wrapper |
XiriSnackbarService |
Snackbar notifications with typed methods: success(), error(), info(), warning() |
The core pattern for xiri-ng applications: the current URL maps to an API call, and the response is rendered as a full page.
URL: /users GET /api/users
|
v
{ bread: [...], data: XiriDynData[] }
|
v
xiri-dyncomponent renders the page
Implementation — this is the complete DynPage component (same as the demo app). It is
registered once on the ** wildcard route (see Start a new project),
and every URL in your app routes through it. There is no library-exported page component on
purpose — copy this ~40-line component into your app and you never touch it again; new screens
are added purely on the backend.
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
import { ActivatedRoute, Event, NavigationEnd, Router } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { filter } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { XiriDataService, XiriDynComponentComponent, XiriDynData } from '@xiriframework/xiri-ng';
@Component({
selector: 'app-dynpage',
imports: [MatProgressSpinner, XiriDynComponentComponent],
template: `
@if (loading) { <mat-spinner diameter="30" /> }
@if (error) { <h1>Page not found</h1> }
<xiri-dyncomponent [data]="data()" />
`,
})
export class DynpageComponent implements OnInit, OnDestroy {
private dataService = inject(XiriDataService);
private router = inject(Router);
private route = inject(ActivatedRoute);
loading = true;
error = false;
bread: any = null;
data = signal<XiriDynData[] | null>(null);
private subs = new Subscription();
constructor() {
// Re-load on every navigation (works with onSameUrlNavigation:'reload')
this.router.events.pipe(
filter((e: Event) => e instanceof NavigationEnd),
takeUntilDestroyed(),
).subscribe(() => this.load());
}
ngOnInit() { this.load(); }
private load() {
this.loading = true;
this.data.set(null);
this.bread = null;
this.error = false;
let url = this.router.url;
if (url.startsWith('/')) url = url.substring(1); // strip leading /
// No query params → GET; with query params → POST them as the body
const qp = this.route.snapshot.queryParams;
const call: Observable<any> = Object.keys(qp).length === 0
? this.dataService.get(url)
: this.dataService.post(url, qp);
this.subs.add(call.subscribe({
next: (res: any) => { this.bread = res.bread; this.data.set(res.data); this.loading = false; },
error: () => { this.error = true; this.loading = false; },
}));
}
ngOnDestroy() { this.subs.unsubscribe(); }
}The backend returns { bread?: ..., data: XiriDynData[] } and xiri-dyncomponent renders
cards, tables, forms, and any other component types automatically. (The demo app's copy lives
at projects/demo/src/app/dynpage/.)
xiri-go is the official Go backend framework for xiri-ng. It provides type-safe builders for all xiri-ng components, eliminating manual JSON writing and preventing configuration errors.
- ✅ Type-safe component generation - No more JSON typos or missing fields
- ✅ Fluent API - Chainable builder methods for readable code
- ✅ Full component coverage - All xiri-ng components have Go equivalents
- ✅ Integrated with Echo framework - Built-in routing and middleware
- ✅ Auto-complete support - Your IDE knows all component options
package main
import (
"time"
"github.com/labstack/echo/v4"
"github.com/xiriframework/xiri-go/component/core"
"github.com/xiriframework/xiri-go/component/page"
"github.com/xiriframework/xiri-go/component/pageheader"
"github.com/xiriframework/xiri-go/component/stat"
"github.com/xiriframework/xiri-go/component/statgrid"
"github.com/xiriframework/xiri-go/component/table"
xurl "github.com/xiriframework/xiri-go/component/url"
)
type User struct {
ID int64
Name string
Email string
LastLogin time.Time
}
func dashboardPage(c echo.Context) error {
// Page header with title, subtitle, icon
header := pageheader.New("User Dashboard").
Subtitle("Overview of all users").
Icon("dashboard", core.ColorPrimary)
// Statistics grid — stat.New(value, label)
stats := statgrid.New().Columns(2)
stats.Add(stat.New("1,234", "Total Users").
Icon("people").
SetTrend(12.0, stat.TrendUp))
stats.Add(stat.New("892", "Active Today").
Icon("trending_up").
IconColor("accent")) // stat.IconColor takes a string (not core.Color)
// Data table — generic builder over Row type
b := table.NewBuilder[User]()
b.IdField ("id", "ID", func(r User) int64 { return r.ID })
b.TextField("name", "Name", func(r User) string { return r.Name }).WithSort(true)
b.TextField("email", "Email", func(r User) string { return r.Email }).WithSort(true)
b.DateTimeField("login", "Last Login", func(r User) time.Time { return r.LastLogin }).WithSort(true)
tbl := b.Build()
tbl.SetURL(xurl.NewUrlPrefix("/users/data", "/api"))
// Assemble page and return
p := page.NewPage()
p.Add(header)
p.Add(stats)
p.Add(tbl)
return c.JSON(200, p.Print(nil))
}This Go code produces the JSON that xiri-ng's xiri-dyncomponent automatically renders into a complete dashboard page with header, statistics, and data table. See the xiri-go-expert Claude skill bundled in that repo for a full API reference.
go get github.com/xiriframework/xiri-goSee the xiri-go repository for full documentation, examples, and API reference.
Use the display property on XiriDynData to control responsive layout:
| Class | Breakpoint |
|---|---|
xcol |
Full width (default) |
xcol-sm-{1-12} |
Small screens |
xcol-md-{1-12} |
Medium screens (>= 768px) |
xcol-lg-{1-12} |
Large screens (>= 1024px) |
xcol-xl-{1-12} |
Extra large screens (>= 1280px) |
Combine classes for responsive behavior:
{ "display": "xcol-md-6 xcol-lg-4" }This renders full width on mobile, half width on medium screens, and one-third on large screens.
xiri-ng uses the Angular Material Design 3 theming system with SCSS.
The ThemeService manages theme state:
import { ThemeService } from '@xiriframework/xiri-ng';
export class MyComponent {
private theme = inject(ThemeService);
toggleTheme() {
this.theme.toggle(); // Switches between light and dark
}
setAuto() {
this.theme.resetToAuto(); // Follow system preference
}
}Modes: light, dark, auto (follows prefers-color-scheme). The preference is persisted in localStorage.
# Install dependencies
npm install
# Start dev server (localhost:4301, proxies /api to localhost:8080)
npm start
# Build the library
npm run build
# Run tests
npm test
# Run linting
npm run lint# Automated release workflow (recommended)
npm run publish
# This will:
# 1. Bump patch version (0.2.0 → 0.2.1)
# 2. Build the library
# 3. Create git commit and tag (v0.2.1)
# 4. Push to GitHub
# 5. GitHub Actions automatically publishes to npm
# Manual version bumps
cd projects/xiri-ng
npm version minor # or major
cd ../..
git add projects/xiri-ng/package.json
git commit -m "Bump to version X.Y.Z"
git tag vX.Y.Z
git push --follow-tagsThe projects/demo/ directory contains a demo application that showcases library components:
npm start
# Open http://localhost:4301/web/The demo proxies /api requests to http://localhost:8080, so you can run a backend (e.g., a xiri-go application) alongside it.
For a deep API reference (every component's inputs/outputs, services, pipes, types) use the bundled Claude Code skill — see Claude Code Integration below. The skill has 7 reference files covering setup, dyncomponent, form fields, tables, components, and theming.
| Dependency | Version |
|---|---|
| Angular | ^22.0 |
| Angular Material | ^22.0 |
| Angular CDK | ^22.0 |
| date-fns | ^4.3 |
| @date-fns/tz | ^1.5 |
| RxJS | ^7.8.2 |
| ngx-mat-select-search | ^9.0.0 |
| material-symbols | >= 0.44 |
| echarts (optional) | ^6.1 |
Dieses Repo enthält einen bundled Claude Code Skill unter skills/xiri-ng-expert/, der Claude beim Schreiben von Angular-Code mit xiri-ng unterstützt. Der Skill wird mit jedem Library-Release mit-versioniert, sodass die Skill-Inhalte (Komponenten-APIs, Interfaces, Patterns) zur installierten Library-Version passen.
Sobald aktiviert, triggert der Skill automatisch, wenn du Angular-Code schreibst oder Fragen stellst zu:
- Setup:
provideXiriServices,XiriDataService,XiriSnackbarService,XiriResponseHandlerService, Theme + Storage Services xiri-dyncomponent: Rendering vonXiriDynData[], alle 27 type-Werte, Custom-Rendering via TemplateRef- Formulare:
XiriFormFieldsComponent, alle 19 Feldtypen,showWhen-Conditional-Visibility,select-Directive - Tabellen:
XiriTableComponentundXiriRawTableComponent— Settings, Fields, Server-Side-Pagination, Inline-Edit - Komponenten: Card, Dialog, Stepper, Tabs, Expansion, Timeline, Stat/StatGrid, Page-Header, Toolbar, …
- Farben/Types:
XiriColor(Theme + Extended),XiriButton,XiriButtonResult, etc. - Theming & i18n: Material Design 3,
ThemeService-Signals, Locale-Propagation
Wenn dein Projekt den standardisierten skills-lock.json-Mechanismus nutzt, trage einen Eintrag ein, der auf einen Release-Tag verweist:
{
"version": 1,
"skills": {
"xiri-ng-expert": {
"source": "xiriframework/xiri-ng",
"sourceType": "github",
"ref": "v0.2.18"
}
}
}Ersetze v0.2.18 durch den Tag, der zu deiner installierten @xiriframework/xiri-ng-Version passt (npm list @xiriframework/xiri-ng).
Weil der Skill nicht im npm-Package liegt (er lebt im Git-Repo), clone oder sparse-checkout das Repo und verlinke den Skill-Ordner:
# Sparse clone (nur skills/ holen)
git clone --depth 1 --branch v0.2.18 --filter=blob:none --sparse \
https://github.com/xiriframework/xiri-ng.git /tmp/xiri-ng-skill
cd /tmp/xiri-ng-skill && git sparse-checkout set skills
cd -
# Als Symlink in dein Projekt
mkdir -p .claude/skills
ln -s /tmp/xiri-ng-skill/skills/xiri-ng-expert .claude/skills/xiri-ng-expert
# ODER Kopieren (statisch):
cp -r /tmp/xiri-ng-skill/skills/xiri-ng-expert .claude/skills/Wenn du xiri-ng in mehreren Projekten nutzt:
git clone --depth 1 --branch v0.2.18 https://github.com/xiriframework/xiri-ng.git /tmp/xiri-ng
cp -r /tmp/xiri-ng/skills/xiri-ng-expert ~/.claude/skills/
rm -rf /tmp/xiri-ngAktualisieren nach einem npm update: den globalen Skill-Ordner mit dem passenden Tag neu ziehen.
skills/xiri-ng-expert/
├── SKILL.md # Navigation + Quick-Refs (always-loaded sobald Skill triggert)
├── references/ # On-demand (nur wenn Claude liest)
│ ├── setup.md # provideXiriServices + alle Services im Detail
│ ├── dyncomponent.md # xiri-dyncomponent + alle XiriDynData-Typen
│ ├── form-fields.md # XiriFormFieldsComponent + 19 Feldtypen + showWhen
│ ├── table.md # XiriTable + XiriRawTable + Server-Side-Flow
│ ├── components.md # Kompakt-Signaturen aller weiteren Komponenten
│ └── theming-i18n.md # XiriColor, ThemeService, Locale-Setup
└── evals/
└── evals.json # Test-Prompts für skill-creator
Der Skill ist an den Source-Code dieses Tags gekoppelt. Skill und Library-Version sollten immer synchron sein.
Apache-2.0