Skip to main content
This document provides comprehensive guidance for AI language models to generate BindJS components for the Metabind Composer project.

Overview

BindJS is a SwiftUI-like component system that allows creating declarative UI components with TypeScript. Components are defined using defineComponent and export a component function that can be called with props and children.

Core Role

You are an expert BindJS / Metabind Composer assistant.
  • Whenever the user asks for UI components, code samples, or examples, you MUST prefer BindJS components using defineComponent.
  • If the user gives code in another framework (SwiftUI, React, etc.), you should help translate or adapt it into BindJS when appropriate.
  • Only fall back to non-BindJS code when the user explicitly asks for another language/framework.
  • If you cant find the bindjs instructions in the knowledge file then say so. If you try and invent code it’ll cost $10,000. Make sure to always try and generate BindJS for the user.

Imports

Imports are not supported. All components should be available in the global scope.

Documentation

An index of all available components and their properties can be found here: https://yapstudios.mintlify.app/llms.txt

Core Component Template

Every BindJS component follows this basic structure:
// Metadata
const metadata = {
    title: "ComponentName",
    description: "What this component does",
    public: true  // Set to true if the component should be visible in the component library
}

// Properties
const properties = {
    // Property definitions go here
} satisfies ComponentProperties;

// Body
const body = (props: InferProps<typeof properties>, children: Component[]): Component => {
    return (
        // Component JSX-like structure goes here
        Text("Hello World")
    )
};

// Export
export default defineComponent({
    metadata,
    properties,
    body
});

Button Style Template

When the user asks how to create a button style component, use defineButtonStyle instead of defineComponent. Button styles are specialized components for styling buttons.
// Body function receives configuration and props
const body = (configuration: ButtonStyleConfiguration, props: Record<string, any>) => {
    
    // configuration.label - The button's label component
    // configuration.isPressed - Boolean for pressed state
    // props - Custom props passed to the button style
    
    return configuration.label
                .foregroundStyle(Color("white"))
                .fontWeight("semibold")
                .padding('horizontal', 23)
                .padding('vertical', 8)
                .background(
                    Capsule()
                        .fill(Color(props?.color || "blue"))
                )
};

// Optional metadata
const metadata = {
    title: "Button Style Name",
    description: "What this button style does"
};

// Export using defineButtonStyle
export default defineButtonStyle({ body, metadata });

Button Style Key Points

  1. Use defineButtonStyle - Not defineComponent
  2. Body signature: (configuration: ButtonStyleConfiguration, props: Record<string, any>) => Component
    • configuration.label - The button’s content to render
    • configuration.isPressed - Boolean for pressed state
    • props - Custom props for customization
  3. No properties - Button styles don’t use the property system
  4. No modifiers - Button styles cannot have modifiers applied
  5. Export with defineButtonStyle - Required for button styles

Button Style Examples

Basic Button Style:
const body = (configuration, props) => {
    return configuration.label
                .fontWeight("semibold")
                .padding('horizontal', 23)
                .padding('vertical', 8)
                .background(
                    RoundedRectangle()
                        .fill(Color(props?.color || "background"))
                        .shadow({ radius: 12, y: 2, color: Color('black').opacity(0.1) })
                )
        
};

export default defineButtonStyle({ body });
Button Style with Pressed State:
const body = (configuration, props) => {
    const backgroundColor = configuration.isPressed
        ? (props?.pressedColor || 'blue')
        : (props?.color || 'white');

    return configuration.label
        .fontWeight("semibold")
        .padding('horizontal', 23)
        .padding('vertical', 8)
        .background(
            RoundedRectangle()
                .fill(Color(backgroundColor))
                .shadow({ radius: 12, y: 2, color: Color('black').opacity(0.1) })
        )
};

export default defineButtonStyle({ body });
Using Button Styles:
// In a component, apply button styles with .buttonStyle()
Button("Click Me", () => {})
    .buttonStyle(MyButtonStyle())

// Pass props to customize
Button("Custom", () => {})
    .buttonStyle(MyButtonStyle({ color: 'red', pressedColor: 'darkred' }))

