npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

structured-render

v2.6.1

Published

A library for safely rendering arbitrary data generated from any source.

Downloads

1,646

Readme

Structured Render

A type-safe library for defining structured data as JSON and rendering it into multiple output formats: HTML, Markdown, PDFs, and images. Useful for rendering arbitrary data from any source (AI outputs, reports, APIs) into consistent, styled views.

Install

npm i structured-render

Concepts

Data hierarchy

Structured Render uses a three-level hierarchy:

  1. StructuredRenderData: the top-level type. An array of cards.
  2. StructuredRenderCard: a container with an optional title, optional title icon, and an array of sections.
  3. StructuredRenderSection: a union of all renderable content types (text, table, list, tag, markdown, code block, etc.).

All data types are defined with object-shape-tester, providing both runtime validation and compile-time TypeScript types.

Section types

Every section has a type field (discriminant), an optional sectionTitle, and optional sources. Use StructuredRenderSectionType to reference type values.

| Type | Description | Key fields | | ------------- | ----------------------------------- | ----------------------------------------------------------- | | text | Plain or styled text | text, style?, icon? | | markdown | Raw markdown content | markdown | | codeBlock | Fenced code block | code, syntax? | | inlineCode | Inline code span | code | | table | Horizontal or vertical table | direction, headers, entries, footerRows? | | list | Bulleted list of items | items (each with content, icon?, sources?) | | tag | Colored label / badge | text, color?, useBigTag? | | icon | SVG icon reference | iconKey, strokeColor?, fillColor? | | collapsible | Collapsible wrapper around sections | header, content | | source | Reference to a source document | fileName?, pageNumbers?, quote?, fileBoundingBoxes? | | empty | Renders nothing (placeholder) | | | processing | Loading indicator | |

Usage

Building structured data

Anything producing outputs to be consumed by this package should produce StructuredRenderData instances.

Text

import {StructuredRenderSectionType, StructuredRenderTextStyle} from 'structured-render';

// Basic text
const textSection = {
    type: StructuredRenderSectionType.text,
    sectionTitle: 'Summary',
    text: 'Overall project health is good.',
};

// Styled text
const boldText = {
    type: StructuredRenderSectionType.text,
    text: 'Important note.',
    style: StructuredRenderTextStyle.Bold,
};

const faintText = {
    type: StructuredRenderSectionType.text,
    text: 'Additional details.',
    style: StructuredRenderTextStyle.Faint,
};

// Text with an icon
const textWithIcon = {
    type: StructuredRenderSectionType.text,
    text: 'Completed task.',
    icon: {
        type: StructuredRenderSectionType.icon,
        iconKey: 'StatusSuccess24Icon',
        strokeColor: 'green',
    },
};

Markdown

const markdownSection = {
    type: StructuredRenderSectionType.markdown,
    sectionTitle: 'Key Findings',
    markdown:
        '### Strengths\n\n- **Code coverage** improved to 89%.\n- Build times remain under 3 minutes.',
};

Code blocks

// Fenced code block with syntax highlighting
const codeBlock = {
    type: StructuredRenderSectionType.codeBlock,
    sectionTitle: 'Config',
    syntax: 'json',
    code: JSON.stringify({project: 'my-app', version: '1.0.0'}, null, 4),
};

// Inline code
const inlineCode = {
    type: StructuredRenderSectionType.inlineCode,
    code: 'const x = 42;',
};

Tables

Tables support two directions: Horizontal (headers as a top row, entries add rows) and Vertical (headers as a left column, entries add columns). Use the createRenderDataTable helper for type-safe table construction.

import {
    createRenderDataTable,
    StructuredRenderCellDirection,
    StructuredRenderSectionType,
} from 'structured-render';

const table = createRenderDataTable(
    StructuredRenderCellDirection.Horizontal,
    [
        {key: 'name'},
        {key: 'status'},
    ],
    [
        {
            data: {
                name: {
                    type: StructuredRenderSectionType.text,
                    text: 'Auth module',
                },
                status: {
                    type: StructuredRenderSectionType.tag,
                    text: 'Healthy',
                    color: {variant: 'positive'},
                },
            },
        },
        {
            data: {
                name: {
                    type: StructuredRenderSectionType.text,
                    text: 'API layer',
                },
                status: {
                    type: StructuredRenderSectionType.tag,
                    text: 'Degraded',
                    color: {variant: 'warning'},
                },
            },
        },
    ],
);

Table cells can contain text, inlineCode, markdown, tag, list, empty, or processing sections.

Headers support custom display text and can be hidden:

