# What Modules Are Available in Instatic and How Do They Render

> Explore 15 Instatic modules that convert JSON page trees to static HTML via a central registry. Each module uses a render function to return HTML and optional CSS.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: how-to-guide
- Published: 2026-07-03

---

**Instatic ships with 15 base modules that transform JSON page trees into static HTML through a centralized registry where each module exposes a `render` function returning `{ html: string, css?: string }`.**

Instatic is a static site generator maintained by CoreBunch that converts authored content into optimized HTML. Understanding what modules are available in Instatic and how they render is essential for developers customizing the publishing pipeline or debugging the static output.

## Layout Modules

Layout modules establish the document structure and content regions.

### base.body

The `base.body` module in [`src/modules/base/body/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/body/index.ts) returns a `<main>` element (or custom tag) that serves as the root content container. According to the source code, it emits `<main data-instatic-content-region></main>` and acts as the anchor point where the publisher injects the page tree.

### base.container

Defined in [`src/modules/base/container/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/container/index.ts), this module emits semantic container elements such as `<section>` or `<article>` based on the `tag` prop. Children are rendered recursively inside the container’s HTML, making it the primary grouping primitive.

### base.outlet

Located at [`src/modules/base/outlet/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/outlet/index.ts), the outlet module renders `<${tag}${attrs}>${props.html}</${tag}>` where the tag may be customized. This represents the main content entry point, and the publisher writes the final page body into this node during the rendering phase.

## Typography Modules

Typography modules handle text content and list structures.

### base.text

In [`src/modules/base/text/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/text/index.ts), the text module returns `{ html: `<${tag}${attrs}>${text}</${tag}>` }` where `text` is escaped via `textToBreakHtml`. The publisher does not modify the output beyond standard sanitization, making this the most direct HTML emission path.

### base.list

The `base.list` module at [`src/modules/base/list/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/list/index.ts) renders `<ul>` or `<ol>` elements depending on the `ordered` prop. It maps each item via `renderListItem()`, applying the same HTML-escaping pipeline used by other modules.

## Media Modules

Media modules handle images, vector graphics, and embedded video.

### base.image

Found in [`src/modules/base/image/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/image/index.ts), this module constructs an `<img>` tag with responsive `srcset` and `sizes` attributes. When no image URL is present, it falls back to an SVG placeholder. Image URLs are pre-escaped by `escapeProps` before markup generation.

### base.svg

The [`src/modules/base/svg/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/svg/index.ts) implementation returns raw SVG markup wrapped in a `<svg>` element with optional `viewBox` handling. The publisher passes this through the sanitizer unchanged for inline vector graphics.

### base.video

Located at [`src/modules/base/video/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/video/index.ts), this module delegates to `renderYoutube()` which emits an `<iframe>` with lazy-loading attributes and CSP `frame-src` declarations. The publisher extracts CSP requirements from the render output at approximately line 184 of the render pipeline.

## Interactive Modules

Interactive modules manage user engagement elements and data-driven repetition.

### base.button

