# How Instatic Visual Components With Slots and Parameters Function

> Learn how Instatic Visual Components use slots and parameters for reusable UI. Understand declarative slots, locked children, and TypeBox validation for synchronized components.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-29

---

**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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts):

```typescript
// 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`](https://github.com/CoreBunch/Instatic/blob/main/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:

```typescript
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`](https://github.com/CoreBunch/Instatic/blob/main/schemas.ts) using TypeBox schemas (specifically `ParamSchema` and `VisualComponentSchema`). When a VC is instantiated, the [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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.

```typescript
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:

```typescript
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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/schemas.ts) enforce validation during instantiation ([`instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/instantiate.ts)), with values stored on VC reference props and managed through [`visualComponentsSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts). During instantiation via [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/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.