const headers = [
    {key: 'name', text: {type: StructuredRenderSectionType.text, text: 'Module Name'}},
    {key: 'internal', hidden: true},
];
Empty table fallback

Use emptyStructuredRenderTableFallback to show a fallback section when a table has no data:

import {emptyStructuredRenderTableFallback} from 'structured-render';

const section = emptyStructuredRenderTableFallback({
    table: createRenderDataTable(StructuredRenderCellDirection.Vertical, headers, []),
    fallback: {
        type: StructuredRenderSectionType.text,
        text: 'No data available.',
    },
    sectionTitle: 'Results',
});

Lists

const list = {
    type: StructuredRenderSectionType.list,
    sectionTitle: 'Action Items',
    items: [
        {
            content: {
                type: StructuredRenderSectionType.text,
                text: 'Increase test coverage.',
            },
            icon: {
                type: StructuredRenderSectionType.icon,
                iconKey: 'StatusWarning24Icon',
                strokeColor: 'orange',
            },
        },
        {
            content: {
                type: StructuredRenderSectionType.text,
                text: 'Update documentation.',
            },
        },
    ],
};

List item content supports text, tag, or empty sections. Each item can have optional sources.

Tags

// Tag with a color variant (from Vira)
const tag = {
    type: StructuredRenderSectionType.tag,
    text: 'Active',
    color: {variant: 'positive'},
};

// Tag with custom colors
const customTag = {
    type: StructuredRenderSectionType.tag,
    text: 'Critical',
    color: {
        custom: {
            backgroundColor: '#e74c3c',
            foregroundColor: '#ffffff',
        },
    },
};

// Big tag
const bigTag = {
    type: StructuredRenderSectionType.tag,
    text: 'Featured',
    useBigTag: true,
};

Sources

Sources provide reference/citation metadata and can be attached to any section or list item via the sources field.

const sectionWithSources = {
    type: StructuredRenderSectionType.text,
    text: 'Patient requires follow-up.',
    sources: [
        {
            type: StructuredRenderSectionType.source,
            fileName: 'intake-report.pdf',
            pageNumbers: [
                3,
                5,
            ],
            quote: 'Follow-up recommended within 30 days.',
            fileBoundingBoxes: [
                {x1: 0.1, y1: 0.2, x2: 0.9, y2: 0.3},
            ],
        },
    ],
};

Collapsible sections

const collapsible = {
    type: StructuredRenderSectionType.collapsible,
    header: 'Details',
    content: [
        {
            type: StructuredRenderSectionType.text,
            text: 'This content is hidden by default.',
        },
        {
            type: StructuredRenderSectionType.list,
            items: [
                {
                    content: {
                        type: StructuredRenderSectionType.text,
                        text: 'Nested list inside collapsible.',
                    },
                },
            ],
        },
    ],
};

Assembling cards

Cards group sections under an optional title and optional title icon.

import {type StructuredRenderData} from 'structured-render';

const data: StructuredRenderData = [
    {
        cardTitle: 'Project Health Report',
        cardTitleIcon: {
            type: StructuredRenderSectionType.icon,
            iconKey: 'StatusSuccess24Icon',
            strokeColor: 'green',
        },
        sections: [
            {
                type: StructuredRenderSectionType.text,
                sectionTitle: 'Summary',
                text: 'All systems operational.',
            },
            {
                type: StructuredRenderSectionType.table,
                sectionTitle: 'Module Metrics',
                direction: 'horizontal',
                headers: [
                    {key: 'module'},
                    {key: 'coverage'},
                ],
                entries: [
                    {
                        data: {
                            module: {type: StructuredRenderSectionType.text, text: 'Core'},
                            coverage: {type: StructuredRenderSectionType.inlineCode, code: '94%'},
                        },
                    },
                ],
            },
            {
                type: StructuredRenderSectionType.text,
                sectionTitle: 'Next Review',
                text: 'Scheduled for Q3 2026.',
                style: StructuredRenderTextStyle.Faint,
            },
        ],
    },
];

Rendering to HTML

Web component (VirStructuredRender)

The easiest way to render structured data in a web app using element-vir.

import {html} from 'element-vir';
import {VirStructuredRender} from 'structured-render';

html`
    <${VirStructuredRender.assign({
        data,
        options: {
            useCardStyles: true,
            expandAllCards: true,
            isPhoneSize: false,
        },
    })}></${VirStructuredRender}>
`;
HTML rendering options

All options are optional and have sensible defaults.

