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

# Media

> Images, video playback, and interactive 3D models

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" />;
};

`Image` displays a bitmap, system symbol, or inline SVG from a URL, an asset name, or base64 data. `Video` plays remote or local video with autoplay, muting, looping, and controls options. `Model3D` renders an interactive 3D model with optional camera controls and auto-rotation.

## Image

Displays an image from a URL, asset name, system icon, or inline SVG.

```typescript theme={null}
Image(props: ImageProps): Image;
```

The `ImageProps` type is a union requiring exactly one image source, plus an optional `contentMode`:

```typescript theme={null}
type ImageProps = { contentMode?: "fit" | "fill" } &
    ({ name: string } | { systemName: string } | { url: string } | { image: string } | { svg: string });
```

<ParamField path="contentMode" type="&#x22;fit&#x22; | &#x22;fill&#x22;" optional>
  How the image should be scaled to fit its container.
</ParamField>

<ParamField path="name" type="string">
  Name of an image asset in the app bundle.
</ParamField>

<ParamField path="systemName" type="string">
  Name of a system-provided symbol (SF Symbols on iOS).
</ParamField>

<ParamField path="url" type="string">
  URL of a remote image to load.
</ParamField>

<ParamField path="image" type="string">
  Base64-encoded image data.
</ParamField>

<ParamField path="svg" type="string">
  Inline SVG markup as a string.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: "URL and SVG images supported. Methods .resizable(), .renderingMode(), .interpolation(), .antialiased(), .symbolRenderingMode(), .imageScale() not supported. systemName limited, name and image props not supported." },
web: { status: "supported" },
}}
/>

The `Image` component returns an extended `Image` interface with image-specific chainable methods in addition to the standard `Component` modifiers:

<ParamField path=".resizable()" type="() => Image">
  Makes the image resizable so it fills its frame. Required for `.frame()` to affect image size.
</ParamField>

<ParamField path=".renderingMode(mode)" type="(mode: &#x22;original&#x22; | &#x22;template&#x22;) => Image">
  Controls how the image is rendered. `"original"` preserves the image's original colors; `"template"` renders as a single color using the current foreground style.
</ParamField>

<ParamField path=".interpolation(quality)" type="(quality: &#x22;none&#x22; | &#x22;low&#x22; | &#x22;medium&#x22; | &#x22;high&#x22;) => Image">
  Sets the interpolation quality for scaled images.
</ParamField>

<ParamField path=".antialiased(isAntialiased?)" type="(isAntialiased?: boolean) => Image">
  Enables or disables antialiasing on image edges. Defaults to `true` if called without arguments.
</ParamField>

<ParamField path=".symbolRenderingMode(mode)" type="(mode: &#x22;monochrome&#x22; | &#x22;hierarchical&#x22; | &#x22;palette&#x22; | &#x22;multicolor&#x22;) => Image">
  Sets how multi-layer system symbols are rendered.
</ParamField>

<ParamField path=".imageScale(scale)" type="(scale: &#x22;small&#x22; | &#x22;medium&#x22; | &#x22;large&#x22;) => Image">
  Sets the symbol scale relative to adjacent text.
</ParamField>

**Image from URL**

```typescript theme={null}
Image({ url: "https://example.com/photo.jpg" })
    .resizable()
    .scaledToFit()
    .frame({ height: 200 })
```

**System symbol**

```typescript theme={null}
Image({ systemName: "star.fill" })
    .foregroundStyle(Color("yellow"))
```

**Asset image**

```typescript theme={null}
Image({ name: "hero-image" })
    .resizable()
    .scaledToFill()
    .frame({ width: 300, height: 200 })
    .clipped()
```

**Inline SVG**

```typescript theme={null}
Image({ svg: "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='blue'/></svg>" })
    .resizable()
    .frame({ width: 100, height: 100 })
```

**Resizable with frame**

```typescript theme={null}
Image({ url: "https://example.com/photo.jpg" })
    .resizable()
    .frame({ width: 200, height: 200 })
```

**Template rendering with foreground color**

```typescript theme={null}
Image({ systemName: "envelope.fill" })
    .renderingMode("template")
    .imageScale("large")
    .foregroundStyle(Color("blue"))
```

**Pixel-art interpolation**

```typescript theme={null}
Image({ name: "pixel-art" })
    .resizable()
    .interpolation("none")
    .frame({ width: 200, height: 200 })
```

**Multicolor system symbol**

```typescript theme={null}
Image({ systemName: "cloud.sun.rain.fill" })
    .symbolRenderingMode("multicolor")
```

## Video

