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 🙏

© 2025 – Pkg Stats / Ryan Hefner

schematastic

v0.0.11

Published

Template engine for dynamic HTML with data binding

Downloads

45

Readme

Schematastic Documentation

Overview

Schematastic is a template data binding system is a templating engine that uses HTML data-* attributes to bind dynamic data to an HTML structure. The system processes these attributes to inject content, bind to any HTML attribute, conditionally show/hide elements, transform elements, and handle loops. It is designed to be declarative and simple yet powerful.

Core Concepts

Data Attributes

The system recognizes the following data attributes:

  • Content Binding:
    • data-html: Sets the innerHTML of an element.
    • data-text: Sets the textContent of an element, safely escaping HTML.
  • Attribute Binding:
    • data-attr:[attribute]: The primary way to bind data to any HTML attribute (e.g., data-attr:href, data-attr:alt, data-attr:class). It also handles boolean attributes like disabled.
    • data-href / data-src: Legacy shorthands for data-attr:href and data-attr:src.
  • Structural Directives:
    • data-if: Conditionally renders an element based on the truthiness of a data value.
  • Transformation:
    • data-tagname: Dynamically changes the tag name of an element.
  • Descriptions / Prompts:
    • data-prompt: Adds a human-readable description used by the schema extractor. When present:
      • On top-level elements, sets the root schema description.
      • On nodes that bind data (data-text, data-html, data-attr:*, data-href, data-src), sets that property's description.
      • On <template> elements, sets descriptions for arrays, objects, or template fragments. The renderer strips data-prompt attributes from the output.

Data Path Resolution

Data paths use dot notation to access nested properties within the provided JSON data object:

  • "title" - Access a top-level property.
  • "user.name" - Access a nested property.
  • "items.0.title" - Access a property of an array element (though this is less common as data-each is the preferred method for handling arrays).

Content Binding

Text Content (data-text)

Sets the text content of an element. Any HTML in the data is automatically escaped, preventing XSS vulnerabilities.

<h1 data-text="pageTitle">Default Title</h1>

HTML Content (data-html)

Sets the inner HTML of an element: only use with trusted data sources to avoid security risks.

<div data-html="richContent">Default <strong>content</strong></div>

Attribute Binding (data-attr)

The data-attr:[attribute] directive is the universal way to bind data to any HTML attribute.

Standard Attributes

This works for any standard attribute like href, src, alt, title, class, id, etc.

<!-- Binds the 'buttonURL' data property to the 'href' attribute -->
<a data-attr:href="buttonURL" data-text="buttonLabel">Default Link</a>

<!-- Binds 'imageUrl' and 'imageAlt' to 'src' and 'alt' attributes -->
<img data-attr:src="imageUrl" data-attr:alt="imageAlt" />

<!-- Binds a dynamic class name -->
<div data-attr:class="cardClasses">...</div>

Boolean Attributes

The engine correctly handles boolean attributes like disabled, checked, or readonly. If the data value is truthy, the attribute is added. If it's falsy (false, null, undefined), the attribute is removed.

<!-- This button will be disabled if 'isSubmitting' is true -->
<button data-attr:disabled="isSubmitting">Submit</button>

Conditional Rendering (data-if)

Elements with data-if are only rendered if the specified data path resolves to a truthy value (i.e., not false, null, undefined, 0, or an empty string). If the condition is falsy, the element and all its children are removed from the DOM.

<!-- This div will be removed if 'optionalContent' is falsy or missing -->
<div data-if="optionalContent">
    <p data-text="optionalContent">This only shows if optionalContent exists.</p>
</div>

Template Features

Schematastic provides powerful templating capabilities through the <template> element and associated data attributes. These features enable loops, composition, context scoping, and sophisticated data binding patterns.

Core Template Directives

Loop Processing (data-each)

The data-each attribute creates loops by iterating over arrays. It must be placed on a <template> element, which gets replaced with repeated content for each array item.

Basic Loop Example:

<ul>
    <template data-each="testimonials">
        <li>
            <blockquote data-text="quote"></blockquote>
            <cite data-text="author"></cite>
        </li>
    </template>
</ul>

Data Structure:

{
    "testimonials": [
        { "quote": "It's amazing!", "author": "Jane Doe" },
        { "quote": "I love it.", "author": "John Smith" }
    ]
}

Primitive Arrays:

For arrays of strings or numbers, use empty data-text bindings:

<ul>
    <template data-each="tags">
        <li data-text></li>
    </template>
</ul>

This binds to data like { "tags": ["javascript", "web", "framework"] }.

Array Size Constraints:

Control array length requirements for schema validation:

<template data-each="features" data-min-items="2" data-max-items="5">
    <div data-text="title">Feature</div>
</template>
  • data-min-items="N": Array must have at least N items
  • data-max-items="N": Array must have at most N items
  • data-items="N": Shorthand for exact length (sets both min and max)

Object Context (data-object)

The data-object attribute scopes data binding to a nested object, simplifying path references within that scope.

