Skip to content

Custom Primitives

A custom primitive is a self-contained React component registered with the IKARY runtime. Manifests reference primitives by key; the runtime renders the matching component with the resolved props.

Custom primitives extend the built-in set with project-specific UI. They follow the same contract format as core primitives and appear in the Primitive Studio alongside them.

Anatomy

Every primitive lives in its own folder with six files:

primitives/<name>/
  <Name>.tsx                    React component, typed with the Zod schema
  <Name>PresentationSchema.ts   Zod schema — source of truth for prop types
  <name>.contract.yaml          Human-readable props contract
  <Name>.resolver.ts            Transforms contract props to resolved props
  <Name>.register.ts            Registers the component with the runtime
  <Name>.example.ts             Named example scenarios for the Studio

All six files are generated by ikary primitive add. You implement the component and Zod schema; the other files need minimal changes for most primitives.

Scaffolding a primitive

bash
ikary primitive add my-widget

The command prompts for a label, description, and category:

Add primitive

  ? Display label › My Widget
  ? Short description › A custom widget primitive
  ? Category › custom

  ✔ Created my-widget primitive

  primitives/my-widget/MyWidget.tsx                React component
  primitives/my-widget/MyWidgetPresentationSchema.ts   Zod props schema
  primitives/my-widget/my-widget.contract.yaml     Human-readable contract
  primitives/my-widget/MyWidget.resolver.ts        Props resolver
  primitives/my-widget/MyWidget.register.ts        Registration
  primitives/my-widget/MyWidget.example.ts         Example scenarios
  ikary-primitives.yaml                            Updated

Implementing the component

Edit two files:

<Name>PresentationSchema.ts — add your props:

typescript
export const MyWidgetPresentationSchema = z.object({
  title: z.string(),
  count: z.number().default(0),
}).strict();

export type MyWidgetProps = z.infer<typeof MyWidgetPresentationSchema>;

<Name>.tsx — implement the component:

tsx
import type { MyWidgetProps } from './MyWidgetPresentationSchema';

export function MyWidget({ title, count }: MyWidgetProps) {
  return (
    <div>
      <h2>{title}</h2>
      <span>{count}</span>
    </div>
  );
}

Update <name>.contract.yaml to match — this drives the Studio props editor:

yaml
key: my-widget
version: "1.0.0"
label: My Widget
category: custom
props:
  type: object
  properties:
    title:
      type: string
      description: Widget heading
    count:
      type: number
      description: Display count
  required: [title]

Previewing in the Studio

Start the local stack and open the Primitive Studio:

bash
ikary local start manifest.json
# then open in your browser:
# http://localhost:4500/__primitive-studio

The Studio shows all custom primitives registered in ikary-primitives.yaml. Select a primitive to see:

  • Left panel — list of custom primitives grouped by category
  • Center panel — scenario tabs and an editable props JSON editor
  • Right panel — live component preview that updates as you edit props

Add scenarios in <Name>.example.ts:

typescript
export const MyWidgetExamples = [
  {
    label: 'Default',
    description: 'Basic example',
    props: { title: 'Hello', count: 0 },
  },
  {
    label: 'With count',
    props: { title: 'Items', count: 42 },
  },
];

Validating the contract

bash
ikary primitive validate

This checks every entry in ikary-primitives.yaml: the contract YAML parses against the schema, the source file exists, and referenced example props match the declared contract.

ikary-primitives.yaml

The config file at the project root lists all registered custom primitives:

yaml
apiVersion: ikary.co/v1alpha1
kind: PrimitiveConfig
primitives:
  - key: my-widget
    version: "1.0.0"
    source: ./primitives/my-widget/MyWidget.register.ts
    contract: ./primitives/my-widget/my-widget.contract.yaml
    examples: ./primitives/my-widget/MyWidget.example.ts

ikary primitive add appends to this file automatically. Edit it manually to adjust paths or add an overrides field to replace a core primitive.

Declaring slots

Primitives that act as containers can expose named zones for further injection. Declare them in the .contract.yaml under a slots key:

yaml
key: my-layout
version: "1.0.0"
label: My Layout
category: layout
props:
  type: object
  properties:
    title:
      type: string
      description: Layout title
  required: [title]
slots:
  - name: header
    description: Top area of the layout
    allowedModes: [replace, prepend, append]
  - name: body
    description: Main content area
    allowedModes: [replace, append]

Each slot entry requires name. The description field is optional but appears in the MCP tool output. The allowedModes field restricts which binding modes are valid. Omit it to allow all three: replace, prepend, append.

Once declared, a manifest can target these slots using slotBindings on any page that renders the primitive.

entityBinding

If your primitive is designed for a specific entity type, set entityBinding in ikary-primitives.yaml:

yaml
primitives:
  - key: product-summary
    version: "1.0.0"
    source: ./primitives/product-summary/ProductSummary.register.ts
    contract: ./primitives/product-summary/product-summary.contract.yaml
    entityBinding: product

Set it to an array to allow multiple entity types:

yaml
entityBinding: [product, variant]

This is metadata only. The runtime does not block bindings that do not match. The validate_slot_bindings MCP tool uses it to generate warnings when a primitive is bound to a page whose entity differs.

Building with Claude Code

Run ikary setup ai once to configure Claude Code for the project. After that, use these slash commands inside Claude Code:

CommandWhat it does
/ikary-create-primitiveScaffolds a new primitive and implements the component based on your description
/ikary-update-primitiveUpdates an existing primitive, with guidance on breaking versus non-breaking changes
/ikary-browse-primitivesLists all custom primitives and shows their contracts and example props

Example session:

ikary primitive add my-widget
cd my-project
claude

> /ikary-create-primitive
> build the my-widget primitive — it should display a count with an icon

Claude reads the scaffolded files, implements the component, updates the Zod schema and contract, runs ikary primitive validate, and prints the Studio URL.