# How Instatic Visual Components Work with Typed Parameters and Slots

> Learn how Instatic Visual Components use TypeBox for typed parameters and auto slot generation for type-safe UI building. Validate data from editor to runtime.

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

---

**Instatic Visual Components combine TypeBox schemas for strongly-typed parameters with an automatic slot-instance generation system, enabling composable, type-safe UI building blocks that validate data at every boundary from editor to runtime.**

In the CoreBunch/Instatic repository, Visual Components (VCs) function as first-class citizens within the page-tree architecture, operating alongside standard pages and layout nodes. Each VC declares a strict TypeBox schema that governs its public parameters and slot definitions, ensuring that developers receive full TypeScript type safety while the editor automatically manages content insertion points.

## Declaring Typed Parameters with TypeBox

Every Visual Component in Instatic begins with a **TypeBox schema** that rigidly defines the shape of its configurable properties. In [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts), the system provides base schemas like `BaseVisualComponentSchema` that all VCs extend.

When defining a component, you create a parameter schema using TypeBox's `Type.Object`:

```typescript
import { Type } from '@core/typebox';

export const MyButtonParamsSchema = Type.Object({
  label: Type.String({ title: 'Button label' }),
  disabled: Type.Boolean({ default: false, title: 'Disabled?' })
});

```

The editor validates all incoming data through `validateParams`, which internally utilizes `safeParseJson` to guarantee type safety before any node insertion occurs. This validation happens in [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts) during the `instantiateVc` call, ensuring that malformed data never enters the tree.

## Automatic Slot Instance Generation

VCs achieve composability through **named slots** defined via `SlotParamSchema`. When a schema includes a slots object, the system automatically manages content insertion points.

In [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts), the `syncSlotInstances` function automatically creates **slot-instance** child nodes (of type `base.slot-instance`) for each declared slot when a VC is instantiated:

```typescript
export const MyButtonSlotSchema = Type.Object({
  icon: Type.Optional(Type.Any())
});

export const MyButtonSchema = Type.Intersect([
  MyButtonParamsSchema,
  Type.Object({ slots: MyButtonSlotSchema })
]);

```

These slot-instance nodes become standard page-tree nodes that serve as insertion points for user-provided content. The architecture treats slots as regular tree children, maintaining consistency with Instatic's generic node management system.

## The Instantiation Pipeline

When a user drops a VC onto the canvas, the editor executes a strict instantiation pipeline centered in [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts). The `instantiateVc` function orchestrates this process through four distinct phases:

1. **Parameter validation** against the TypeBox schema
2. **Slot instance creation** via `syncSlotInstances`
3. **Node construction** with validated parameters and generated children
4. **Tree registration** through `mutateActiveTree`

This pipeline is **tree-agnostic**, meaning identical code paths handle mutations for pages, components, and layout trees. The `mutateActiveTree` API registers the new node atomically, ensuring the editor state remains consistent whether you're working with a root page or a nested component.

Utilities in [`src/core/visualComponents/vcRefs.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/vcRefs.ts) provide resolution mechanisms for referencing VCs by ID and retrieving their source definitions throughout this pipeline.

## Runtime Slot Resolution

At publish time, the rendering engine must link slot instances to their corresponding outlets. In [`src/core/visualComponents/origin.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/origin.ts), the system tracks the **origin** of each VC instance—its source definition and slot configuration.

The publisher walks the tree and, for every `base.slot-instance` node, locates the matching `base.slot-outlet` in the component definition. The slot instance's content then renders in place of the outlet, preserving the declarative order defined in the schema. This **origin linking** ensures that runtime rendering respects the component author's intended slot structure while allowing content authors to fill slots with arbitrary compatible components.

## Validation and Type Safety Guards