Basic Object Context:

<div data-object="userProfile">
    <h2 data-text="name">Default Name</h2>
    <p data-text="email">[email protected]</p>
</div>

Data Structure:

{
    "userProfile": {
        "name": "John Doe",
        "email": "[email protected]"
    }
}

Template Composition (data-use)

The data-use attribute enables template composition by injecting reusable fragments. Available fragments are injected by the consumer application via a template map.

Fragment Definition:

export const fragments = {
  hero: `
    <section class="hero">
      <h1 data-text="title">Default Title</h1>
      <p data-html="subtitle"><em>Default subtitle</em></p>
      <a data-attr:href="cta.url" data-text="cta.label">Learn More</a>
    </section>
  `,
  stats: `
    <div class="stats">
      <template data-each="metrics">
        <div><strong data-text="label"></strong>: <span data-text="value"></span></div>
      </template>
    </div>
  `
};

Fragment Usage:

<!-- Uses current context (no data-object needed) -->
<template data-use="hero"></template>

<!-- Explicit context scoping -->
<section data-object="page">
    <template data-use="hero" data-object="hero"></template>
    <template data-use="stats" data-object="stats"></template>
</section>

API Integration:

import { renderTemplate, extractSchema } from 'schematastic';
import { fragments } from './fragments';

// Simple usage - fragments use current context
const template = `<template data-use="hero"></template>`;
const data = {
    title: "Welcome",
    subtitle: "Build fast",
    cta: { url: "/start", label: "Get Started" }
};

const html = renderTemplate(template, data, undefined, fragments);
const schema = extractSchema(template, fragments);

Context Behavior: When no data-object or data-each is specified on the usage template, fragments use the current context automatically (equivalent to data-object=".").

Advanced Template Patterns

Combined Loop and Composition

You can combine data-each and data-use on the same template element:

<template data-each="heroes" data-use="heroCard" data-object=".">
    <!-- Fragment content replaces any existing template children -->
</template>

This iterates over the heroes array and uses the heroCard fragment for each item.

Nested Templates

Templates can be nested to create complex structures:

<template data-each="categories">
    <div data-text="name">Category</div>
    <ul>
        <template data-each="items">
            <li data-text="title">Item</li>
        </template>
    </ul>
</template>

Template Context Scoping

Context scoping follows specific rules when templates are combined:

  1. Default context: Templates without data-object use the current context automatically
  2. Usage context: data-object on a data-use template sets context for injected fragments
  3. Fragment override: Fragment root elements with their own data-object override usage context
  4. Context inheritance: Child elements inherit context from their parents
  5. Identity passthrough: data-object="." explicitly passes current context unchanged

Example:

<div data-object="page">
    <!-- Fragment gets current page context by default -->
    <template data-use="footer"/>

    <!-- Fragment gets page.hero as context -->
    <template data-use="hero" data-object="hero"/>

    <!-- Fragment gets current page context (explicit) -->
    <template data-use="stats" data-object="."/>
</div>

Processing Order

Template processing follows a specific order to ensure predictable results:

  1. Fragment expansion (data-use): Inject fragment content, remove usage templates
  2. Loop processing (data-each): Unroll loops, stamp out template content
  3. Conditional rendering (data-if): Remove elements that fail conditions
  4. Object context (data-object): Apply data scoping
  5. Element transformation (data-tagname): Change element tags
  6. Attribute & content binding: Apply data-attr:*, data-text, data-html
  7. Cleanup: Remove authoring attributes like data-prompt

Error Conditions

| Condition | Error Message | |-----------|---------------| | data-use without template map | Template uses data-use but no templates map was provided | | Missing fragment | Missing template for data-use="fragmentName" | | Circular fragment references | Cyclic data-use detected: a → b → a | | Invalid data-object path | Silent removal of affected elements |

Best Practices

Template Organization

  • Keep fragments focused: Create small, single-purpose fragments
  • Use meaningful names: Fragment names should clearly indicate their purpose
  • Document fragment contracts: Use data-prompt to describe expected data structure

Context Management

  • Leverage implicit context: Omit data-object when fragments should use current context
  • Minimize context switching: Avoid excessive data-object nesting
  • Use identity context wisely: data-object="." is explicit but often unnecessary
  • Test context boundaries: Verify data flows correctly across template boundaries

Performance Considerations

  • Limit nesting depth: Deep template nesting can impact performance
  • Avoid circular references: The engine will catch these but prevention is better
  • Cache fragment maps: Reuse template maps across multiple renders when possible

Schema Design

  • Add meaningful constraints: Use data-min-items, data-max-items for better validation
  • Document with prompts: Use data-prompt on templates to describe array/object purposes
  • Test primitive arrays: Ensure empty data-text bindings work correctly for string arrays

Advanced Features

Nesting Directives

Directives can be nested to handle complex data structures. See the Template Features section for comprehensive coverage of nested templates, context scoping, and advanced patterns.

Element Transformation (data-tagname)

