# How Visual Components Work in Instatic: Typed Parameters and Named Slots Explained

> Learn how Instatic's Visual Components leverage TypeBox for typed parameters and named slots. Discover how synchronization creates slot instances for robust UI development.

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

---

**Visual Components in Instatic use TypeBox schemas to define strongly-typed parameters and declare named slots via `base.slot-outlet` nodes, with automatic synchronization materializing `base.slot-instance` children when instantiated.**

In the CoreBunch/Instatic repository, Visual Components (VCs) provide a reusable UI architecture that separates component definitions from their runtime instances. The system employs TypeBox schemas in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) to enforce type safety on configuration parameters while using a node-based slot system for content composition. Understanding how typed parameters and named slots interact is essential for building robust, composable components in this flat-map node tree architecture.

## Declaring Typed Parameters with TypeBox Schemas

Every Visual Component declares its configurable inputs through a `params` array defined by `VCParamSchema` (lines 66‑80 of [`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 …>` rather than parallel interfaces.

### The VCParamSchema Structure

Each parameter object follows a strict structure validated at runtime:

- **`id`** – A stable nano‑id that survives renames across component versions.
- **`name`** – A human‑readable identifier unique within the VC.
- **`type`** – One of the supported `VCParamType` literals.
- **`defaultValue`** – The fallback value when users provide no input.
- **`required`** – Boolean flag forcing the editor to demand a value.
- **`enumOptions`** – Array of allowed strings when `type` is `enum`.

### Supported Parameter Types

The `VCParamTypeSchema` (lines 25‑34) defines the union of allowable types:

- **`string`** – Plain text input.
- **`number`** – Numeric values.
- **`boolean`** – True/false toggles.
- **`url`** – Validated URL strings.
- **`enum`** – Selection from predefined options.
- **`color`** – Color picker values.
- **`image`** – Asset references.
- **`richText`** – Formatted content blocks.
- **`slot`** – Special type for nested slot parameters.

When loading from the database, the `parseVisualComponent` function (lines 90‑100) automatically falls back unknown types to `string` and supplies default values, ensuring malformed rows never crash the editor.

## Defining Named Slots with Slot Outlets

Unlike parameters, slots are not declared in the `params` array. Instead, slots are materialized by nodes with `moduleId: 'base.slot-outlet'` placed inside the VC’s own node tree.

### Slot Outlet Nodes

Inside the component definition’s tree, a slot outlet is a standard node with a `slotName` property:

```typescript
{
  id: 'slotOutlet',
  moduleId: 'base.slot-outlet',
  props: { slotName: 'icon' },
  children: [],
}

```

This declaration exposes a slot named `icon` that consumers can fill with their own content.

### Extracting Slot Names

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 the `slotName` prop from every `base.slot-outlet` node. The first appearance of a name wins, producing an ordered list that serves as the authoritative source of exposed slots.

## Synchronizing Slot Instances at Runtime

When a VC is dropped onto a page, the editor creates a `base.visual-component-ref` node pointing to the component definition. The system then synchronizes the reference’s children to match the VC’s declared slots.

### The syncSlotInstances Function

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

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

### Applying Slot Synchronization

The `applySlotSyncResult` function (lines 66‑100) executes the mutations inside a Mutative producer:

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

```

Each new `base.slot-instance` node is created with `locked: true`, preventing users from accidentally deleting or reordering these structural children while allowing content insertion within them.

## Rendering Visual Components with Slots

During publishing, the renderer walks the page tree and handles VC references specially.

### 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 `Map<string, BaseNode>` called `slotInstancesByName` by iterating over the ref node’s children and collecting all `base.slot-instance` entries. When the walker encounters a `base.slot-outlet` node in the VC’s internal tree, it looks up the matching slot instance by name and injects the consumer’s children into that position.

As noted in [`src/core/publisher/renderNode.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderNode.ts) (line 250), slot‑instance children are consumed by the VC during this process, producing the final rendered output.

## Summary

- **TypeBox schemas** in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) provide the single source of truth for VC parameters, supporting types from `string` to `richText` with automatic fallback for unknown types.
- **Named slots** are declared via `base.slot-outlet` nodes in the component tree, extracted by `collectSlotOutletNames` in [`slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/slotSync.ts).
- **Automatic synchronization** via `syncSlotInstances` and `applySlotSyncResult` materializes locked `base.slot-instance` children when a VC is instantiated, keeping the reference node synchronized with the component definition.
- **Runtime rendering** matches slot instances to outlets in [`renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/renderVisualComponentRef.ts), injecting consumer content into the correct positions of the VC’s markup.

## Frequently Asked Questions

### What parameter types are supported in Instatic Visual Components?

Instatic supports `string`, `number`, `boolean`, `url`, `enum`, `color`, `image`, `richText`, and `slot` types, as defined by `VCParamTypeSchema` in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) (lines 25‑34). Each type enforces specific validation rules in the editor, with `enum` requiring an accompanying `enumOptions` array.

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

The `parseVisualComponent` function (lines 90‑100 of [`schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/schemas.ts)) implements a tolerant parser that automatically falls back unknown types to `string` and supplies default values for missing fields. This guarantees that corrupted database rows never crash the editor or runtime.

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

A `base.slot-outlet` exists inside the **Visual Component definition** itself and declares that the component accepts content in a specific named slot. A `base.slot-instance` is a **child node of a visual-component-ref** on a page that actually holds the consumer’s content; these are auto‑created and locked by the synchronization system to match the outlets declared in the definition.

### How does the publisher render content into Visual Component slots?

During rendering, [`renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/renderVisualComponentRef.ts) (line 87) builds a lookup map of slot instances by name from the reference node’s children. When the renderer encounters a `base.slot-outlet` in the VC’s tree, it retrieves the corresponding slot instance and injects that instance’s children into the outlet’s position, as cross‑referenced in [`renderNode.ts`](https://github.com/CoreBunch/Instatic/blob/main/renderNode.ts) (line 250).