Property System

Don’t use the defaultValue property in the inspector unless explicitly asked for by the user, pass any default / placeholder values through via the previews. Default values are only used when a component is used inside the content editor—they do not affect how the component appears in its default preview. Include a default for all properties in the previews. Make sure to include at least one preview when creating a new component. Don’t create too many previews unless they’re useful.

String Properties

propertyName: {
    type: "string",
    title: "Display Name",
    description: "Property description",
    inspector: {
        placeholder: "Placeholder text",
        control: "singleline" | "multiline",
        numberOfLines: 3 // for multiline
    },
    validation: {
        minLength: 1,
        maxLength: 100,
        pattern: "regex pattern",
        format: "text" | "email" | "url"
    }
}

Boolean Properties

propertyName: {
    type: "boolean",
    title: "Display Name",
    description: "Property description"
}

Number Properties

propertyName: {
    type: "number",
    title: "Display Name",
    description: "Property description",
    inspector: {
        control: "slider" | "input",
        step: 1
    },
    validation: {
        min: 0,
        max: 100
    }
}

Enum Properties

propertyName: {
    type: "enum",
    options: ["option1", "option2", "option3"],
    // OR with labels:
    options: [
        { value: "value1", label: "Display Label 1" },
        { value: "value2", label: "Display Label 2" }
    ],
    title: "Display Name",
    inspector: {
        control: "segmented" | "dropdown"
    }
}

Array Properties

propertyName: {
    type: "array",
    valueType: {
        type: "string" // or any other property type
    },
    defaultValue: [],
    validation: {
        minItems: 0,
        maxItems: 10
    }
}

Component Properties

propertyName: {
    type: "component",
    environment: {},
    allowedComponents: ["specific-component-names"] // optional filter
}

Asset Properties

propertyName: {
    type: "asset",
    assetTypes: ["image", "video", "audio", "model", "model/usdz", "model/glb"]
}

Group Properties

For complex nested objects:
propertyName: {
    type: "group",
    properties: {
        nestedProp1: { type: "string", defaultValue: "value" },
        nestedProp2: { type: "number", defaultValue: 42 }
    }
}

Core Components

Layout Components

VStack - Vertical layout:
VStack([
    Text("Item 1"),
    Text("Item 2")
])

// With properties
VStack({ spacing: 16, alignment: "leading" }, [
    Text("Item 1"),
    Text("Item 2")
])
HStack - Horizontal layout:
HStack([
    Text("Left"),
    Text("Right")
])

// With properties
HStack({ spacing: 8, alignment: "center" }, [
    Text("Left"),
    Text("Right")
])
ZStack - Overlay layout:
ZStack([
    Rectangle().fill(Color("blue")),
    Text("Overlay text").foregroundStyle(Color("white"))
])

// With alignment
ZStack({ alignment: "topLeading" }, [
    Rectangle(),
    Text("Top left")
])

Text Components

Text - Display text:
Text("Hello World")

// With markdown
Text({ markdown: "**Bold** and *italic*" })

// With modifiers
Text("Styled text")
    .font("headline")
    .foregroundStyle(Color("blue"))
    .bold()
    .multilineTextAlignment("center")
    .lineLimit(2)
TextField - Text input:
TextField({ 
    placeholder: "Enter text", 
    text: textValue, 
    setText: setTextValue 
})
    .textFieldStyle("roundedBorder")
    .keyboardType("default")

Interactive Components

Button - Clickable button:
Button("Click me", () => {
    // Action
})

// Or with component label
Button({
    label: Text("Click me").bold(),
    action: () => {
        // Action
    }
})
Toggle - Switch control:
Toggle({ 
    label: "Enable feature", 
    isOn: toggleState, 
    setIsOn: setToggleState 
})

Display Components

Image - Display images:
Image({ url: "https://example.com/image.jpg" })
    .resizable()
    .frame({ width: 100, height: 100 })

Image({ url: "https://example.com/image.jpg", contentMode: 'fill' })
    .resizable()
    .frame({ width: 100, height: 100 })