Dynamically change an element's tag name. This is useful for rendering content blocks with varying heading levels (h2, h3, p, etc.).

<h2 data-tagname="elementType" data-html="title">Default Title</h2>

Data Structure:

{
    "elementType": "h3",
    "title": "This is now an H3"
}

Enum Syntax for Schema

To enforce specific schema requirements for a data node, you can use an enum syntax in an attribute's value. The schema extractor will parse this and create a JSON schema with an enum constraint.

Syntax: key=value1|value2|value3

<p>Status: <span data-text="status=live|beta|deprecated"></span></p>

Generated Schema Property:

"status": {
    "type": "string",
    "enum": ["live", "beta", "deprecated"],
    "description": "Select one of: live, beta, deprecated"
}

The rendering engine correctly uses status as the key to find the value in the data.

Descriptions for Schema and Authoring (data-prompt)

Use data-prompt to attach meaningful, human-readable descriptions to your templates. These are collected into the generated JSON Schema and are stripped from the final rendered HTML.

At a glance:

  • Top-level elements with data-prompt set the root schema description.
  • Base data-prompt="..." on a node applies only to:
    • Content bindings: data-text, data-html
    • Array templates: <template data-each="...">
    • Object scopes: data-object="..."
    • Top-level nodes (root schema)
  • Attribute bindings do not use the base data-prompt. Instead, use namespaced prompts: data-prompt:[attr].

Notes:

  • Authoring-only: data-prompt and data-prompt:* are removed from the rendered output.

  • Tag transformation:

    • data-tagname uses data-prompt:tagname

Start simple: root description

Add a human-readable summary for the entire data model:

<section data-prompt="Page description">
  <h1 data-text="title">Default Title</h1>
</section>

This sets the root schema description to "Page description".

Content bindings: data-text and data-html

Use base data-prompt to describe the content property:

<h1 data-text="title" data-prompt="The page title">Default</h1>
<div data-html="subtitle" data-prompt="Rich subtitle">Default</div>
  • title gets description "The page title".
  • subtitle gets description "Rich subtitle".

Arrays: data-each

Use base data-prompt on the <template data-each="..."> to describe the array:

<template data-each="items" data-prompt="List of user items">
  <div data-text="name">Name</div>
</template>
  • items array gets description "List of user items".
  • The prompt on the array does not apply to nested attribute bindings inside the template.

Objects: data-object

Use base data-prompt on an element with data-object to describe the object:

<div data-object="user" data-prompt="User information">
  <span data-text="name">Name</span>
</div>
  • user object gets description "User information".

Attribute bindings: use data-prompt:[attr]

Do not use base data-prompt for attribute-bound properties. Instead, scope the prompt per attribute:

<a
  data-attr:href="url"
  data-prompt:href="Link URL"
  data-text="label"
  data-prompt="Link label"
>Default</a>

Results:

  • label uses "Link label" (base prompt applies to content).
  • url uses "Link URL" (scoped via data-prompt:href).
  • The base data-prompt is not applied to url.

Template fragments: data-use + data-prompt

When you place data-prompt on a <template data-use="...">, the prompt must land on a concrete array/object container so it can be included in the schema.

  • Usage with data-each (arrays): the usage prompt describes the array.
<template data-use="_content_items" data-prompt="List of items with labels"></template>
  • Usage with data-object (objects): the usage prompt describes that object (data-object must not be "." or empty).
<!-- external fragment file contents for _content_list.html -->
<div>
    <h2 data-text="title">Default Title</h2>
    <p data-text="para">The text</p>
</div>

<!-- usage -->
<template
  data-use="_content_list"
  data-object="content"
  data-prompt="Content curation prompt goes here"
></template>
  • Note: Template parsing errors to guide in development and avoid silent failures are thrown when there is no resolvable container for the prompt to attach as a schema descriptor.

Tag transformation: data-tagname

Describe the tag selection using data-prompt:tagname:

<h2 data-tagname="headingTag" data-prompt:tagname="Heading element" data-text="title">Default</h2>
  • headingTag gets "Heading element".
  • title can have its own base data-prompt if desired.

Render behavior: prompts are stripped

data-prompt and data-prompt:* are not rendered:

<!-- Template -->
<div data-prompt="Wrapper">
  <span data-text="title" data-prompt="Title text">x</span>
  <a data-attr:href="url" data-prompt:href="Link URL">y</a>
</div>

<!-- Rendered Output -->
<div>
  <span>Hello</span>
  <a href="/start">y</a>
</div>

Best Practices

Testing with data-testid

To facilitate robust, automated testing of your rendered templates, add data-testid attributes to key elements. These attributes are ignored by the rendering engine but can be targeted by testing libraries to assert that the correct content has been rendered.

<h1 data-text="pageTitle" data-testid="page-title">Default Title</h1>

Meaningful Defaults

Include meaningful default content and attributes in your HTML templates. This helps during development, provides a fallback if data is missing, and makes the raw template more readable.