Plays video from a URL or asset name with configurable playback options.

```typescript theme={null}
Video(props: VideoProps): Component;
```

The `VideoProps` type requires either a `url` or `video` source, plus optional playback settings:

```typescript theme={null}
type VideoProps = { autoplay?: boolean; muted?: boolean; controls?: boolean; loop?: boolean; contentMode?: "fit" | "fill" } &
    ({ url: string } | { video: string });
```

<ParamField path="url" type="string">
  URL of a remote video to load.
</ParamField>

<ParamField path="video" type="string">
  Local video asset name.
</ParamField>

<ParamField path="autoplay" type="boolean" optional>
  Whether the video starts playing automatically. Defaults to `false`.
</ParamField>

<ParamField path="muted" type="boolean" optional>
  Whether the video is muted. Defaults to `false`.
</ParamField>

<ParamField path="controls" type="boolean" optional>
  Whether playback controls are shown. Defaults to `true`.
</ParamField>

<ParamField path="loop" type="boolean" optional>
  Whether the video loops continuously. Defaults to `false`.
</ParamField>

<ParamField path="contentMode" type="&#x22;fit&#x22; | &#x22;fill&#x22;" optional>
  How the video is scaled within its frame. Defaults to `"fit"`.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: "video prop (local assets) is not implemented; use url instead" },
web: { status: "supported" },
}}
/>

**Basic video**

```typescript theme={null}
Video({ url: "https://example.com/video.mp4" })
```

**Autoplay background video**

```typescript theme={null}
Video({
    url: "https://example.com/hero.mp4",
    autoplay: true,
    muted: true,
    loop: true,
    controls: false,
    contentMode: "fill"
})
    .frame({ height: 300 })
    .cornerRadius(12)
```

**Local video asset**

```typescript theme={null}
Video({ video: "intro.mp4" })
```

**Fit content mode**

```typescript theme={null}
Video({ url: "https://example.com/video.mp4", contentMode: "fit" })
    .frame({ width: 400, height: 300 })
```

**Fill content mode**

```typescript theme={null}
Video({ url: "https://example.com/video.mp4", contentMode: "fill" })
    .frame({ width: 400, height: 300 })
    .clipped()
```

## Model3D

Displays an interactive 3D model from a URL.

```typescript theme={null}
Model3D(props: Model3DProps): Component;
```

```typescript theme={null}
type Model3DProps = {
    url: string;
    iOSURL?: string;
    description?: string;
    cameraControls: boolean;
    autoRotate: boolean;
};
```

<ParamField path="url" type="string" required>
  URL to the 3D model file. Supports common formats like `.glb`, `.gltf`, and `.usdz`.
</ParamField>

<ParamField path="iOSURL" type="string" optional>
  iOS-specific URL for the model, typically a `.usdz` file optimized for Apple platforms. When provided, iOS uses this URL while other platforms use `url`.
</ParamField>

<ParamField path="description" type="string" optional>
  Accessibility description of the 3D model for screen readers.
</ParamField>

<ParamField path="cameraControls" type="boolean" required>
  Whether the user can zoom, pan, and rotate the camera to examine the model.
</ParamField>

<ParamField path="autoRotate" type="boolean" required>
  Whether the model automatically rotates for showcase purposes.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: "Scene lifecycle issues when used inside ScrollView" },
web: { status: "supported" },
}}
/>

**Basic 3D model**

```typescript theme={null}
Model3D({
    url: "https://example.com/models/chair.glb",
    cameraControls: true,
    autoRotate: false
})
```

**Auto-rotating product showcase**

```typescript theme={null}
Model3D({
    url: "https://example.com/models/shoe.glb",
    iOSURL: "https://example.com/models/shoe.usdz",
    description: "Running shoe 3D model",
    cameraControls: true,
    autoRotate: true
})
    .frame({ height: 400 })
```

**Static display**

```typescript theme={null}
Model3D({
    url: "https://example.com/models/logo.glb",
    cameraControls: false,
    autoRotate: true
})
    .frame({ width: 200, height: 200 })
```

<Note>
  `.usdz` format is recommended for iOS AR Quick Look integration. `.glb` / `.gltf` formats provide good cross-platform compatibility. Auto-rotate is useful for product showcases where user interaction is not required.
</Note>

## See also

* [Text](/bindjs/components/Text) — captions and overlays for media
* [Markdown](/bindjs/components/Markdown) — render rich text alongside media
* [ZStack](/bindjs/components/ZStack) — overlay text or controls onto media
* [LinearGradient](/bindjs/styles/LinearGradient) — readability gradient over images