| Option | Default | Description | | ------------------------ | ------------------------- | -------------------------------------------------------- | | useCardStyles | false | Wrap cards in collapsible card components with borders | | expandAllCards | false | Start all cards expanded | | expandFirstCard | false | Start only the first card expanded | | blockCardExpansion | false | Expand all cards and disable toggling | | isPhoneSize | false | Use phone-optimized layout (tables become stacked cards) | | useDrawerForSources | false | Render sources inside a drawer instead of collapsible | | isTabletSize | false | Use tablet-optimized layout (VirStructuredRender only) | | expandSourcesOnPrint | false | Auto-expand sources when printing | | hideViewOnPageButtons | false | Hide "view on page" buttons in sources | | currentlyExpanded | {} | Track which sections/sources are expanded | | icons | Vira's allIconsByName | Override the icon set for iconKey lookups | | sourceIcon | DocumentSearch24Icon | Icon shown for source expansion | | processingIcon | LoaderAnimated24Icon | Icon shown next to processing text | | viewOnPageIcon | EyeOpen24Icon | Icon for "view on page" buttons | | markdownStyles | Built-in styles | CSS for internal markdown rendering | | createViewOnPageString | `View on page ${n}` | Custom text for page navigation buttons |

The element emits SourceExpansionEvent when a source is expanded or collapsed. You can also provide custom icons by passing an icons map keyed by icon name.

CSS variables

VirStructuredRender exposes CSS variables for font size customization:

  • --vir-structured-render-h1-font-size (default: 24px)
  • --vir-structured-render-h2-font-size (default: 18px)
  • --vir-structured-render-h3-font-size (default: 16px)
  • --vir-structured-render-small-font-size (default: 12px)

VirExpandableSource

A standalone element for rendering content with an expandable source citation, without needing a full VirStructuredRender wrapper.

import {html} from 'element-vir';
import {VirExpandableSource, StructuredRenderSectionType} from 'structured-render';

html`
    <${VirExpandableSource.assign({
        sources: [
            {
                type: StructuredRenderSectionType.source,
                fileName: 'report.pdf',
                pageNumbers: [
                    1,
                    2,
                ],
                quote: 'Relevant excerpt from the document.',
            },
        ],
    })}>
        <span>Content that has a source citation.</span>
    </${VirExpandableSource}>
`;

renderStructuredHtml

For lower-level control, render directly to HTML templates without a web component:

import {renderStructuredHtml} from 'structured-render';

const htmlTemplate = renderStructuredHtml(data, {
    useCardStyles: true,
    expandAllCards: true,
});

Rendering to Markdown

import {renderStructuredMarkdown} from 'structured-render';

const markdown = renderStructuredMarkdown(data);
// Returns a markdown string with headings, tables, lists, etc.

Rendering to PDF

// Node.js
import {renderToNodePdf} from 'structured-render';

const outputPath = await renderToNodePdf(data, {
    saveLocationPath: '/tmp/report.pdf',
});

// Browser - download
import {renderToBrowserPdf} from 'structured-render';

await renderToBrowserPdf(data, {
    fileName: 'report',
});

// Browser - print dialog
import {printPdf} from 'structured-render';

await printPdf(data, {
    fileName: 'report',
});

Rendering to image

// Node.js
import {renderToNodeImage} from 'structured-render';

const outputPath = await renderToNodeImage(data, {
    saveLocationPath: '/tmp/report.png',
});

// Browser - download
import {renderToBrowserImage} from 'structured-render';

await renderToBrowserImage(data, {
    fileName: 'report',
});

Utilities

doesSectionHaveContent

Recursively checks if a section has any meaningful content. Useful for conditionally rendering sections.

import {doesSectionHaveContent} from 'structured-render';

if (doesSectionHaveContent(section)) {
    // render the section
}

createCleanSources

Normalizes source input (single or array, with possible nulls) into a clean array or undefined.

import {createCleanSources} from 'structured-render';

const sources = createCleanSources(rawSources);
// Returns AtLeastTuple<StructuredRenderSource, 1> | undefined

sourceHasContent

Type guard that checks whether a source has any meaningful content (quote, file name, or page numbers).

VirMarkdown

A web component that safely renders markdown as sanitized HTML using marked and DOMPurify.

import {html} from 'element-vir';
import {VirMarkdown} from 'structured-render';

html`
    <${VirMarkdown.assign({
        markdownString: '**Hello** world',
    })}></${VirMarkdown}>
`;

Flexible input

All rendering functions accept RenderInput, which is flexible about what you pass in:

type RenderInput = MaybeArray<
    StructuredRenderData | StructuredRenderCard | StructuredRenderSection | null | undefined
>;

You can pass a full StructuredRenderData array, a single card, a single section, or even an array mixing these. This makes it easy to render just a piece of structured data without wrapping it in the full hierarchy.