# How Instatic Handles Visual Components and Slots: Outlet and Instance Synchronization

> Discover how Instatic synchronizes visual components and slots using base.slot-outlet and base.slot-instance nodes. Learn about the syncSlotInstances function for seamless content management. Optimize your Instatic development ...

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

---

**Instatic implements Visual Components and slots through a bidirectional synchronization system where `base.slot-outlet` nodes define editable regions in a component definition and `base.slot-instance` nodes hold the actual content, kept in perfect alignment by the `syncSlotInstances` pure function.**

In the CoreBunch/Instatic open-source repository, Visual Components and slots form the foundation of reusable UI architecture. A **Visual Component (VC)** is essentially a flat node tree (`NodeTree<VCNode>`) that exposes specific editable "holes" called slots, allowing page authors to inject custom content without modifying the component's core structure. This system relies on two distinct module types that work in tandem to maintain a strict one-to-one relationship between slot definitions and their implementations.

## The Two-Sided Slot Architecture

The slot mechanism is built on two complementary module types that separate the declaration of editable areas from the content that fills them.

**`base.slot-outlet`** acts as the declaration phase. Inside a VC’s definition tree, these nodes mark positions where content will eventually render. The `slotName` prop on a `slot-outlet` becomes the unique identifier for that slot, establishing the contract that page authors must fulfill.

**`base.slot-instance`** represents the consumption phase. When a VC is dropped onto a page, Instatic automatically materializes these nodes under the *visual-component-ref* node in the page tree. Each instance holds user-authored children that fill the corresponding outlet. According to the source code in [`src/modules/base/slotInstance/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotInstance/index.ts), these nodes are **locked** (cannot be moved or deleted) and must carry a `slotName` property that matches the outlet’s name exactly.

## Synchronizing Slots with syncSlotInstances

The core synchronization logic lives in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts) within the pure function **`syncSlotInstances`**. This function guarantees that every `slot-outlet` declared in a VC definition has exactly one corresponding `slot-instance` child in the page tree, preserving the order defined by the component author.

The algorithm executes six distinct steps to reconcile the page tree with the VC definition:

1. **Collect outlet names** – `collectSlotOutletNames` walks the VC tree and gathers the names of all `slot-outlet` nodes, deduplicating by name to establish the target state.
2. **Classify existing children** – The ref node’s children are inspected; only nodes with `moduleId === 'base.slot-instance'` are kept as candidates for retention, while everything else is marked for deletion.
3. **Name-based matching** – Existing slot-instances whose `slotName` already matches a target outlet name are retained immediately, covering cases where slots have been reordered in the definition.
4. **Positional matching** – Remaining unmatched instances are paired with the remaining outlet names by their current position, generating *rename* operations for any slots that have been renamed rather than added or removed.
5. **Delete and insert** – Unmatched instances are deleted, and any still-unmatched outlet names result in *insert* operations. These create new `base.slot-instance` nodes (with generated NanoIDs) that are locked and pre-filled with the correct `slotName`.
6. **Build ordered child list** – The final order of children matches the order of outlet appearance in the VC tree, ensuring visual consistency.

The function returns a `SyncResult` containing operations, new nodes, and ordered child IDs. The companion function **`applySlotSyncResult`** applies these mutations to the mutable page-tree map and updates parent references accordingly.

## Rendering and Publishing Behavior

During the publishing phase, the VC ref renderer in [`src/core/publisher/renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderVisualComponentRef.ts) extracts the children of each `base.slot-instance` and injects them into the matching `slot-outlet` positions of a synthetic VC page built from the component definition.

Notably, the `base.slot-instance` itself never renders any markup. Its `publishBehavior` is explicitly set to `'transparent'` in the module definition, meaning only the outlet’s rendered output appears in the final HTML. This architectural choice ensures that the slot mechanism leaves no trace in the published output while maintaining strict editorial boundaries during the editing phase.

## Practical Implementation Example

When defining a Visual Component, authors declare slots using the outlet module in the VC’s node tree:

