# How Instatic Visual Components Use Typed Parameters and Named Slots: A Complete Guide

> Learn how Instatic Visual Components leverage TypeBox for typed parameters and base.slot-outlet for named slots, enabling streamlined component integration and management.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-07-27

---

**Instatic Visual Components declare typed parameters via TypeBox schemas and define named slots through `base.slot-outlet` nodes, which the editor automatically materializes as locked `base.slot-instance` children when a component is placed on a page.**

Instatic, an open-source visual site builder maintained by CoreBunch, treats Visual Components (VCs) as reusable UI elements backed by a flat-map node tree. Understanding how Instatic Visual Components use typed parameters and named slots is essential for building robust, type-safe modules that survive editor mutations and render correctly at publish time.

## Declaring Typed Parameters with TypeBox Schemas

Every Visual Component in Instatic describes its configuration surface through **TypeBox schemas** defined in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts). These schemas serve as the single source of truth, with TypeScript types derived via `Static<typeof …>` and no parallel interfaces maintained.

### The VCParamSchema Structure

Parameters are declared in the `params` array of a `VisualComponent`. Each entry follows `VCParamSchema` (lines 66‑80) and requires:

- **`id`**: A stable nano-id that persists across renames.
- **`name`**: A human-readable identifier unique within the VC.
- **`type`**: A literal from `VCParamTypeSchema` (lines 25‑34): `string`, `number`, `boolean`, `url`, `enum`, `color`, `image`, `richText`, or `slot`.
- **`defaultValue`**: The fallback used when no value is provided.
- **`required`**: Boolean flag forcing the editor to demand a value.
- **`enumOptions`**: String array for `enum` types.

```typescript
// Example parameter definition inside a VC schema
{
  id: 'param_123',
  name: 'label',
  type: 'string',
  defaultValue: 'Click me',
  required: true
}

```

### Tolerant Parsing for Robustness

When loading from the database, the `parseVisualComponent` function (lines 90‑100) enforces safety. Unknown parameter types automatically fall back to `string`, and missing values receive their `defaultValue`. This guarantees that malformed rows never crash the editor, preserving the integrity of **Instatic Visual Components** across schema migrations.

## Defining Named Slots with Slot Outlets

Unlike parameters, **named slots** are not declared in the `params` array. Instead, slots materialize through special nodes inside the VC’s own tree.

### Slot Declaration in the Component Tree

A slot is declared by placing a node with **`moduleId: 'base.slot-outlet'`** anywhere in the component’s node tree. The `slotName` prop on this node defines the slot’s public identity.

```typescript
// Inside the VC tree definition
slotOutlet: {
  id: 'slotOutlet',
  moduleId: 'base.slot-outlet',
  props: { slotName: 'icon' },  // Declares a slot named "icon"
  children: [],
}

```

### Extracting Slot Names from the Tree

The helper `collectSlotOutletNames` in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts) (lines 26‑70) walks the VC tree and extracts `slotName` values from every `base.slot-outlet`. The first appearance of a name wins, producing an ordered list that serves as the authoritative source of which slots the VC exposes.

## Synchronizing Slot Instances on the Page

When a user drops a Visual Component onto a page, the editor creates a **`base.visual-component-ref`** node pointing to the VC definition. The system then ensures the ref’s children match the VC’s slot declarations.

### The syncSlotInstances Algorithm

The `syncSlotInstances` function (lines 135‑148 of [`slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/slotSync.ts)) performs a pure, side-effect-free comparison:

1. **Collects** current slot names from the VC via `collectSlotOutletNames`.
2. **Matches** existing `base.slot-instance` children of the ref by name (or position for renamed slots).
3. **Generates** insert, rename, and delete operations to align the ref’s children with the VC’s current slot list.
4. **Returns** a `SyncResult` containing the operations, newly created nodes, and the final `orderedChildIds`.

### Applying Changes with applySlotSyncResult

The `applySlotSyncResult` function (lines 66‑100) executes the generated plan inside a Mutative producer. It inserts new `base.slot-instance` nodes, applies renames, and removes stale slots. Each new slot-instance is created with **`locked: true`**, preventing users from accidentally deleting or reordering these structural children.

```typescript
// Inside a Mutative producer (e.g., siteSlice)
import { syncSlotInstances, applySlotSyncResult } from '@core/visualComponents/slotSync';

const syncResult = syncSlotInstances(vcRefNode, myButtonVC, state.page.nodes);
applySlotSyncResult(state.page.nodes, syncResult, vcRefNode.id);

```

## Runtime Rendering of Slots

During publishing, the renderer pairs consumer-provided content with the VC’s internal outlets.

### Matching Slot Instances to Outlets

In [`src/core/publisher/renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderVisualComponentRef.ts) (line 87), the renderer builds a `slotInstancesByName` map from the ref’s `base.slot-instance` children. When walking the VC’s tree, each `base.slot-outlet` node looks up its `slotName` in this map. The children of the matching slot-instance replace the outlet in the final output, as noted in [`src/core/publisher/renderNode.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderNode.ts) (line 250).

```typescript
// Excerpt from renderVisualComponentRef.ts
const slotInstancesByName = new Map<string, BaseNode>();
for (const childId of vcRefNode.children) {
  const child = tree.nodes[childId];
  if (child?.moduleId === 'base.slot-instance') {
    slotInstancesByName.set(child.props.slotName, child);
  }
}

// Later, when encountering a slot-outlet
if (node.moduleId === 'base.slot-outlet') {
  const slotInst = slotInstancesByName.get(node.props.slotName);
  // Render slotInst.children in place of the outlet
}

```

## Summary

- **TypeBox schemas** in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) define typed parameters via `VCParamSchema`, supporting types from `string` to `richText` with automatic fallback handling in `parseVisualComponent`.
- **Named slots** are declared implicitly by `base.slot-outlet` nodes in the VC tree, extracted by `collectSlotOutletNames` in [`slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/slotSync.ts).
- **Automatic synchronization** occurs through `syncSlotInstances` and `applySlotSyncResult`, which materialize locked `base.slot-instance` children on every `base.visual-component-ref`.
- **Runtime rendering** maps slot-instances to outlets in [`renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/renderVisualComponentRef.ts), injecting consumer content into the correct positions.

## Frequently Asked Questions

### How do I add a new typed parameter to an Instatic Visual Component?

Extend the `params` array in your component definition with an object conforming to `VCParamSchema`. Specify the `type` using one of the literals defined in `VCParamTypeSchema` (lines 25‑34), provide a `defaultValue`, and ensure the `id` uses a stable nano-id. The tolerant parser `parseVisualComponent` (lines 90‑100) will handle backward compatibility automatically.

### What happens if I rename a slot in my Visual Component?

When you change the `slotName` prop of a `base.slot-outlet` node, `syncSlotInstances` detects the mismatch during the next editor session. It generates a rename operation that updates the corresponding `base.slot-instance` child’s `slotName` prop, preserving the existing children (content) while updating the identifier.

### Can users delete slot instances from a Visual Component reference?

No. The `applySlotSyncResult` function creates all `base.slot-instance` nodes with `locked: true`. This flag prevents users from deleting or reordering these structural children in the editor, ensuring the VC’s slot contract remains intact. Users can only edit the content inside the slot instances.

### Where is the single source of truth for Visual Component types?

The [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) file contains the TypeBox schemas (`VisualComponentSchema`, `VCParamSchema`, `VCParamTypeSchema`) that serve as the canonical definitions. TypeScript types like `VisualComponent` and `VCParam` are derived using `Static<typeof …>`, ensuring runtime validation and static analysis never drift apart.