Beyond schema validation, Instatic implements runtime guards in [`src/core/visualComponents/propGuards.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/propGuards.ts). These **property guards** enforce correct parameter shapes before mutations apply to the tree, catching edge cases that static typing might miss during dynamic operations.

Every boundary—UI to editor, editor to publisher, and persistence to storage—validates data against the component's TypeBox schema. When validation fails, the system surfaces toast errors in the UI immediately, preventing corrupt data from persisting or rendering.

## Complete Implementation Example

The following demonstrates the full lifecycle from schema definition to slot population:

```typescript
// 1️⃣ Define schema with typed params and slots
import { Type } from '@core/typebox';
import { registerVc } from '@core/visualComponents';

export const MyButtonParamsSchema = Type.Object({
  label: Type.String({ title: 'Button label' }),
  disabled: Type.Boolean({ default: false, title: 'Disabled?' })
});

export const MyButtonSlotSchema = Type.Object({
  icon: Type.Optional(Type.Any())
});

export const MyButtonSchema = Type.Intersect([
  MyButtonParamsSchema,
  Type.Object({ slots: MyButtonSlotSchema })
]);

// 2️⃣ Register the VC
registerVc('my.button', MyButtonSchema);

// 3️⃣ Create instance via API (validation automatic)
await apiRequest('/admin/api/cms/vc/create', {
  body: {
    type: 'my.button',
    params: { label: 'Buy now', disabled: false }
  },
  schema: MyButtonSchema
});

// 4️⃣ Populate slot with content
await apiRequest('/admin/api/cms/vc/slot/append', {
  body: {
    parentNodeId: '<node-id-of-my.button>',
    slotName: 'icon',
    child: { type: 'my.icon', params: { name: 'arrow' } }
  }
});

```

## Summary

- **TypeBox schemas** provide compile-time and runtime type safety for Visual Component parameters through [`schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/schemas.ts) and `validateParams`.
- **Automatic slot generation** via `syncSlotInstances` in [`slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/slotSync.ts) creates `base.slot-instance` nodes for every declared slot.
- **Tree-agnostic instantiation** through `instantiateVc` and `mutateActiveTree` allows VCs to function identically across pages, layouts, and nested components.
- **Origin tracking** in [`origin.ts`](https://github.com/CoreBunch/Instatic/blob/main/origin.ts) enables the publisher to resolve `base.slot-outlet` definitions against runtime `base.slot-instance` content.
- **Runtime guards** in [`propGuards.ts`](https://github.com/CoreBunch/Instatic/blob/main/propGuards.ts) enforce parameter validation at every system boundary, surfacing errors before corrupt data enters the tree.
- **Component referencing** utilities in [`vcRefs.ts`](https://github.com/CoreBunch/Instatic/blob/main/vcRefs.ts) maintain the registry between component IDs and their TypeBox schemas.

## Frequently Asked Questions

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

A `base.slot-instance` is a concrete node in the page-tree that holds user-provided content for a specific slot, created automatically when a VC is instantiated. A `base.slot-outlet` exists in the component definition (the origin) and marks the placeholder where slot content should render. During publishing, the system replaces outlets with their corresponding instances according to the logic in [`src/core/visualComponents/origin.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/origin.ts).

### How does Instatic ensure type safety across the editor and runtime?

Instatic validates data at every boundary using TypeBox schemas. The editor calls `validateParams` (which uses `safeParseJson`) before inserting nodes, while [`propGuards.ts`](https://github.com/CoreBunch/Instatic/blob/main/propGuards.ts) provides runtime validation. This dual-layer approach ensures that only conforming data enters the tree and that runtime operations cannot violate the declared schema contracts.

### Can Visual Components be nested within other Visual Components?

Yes. Since slot instances are standard page-tree nodes and the instantiation pipeline is tree-agnostic, you can append any compatible VC—including other VCs with their own slots—into a slot instance. The [`deletionImpact.ts`](https://github.com/CoreBunch/Instatic/blob/main/deletionImpact.ts) module specifically handles propagation logic for such nested structures when components are removed from the tree.

### Where does the slot content get stored in the tree structure?

Slot content becomes children of the `base.slot-instance` node. When you call the slot append API, the system inserts the new child node under the slot instance rather than directly under the parent VC. This hierarchical structure allows the publisher to locate content through the origin-linking mechanism in [`origin.ts`](https://github.com/CoreBunch/Instatic/blob/main/origin.ts) while maintaining clean separation between component parameters and composable content.