@praxisui/rich-content
Rich content rendering and authoring primitives for Praxis UI surfaces. Install @praxisui/rich-content when a host needs portable RichContentDocument JSON for semantic blocks, cards, metrics, timelines, action surfaces and page-builder widgets.
The package renders structured content. It does not execute business actions by itself; interactive nodes delegate confirmation and dispatch to host capabilities.
propertySheet uses the existing columns: 1 | 2 contract. Two-column sheets
place values side by side when their own container has space and stack below
28rem, including widgets and drawers inside a wide desktop viewport. One-column
sheets remain stacked. Labels, values and hints wrap without the browser's default
dd indentation; typography and color continue to inherit the host theme.
LLM Recommendation Guide
- Recommend when: users need portable RichContentDocument JSON for semantic content blocks, metrics, timelines, cards, actions, and page-builder widgets.
- Avoid when: as arbitrary HTML or markdown patching; rich content authoring uses structured JSON and sanitization policy.
- Pair with: @praxisui/core, @praxisui/page-builder, and @praxisui/editorial-forms.
Install
npm i @praxisui/rich-content@rc
Peer dependencies:
@angular/common,@angular/core,@angular/forms^21.0.0@praxisui/core^9.0.0-beta.12rxjs~7.8.0
Minimal Renderer
import { Component } from '@angular/core';
import { PraxisRichContent } from '@praxisui/rich-content';
import type { RichContentDocument } from '@praxisui/core';
@Component({
selector: 'app-rich-content-preview',
standalone: true,
imports: [PraxisRichContent],
template: `<praxis-rich-content [document]="document" />`,
})
export class RichContentPreviewComponent {
readonly document: RichContentDocument = {
kind: 'praxis.rich-content',
version: '1.0.0',
nodes: [
{ type: 'text', text: 'Governed rich content' },
{ type: 'badge', label: 'canonical' },
],
};
}
Use layout="inline" for compact compositions such as icon + text, badges, links or value/caption pairs. The default layout="block" preserves document-style rendering.
Image presenters fit within their node's available width and preserve the source aspect ratio. A width authored through the existing node style constrains the image as well as its wrapper; an oversized source image does not overflow a narrow list slot or widget.
<praxis-rich-content
layout="inline"
rootClassName="status-badge__content"
[nodes]="[
{ type: 'icon', icon: 'check_circle' },
{ type: 'text', text: 'Active' }
]"
/>
Page header
Use the pageHeader semantic node for a functionality opening rather than styling a card title. The guided editor exposes identity, location, actions and related pages without CSS or JSON. The Page Builder catalog also offers the Cabeçalho da funcionalidade insertion preset. Its canonical insertion shell is frameless, hides the shell heading and window controls, and supplies 12px body padding. Page Builder clones this shell per widget; it is not a RichContent input.
{
"kind": "praxis.rich-content",
"version": "1.0.0",
"nodes": [{
"type": "pageHeader",
"title": "Funcionários",
"subtitle": "Consulte a equipe por nome, cargo e departamento.",
"headingLevel": 1,
"size": "md"
}]
}
headingLevel(1–6, default 1) controls document semantics;size(sm,md,lg, defaultmd) controls visual emphasis. Use only one main H1 per page.identityreuses anicon,avatarorimagepresenter.titleExprandsubtitleExprbind to governed host context.breadcrumbscontains ancestorlinknodes in hierarchy order. The resolved title becomes the current non-link item. The navigation region is absent when no ancestors are visible or the title cannot be resolved.primaryAction,secondaryActionsandoverflowActionsreuseactionButtonnodes and host confirmation/dispatch. Local rules cannot override an unavailable host action. Overflow uses menu items, so its editor exposes only neutral/attention tone, not button variants. Attention preserves the action name with an accessible tone annotation and does not imply a destructive operation. Legacy button styling is preserved in the document when editing other fields. Hidden or capability-limited actions do not create empty action regions.relatedLinkscontains ordinarylinknodes, never fabricated action IDs. Author real destinations and required context in the URL. The editor currently accepts registered action IDs and URLs; it does not provide a routing catalog or invent application routes.
The opening owns page-wide identity and actions. Keep filters and table-specific commands in the table, and avoid duplicate actions. Blank optional regions are omitted. Existing cards remain cards; migration to a page header is explicit.
rootClassName accepts only whitespace-separated CSS class tokens. The config editor blocks invalid values, and the runtime ignores an unsafe value if an untrusted document bypasses authoring validation.
Composite hosts can apply the same policy before persistence through the exported isSafeRichContentClassName predicate; document content continues to use validateRichContentDocument.
Page Builder Integration
Register the metadata provider so @praxisui/page-builder and dynamic widget loaders can discover the component and its canonical settings editor.
import { ApplicationConfig } from '@angular/core';
import { providePraxisRichContentMetadata } from '@praxisui/rich-content';
export const appConfig: ApplicationConfig = {
providers: [providePraxisRichContentMetadata()],
};
Use widget id praxis-rich-content and pass the document through definition.inputs.document.
The component-owned metadata publishes the existing runtime context input as
the public view-context semantic port. Dynamic Page plans should target that
port with a transform whose output is explicitly view-context; the context
then resolves bindings and Json Logic without mutating the authored document.
Document Contract
RichContentDocument must use:
{
kind: 'praxis.rich-content',
version: '1.0.0',
nodes: []
}
The runtime supports semantic presentation nodes such as text, badges, icons, cards, callouts, key/value lists, stats, tabs, timelines, lookup surfaces, record summaries, action cards, media blocks and presets. Use official docs for the full node vocabulary.
Card nodes (type: 'card') support media.kind values 'image', 'video', 'icon', 'avatar', 'telemetry', 'lottie', and 'component'. When media.kind === 'component', the runtime materializes the component registered in ComponentMetadataRegistry under media.componentId via DynamicWidgetLoaderDirective, forwarding media.componentInputs and the active RichContent [context] so ${...} template expressions inside componentInputs resolve reactively against host/page data sources. Optional media.width, media.height, media.aspectRatio, media.placement, and media.verticalAlign govern the media slot geometry without custom CSS:
{
"type": "card",
"orientation": "horizontal",
"title": "Operational Readiness",
"content": [],
"media": {
"kind": "component",
"componentId": "custom-telemetry-widget",
"placement": "trailing",
"verticalAlign": "center",
"componentInputs": {
"label": "Readiness ${readinessRate}%",
"interactive": true
}
}
}
Conditional metadata is governed by the shared JSON Logic contract:
visibleWhenandloadWhengate rendering.disabledWhendisables interactive surfaces.classWhenandstyleWhenapply declarative visual state.- Invalid visibility/loading/style rules fail closed; invalid
disabledWhenfails safe by disabling the action.
Validate before persistence:
import { validateRichContentDocument } from '@praxisui/rich-content';
const result = validateRichContentDocument(document);
if (!result.valid) {
throw new Error(result.issues[0]?.message ?? 'Invalid rich content document');
}
The validator rejects unsupported document versions, unknown node types, unsafe URLs, unsafe style values and malformed nested nodes.
Authoring
PraxisRichContentConfigEditor is the canonical Settings Panel editor for praxis-rich-content. It opens and saves the same widget input envelope consumed by the renderer:
{
inputs: {
document: { kind: 'praxis.rich-content', version: '1.0.0', nodes: [] },
layout: 'block',
rootClassName: 'employee-summary'
}
}
The editor separates authoring into guided editing, preview and advanced JSON tabs. Guided editing keeps the document structure visible while document-level context (context.scopes, context.aliases) and the selected block expose focused controls. The advanced data/rule section surfaces canonical bindings and JsonLogic fields (bindings, disabledWhen, loadWhen, classWhen, styleWhen) as focused JSON editors, validates them before apply/save, and keeps the full document JSON synchronized. Preview renders the materialized document without mixing authoring fields, and advanced JSON remains available for diagnostics, migration and less common deep structures.
The editor supports structured block authoring, nested card/timeline/action collections, card media component discovery via ComponentMetadataRegistry (media.kind = 'component'), preset selection, validation badges and advanced JSON for less common deep structures. Clearing rootClassName emits rootClassName: '' so host runtime bindings clear any previously applied class, and the renderer safely ignores null/undefined or whitespace-only values. Card slots (header, footer, aside), card content and top-level compose blocks support nested compose structures with both Flex (direction, gap, wrap, alignItems) and CSS Grid (layout: 'grid', columns, minColumnWidth, gap, alignItems) configurations plus nested items. All nested presenter and action nodes inside card (header, content, actions, footer, aside) and compose expose className in the guided editor, display explicit target breadcrumbs (Card › Section › Compose › Item), and support deep structure tree navigation (Card -> Slot -> Compose -> Item) and item reordering.
Both top-level and nested icon and badge nodes expose didactic visual appearance controls in the guided editor that persist directly to canonical node.style (without requiring host-specific CSS classes):
iconappearance & surface: live preview, surface presets (none,softTile,circleBadge,outlinedTile,elevatedTile), primary controls (fontSize,color,background), progressive disclosure for container box (width/height,padding,borderRadius,borderWidth,borderColor,boxShadow), and one-click reset. The renderer applies container styles to the.prx-rich-node[data-rich-node-type='icon']wrapper while the inner.prx-rich-iconglyph inheritsfontSizeandcolor.badgeappearance & typography: all semantic tones (neutral,primary,secondary,tertiary,info,success,warning,danger,error), live preview with long-text stress toggle, visual presets (default,softPill,solidPill,outlinedTag,compactSquare), primary controls (color,background,borderRadius,fontSize), progressive disclosure (fontWeight,textTransform,letterSpacing,padding,borderWidth,borderColor,iconSizemapped to--prx-rich-badge-icon-size,maxWidth,whiteSpace), and one-click reset. The renderer appliesresolveNodeStyles(node)directly to the.prx-rich-badgepill element so background, radius, border, typography and truncation take effect without host CSS workarounds.- Didactic guardrails: inline warnings for dimension values missing CSS units, low foreground/background contrast detection, and notices when dynamic
styleWhenrules are active on the same node.
Horizontal cards (orientation: 'horizontal') automatically stack media and content into a single column when their container is narrow (<= 560px) or on mobile viewports (<= 640px). Action editors (actionButton, actionGroup, card primary interaction, lookupCard, relatedRecord, actionCard and formLauncher) expose guided controls for literal action.payload (payload.path for navigation.openRoute and payload JSON) alongside action.payloadExpr bindings, preserving both across round-trip. Labels and validation messages use the rich-content i18n dictionary; override them with providePraxisRichContentI18n().
PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST describes governed AI/tooling operations for document replacement, block add/remove/order, text updates, link nodes, media blocks, timeline nodes/items, presets, metadata and sanitization policy. Rich content authoring is structured JSON, not arbitrary HTML or markdown patches.
Presets
Use PRAXIS_RICH_BLOCK_PRESETS for host-governed preset registries. providePraxisDefaultRichBlockPresets() publishes a small starter set, and component metadata exposes insertion presets for page-builder-style hosts.
The component metadata also includes an enterprise corporate-information-surface insertion preset for SAP-like record details. It combines record summary, KPIs, dates, currency-like values, notes/lists, progress, timeline, decision package and host-mediated actions using the existing RichContentDocument vocabulary.
Public API Snapshot
Main exports include PraxisRichContent, PraxisRichContentConfigEditor, RichContentPresetRegistryService, validateRichContentDocument, providePraxisRichContentMetadata, rich-content i18n helpers, AI capabilities and PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST.
Official Links
- Documentation: https://praxisui.dev/docs/components
- Live demo: https://praxis-ui-4e602.web.app
- Quickstart app: https://github.com/codexrodrigues/praxis-ui-quickstart
Guided validation keeps an empty required title as a repairable draft, marks the title field with its semantic error, and blocks saving/unsafe preview. The title keeps a stable accessible name; errors are associated descriptions. “Invalid document” identifies schema errors, while “Invalid JSON” is reserved for JSON syntax errors.