Skip to main content

Overview

BindJS is built around a few core concepts that make it powerful and easy to use. Understanding these concepts will help you build better components and make the most of the framework.

Components

Components are the building blocks of your UI. Every BindJS component is defined using the defineComponent function and consists of three main parts: metadata, properties, and a body function.

Basic Component Structure

const metadata = {
    title: "Greeting Card",
    description: "A simple greeting card component",
    category: "Display"
}

const properties = {
    name: {
        type: "string",
        title: "Name"
    }
} satisfies ComponentProperties;

const body = (props, children) => {
    return VStack([
        Text(`Hello, ${props.name}!`)
            .font("headline")
            .foregroundStyle(Color("blue")),
        ...children
    ])
    .padding(16)
    .background(Color("background"))
    .cornerRadius(12)
};

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

Component Metadata

Metadata describes your component and helps organize it in the component library:
const metadata = {
    title: "Component Name",           // Display name
    description: "What it does",       // Brief description
    category: "Display",               // Category for organization
    public: true                       // Whether visible in library
}

Component Properties

Properties define the configurable inputs for your component. BindJS supports various property types: String Properties:
propertyName: {
    type: "string",
    title: "Display Name"
}
Number Properties:
count: {
    type: "number",
    title: "Count",
    validation: { min: 0, max: 100 }
}
Enum Properties:
size: {
    type: "enum",
    options: ["small", "medium", "large"],
    title: "Size"
}
Asset Properties:
image: {
    type: "asset",
    assetTypes: ["image"],
    title: "Image"
}

Component Body

The body function receives props and children, and returns the component’s UI:
const body = (props, children) => {
    // Access props
    const title = props.title
    
    // Render UI
    return VStack([
        Text(title),
        ...children  // Spread children into layout
    ])
}

Modifiers

Modifiers allow you to customize the appearance and behavior of components. They’re chainable methods that return a modified version of the component.

Styling Modifiers

Text("Welcome")
    .font("title")
    .fontWeight("bold")
    .foregroundStyle(Color("primary"))
    .padding(20)
    .background(Color("background"))
    .cornerRadius(8)
    .shadow({ radius: 4, y: 2 })

Layout Modifiers

VStack([...])
    .frame({ width: 300, height: 200 })
    .padding(16)
    .offset({ x: 10, y: 20 })

Interactive Modifiers

Text("Click me")
    .onTapGesture(() => {
        console.log("Tapped!")
    })
    .disabled(false)
    .opacity(0.8)

Common Modifiers

  • .padding() - Add space around content
  • .background() - Set background color or component
  • .foregroundStyle() - Set text/icon color
  • .font() - Set text font
  • .frame() - Set size constraints
  • .cornerRadius() - Round corners
  • .shadow() - Add drop shadow
  • .opacity() - Set transparency

State Management

BindJS provides hooks for managing component state, similar to React hooks.

useState Hook

The useState hook lets you add state to your components:
const body = (props, children) => {
    const [count, setCount] = useState(0)
    
    return VStack([
        Text(`Count: ${count}`)
            .font("title2"),
        Button("Increment", () => setCount(count + 1)),
        Button("Decrement", () => setCount(count - 1))
    ])
}

useStore Hook

For shared state across components, use useStore:
const body = (props, children) => {
    const store = useStore("userPreferences", { 
        theme: "light", 
        fontSize: 16 
    })
    
    return VStack([
        Text(`Theme: ${store.theme}`),
        Button("Toggle Theme", () => {
            store.setTheme(store.theme === "light" ? "dark" : "light")
        })
    ])
}

State Best Practices

  1. Keep state minimal - Only store what you need
  2. Derive values - Calculate derived values in the body, don’t store them
  3. Use appropriate hooks - useState for local state, useStore for shared state
  4. Initialize with defaults - Always provide initial values

Layout Components

BindJS provides three main layout components for organizing your UI.

VStack - Vertical Stack

Arranges children vertically:
VStack({ spacing: 16, alignment: "leading" }, [
    Text("Title").font("headline"),
    Text("Subtitle").font("subheadline"),
    Text("Body text")
])
Properties:
  • spacing - Space between children
  • alignment - Horizontal alignment (leading, center, trailing)

HStack - Horizontal Stack

Arranges children horizontally:
HStack({ spacing: 8, alignment: "center" }, [
    Image({ systemName: "star.fill" }),
    Text("Featured"),
    Spacer()
])
Properties:
  • spacing - Space between children
  • alignment - Vertical alignment (top, center, bottom)

ZStack - Overlay Stack

Layers children on top of each other:
ZStack({ alignment: "topLeading" }, [
    Rectangle()
        .fill(Color("blue"))
        .frame({ width: 200, height: 200 }),
    Text("Overlay")
        .foregroundStyle(Color("white"))
        .padding(8)
])
Properties:
  • alignment - Where to align children (topLeading, center, bottomTrailing, etc.)

Layout Best Practices

  1. Use the right stack - VStack for vertical, HStack for horizontal, ZStack for overlays
  2. Set spacing - Consistent spacing improves visual hierarchy
  3. Use Spacer() - Push content to edges when needed
  4. Nest stacks - Combine stacks for complex layouts

Children

Components can accept children, which are passed as the second argument to the body function:
const body = (props, children) => {
    return VStack([
        Text(props.title).font("headline"),
        ...children  // Spread children into the layout
    ])
    .padding(16)
}

Using Children

// Parent component accepts children
MyCard({ title: "Card Title" }, [
    Text("This is content"),
    Button("Action", () => {})
])

Conditional Children

const body = (props, children) => {
    return VStack([
        Text(props.title),
        // Only show children if they exist
        ...(children.length > 0 ? children : [Text("No content")])
    ])
}

Previews

Previews show different states and configurations of your component:
const previews = [
    Self({ title: "Default" }).previewName("Default State"),
    Self({ title: "With Content" }, [
        Text("Sample content")
    ]).previewName("With Content"),
    Self({ title: "Error", color: "red" }).previewName("Error State")
]

Preview Best Practices

  1. Show different states - Empty, loading, error, success
  2. Demonstrate variations - Different colors, sizes, configurations
  3. Use meaningful names - Clear .previewName() descriptions
  4. Keep realistic - Use representative content

Next Steps

Now that you understand the core concepts, you’re ready to: