> ## 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.

# Components and bodies

> How a BindJS component is structured — the defineComponent shape, the body signature, and the optional metadata, previews, thumbnail, and icon fields

Every BindJS component is a single `defineComponent` call exported as the module default. The shape is small: a render `body` function, a `properties` schema describing the inputs the body receives, and a handful of optional fields — `metadata`, `previews`, `thumbnail`, and `icon` — that surface the component in editors, design tools, and component pickers.

The same source produces native SwiftUI on iOS, Jetpack Compose on Android, and React on the web. You write one definition; the per-platform renderer turns the resulting AST into native views. See [Native rendering](/guides/concepts/native-rendering) for the runtime model.

## A complete component

A typical component declares its `properties` and `body` as top-level constants and exports the `defineComponent` call. The body's `props` argument is fully typed against the schema — there's no manual `ComponentProps` interface to write.

```typescript theme={null}
const properties = {
    title: PropertyString({
        title: "Title",
        required: true,
        defaultValue: "Default Title",
    }),
    showSecondary: PropertyBoolean({
        title: "Show Secondary",
        defaultValue: true,
    }),
}

const body = (props, children) => {
    return VStack({ spacing: 20 }, [
        Text(props.title)
            .font("headline")
            .foregroundStyle(Color("primary")),

        props.showSecondary
            ? HStack({ spacing: 10 }, [
                  Button("Click Me", () => console.log("Clicked")),
                  Text("Secondary text").foregroundStyle(Color("secondary")),
              ])
            : Empty(),
    ])
}

export default defineComponent({
    metadata: {
        title: "My Component",
        description: "An example BindJS component",
    },
    properties,
    body,
})
```

Three things are happening here:

1. **`properties`** declares the inputs the component accepts. Each entry uses a property helper (`PropertyString`, `PropertyBoolean`, etc.) that contributes both runtime validation and a JSON Schema fragment editors and LLMs can read. The full set of helpers is in [Properties](/bindjs/authoring/properties).
2. **`body`** is the render function. It receives the typed `props` derived from the schema, plus a `children` array of any nested components passed by a parent. It returns an AST built from BindJS components and modifiers — no JSX.
3. **`defineComponent`** is the canonical export. The runtime calls `body` with the resolved props on every render and walks the returned AST.

The body uses standard BindJS patterns: function calls instead of JSX, method chaining for [modifiers](/bindjs/authoring/modifiers), and conditional expressions to vary the tree based on props. Components that need state or context use [hooks](/bindjs/authoring/hooks) such as `useState` and `useEnvironment`.

## The body signature

The `body` field takes a function of the form:

```typescript theme={null}
(props, children) => Component
```

