xsd-ui
v1.8.4
Published
Independent Angular UI component library workspace
Downloads
546
Readme
xsd-ui
Dynamic Angular form library for ISO 20022 workflows.
xsd-ui renders schema-driven forms from XSD-to-JSON metadata, supports recursive complex structures, validates according to schema constraints, and exports data as clean output JSON.
Why xsd-ui
- Schema-driven rendering for nested ISO 20022 structures
- Reactive Forms architecture with recursive field generation
- Built-in validation from schema rules (length, pattern, numeric constraints, enum)
- Repeatable groups,
choicestructures, currency amounts, and one-click UETR generation - XML and JSON patching APIs (
setXmlForm,setJsonForm) - Output shaping with optional empty-field omission
- Precise validity reporting via
getInvalidFields()(field path + reason) - Fully themeable through CSS custom properties — no build tooling required
- Event-based API access (
onFormReadyEvent) withoutViewChild - Standalone Angular components, ready for library consumption
Installation
npm install xsd-uiPeer dependencies:
@angular/common^21.2.10@angular/core^21.2.10@angular/forms^21.2.10@angular/animations^21.2.10rxjs^7.8.1
Quick Start
Register license once (recommended):
import {
configureXsdUiLicense,
XsdLicensePayload,
XsdLicenseValidationResult,
XsdLicenseValidator,
} from 'xsd-ui';
const validateLicenseOnServer: XsdLicenseValidator = async (
licenseKey: string,
payload: XsdLicensePayload,
): Promise<XsdLicenseValidationResult> => {
const response = await fetch('/api/licenses/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ licenseKey, payload }),
});
if (!response.ok) {
return { valid: false, reason: 'License API unavailable.' };
}
return response.json() as Promise<XsdLicenseValidationResult>;
};
configureXsdUiLicense({
licenseKey: '<YOUR_LICENSE_KEY>',
licenseValidator: validateLicenseOnServer,
requireServerValidation: true,
offlineFallbackOnServerError: true,
});Use component:
import { Component } from '@angular/core';
import {
XsdFormComponent,
XsdLicenseValidationResult,
XsdFormPublicApi,
XsdFormSchema,
} from 'xsd-ui';
@Component({
selector: 'app-payment-form',
standalone: true,
imports: [XsdFormComponent],
template: `
<xsd-form
[schema]="schema"
(onFormReadyEvent)="onFormReady($event)"
(licenseValidationChange)="onLicenseValidationChange($event)"
(change)="onFormChange()"
/>
<button type="button" (click)="submit()">Submit</button>
`,
})
export class PaymentFormComponent {
schema!: XsdFormSchema;
formApi: XsdFormPublicApi | null = null;
onFormReady(api: XsdFormPublicApi): void {
this.formApi = api;
}
onFormChange(): void {
// Live updates, autosave, previews, etc.
}
onLicenseValidationChange(result: XsdLicenseValidationResult): void {
if (!result.valid) {
console.error(result.reason);
}
}
submit(): void {
if (!this.formApi) return;
if (!this.formApi.isValid()) return;
const payload = this.formApi.getForm(true);
console.log(payload);
}
}Core Components
XsdFormComponent(xsd-form): root orchestration componentXsdFieldComponent(xsd-field): recursive field renderer
Edit form layout
The editable form uses the same visual system as the read-only view — fonts, type scale and palette — without changing the document structure: every section renders in schema order.
- One hierarchy device. A section is a heading plus a left rail; there are no nested cards. Only repeated items get a card. The innermost section holding focus gets an accented rail.
- Optional empty sections collapse in place to a single muted heading line (
› Proxy,› Interest 0 [+ Add]). Click to expand; they expand automatically whensetJsonForm/setXmlFormfills them. - Choices live in the heading.
Identification [Other ▾]— the option selector sits beside the section name and the chosen option's fields render directly beneath it. - Add on a repeat scrolls to the new item, highlights it briefly and focuses its first input.
- Booleans get a
true/falseselect; an amount'sCcyattribute is a labelled tag beside it. - Accessible by construction: real
<button>toggles witharia-expandedand names, no interactive element nested in another,aria-required/aria-invalid/aria-describedbyon inputs, unique ids per rendered field.
Inputs
| Input | Type | Required | Description |
|---|---|---|---|
| schema | XsdFormSchema | Yes | Root schema including namespace and schemaElement. |
| licenseKey | string | No | Optional override for global registered key. |
| licenseValidator | XsdLicenseValidator | No | Optional override for global server validator. |
| requireServerValidation | boolean | No | Optional override for global server-validation enforcement. |
| offlineFallbackOnServerError | boolean | No | Optional override to allow offline fallback when server validation errors out. |
| translations | TranslationConfig | No | Label and error message overrides. |
| readonly | boolean | No | Renders a read-only document view instead of the editable form. See Read-only view. |
| showValidationInView | boolean | No | In read-only mode, marks fields that fail schema validation with ⚠. |
| readonlyOptions | XsdReadonlyOptions | No | Read-only view tuning: collapse, formatValues, headingDepth, tierThresholds. Defaults: verbatim values, full hierarchy. |
| readonlyTitle | string | No | Read-only header title. Defaults to the business element's label (e.g. "Bank To Customer Account Report V08"). |
| readonlyMeta | string | No | Line under the read-only title. Defaults to the message id from the namespace (e.g. camt.052.001.08); '' hides it. |
Outputs
| Output | Payload | Description |
|---|---|---|
| onFormReadyEvent | XsdFormPublicApi | Emits when form API is ready (and on schema rebuild). |
| licenseValidationChange | XsdLicenseValidationResult | Emits license validation state and failure reason. |
| change | void | Emits on value changes and programmatic updates. |
Licensing Best Practice
For browser libraries, client-side checks alone are not tamper-proof. Recommended production setup:
- Register licensing once at app startup with
configureXsdUiLicense(...). - Keep signed offline key verification enabled (
licenseKey+ RSA signature). - Add backend entitlement validation using
licenseValidator. - Set
requireServerValidationtotruein production. - Keep
offlineFallbackOnServerErrorenabled if you want graceful offline operation. - Return short-lived entitlements from your backend and rotate/revoke there.
Note:
When implementing licenseValidator, return { valid: false } for explicit entitlement rejection (revoked, over-limit, wrong tenant), and throw errors for network/server failures. This lets xsd-ui distinguish deny vs fallback conditions.
This hybrid approach is the strongest practical model for Angular UI components.
Public API
Exposed through onFormReadyEvent.
| Method | Description |
|---|---|
| isValid(): boolean | Returns schema-aware validity (optional empty branches are handled). |
| getInvalidFields(): XsdInvalidField[] | Returns every failing field as { path, reason } — ideal for surfacing which fields are invalid after a patch. |
| getForm(omitEmpty?: boolean) | Returns output JSON in root-keyed structure. |
| setJsonForm(json) | Patches form using either full root-keyed JSON or inner object. |
| setXmlForm(xml) | Parses and patches ISO 20022 XML payloads. |
| getNamespace() | Returns schema namespace. |
| clearForm() | Resets form values. |
| disableForm() | Disables all controls. |
| enableForm() | Enables all controls. |
| setReadonly(readonly) | Switches between the editable form and the read-only document view. |
Read-only view
When you only need to show a message — e.g. an incoming XML you received rather than one the
user is composing — a disabled form is hard to read: every input, empty optional group and
placeholder is still rendered. Set readonly instead and xsd-form renders a compact,
document-style view:
<xsd-form [schema]="schema" [readonly]="true" [showValidationInView]="true" />onFormReady(api: XsdFormPublicApi): void {
api.setXmlForm(incomingXml); // patch as usual
api.setReadonly(true); // or bind [readonly] in the template
}The view is produced by a projection pass (projectView(), exported and framework-free) that
walks schema and instance together and emits a small tree before anything renders. A camt.052
schema has ~2 300 leaves; a typical message carries ~30 values — the projection keeps only those.
Defaults are faithful: every value is shown exactly as written in the message (no date reformatting, no digit grouping, no timezone conversion — the original text of date-times is kept even though the edit form stores a browser-local copy), and every container keeps its own heading and rail so it is always clear which section a value belongs to.
What always happens (schema-driven, no per-message templates):
| | Rule | Example |
|---|---|---|
| Prune | Empty leaves and containers are dropped; repeats keep present items; a choice shows only its filled branch; Ccy folds into the amount | <Amt Ccy="EUR">704873.77</Amt> → 704873.77 EUR |
| Style | External code sets (…Cd, ≤ 4 chars) and enumerations render as chips; CRDT/DBIT as a badge; identifiers and amounts are monospaced | ITAV, CRDT |
| Amounts | A currency amount stays one labelled row, with the Ccy attribute shown as a named tag beside the value — mirroring <Amt Ccy="EUR">704873.77</Amt> | Amount → 704873.77 [Ccy EUR] |
| Cards | Repeats become numbered cards; every value inside keeps its own labelled row | 1 → Type › Code ITAV, Amount, Credit Debit Indicator, Date |
| Index | Documents with more than 150 values get a sticky section index | |
Optional compaction via readonlyOptions ([readonlyOptions]="{ collapse: true, formatValues: true }"):
| Option | Effect | Example |
|---|---|---|
| collapse | A container with exactly one surviving child merges into it; code-only groups become one chip row; a repeated item's code and amount move into its card title with CRDT/DBIT merged into the amount; repeats are hoisted to top-level headings; very small documents drop headings | Acct/Id/Othr/Id → Account · Other · Identification, BkTxCd → PMNT / RCDT / XBCT, card 1 ITAV 704 873.77 EUR CRDT |
| headingDepth | Containers deeper than this become a muted label prefix instead of a heading | Report Pagination · Page Number |
| formatValues | Thin-space digit grouping, ≥ 2 decimals on amounts, 24 Nov 2020 dates, 11:00:00 UTC+01:00 date-times, Yes/No booleans | 704 873.77 EUR |
| tierThresholds | Row counts at which the layout switches tier (default [20, 150]) | |
With showValidationInView, fields that fail schema constraints get a ⚠ marker with the reason
(missing required fields are shown as —), plus a summary banner — handy for reviewing
messages that were not produced by this form.
Project a summary band of your own (e.g. account / available balance / as-of) with the
xsdViewSummary slot:
<xsd-form [schema]="schema" [readonly]="true">
<div xsdViewSummary class="my-summary">…</div>
</xsd-form>The underlying form model stays alive, so getForm(), getInvalidFields(), setJsonForm() and
setXmlForm() keep working and switching back to edit mode is lossless. readonly is independent
of disableForm() (which changes control state, not presentation).
Theming: the view shares the --xsd-ui-* palette and adds --xsd-ui-ink, --xsd-ui-font and
--xsd-ui-mono. It prefers IBM Plex Sans/Mono when the host app loads them and falls back to
system fonts otherwise.
Theming
xsd-ui ships with a clean, accessible default theme built on a five-color palette,
where each color carries a consistent meaning:
| Role | Default | Used for |
|---|---|---|
| Structure | #006ba6 | Field labels, choice selectors, Add, select carets |
| Interaction | #0496ff | Focus rings, input hover, active-section rail, new-item highlight |
| Highlight | #ffbc42 | Accent highlights |
| Required / Error | #d81159 | Required marker, invalid inputs, error messages, Remove |
| Ink | #15171b (--xsd-ui-ink) | Text, headings, rails and lines (mixed to tints) |
Fonts: --xsd-ui-font / --xsd-ui-mono (default IBM Plex Sans/Mono when the host loads them,
system fonts otherwise) apply to both the edit form and the read-only view.
Re-brand the entire form by overriding the public --xsd-ui-* custom properties
from any ancestor (typically :root in your global stylesheet) — no recompilation needed:
:root {
--xsd-ui-ocean: #1e3a8a; /* structure */
--xsd-ui-blue: #2563eb; /* interaction */
--xsd-ui-gold: #f59e0b; /* highlight */
--xsd-ui-raspberry: #dc2626; /* required / error */
--xsd-ui-berry: #7c3aed; /* repeatable sets */
}Optional darker hover shades — --xsd-ui-ocean-dark, --xsd-ui-blue-dark,
--xsd-ui-raspberry-dark — can be overridden the same way; otherwise sensible
defaults are derived from the base colors.
Schema Model Overview
xsd-ui expects an XsdFormSchema shape similar to:
interface XsdFormSchema {
namespace: string;
schemaElement: SchemaElement | SchemaElement[];
}Each SchemaElement contains metadata such as:
name,id,xpathdataType- cardinality:
minOccurs,maxOccurs - validation constraints:
minLength,maxLength,pattern,minInclusive,maxInclusive, etc. - structure:
elements(children)
Validations Supported
- Required checks from
minOccurs - String length (
minLength,maxLength) - Regex pattern
- Numeric boundaries (
minInclusive,maxInclusive,minExclusive,maxExclusive) fractionDigits,totalDigits- Enumerations (
values) - Choice structures and unbounded arrays
XML / JSON Patching
JSON patch
formApi.setJsonForm({
FIToFICstmrCdtTrf: {
GrpHdr: {
MsgId: 'MSG-001',
CreDtTm: '2026-04-25T12:41:52.673+08:00',
NbOfTxs: '1'
}
}
});XML patch
formApi.setXmlForm(`
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08">
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>XML-PATCH-001</MsgId>
<NbOfTxs>1</NbOfTxs>
</GrpHdr>
</FIToFICstmrCdtTrf>
</Document>
`);License
This package is distributed under a commercial license. Contact the publisher for licensing terms and usage rights.
