How Instatic Visual Components Work: A Complete Guide to Typed Parameters and Named Slots

Instatic Visual Components combine strict TypeBox schemas for parameters with a synchronization algorithm that automatically manages slot-outlet pairs, enabling reusable UI blocks where content is injected into placeholder positions defined by the component author.

Instatic is an open-source static site builder that treats UI components as first-class entities within the page tree. Understanding how Instatic Visual Components handle typed parameters and content slots is essential for building reusable, maintainable templates that stay synchronized between the editor and the published output.

Typed Parameters in Visual Components

Every Visual Component (VC) declares its configurable interface through a structured parameter schema. These definitions live in the site.visualComponents array and enforce type safety at both edit-time and publish-time.

Parameter Schema Definition

Parameters are defined using the VCParam schema located in [src/core/visualComponents/schemas.ts](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts). Each parameter requires a stable id, human-readable name, and a type from the VCParamTypeSchema enumeration.

The supported types include:

  • string, number, boolean – Primitive values
  • url, color, image – Specialized string formats
  • enum – Constrained selection with enumOptions array
  • richText – Formatted content blocks
  • slot – Named content insertion points

A complete parameter definition also includes optional description, a defaultValue, a boolean required flag, and conditional fields like enumOptions for enumerated types. The runtime validates incoming values against this schema using TypeBox before the component logic executes.

Validation and Fallback Behavior

The parsing logic in src/core/visualComponents/schemas.ts implements defensive defaults. Unknown parameter types gracefully fall back to string, missing fields receive sensible empty values, and malformed entries are filtered out without corrupting the entire component definition. This tolerance ensures that version mismatches between saved content and code updates don't break existing pages.

The Slot Mechanism: Outlets and Instances

Slots enable Visual Components to expose placeholder positions where page authors can inject custom content. The architecture separates the declaration (where content should go) from the materialization (what content actually goes there).

Declaring Slot Outlets in VC Definitions

Inside a Visual Component's NodeTree<VCNode>, authors place slot outlet nodes using the module ID base.slot-outlet. Defined in [src/modules/base/slotOutlet/index.ts](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotOutlet/index.ts), these nodes act as markers with a slotName property (defaulting to "children") that identifies the specific insertion point.

The slot outlet module's render method returns empty HTML; it serves purely as a coordination marker for the publisher. During static site generation, the walker detects these outlets and substitutes them with content from the corresponding slot instance.

Materializing Slots with Slot Instances

When a page author references a Visual Component via base.visualComponentRef, the system automatically creates locked child nodes of type base.slot-instance for every slot parameter declared in the VC's params array. This materialization logic resides in [src/modules/base/slotInstance/index.ts](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotInstance/index.ts).

Key characteristics of slot instances:

  • Locked state: Users cannot delete or reorder the slot-instance node itself, preventing accidental destruction of the slot structure
  • Editable children: The children inside a slot instance (the actual content nodes) remain fully editable
  • Transparent publishing: The slot instance module sets publishBehavior to "transparent" and returns empty HTML, ensuring only the content children render

The Slot Synchronization Algorithm

The syncSlotInstances function in [src/core/visualComponents/slotSync.ts](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts) maintains consistency between VC definitions and their page references. The algorithm performs the following steps:

  1. Walks the VC's node tree to collect all unique slotName values from base.slot-outlet nodes
  2. Compares the discovered slots against existing base.slot-instance children in the page reference
  3. Generates operations to insert missing slots, rename changed slots, or delete orphaned instances

This synchronization guarantees that adding, removing, or renaming a slot in the Visual Component definition automatically updates every existing reference across the site without manual user intervention.

Publishing and Render Behavior

During static site rendering, the publisher's walker encounters a base.slot-outlet node and performs a lookup in [src/core/publisher/renderVisualComponentRef.ts](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderVisualComponentRef.ts). It finds the matching base.slot-instance child by slotName and substitutes the outlet position with the instance's children.

If a slot contains no content and lacks a defaultValue, the outlet emits empty HTML as a safety net, preventing rendering errors in the final output.

Visual Component References on Pages

