How Instatic Visual Components With Slots and Parameters Function

Instatic Visual Components (VCs) combine declared slot outlets with locked slot-instance children and TypeBox-validated parameters to create reusable UI blocks that stay synchronized with their definition through deterministic diff operations.

In the CoreBunch/Instatic codebase, Visual Components serve as the primary reusable UI building blocks within a flat page-tree architecture. These components expose slots (content insertion points) and parameters (configurable props) through a system that guarantees every VC reference on a page maintains structural parity with its definition via automated synchronization logic.

Declaring Slots via base.slot-outlet Nodes

Inside a Visual Component’s own tree, authors define insertion points using base.slot-outlet nodes. Each outlet declares a slotName property (defaulting to children) that marks where nested content should appear when the component is rendered.

The function collectSlotOutletNames traverses the VC’s flat tree to extract an ordered list of unique slot identifiers. This collection runs from lines 39 through 70 in src/core/visualComponents/slotSync.ts:

// Simplified representation of slot discovery
function collectSlotOutletNames(vcTree: BaseNode[]): string[] {
  // Walks the VC definition tree to find all base.slot-outlet nodes
  // Returns ordered unique slot names
}

These declared names act as the source of truth for what slots must exist on every instance of this Visual Component across the site.

Synchronizing Slot Instances on VC References

When a Visual Component is dropped onto a page, the system creates a base.visual-component-ref node. This reference node must maintain children that are base.slot-instance nodes (moduleId: 'base.slot-instance'), each matching a slotName from the VC definition.

The core synchronization engine lives in src/core/visualComponents/slotSync.ts. The pure function syncSlotInstances (lines 58–148) computes the minimal set of operations required to align existing slot-instance children with the VC’s current slot declarations:

  1. Name-based matching aligns existing instances by their slotName property.
  2. Positional matching handles renamed slots by comparing positions in the ordered list.
  3. Operation generation produces insert, rename, and delete commands to reconcile differences.

Unmatched instances are deleted, missing slots are inserted as locked slot-instance nodes, and renamed slots trigger update operations.

To apply these changes within the immutable state management used throughout Instatic, the system uses applySlotSyncResult (lines 52–101 in the same file), which mutates a mutable nodes map inside a Mutative recipe:

import { syncSlotInstances, applySlotSyncResult } from '@core/visualComponents/slotSync';
import type { BaseNode } from '@core/page-tree-schema';
import type { VisualComponent } from '@core/visualComponents/schemas';

function updateVcRefSlots(
  vcRefNode: BaseNode,
  vc: VisualComponent,
  nodesMap: Record<string, BaseNode>,
) {
  // 1️⃣ Compute the diff between declared slots and current children
  const result = syncSlotInstances(vcRefNode, vc, nodesMap);

  // 2️⃣ Apply the diff inside a Mutative recipe
  applySlotSyncResult(nodesMap, result, vcRefNode.id);
}

Parameter Validation and Instantiation

Parameters are defined in the VC’s schemas.ts using TypeBox schemas (specifically ParamSchema and VisualComponentSchema). When a VC is instantiated, the src/core/visualComponents/instantiate.ts module validates provided values against these schemas and stores them on the props object of the VC reference node.

The editor UI renders parameter panels by reading from and writing to the store slice defined in src/admin/pages/site/store/slices/visualComponentsSlice.ts. This slice manages VC data and triggers slot synchronization when a component’s definition changes, ensuring that parameter updates and structural changes propagate consistently.

import { instantiateVisualComponent } from '@core/visualComponents/instantiate';

// vcDef is the saved VC definition; params are user-provided values
const vcInstance = instantiateVisualComponent({
  vcDef,
  paramValues: { title: 'Hello', count: 3 },
});

Runtime Rendering in the Publisher

During publishing, the rendering engine walks the page tree and processes each base.visual-component-ref node. For each reference, the system loads the corresponding VC definition by refId, retrieves the synchronized slot-instance children, and injects them at the positions marked by base.slot-outlet nodes in the component’s internal structure.

The parameter values stored on the reference node’s props are passed as props to the component’s React implementation:

function renderVisualComponentRef(node: BaseNode, tree: NodeTree) {
  const vc = loadVcById(node.props.refId);
  // Slot children are already synced; they appear where the VC’s slot-outlets exist
  return <vc.component {...node.props.params}>
    {/* slot children injected at corresponding outlets */}
  </vc.component>;
}

This architecture guarantees idempotent synchronization: a VC reference that is already aligned with its definition produces an empty operations list, while any structural change to the VC (adding, removing, or renaming slots) automatically updates all existing page instances through the computed diff.

Summary

  • Slot Declaration: base.slot-outlet nodes in VC definitions declare named insertion points collected by collectSlotOutletNames in slotSync.ts.
  • Synchronization: syncSlotInstances computes minimal insert, rename, and delete operations to align slot-instance children with declared slots, applied via applySlotSyncResult.
  • Parameter Handling: TypeBox schemas in schemas.ts enforce validation during instantiation (instantiate.ts), with values stored on VC reference props and managed through visualComponentsSlice.ts.
  • Structural Guarantee: The system maintains locked base.slot-instance children on every base.visual-component-ref, ensuring runtime rendering matches the VC definition through deterministic diff logic.

Frequently Asked Questions

What happens when I rename a slot in a Visual Component definition?

When a slot is renamed in the VC definition, syncSlotInstances detects the change through positional matching during the next synchronization pass. It generates a rename operation for existing slot-instance children that occupy the same position in the ordered list, preserving their nested content while updating the slotName property to match the new declaration.

How are parameters validated in Instatic Visual Components?

Parameters are validated using TypeBox schemas defined in src/core/visualComponents/schemas.ts. During instantiation via src/core/visualComponents/instantiate.ts, user-provided values are checked against ParamSchema definitions. Invalid values are rejected before the VC reference node is created, ensuring type safety across the editor and runtime environments.

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

Slot-instance nodes are locked to prevent users from manually deleting or reordering them independently of the Visual Component definition. This enforcement guarantees that every VC reference maintains the exact slot structure declared in its definition, preventing structural drift and ensuring that content always maps to the correct outlet positions during rendering.

How does the publisher resolve which content goes into which slot?

The publisher loads the VC definition referenced by node.props.refId and walks its internal tree to locate base.slot-outlet nodes. It then maps the synchronized base.slot-instance children from the page tree to these outlets by matching slotName properties, injecting the nested content at the corresponding positions before rendering the final React component with its parameter props.

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 →