In [`src/modules/base/button/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/button/index.ts), the module emits either a `<button>` or an `<a>` element (if `href` is set) with appropriate ARIA attributes. The `htmlAttributesAttr` utility adds data-attributes for client-side behaviors.

### base.link

The `base.link` module at [`src/modules/base/link/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/link/index.ts) uses the `linkUsesChildren()` helper from [`link/content.ts`](https://github.com/CoreBunch/Instatic/blob/main/link/content.ts) to determine whether to render child nodes or the `text` prop:

```ts
const content = linkUsesChildren(renderedChildren.length) ? renderedChildren.join('') : props.text
return { html: `<a${attrs}>${content}</a>` }

```

### base.loop

Defined in [`src/modules/base/loop/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/loop/index.ts), this module returns a no-op comment `{/* fallback comment */}` because the publisher intercepts `base.loop` nodes via `renderLoop()` in [`src/core/publisher/renderLoop.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderLoop.ts). This allows repeating a subtree over a data source and injecting pagination or "load more" UI when needed.

## Form Modules

### base.forms

The forms module at [`src/modules/base/forms/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/index.ts) bundles multiple sub-renderers (`input`, `select`, `textarea`) that each return HTML fragments. The main `form` renderer stitches these into a single `<form>` element with a hidden honeypot field for spam protection. Forms are fully static; dynamic validation is performed client-side by the editor only.

## Component System Modules

These modules enable visual component composition and slot-based architecture.

### base.visualComponentRef

In [`src/modules/base/visualComponentRef/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/visualComponentRef/index.ts), this module calls `renderVisualComponentRef()` from [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts). It resolves the referenced component and renders its generated HTML, serving as the bridge between the page-tree’s vc-ref nodes and actual component implementations.

### base.slotInstance

Located at [`src/modules/base/slotInstance/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotInstance/index.ts), this module returns an empty `{ html: '', css: '' }` object because slots are rendered by the vc-ref renderer. Children are rendered at the matching `slotOutlet` location.

### base.slotOutlet

The `base.slotOutlet` module in [`src/modules/base/slotOutlet/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotOutlet/index.ts) also returns an empty node. The outlet acts as a visual placeholder in the editor only, while the real output is emitted by the `visualComponentRef` renderer that stitches slot fills together.

## How the Rendering Pipeline Works

### Module Registration and Contracts

Each module registers a `ModuleDefinition` with the global module-engine registry at [`src/core/module-engine/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/module-engine/index.ts). The definition must expose a `render` function that receives validated props (via TypeBox) and returns an object with the shape `{ html: string, css?: string }`.

### Publisher Execution Flow

The publisher in [`src/core/publisher/render.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/render.ts) walks the page tree and invokes each module’s `render` method, concatenating HTML fragments. For special modules, the publisher replaces the default implementation with custom interceptors:

- **base.loop**: Uses `renderLoop()` for data-driven repetition
- **base.visualComponentRef**: Uses `renderVisualComponentRef()` for component resolution
- **base.slotInstance** and **base.slotOutlet**: Handled by the vc-ref renderer

## Practical Rendering Examples

### Registering and Rendering a Text Module

```ts
import { registry } from '@core/module-engine';
import { TextModule } from '@modules/base/text';
import { renderNode } from '@core/publisher/render';

// The module registers itself on import.
registry.registerOrReplace(TextModule);

// Example node that a page-tree might contain:
const textNode = {
  id: 'n1',
  type: 'base.text',
  props: {
    text: 'Hello, world!',
    tag: 'p',
    htmlAttributes: {}
  }
};

// Render the node – publisher will call TextModule.render internally.
const out = renderNode(textNode);
console.log(out.html);   // → <p>Hello, world!</p>

```

### Rendering a Loop with Interceptor

```ts
import { renderLoop } from '@core/publisher/renderLoop';

// The loop node contains a `source` reference and child template.
const loopNode = { id: 'l1', type: 'base.loop', props: { mode: 'none' }, children: [...] };

const html = renderLoop(loopNode); // Returns the repeated HTML or the fallback comment.

```

### Rendering Visual Component References

```ts
import { renderVisualComponentRef } from '@core/visualComponents/instantiate';

const vcRefNode = {
  id: 'vc1',
  type: 'base.visualComponentRef',
  props: { componentId: 'my-component', ... }
};

const { html } = renderVisualComponentRef(vcRefNode);
// The returned HTML is the fully rendered component, including any slot fills.

```

## Summary

- **Instatic provides 15 base modules** covering layout, typography, media, interactive elements, forms, and component composition.
- **Each module implements a `render` function** returning `{ html, css? }` and registers with the global module-engine at [`src/core/module-engine/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/module-engine/index.ts).
- **Special modules** like `base.loop`, `base.visualComponentRef`, and slot-related modules are intercepted by the publisher and rendered via dedicated functions in [`src/core/publisher/render.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/render.ts) and [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts).
- **The publisher walks the page tree** and concatenates HTML fragments, with slot composition handled by the vc-ref renderer rather than the modules themselves.
- **Forms and links** include security features like honeypot fields and CSP-aware attribute handling.

## Frequently Asked Questions

### What is the return type of an Instatic module render function?

Every module’s `render` function must return an object with the shape `{ html: string, css?: string }`. This contract is enforced by the module registry in [`src/core/module-engine/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/module-engine/index.ts) and consumed by the publisher pipeline to concatenate static output.

### How does Instatic handle repeating content?

The `base.loop` module acts as a marker in the page tree but returns only a fallback comment. The publisher intercepts loop nodes via `renderLoop()` in [`src/core/publisher/renderLoop.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderLoop.ts), which handles the actual repetition over data sources and can inject pagination controls.

### Where does slot content actually get rendered?

Slot content is rendered by the `visualComponentRef` renderer, not by `base.slotInstance` or `base.slotOutlet` modules themselves. These modules return empty HTML strings; the vc-ref renderer in [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts) stitches slot fills together at the correct outlet locations.

### Can I manually render a module for testing?

Yes. Import the module and the `renderNode` function from `@core/publisher/render`, ensure the module is registered with the global registry, then call `renderNode(yourNode)` to receive the HTML output. This pattern is used for unit testing individual modules outside the full publishing pipeline.