Documentation Index
Fetch the complete documentation index at: https://docs.metabind.ai/llms.txt
Use this file to discover all available pages before exploring further.
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://docs.metabind.ai/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
});
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 });
- Use
defineButtonStyle - Not defineComponent
- 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
- No properties - Button styles don’t use the property system
- No modifiers - Button styles cannot have modifiers applied
- Export with
defineButtonStyle - Required for button styles
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
Color("systemGray5") // system semantic color
Supported Named Colors
Named: "clear", "red", "orange", "yellow", "green", "mint", "teal", "cyan", "blue", "indigo", "purple", "pink", "brown", "black", "white", "gray"
Semantic: "primary", "secondary", "tertiary", "quaternary", "accent", "background"
System semantic (all adaptive to light/dark mode):
- Labels:
"label", "secondaryLabel", "tertiaryLabel", "quaternaryLabel", "placeholderText", "link"
- Grays:
"systemGray", "systemGray2", "systemGray3", "systemGray4", "systemGray5", "systemGray6"
- Backgrounds:
"systemBackground", "secondarySystemBackground", "tertiarySystemBackground", "systemGroupedBackground", "secondarySystemGroupedBackground", "tertiarySystemGroupedBackground"
- Fills:
"systemFill", "secondarySystemFill", "tertiarySystemFill", "quaternarySystemFill"
- Separators:
"separator", "opaqueSeparator"
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
- Asset properties must be defined using this structure:
asset: {
type: "asset",
title: "Image",
assetTypes: ["image"],
description: "The main image displayed in the card"
}
- Asset properties always decode into an object keyed by asset type.
Valid keys:
- 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
- Show different states: Create previews for empty, loading, error, and success states
- Demonstrate variations: Show different color schemes, sizes, or configurations
- Include edge cases: Show how the component handles long text, no content, etc.
- Use meaningful names: Provide clear
.previewName() descriptions for each variant
- 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
Padding
Background
.background(Color("background"))
Best Practices
- Always define metadata with meaningful title, description, and category
- Use semantic property names that clearly indicate purpose
- Provide sensible defaults for all properties
- Include proper TypeScript typing with
InferProps<typeof properties>
- Use appropriate validation for property constraints
- Structure components logically with clear layout hierarchy
- Handle edge cases like empty children arrays or missing props
- 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.