```typescript
// Defining a VC with two slots in its definition tree
import { makeNode } from '@core/utils/testing'

export const myComponent = {
  id: 'my-vc',
  name: 'MyComponent',
  tree: {
    rootNodeId: 'root',
    nodes: {
      root: makeNode('root', 'base.container', ['slotA', 'slotB']),
      // Slot outlets mark editable "holes"
      slotA: { 
        id: 'slotA', 
        moduleId: 'base.slot-outlet', 
        props: { slotName: 'header' }, 
        children: [] 
      },
      slotB: { 
        id: 'slotB', 
        moduleId: 'base.slot-outlet', 
        props: { slotName: 'footer' }, 
        children: [] 
      },
    },
  },
  params: [],
  classIds: [],
  createdAt: Date.now(),
}

```

When this component is instantiated on a page, `syncSlotInstances` automatically generates the corresponding slot instances:

```typescript
// Page tree structure after synchronization
{
  // The reference node
  vcRefNode: {
    id: 'ref-1',
    moduleId: 'base.visual-component-ref',
    props: { componentId: 'my-vc' },
    children: ['inst-header', 'inst-footer'], // Ordered by outlet position
  },
  // Locked slot instances created automatically
  'inst-header': {
    id: 'inst-header',
    moduleId: 'base.slot-instance',
    props: { slotName: 'header' },
    children: [], // Authors add content here
    locked: true,
  },
  'inst-footer': {
    id: 'inst-footer',
    moduleId: 'base.slot-instance',
    props: { slotName: 'footer' },
    children: [],
    locked: true,
  },
}

```

Finally, the publisher renders the component by merging slot content into the outlet positions:

```typescript
import { renderVisualComponentRef } from '@core/publisher'

const html = renderVisualComponentRef(vcRefNode, config, acc, renderNode)
// Output contains the VC markup with slot-instance children 
// rendered at the corresponding base.slot-outlet positions

```

## Summary

- **Visual Components** in Instatic are defined as flat node trees that use `base.slot-outlet` nodes to declare editable regions identified by a `slotName` property.
- **`base.slot-instance`** nodes are automatically generated and locked children of a VC reference that hold the actual user-authored content for each slot.
- The **`syncSlotInstances`** function in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts) maintains a one-to-one, ordered relationship between outlets and instances through a six-step reconciliation algorithm.
- Slot instances are **transparent** during publishing (`publishBehavior: 'transparent'`), meaning only the content they carry renders in the final output, not the instance wrapper itself.

## Frequently Asked Questions

### What is the difference between base.slot-outlet and base.slot-instance?

The `base.slot-outlet` module is declared inside a Visual Component’s definition tree and marks where content should render, acting as the contract or interface. The `base.slot-instance` module is created automatically in the page tree when a VC is used, acting as the implementation that holds the actual user-authored content. Outlets define where content goes; instances hold what content is actually there.

### How does Instatic handle slot reordering when a Visual Component definition changes?

When a VC definition is updated, `syncSlotInstances` reorders the page tree children to match the new outlet order. First, it matches existing instances to outlets by `slotName` regardless of position. Then, it rebuilds the child list to match the sequence in which outlets appear in the VC tree, ensuring the visual hierarchy remains consistent with the component author's intent.

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

Slot-instance nodes carry the `locked: true` property to prevent page authors from accidentally deleting or moving the structural containers that connect their content to the VC’s outlet positions. This lock ensures the integrity of the slot synchronization system, as these nodes are managed entirely by the `syncSlotInstances` algorithm rather than manual user manipulation.

### What happens to slot content during the publishing phase?

During rendering, the `renderVisualComponentRef` function in [`src/core/publisher/renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderVisualComponentRef.ts) extracts the children from each `base.slot-instance` and injects them into the corresponding `base.slot-outlet` positions. Because slot instances have `publishBehavior: 'transparent'`, they output no markup themselves—only the content they wrap appears in the final HTML, creating a clean semantic output.