Pages embed Visual Components using the base.visualComponentRef module. This reference node stores the target visualComponentId and, through the slot-sync routine, automatically gains the appropriate locked slot-instance children.

The ref's renderer extracts the VC's complete node tree, merges the consumer's slot content at the outlet positions, and produces the final HTML/CSS. Site selectors in [src/core/page-tree/siteSelectors.ts](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/siteSelectors.ts) handle the resolution of VC definitions from the site-wide registry.

Complete Workflow Example

The following examples demonstrate the full lifecycle from VC definition to published output.

1. Visual Component Definition

// src/core/visualComponents/schemas.ts (conceptual usage)
export const CardVC = {
  id: 'vc-card',
  name: 'Card',
  tree: {
    rootNodeId: 'root',
    nodes: {
      root: { 
        moduleId: 'base.container', 
        children: ['header', 'content'] 
      },
      header: { 
        moduleId: 'base.text', 
        props: { text: 'Card Header' } 
      },
      content: { 
        moduleId: 'base.slot-outlet', 
        props: { slotName: 'body' } 
      },
    },
  },
  params: [
    { 
      id: 'p1', 
      name: 'title', 
      type: 'string', 
      defaultValue: 'Untitled' 
    },
    { 
      id: 'p2', 
      name: 'body', 
      type: 'slot' 
    },
  ],
  classIds: [],
  createdAt: Date.now(),
}

2. Page Reference with Auto-Generated Slot Instance

// Page node tree structure
{
  id: 'ref-123',
  moduleId: 'base.visualComponentRef',
  props: { visualComponentId: 'vc-card' },
  children: [
    {
      id: 'slot-inst-body',
      moduleId: 'base.slot-instance',
      props: { slotName: 'body' },
      // This node is locked; children below are editable
      children: [
        {
          id: 'text-1',
          moduleId: 'base.text',
          props: { text: 'Hello from the slot!' }
        }
      ]
    }
  ]
}

3. Publisher Resolution

When the publisher processes the base.slot-outlet with slotName: 'body', it retrieves the children from the matching base.slot-instance and renders them at that position in the output HTML.

Summary

Instatic's Visual Component system provides a robust, type-safe approach to reusable UI construction:

  • Schema-driven parameters using VCParam definitions enforce type safety across string, number, boolean, enum, color, image, richText, and slot types
  • Automatic slot synchronization via syncSlotInstances ensures page references stay aligned with VC definitions
  • Separation of concerns between base.slot-outlet (declaration) and base.slot-instance (materialization) enables safe content injection
  • Locked tree structures prevent users from accidentally breaking component contracts while preserving content editing flexibility

Frequently Asked Questions

What happens when a new slot parameter is added to an existing Visual Component?

When you add a new type: 'slot' entry to a VC's params array, the syncSlotInstances algorithm detects the change during the next editor session or site build. It automatically inserts a new locked base.slot-instance child node into every existing base.visualComponentRef on the site. The slot starts empty (or uses the defaultValue if provided) and awaits content from page authors.

How does Instatic handle invalid parameter values in Visual Components?

The runtime validates all incoming props against the VCParam schema before component execution. If a parameter contains an unknown type, the system falls back to string. Missing fields receive sensible defaults defined in the schema, and completely malformed entries are dropped silently. This defensive parsing prevents individual parameter errors from crashing the entire component or page.

Can a single Visual Component expose multiple named slots?

Yes. You can declare multiple parameters with type: 'slot' and distinct names like "header", "sidebar", and "footer". Inside the VC's NodeTree, you place corresponding base.slot-outlet nodes with matching slotName properties. The slot-sync routine creates a separate base.slot-instance child for each slot parameter, allowing page authors to populate each area with independent content.

Why are slot-instance nodes locked in the page tree?

base.slot-instance nodes are locked to preserve the structural contract between the Visual Component definition and its usage. This prevents authors from accidentally deleting the slot container or reordering it outside the component's expected hierarchy. However, the children inside the slot instance—where the actual content lives—remain fully editable, providing the necessary flexibility while maintaining architectural integrity.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →