# How Visual Components Handle Typed Parameters and Slot Synchronization in Instatic

> Learn how Instatic Visual Components manage typed parameters with TypeBox schemas and ensure slot synchronization for seamless updates. Discover efficient component management.

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

---

**Visual Components in Instatic use a TypeBox schema for typed parameters and a dedicated slot synchronization module to keep page instances in sync with component definition changes.**

Visual Components (VCs) are the reusable building blocks of Instatic's visual editing system. They expose a strongly-typed parameter interface through a declarative schema, while slot-based composition allows nested content. Understanding how typed parameters and slot synchronization work together is essential for anyone extending or debugging the Instatic editor.

## Typed Parameters Through TypeBox Schemas

Every Visual Component is defined by a **TypeBox schema** located in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts). This schema establishes the contract between component authors, the editor UI, and the runtime engine.

### The VCParam Schema

Each parameter in a VC's `params` array conforms to the **VCParam** schema. The supported types include:

- `string` – plain text input
- `number` – numeric values with optional min/max constraints
- `boolean` – toggle switches
- `url` – validated external links
- `enum` – dropdown selections with predefined options
- `color` – color picker values
- `image` – asset references
- `richText` – formatted content blocks
- `slot` – nested content areas

During parsing, the `parseVisualComponent` function validates raw data against this schema. Unknown types gracefully fall back to `string`, while helper logic normalizes default values, enum options, and required flags. The output is a **typed `VCParam[]`** that both the editor and runtime can rely on without additional runtime checks.

```typescript
import { VisualComponentSchema } from '@core/visualComponents/schemas'

const myButtonVC = {
  id: 'btn-01',
  name: 'Button',
  tree: { /* VC node tree */ },
  params: [
    { id: 'p1', name: 'label', type: 'string', defaultValue: 'Click me', required: true },
    { id: 'p2', name: 'href',  type: 'url',    defaultValue: '', required: false },
    { id: 'p3', name: 'icon',  type: 'slot',   defaultValue: null, required: false },
  ],
  classIds: [],
  createdAt: Date.now(),
}

```

## How Slots Are Represented in the VC Tree

Slot-type parameters have a special representation. Unlike other parameters, **slots do not appear as entries in the `params` array with storage semantics**. Instead, each slot is represented by a **`base.slot-outlet` node** embedded directly in the VC's own node tree.

The `props.slotName` property on each `base.slot-outlet` node serves as the canonical identifier for that slot. This design separates slot *definitions* (what slots exist) from slot *instances* (what content occupies them on a specific page).

## Slot Synchronization When Definitions Change

When a Visual Component definition changes—adding a new slot, renaming an existing one, or removing a slot—the editor must reconcile all existing VC references on pages. This is handled by the **slot synchronization module** in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts).

### Extracting Slot Names from the VC Definition

The `collectSlotOutletNames` function walks the VC's flat node map and extracts all unique slot names from `base.slot-outlet` nodes. This produces the authoritative set of slots that should exist for any reference to this VC.

### Computing Required Synchronization Operations

The `syncSlotInstances` function performs a **pure comparison** between the VC's slot definitions and the existing `base.slot-instance` children of a VC reference. It returns a `SyncResult` describing the minimal operations needed to bring the page tree into alignment:

| Operation | Trigger | Implementation |
|-----------|---------|---------------|
| **Insert** | New slot in VC not present in VC reference | Create locked `base.slot-instance` node with matching `slotName` |
| **Rename** | Slot name changed in VC definition | Update `props.slotName` on existing `base.slot-instance` |
| **Delete** | Slot removed from VC definition | Remove orphaned `base.slot-instance` nodes including all subtrees |

### Applying Synchronization Results

The `applySlotSyncResult` function executes these operations within a **Mutative recipe**. It mutates the page tree draft by adding new nodes, applying rename operations, deleting orphans, and finally re-ordering the VC reference's `children` array to match the VC's slot definition order.

```typescript
import { syncSlotInstances, applySlotSyncResult } from '@core/visualComponents/slotSync'
import { mutateActiveTree } from '@admin/pages/site/store/slices/site/helpers'

// vcRefNode = node representing the VC reference on the page
// vc = the VisualComponent definition (as parsed above)
// treeNodes = mutable draft of the page's node map

mutateActiveTree((draft) => {
  const result = syncSlotInstances(vcRefNode, vc, draft)
  applySlotSyncResult(draft, result, vcRefNode.id)
})

```

## Resolving Parameter Origins

The **origin lookup system** in [`src/core/visualComponents/origin.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/origin.ts) connects a VC parameter back to the specific node that provides its binding. This enables the editor to highlight source nodes and validate bindings.

For **non-slot parameters**, `findParamOrigin` scans `node.propBindings` for a matching `paramId`. This reveals which node property is bound to the parameter.

For **slot parameters**, the system locates the `base.slot-outlet` node whose `props.slotName` matches the parameter name. This bridges the semantic parameter name to the structural node in the VC tree.

```typescript
import { findParamOrigin } from '@core/visualComponents/origin'

const origin = findParamOrigin(vc, 'p1')
if (origin) {
  console.log('Parameter lives on node', origin.nodeId, 'prop key', origin.propKey)
}

```

## Integration with the Editor Store

Slot synchronization is triggered automatically when VC definitions change. The [`visualComponentsSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/visualComponentsSlice.ts) file integrates this into the Redux store, calling `syncSlotInstances` whenever parameters are modified and ensuring all page instances stay consistent.

## Summary

- **TypeBox schemas** in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) enforce typed parameters with graceful fallbacks for unknown types
- **Slot definitions** exist as `base.slot-outlet` nodes in the VC tree rather than parameter entries
- **Slot synchronization** ([`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts)) uses pure `SyncResult` operations for insert, rename, and delete actions
- **Origin resolution** ([`src/core/visualComponents/origin.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/origin.ts) distinguishes binding lookup for slot vs. non-slot parameters
- Mutative recipes ensure all tree mutations remain traceable and undoable

## Frequently Asked Questions

### What happens when a Visual Component adds a new slot parameter?

The editor detects the change in the VC definition and triggers `syncSlotInstances`. For each VC reference on the page, it creates a new locked `base.slot-instance` node with the appropriate `slotName`. Existing page content remains untouched, and the new slot appears empty and ready for content.

### How does Instatic handle unknown or invalid parameter types?

During `parseVisualComponent`, any type not recognized in the VCParam schema falls back to `string`. This prevents crashes while maintaining forward compatibility. Component authors can see the fallback behavior in the editor and correct the type definition.

### Can slot instances contain their own nested Visual Components?

Yes. Slot instances are full nodes in the page tree with their own `children` array. They can contain any valid node type, including other VC references. When a slot is deleted during synchronization, the entire subtree—including any nested components—is removed.

### What's the difference between `base.slot-outlet` and `base.slot-instance`?

`base.slot-outlet` appears in the **VC definition** and declares that the component accepts content in a named slot. `base.slot-instance` appears in **page trees** as a child of a VC reference, representing the actual container where editors place content for that slot.