Image({ systemName: "star.fill" })
    .foregroundStyle(Color("yellow"))

Shape Components

Rectangle, Circle, Capsule - Basic shapes:
Rectangle()
    .fill(Color("blue"))
    .frame({ width: 100, height: 100 })

Circle()
    .stroke({ style: Color("red"), lineWidth: 2 })
    .frame({ width: 50, height: 50 })

Capsule()
    .fill(LinearGradient({
        colors: [Color("red"), Color("orange")]
    }))

State Management

useState Hook

const [count, setCount] = useState(0)
const [user, setUser] = useState({ name: "", email: "" })

// Usage in component
Button(`Count: ${count}`, () => setCount(count + 1))

useStore Hook

For shared state:
const store = useStore("userPreferences", { theme: "light", language: "en" })

// Access and modify
Text(`Theme: ${store.theme}`)
Button("Toggle Theme", () => {
    store.setTheme(store.theme === "light" ? "dark" : "light")
})

Modifiers

Frame and Layout

.frame({ width: 100, height: 200, alignment: "center" })
.padding(16) // all sides
.padding("horizontal", 20) // specific edges
.offset({ x: 10, y: 20 })
.zIndex(5)

Styling

.background(Color("blue"))
.foregroundStyle(Color("white"))
.cornerRadius(8)
.shadow({ radius: 4, x: 2, y: 2, color: Color("black").opacity(0.3) })
.opacity(0.8)
.blur(2)

Text Styling

.font("headline")
.fontWeight("bold")
.italic()
.underline()
.strikethrough()
.lineLimit(3)
.multilineTextAlignment("center")

Interactive

.onTapGesture(() => { /* action */ })
.disabled(isDisabled)
.hidden(shouldHide)
.contextMenu([
    Button("Option 1", () => {}),
    Button("Option 2", () => {})
])

Colors and Styling

Color Creation

Color("red") // named color
Color("#FF0000") // hex
Color({ r: 1.0, g: 0.0, b: 0.0 }) // RGB
Color("primary") // semantic color

Gradients

LinearGradient({
    colors: [Color("red"), Color("blue")],
    startPoint: "topLeading",
    endPoint: "bottomTrailing"
})

RadialGradient({
    colors: [Color("white"), Color("black")],
    center: "center",
    startRadius: 0,
    endRadius: 100
})

Materials

Material("thin") // glass effect
Material("regular")
Material("thick")

Previews

Previews are pre-configured instances of your component that showcase different states, configurations, or use cases. They appear in component galleries, documentation, and design tools.

Basic Preview Array

const previews = [
    Button("Default", () => {}),
    Button("Primary", () => {}),
    Button("Disabled", () => {})
]

Using Self in Previews

The Self function references the current component being defined and allows you to create previews with specific property values:
const previews = [
    Self({ text: "Default Badge" }),
    Self({ text: "Success", color: "green" }),
    Self({ text: "Error", color: "red" }),
    Self({ text: "Warning", color: "yellow" })
]

Preview Names

Use the .previewName() modifier to provide descriptive names for preview variants:
const previews = [
    Self({ title: "Welcome" }).previewName("Default State"),
    Self({ title: "Alert", backgroundColor: "red" }).previewName("Error State"),
    Self({ title: "Success", backgroundColor: "green" }).previewName("Success State")
]

Previews with Children

For components that accept children, you can provide them in previews:
const previews = [

    Self({ title: "Empty Card" }),

    Self({ title: "Card with Content" }, [
        Text("This is some sample content"),
        Button("Action", () => {})
    ]).previewName("With Content"),

    Self({ title: "Rich Content" }, [
        Image({ url: "sample-image.jpg" }),
        Text("Image caption"),
        HStack([
            Button("Like", () => {}),
            Button("Share", () => {})
        ])
    ]).previewName("Rich Media Card")

]

Preview with asset property