* **`props`** — an object whose type is inferred from the `properties` schema via `InferProps<typeof properties>` inside the `defineComponent` overload. You don't declare it manually.
* **`children`** — a `Component[]` array of components passed in by the caller, in the order they were supplied. Spread it into a layout (`...children`) or wrap it in a stack to render the contents.
* **Return value** — a single `Component`. Use [VStack](/bindjs/components/layout-stacks#vstack), `HStack`, or `Group` to return multiple top-level views.

### Inferred prop types

Given a schema, the body's `props` argument has exactly the shape you'd expect:

```typescript theme={null}
const properties = {
    title: PropertyString({ title: "Title", required: true }),
    count: PropertyNumber({ title: "Count", defaultValue: 0 }),
    isEnabled: PropertyBoolean({ title: "Enabled", defaultValue: true }),
}

// Inferred body signature:
//   (props: { title: string; count: number; isEnabled: boolean },
//    children: Component[]) => Component
const body = (props, children) => {
    return VStack([
        Text(props.title),
        Text(`Count: ${props.count}`),
        props.isEnabled ? Text("Enabled") : Empty(),
    ])
}

export default defineComponent({ properties, body })
```

If you change a helper — say, swap `PropertyNumber` for `PropertyString` — the body's `props.count` becomes `string` automatically. The schema and the body type can't drift.

<Tip>
  Type inference works because the helpers carry their runtime type through the type system. Declaring `const properties = { ... } as const` is unnecessary — and counterproductive, since it loses helper-level metadata that the inference needs.
</Tip>

### Working with children

The `children` argument is a `Component[]` of the components a parent passed in. Two patterns cover most cases — spread the array into a layout container, or wrap it in a stack to apply common modifiers across the whole group:

```typescript theme={null}
const body = (props, children) =>
    VStack({ spacing: 12 }, [
        Text(props.title).font("headline"),
        ...children,
    ])
```

A caller supplies children as the second argument to the component's invocation:

```typescript theme={null}
MyCard({ title: "Welcome" }, [
    Text("Hello, world."),
    Button("Continue", () => proceed()),
])
```

For components that accept slots of constrained child types — a layout that only allows `Heading`, `Paragraph`, and `Image` children, for example — declare those slots as named properties using `PropertyComponent` and `PropertyArray` rather than relying on the implicit `children` array. See [Slots of child components](/bindjs/authoring/properties#slots-of-child-components) for the patterns.

### How a body differs from React

A few mechanics carry over from React, but the surface is different:

* **No JSX.** Components are function calls (`Text("Hi")`), not elements (`<Text>Hi</Text>`). Modifiers chain off the result.
* **Return an AST, not React elements.** The renderer walks the AST and produces native views — SwiftUI, Compose, or React DOM — depending on the platform.
* **Props are typed via the schema.** No `interface MyProps {...}`; the helper-built `properties` record drives the inferred type.
* **State and context use runtime hooks.** [`useState`](/bindjs/functions/useState), [`useStore`](/bindjs/functions/useStore), and `useEnvironment` are runtime-injected — see [State and environment](/bindjs/authoring/state).
* **Styling and behavior come from modifiers.** `.padding`, `.font`, `.onTapGesture`, and the rest are method calls on a component, not props you pass in.

## Optional fields

`metadata`, `previews`, `thumbnail`, and `icon` are optional. Components without them still render correctly; they exist to make the component discoverable, previewable, and recognizable inside editors and design tools.

### `metadata`

Identification and discoverability information surfaced in editors, galleries, and documentation:

```typescript theme={null}
defineComponent({
    metadata: {
        title: "Primary Button",
        description: "A reusable filled button with a brand-colored background.",
        category: "Controls",
    },
    properties,
    body,
})
```

| Field         | Type     | Description                                              |
| ------------- | -------- | -------------------------------------------------------- |
| `title`       | `string` | Display name shown in component pickers and inspectors.  |
| `description` | `string` | One-sentence description used in galleries and tooltips. |
| `category`    | `string` | Optional category label used to group components.        |

### `previews`

Preview instances rendered in galleries and design tools. Use `Self({...})` to instantiate the component itself with sample props, then attach a `.previewName(...)` so each variant is labeled:

```typescript theme={null}
defineComponent({
    properties,
    body,
    previews: [
        Self({ title: "Preview Title", showSecondary: true })
            .previewName("Default"),
        Self({ title: "A long title that wraps", showSecondary: false })
            .previewName("Long title"),
    ],
})
```

`Self`'s prop types are inferred from the component's own `properties` schema, so previews stay in sync with the inputs the body actually accepts.

### `thumbnail`

A representative image used in component pickers. It accepts either an SVG string or a render function returning a `Component`:

```typescript theme={null}
defineComponent({
    properties,
    body,
    thumbnail: () =>
        Rectangle()
            .fill(Color("blue"))
            .frame({ width: 64, height: 32 })
            .cornerRadius(8),
})
```

### `icon`

A short icon name used in menus and context menus. The host resolves the name against its icon registry:

```typescript theme={null}
defineComponent({
    properties,
    body,
    icon: "rectangle.fill",
})
```

## Custom components are `defineComponent` calls

There's no separate "custom component" primitive. A reusable component — a button, a card, a chart row — is defined with the same `defineComponent` shape as any other component. Once published, the component is callable from any other component in the same package.

```typescript theme={null}
// PrimaryButton.js
const properties = {
    title: PropertyString({
        title: "Button Title",
        defaultValue: "Button",
        required: true,
    }),
}

const body = (props, children) =>
    Button({
        label: Text(props.title)
            .foregroundStyle(Color("white"))
            .fontWeight("bold"),
        action: () => props.onPress?.(),
    })
        .padding(16)
        .background(Color("blue"))
        .cornerRadius(8)

export default defineComponent({
    metadata: { title: "PrimaryButton" },
    properties,
    body,
})
```

From any other component in the same package:

```typescript theme={null}
const body = (props, children) =>
    VStack({ spacing: 12 }, [
        Text("Save your work"),
        PrimaryButton({
            title: "Save Changes",
            onPress: () => console.log("Saving…"),
        }),
    ])
```

How user-authored components reach the runtime's lookup is implementation-defined — see [Composition and lookup](/bindjs/authoring/composition) for the resolution model. For Metabind specifically, components live inside immutable, semantically-versioned [packages](/guides/concepts/components).

<Note>
  A component that other components call doesn't need a `metadata` block, but providing one makes it discoverable in MCP App Studio and any editor that browses the package's component list.
</Note>

## `defineButtonStyle` — the sibling primitive

BindJS exposes one other authoring primitive: `defineButtonStyle`, used to define a custom button style applied via the `.buttonStyle()` modifier. It uses the same default-export pattern as `defineComponent`, but with a different body signature.

```typescript theme={null}
export default defineButtonStyle({
    metadata: {
        title: "Capsule Button",
        description: "A pill-shaped filled button style.",
    },
    body: (configuration, props) =>
        Capsule()
            .fill(Color(props?.color || "blue"))
            .overlay(configuration.label)
            .frame({ height: 44 }),
})
```

The body for a button style takes a different first argument:

| Argument        | Type                                       | Description                                                             |
| --------------- | ------------------------------------------ | ----------------------------------------------------------------------- |
| `configuration` | `{ label: Component, isPressed: boolean }` | The label the caller supplied and the current pressed state.            |
| `props`         | `object` (optional)                        | Style-level props. Type-inferred from `properties` if you declare them. |

To apply a custom button style, pass it to `.buttonStyle()`:

```typescript theme={null}
Button("Save", () => save())
    .buttonStyle(CapsuleButton({ color: "green" }))
```

<Tip>
  Use `defineButtonStyle` when you want one styling treatment to wrap many call sites uniformly. For one-off buttons, apply modifiers directly to a `Button(...)` and skip the style primitive.
</Tip>

## What to read next

<CardGroup cols={2}>
  <Card title="Properties" icon="list-check" href="/bindjs/authoring/properties">
    The schema-and-validation layer behind the body's typed `props`.
  </Card>

  <Card title="Composition" icon="diagram-project" href="/bindjs/authoring/composition">
    How components find each other at runtime, recursive `Self`, and slots of child components.
  </Card>

  <Card title="State and environment" icon="memory" href="/bindjs/authoring/state">
    Component-local state with `useState`, shared state with `useStore`, and context with `useEnvironment`.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/bindjs/quickstart">
    A short walkthrough that puts a component on screen end-to-end.
  </Card>
</CardGroup>
