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

# Identity and lifecycle

> Modifiers that establish a stable identity for diffing and animation, and that run callbacks when a component appears or disappears

export const PlatformStatuses = ({statuses}) => {
  const StatusBadge = ({status, label}) => {
    const styles = {
      green: {
        backgroundColor: '#dcfce7',
        color: '#166534'
      },
      orange: {
        backgroundColor: '#fed7aa',
        color: '#9a3412'
      },
      red: {
        backgroundColor: '#fecaca',
        color: '#991b1b'
      },
      gray: {
        backgroundColor: '#f3f4f6',
        color: '#4b5563'
      }
    };
    const baseStyle = {
      display: 'inline-flex',
      alignItems: 'center',
      padding: '0.125rem 0.625rem',
      borderRadius: '9999px',
      fontSize: '0.875rem',
      fontWeight: '500'
    };
    const colorStyle = styles[status] || styles.green;
    return <span style={{
      ...baseStyle,
      ...colorStyle
    }}>
        {label || status}
      </span>;
  };
  const STATUS_CONFIG = {
    supported: {
      label: "Supported",
      color: "green"
    },
    partial: {
      label: "Partial",
      color: "orange"
    },
    "not-implemented": {
      label: "Not Implemented",
      color: "gray"
    }
  };
  const renderCard = (platform, value) => {
    if (!value) return null;
    const {status, note} = typeof value === "string" ? {
      status: value
    } : value;
    const config = STATUS_CONFIG[status];
    if (!config) return null;
    const titleMap = {
      ios: "SwiftUI",
      android: "Jetpack Compose",
      web: "Web"
    };
    return <Card key={platform} title={titleMap[platform] || platform}>
          <StatusBadge status={config.color} label={config.label} />
          {note && <div style={{
      marginTop: '0.5rem',
      fontSize: '0.875rem',
      color: '#6b7280'
    }}>
              {note}
            </div>}
      </Card>;
  };
  if (statuses == null) {
    return null;
  }
  return <Columns cols="3">
      {Object.entries(statuses).map(([platform, value]) => renderCard(platform, value))}
    </Columns>;
};

export const ComposeJS = ({code, name, height}) => {
  const encodedCode = useMemo(() => {
    if (!code) return "";
    try {
      return btoa(code);
    } catch (e) {
      console.error("Failed to encode code", e);
      return "";
    }
  }, [code]);
  if (!encodedCode) {
    return null;
  }
  return <iframe src={`https://www.metabind.ai/embed?code=${encodedCode}&name=${name ?? 'Example'}`} loading="lazy" style={{
    width: "100%",
    height: height || '350px',
    border: "1px solid #e5e7eb",
    borderRadius: "var(--rounded-2xl,1rem)",
    overflow: "hidden"
  }} title="ComposeJS Preview" />;
};

These modifiers connect a component to the framework's identity and lifecycle machinery. `id` gives a component a stable identity for diffing, animation, and scroll position tracking. `tag` associates a component with a selection value inside controls like [Picker](/bindjs/components/Picker).

`onAppear` and `onDisappear` run a callback when the component is inserted into or removed from the view tree — use them to load data, start and stop timers, or trigger animations.

## id

Sets a stable identity for diffing and animations.

```typescript theme={null}
.id(value: string | number): Component
```

<ParamField path="value" type="string | number" required>
  A unique identifier for this component. Used by the framework for diffing, animations, and scroll position tracking.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Identifying items in a list**

```typescript theme={null}
ForEach(items, (item) =>
    Text(item.name).id(item.id)
)
```

**Triggering view replacement with identity changes**

Changing the `id` value tells the framework this is a new component, triggering removal of the old one and insertion of the new one (including any transition animations).

```typescript theme={null}
const body = () => {
    const [selectedTab, setSelectedTab] = useState("home")

    return Text(selectedTab)
        .id(selectedTab)
        .transition("opacity")
}
```

**Scroll position tracking**

Use `.id()` together with `.scrollPosition()` to track or scroll to specific children.

```typescript theme={null}
const body = () => {
    const [scrollId, setScrollId] = useState(null)

    return ScrollView([
        ForEach(items, (item) =>
            Text(item.name).id(item.id)
        )
    ]).scrollPosition({ id: scrollId, setId: setScrollId })
}
```

## tag

Sets a tag value for identifying a component in selection contexts.

```typescript theme={null}
.tag(value: any): Component
```

<ParamField path="value" type="any" required>
  The tag value used to identify this component. Typically a string or number. Must match the type of the Picker's selection value.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Picker items**

Each item in a Picker must have a `.tag()` that corresponds to the selection value.

```typescript theme={null}
const body = () => {
    const [size, setSize] = useState("m")
    return Picker("Size", { value: size, setValue: setSize }, [
        Text("Small").tag("s"),
        Text("Medium").tag("m"),
        Text("Large").tag("l"),
    ])
}
```

**Numeric tags**

```typescript theme={null}
const body = () => {
    const [rating, setRating] = useState(3)
    return Picker("Rating", { value: rating, setValue: setRating }, [
        Text("1 Star").tag(1),
        Text("2 Stars").tag(2),
        Text("3 Stars").tag(3),
        Text("4 Stars").tag(4),
        Text("5 Stars").tag(5),
    ]).pickerStyle("wheel")
}
```

The `.tag()` value must match the type used in the Picker's `value` and `setValue` props. Tags are primarily used with Picker components to associate each option with a selection value.

## onAppear

Runs an action when the component first appears on screen.

```typescript theme={null}
.onAppear(action: () => void): Component
```

<ParamField path="action" type="() => void" required>
  A callback that runs when the component appears on screen for the first time.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Logging when a view appears**

```typescript theme={null}
Text("Hello")
    .onAppear(() => {
        console.log("View appeared")
    })
```

**Loading data on appear**

```typescript theme={null}
const body = () => {
    const [data, setData] = useState(null)

    return VStack([
        data
            ? Text(data.title)
            : Text("Loading...")
    ]).onAppear(() => {
        fetchData().then(setData)
    })
}
```

**Triggering animations**

```typescript theme={null}
const body = () => {
    const [isVisible, setIsVisible] = useState(false)

    return Text("Fade in")
        .opacity(isVisible ? 1 : 0)
        .onAppear(() => setIsVisible(true))
}
```

The action runs once when the component is first inserted into the view tree, not every time it becomes visible (e.g., after scrolling back into view).

## onDisappear

Runs an action when the component is removed from the screen.

```typescript theme={null}
.onDisappear(action: () => void): Component
```

<ParamField path="action" type="() => void" required>
  A callback that runs when the component is removed from the view tree.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Cleanup on removal**

```typescript theme={null}
Text("Temporary view")
    .onDisappear(() => {
        console.log("View removed")
    })
```

**Stopping a timer**

```typescript theme={null}
const body = () => {
    const [count, setCount] = useState(0)
    const [timerId, setTimerId] = useState(null)

    return Text(String(count))
        .onAppear(() => {
            const id = setInterval(() => {
                setCount((c) => c + 1)
            }, 1000)
            setTimerId(id)
        })
        .onDisappear(() => {
            clearInterval(timerId)
        })
}
```

The action runs when the component is removed from the view tree, such as when a conditional hides it or when navigating away.

## See also

* [onChange](/bindjs/modifiers/onChange) — runs an action when a value changes
* [Picker](/bindjs/components/Picker) — selection control that uses `tag` values
* [scrollPosition](/bindjs/modifiers/scrollPosition) — track or scroll to children identified by `id`
* [transition](/bindjs/modifiers/transition) — animate insertion and removal triggered by identity changes