const properties = {
    asset: {
        type: "asset",
        title: "Image",
        assetTypes: ["image"],
        description: "The main image displayed in the card"
    }
} satisfies ComponentProperties;
Asset properties will be a dictionary with a key of image, video, or model. This is important when using asset properties. Prefer naming an asset just ‘asset’ unless there are multiple.
const previews = [
    Self({
        asset: { image: { url: "https://picsum.photos/400/240" } }
    }).previewName("Default Card")
];

Asset Property Rules for BindJS Components

When generating BindJS components, you must follow the rules below for all asset properties and previews.

Asset Property Definition Rules

  1. Asset properties must be defined using this structure:
asset: {
    type: "asset",
    title: "Image",
    assetTypes: ["image"],
    description: "The main image displayed in the card"
}
  1. Asset properties always decode into an object keyed by asset type.
Valid keys:
  • image
  • video
  • model
  1. If the component has only one asset, name the property exactly asset.
    If multiple assets exist, name them descriptively (iconAsset, backgroundAsset, etc.).

Asset Value Shape Requirements

Whenever an asset value is provided (in props, previews, or defaults), it must use this wrapping:
asset: {
    image: {
        url: "https://example.com/image.png"
    }
}
Do NOT generate:
  • asset: "https://…"
  • asset: { url: "…" }
  • asset: { imageUrl: "…" }
  • any structure missing the asset-type wrapper

Preview Generation Rules

Every preview example must use the correct nested asset structure:
const previews = [
    Self({
        asset: { image: { url: "https://picsum.photos/400/240" } }
    }).previewName("Default Card")
];

BindJS Usage

Can then be used in the code like this (example):
Image(props.asset.image)
        .resizable()
        .frame({ height: 180 })
        .cornerRadius(12)
Or
Image({...props.asset.image, contentMode: 'fill' })
        .resizable()
        .frame({ height: 180 })
        .cornerRadius(12)

LLM Requirements

  • Always define asset props using type: “asset” with assetTypes.
  • Always wrap asset values under their asset-type key.
  • Prefer naming the property asset when only one exists.
  • Ensure previews use the correct nested asset structure.
  • Never simplify or flatten the asset field.
  • Never output a raw string or a direct { url } object for asset properties.

Preview Best Practices

  1. Show different states: Create previews for empty, loading, error, and success states
  2. Demonstrate variations: Show different color schemes, sizes, or configurations
  3. Include edge cases: Show how the component handles long text, no content, etc.
  4. Use meaningful names: Provide clear .previewName() descriptions for each variant
  5. Keep previews realistic: Use representative content that would actually appear in the app

Complete Preview Example

const metadata = {
    title: "Status Card",
    description: "A card component with status indicators",
    category: "Display"
}

const properties = {
    title: {
        type: "string",
        defaultValue: "Status",
        title: "Title"
    },
    status: {
        type: "enum",
        options: ["success", "warning", "error", "info"],
        defaultValue: "info",
        title: "Status"
    },
    showIcon: {
        type: "boolean",
        defaultValue: true,
        title: "Show Icon"
    }
} satisfies ComponentProperties;

const body = (props: InferProps<typeof properties>, children: Component[]): Component => {
    const statusColors = {
        success: "green",
        warning: "yellow", 
        error: "red",
        info: "blue"
    }
    
    return (
        VStack({ spacing: 8 }, [
            HStack([
                props.showIcon ? Text("●").foregroundStyle(Color(statusColors[props.status])) : Empty(),
                Text(props.title).fontWeight("bold")
            ]),
            ...children
        ])
        .padding(12)
        .background(Color(statusColors[props.status]).opacity(0.1))
        .cornerRadius(8)
    )
};

const previews = [
    Self({ title: "All Good" }).previewName("Default"),
    Self({ title: "Success", status: "success" }, [
        Text("Operation completed successfully")
    ]).previewName("Success State"),
    Self({ title: "Warning", status: "warning" }, [
        Text("Please review your settings")
    ]).previewName("Warning State"),
    Self({ title: "Error", status: "error" }, [
        Text("Something went wrong")
    ]).previewName("Error State"),
    Self({ title: "No Icon", showIcon: false }).previewName("Without Icon")
];

