How Slot-Instance and Slot-Outlet Pairing Works in Instatic Visual Components

In Instatic, slot-instance and slot-outlet nodes are paired by matching the slotName property, allowing Visual Component references to automatically materialize container nodes that inject content into designated placeholder positions during rendering.

The CoreBunch/Instatic repository implements a declarative slot system where Visual Components (VCs) define injection points using base.slot-outlet nodes. When a VC is instantiated on a page via a base.visual-component-ref, the framework ensures a corresponding base.slot-instance exists for every declared outlet, maintaining synchronization through a deterministic naming contract.

The Architecture of Slot Pairing

Visual Components in Instatic use a strict separation between template definitions and content consumption. The slot-instance and slot-outlet pairing relies on a shared identifier that bridges the VC definition and its runtime usage.

Slot Outlets: The Template Side

A slot-outlet acts as a placeholder marker inside the VC tree. According to the source in src/modules/base/slotOutlet/index.ts, each outlet node declares a slotName property that identifies where content should appear.

// src/modules/base/slotOutlet/index.ts
// Module definition declaring the slotName prop
{
  moduleId: 'base.slot-outlet',
  props: {
    slotName: 'header' // Unique identifier for this injection point
  }
}

Slot Instances: The Consumer Side

When a user drops a Visual Component onto a page, the editor creates a base.visual-component-ref node. The framework automatically materializes slot-instance children under this reference, as defined in src/modules/base/slotInstance/index.ts. Each instance carries a props.slotName that must match an outlet's slotName to establish the connection.

The Synchronization Algorithm

The syncSlotInstances function in src/core/visualComponents/slotSync.ts maintains the relationship between outlets and instances. This pure algorithm computes the minimal set of tree mutations required to keep the VC reference synchronized with its definition.

Collecting Outlet Definitions

The process begins with collectSlotOutletNames, which traverses the VC tree using a DFS pre-order traversal (first appearance wins). This generates an ordered list of slot names that defines the expected child structure.

Matching and Reconciliation

The algorithm performs three operations to reconcile the existing slot-instance children with the required outlets:

  1. Insertion: Creates new base.slot-instance nodes for outlets that lack corresponding instances.
  2. Renaming: Adjusts slotName properties when instances exist but names differ, matching first by name then by position.
  3. Deletion: Removes stray slot-instance children that no longer map to declared outlets.

The result is an ordered list of child IDs that mirrors the slot-outlet declaration order, applied via applySlotSyncResult.

// src/core/visualComponents/slotSync.ts
import { syncSlotInstances, applySlotSyncResult } from '@core/visualComponents/slotSync'

const result = syncSlotInstances(vcRefNode, visualComponentDefinition, pageNodes)
applySlotSyncResult(pageNodes, result, vcRefNode.id)

Rendering and Content Injection

During the publish phase, the renderer processes the base.visual-component-ref by executing renderVisualComponentRef in src/core/publisher/renderVisualComponentRef.ts.

Transparent Substitution

Because base.slot-outlet nodes have transparent publish behavior, they emit no HTML of their own. Instead, the renderer substitutes the subtree of each matching base.slot-instance into the outlet's position. This means the published output contains only the injected content, not the placeholder wrappers.

Shared Content Across Multiple Outlets

If a Visual Component declares multiple base.slot-outlet nodes with the identical slotName, they all render the same slot-instance content. This allows a single consumer node to populate several template positions simultaneously.

Practical Implementation Example

The following workflow demonstrates the complete lifecycle from VC definition to rendered output.

1. Define a Visual Component with Slots

// Define a VC with two distinct slot-outlets
export const MyCardVC = {
  moduleId: 'my-extensions.card',
  tree: {
    rootNodeId: 'root',
    nodes: {
      root: { 
        id: 'root', 
        moduleId: 'base.container', 
        children: ['header', 'body'] 
      },
      header: { 
        id: 'header', 
        moduleId: 'base.slot-outlet', 
        props: { slotName: 'header' }, 
        children: [] 
      },
      body: { 
        id: 'body', 
        moduleId: 'base.slot-outlet', 
        props: { slotName: 'body' }, 
        children: [] 
      },
    },
  },
}

2. Synchronize on Instantiation

When the editor adds this VC to a page, it calls the synchronization routine to materialize the slot instances:

import { syncSlotInstances, applySlotSyncResult } from '@core/visualComponents/slotSync'

// vcRefNode is the new base.visual-component-ref in the page tree
const syncResult = syncSlotInstances(vcRefNode, MyCardVC, page.nodes)

// Apply the mutative recipe: adds, renames, deletes, and reorders children
applySlotSyncResult(page.nodes, syncResult, vcRefNode.id)

3. Publish with Content Injection

During rendering, the publisher walks the VC reference and injects user-created content (placed under the slot-instance nodes) into the matching outlet positions:

// In the render pipeline (src/core/publisher/renderVisualComponentRef.ts)
renderVisualComponentRef(vcRefNode, MyCardVC, renderContext)
// Walks each base.slot-instance child (e.g., Text blocks, Images)
// Injects their rendered output into every matching base.slot-outlet position

Summary

Frequently Asked Questions

What is the role of the slotName property in Instatic?

The slotName property acts as the deterministic key that links a base.slot-outlet declaration in a Visual Component template to a base.slot-instance consumer node on the page. Both sides must share the identical string value for the pairing to succeed, as implemented in the synchronization logic within src/core/visualComponents/slotSync.ts.

How does Instatic handle slot synchronization when a Visual Component is updated?

When a VC definition changes, the system re-runs syncSlotInstances against existing page references. The algorithm collects the new set of outlet names via collectSlotOutletNames, then inserts missing instances, renames mismatched ones based on position, and deletes orphaned children, ensuring the page tree remains valid without manual intervention.

Can multiple slot-outlets share the same slotName?

Yes. When a Visual Component contains multiple base.slot-outlet nodes with identical slotName values, the publisher injects the single corresponding base.slot-instance content into every matching position. This allows content authors to populate multiple template areas simultaneously from one content source.

Where is the slot pairing logic defined in the CoreBunch/Instatic repository?

The core pairing algorithm resides in src/core/visualComponents/slotSync.ts, specifically within the syncSlotInstances function. Module definitions for the nodes themselves are located at src/modules/base/slotOutlet/index.ts and src/modules/base/slotInstance/index.ts, while the rendering substitution logic is implemented in src/core/publisher/renderVisualComponentRef.ts.

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 →