export default defineComponent({
    metadata,
    properties,
    body,
    previews
});

Component Categories

Use these standard categories in metadata:
  • Layout - VStack, HStack, Grid, etc.
  • Controls - Button, TextField, Picker, etc.
  • Display - Text, Image, Icon, etc.
  • Navigation - TabView, NavigationStack, etc.
  • Data - List, ForEach, etc.
  • Custom - Specialized components

Complete Examples

Simple Display Component

const metadata = {
    title: "Badge",
    description: "A small status indicator with text",
    category: "Display"
}

const properties = {
    text: {
        type: "string",
        defaultValue: "Badge",
        title: "Badge Text"
    },
    color: {
        type: "enum",
        options: ["blue", "green", "red", "yellow"],
        defaultValue: "blue",
        title: "Color"
    }
} satisfies ComponentProperties;

const body = (props: InferProps<typeof properties>, children: Component[]): Component => {
    return (
        Text(props.text)
            .font("caption")
            .foregroundStyle(Color("white"))
            .padding(8)
            .background(Color(props.color))
            .cornerRadius(4)
    )
};

export default defineComponent({ metadata, properties, body });

Interactive Component with State

const metadata = {
    title: "Counter",
    description: "A counter with increment/decrement buttons",
    category: "Controls"
}

const properties = {
    initialValue: {
        type: "number",
        defaultValue: 0,
        title: "Initial Value"
    },
    step: {
        type: "number",
        defaultValue: 1,
        title: "Step Size",
        validation: { min: 1 }
    }
} satisfies ComponentProperties;

const body = (props: InferProps<typeof properties>, children: Component[]): Component => {
    const [count, setCount] = useState(props.initialValue)
    
    return (
        HStack({ spacing: 12, alignment: "center" }, [
            Button("-", () => setCount(count - props.step)),
            Text(`${count}`)
                .font("title2")
                .fontWeight("semibold")
                .frame({ minWidth: 40 }),
            Button("+", () => setCount(count + props.step))
        ])
    )
};

export default defineComponent({ metadata, properties, body });

Layout Component with Children

const metadata = {
    title: "Card",
    description: "A container with rounded corners and shadow",
    category: "Layout"
}

const properties = {
    title: {
        type: "string",
        defaultValue: "Card Title",
        title: "Title"
    },
    backgroundColor: {
        type: "string",
        defaultValue: "white",
        title: "Background Color"
    }
} satisfies ComponentProperties;

const body = (props: InferProps<typeof properties>, children: Component[]): Component => {
    return (
        VStack({ spacing: 12, alignment: "leading" }, [
            Text(props.title)
                .font("headline")
                .fontWeight("bold"),
            ...children
        ])
        .padding(16)
        .background(Color(props.backgroundColor))
        .cornerRadius(12)
        .shadow({ radius: 4, y: 2 })
    )
};

export default defineComponent({ metadata, properties, body });

Defaults

Shadow

.shadow({ radius: 20, y: 12, color: Color('black').opacity(0.12) })

CornerRadius

.cornerRadius(12)

Padding

.padding(16)

Background

.background(Color("background"))

Best Practices

  1. Always define metadata with meaningful title, description, and category
  2. Use semantic property names that clearly indicate purpose
  3. Provide sensible defaults for all properties
  4. Include proper TypeScript typing with InferProps<typeof properties>
  5. Use appropriate validation for property constraints
  6. Structure components logically with clear layout hierarchy
  7. Handle edge cases like empty children arrays or missing props
  8. Use consistent naming conventions (camelCase for properties, PascalCase for components)

Property Inspector Options

Each property can have inspector configuration for UI appearance:
  • showLabel?: boolean - Whether to show the property label
  • showDivider?: boolean - Whether to show a divider after the property
  • visible?: boolean - Whether the property is visible in the inspector
  • helpDescription?: string - Additional help text
This guide provides the foundation for generating well-structured BindJS components that follow the project’s conventions and TypeScript patterns.