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

# Presentation

> Modifiers for presenting modal sheets, full-screen covers, and Quick Look previews

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 iOS-only modifiers present content over the current view — half-height sheets, full-screen covers, and Quick Look previews for files. Each is bound to a `boolean` (or URL) state and toggles presentation when that state changes.

`sheet` and `fullScreenCover` are the two primary modal patterns. Combine `sheet` with [presentationDetents](#presentationdetents) to control sheet snap heights. `quickLookPreview` opens the system Quick Look UI for documents, images, and 3D models.

## sheet

Presents a modal sheet over the current content.

```typescript theme={null}
.sheet(props: {
    isPresented: boolean;
    setIsPresented: (value: boolean) => void;
    content: () => Component;
    onDismiss?: () => void;
}): Component
```

<ParamField path="props" type="object" required>
  <Expandable title="properties">
    <ParamField path="isPresented" type="boolean" required>
      Controls whether the sheet is visible. Set to `true` to present, `false` to dismiss.
    </ParamField>

    <ParamField path="setIsPresented" type="(value: boolean) => void" required>
      Callback to update the presentation state. Called with `false` when the user dismisses the sheet by swiping down or tapping outside.
    </ParamField>

    <ParamField path="content" type="() => Component" required>
      A function that returns the sheet's content. Called lazily when the sheet is presented.
    </ParamField>

    <ParamField path="onDismiss" type="() => void" optional>
      Callback fired after the sheet has been fully dismissed.
    </ParamField>
  </Expandable>
</ParamField>

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

**Basic sheet**

```typescript theme={null}
const body = () => {
    const [showSheet, setShowSheet] = useState(false)
    return VStack([
        Button("Show Sheet", () => setShowSheet(true))
    ]).sheet({
        isPresented: showSheet,
        setIsPresented: setShowSheet,
        content: () => VStack([
            Text("Sheet Content")
                .font("title"),
            Button("Close", () => setShowSheet(false))
        ]).padding(16)
    })
}
```

**Sheet with dismiss callback**

```typescript theme={null}
const body = () => {
    const [showSheet, setShowSheet] = useState(false)
    return VStack([
        Button("Edit", () => setShowSheet(true))
    ]).sheet({
        isPresented: showSheet,
        setIsPresented: setShowSheet,
        content: () => Text("Editor").padding(16),
        onDismiss: () => {
            console.log("Sheet dismissed")
        }
    })
}
```

**Sheet with detents**

Combine with [presentationDetents](#presentationdetents) to control the sheet's available heights.

```typescript theme={null}
const body = () => {
    const [showSheet, setShowSheet] = useState(false)
    return VStack([
        Button("Open", () => setShowSheet(true))
    ]).sheet({
        isPresented: showSheet,
        setIsPresented: setShowSheet,
        content: () => VStack([
            Text("Half sheet")
                .font("headline"),
            Spacer()
        ]).padding(16)
    }).presentationDetents([Detent.medium, Detent.large])
}
```

## fullScreenCover

Presents a full-screen modal cover over the current content.

```typescript theme={null}
.fullScreenCover(props: {
    isPresented: boolean,
    setIsPresented: (value: boolean) => void,
    content: () => Component,
    onDismiss?: () => void
}): Component
```

<ParamField path="props" type="object" required>
  Configuration for the full-screen cover presentation.

  <Expandable title="properties">
    <ParamField path="isPresented" type="boolean" required>
      Whether the full-screen cover is currently shown.
    </ParamField>

    <ParamField path="setIsPresented" type="(value: boolean) => void" required>
      Callback to update the presented state. Called with `false` when the cover is dismissed.
    </ParamField>

    <ParamField path="content" type="() => Component" required>
      A function returning the component to display inside the full-screen cover.
    </ParamField>

    <ParamField path="onDismiss" type="() => void" optional>
      Called after the cover has been dismissed.
    </ParamField>
  </Expandable>
</ParamField>

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

**Basic full-screen cover**

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

    return VStack([
        Button("Show Full Screen", () => setShowCover(true))
    ]).fullScreenCover({
        isPresented: showCover,
        setIsPresented: setShowCover,
        content: () => VStack([
            Text("Full Screen Content")
                .font("title"),
            Button("Dismiss", () => setShowCover(false))
        ])
    })
}
```

**With onDismiss callback**

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

    return VStack([
        Button("Open", () => setShowCover(true))
    ]).fullScreenCover({
        isPresented: showCover,
        setIsPresented: setShowCover,
        content: () => Text("Cover content"),
        onDismiss: () => {
            console.log("Cover dismissed")
        }
    })
}
```

<Note>
  Unlike [sheet](#sheet), a full-screen cover takes over the entire screen and does not show a drag indicator. The user cannot dismiss it by swiping down — provide an explicit dismiss action (e.g., a button that sets `isPresented` to `false`).
</Note>

## presentationDetents

Sets the available sheet size detents (snap points) for a presented sheet.

```typescript theme={null}
.presentationDetents(detents: PresentationDetents): Component
```

<ParamField path="detents" type="PresentationDetents" required>
  An array of detent configurations that define the available sheet heights. Each detent is a `PresentationDetent` object with a `detentType` property.

  Use the `Detent` convenience constructors:

  * `Detent.medium` -- half-screen height
  * `Detent.large` -- full-screen height
  * `Detent.fraction(value)` -- fraction of screen height (0 to 1)
  * `Detent.height(value)` -- exact height in points
</ParamField>

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

**Medium and large detents**

```typescript theme={null}
const body = () => {
    const [showSheet, setShowSheet] = useState(false)
    return VStack([
        Button("Open Sheet", () => setShowSheet(true))
    ]).sheet({
        isPresented: showSheet,
        setIsPresented: setShowSheet,
        content: () => Text("Sheet content").padding(16)
    }).presentationDetents([Detent.medium, Detent.large])
}
```

**Custom fraction detent**

```typescript theme={null}
const body = () => {
    const [showSheet, setShowSheet] = useState(false)
    return VStack([
        Button("Show", () => setShowSheet(true))
    ]).sheet({
        isPresented: showSheet,
        setIsPresented: setShowSheet,
        content: () => Text("Quarter sheet").padding(16)
    }).presentationDetents([Detent.fraction(0.25), Detent.medium])
}
```

**Fixed height detent**

```typescript theme={null}
const body = () => {
    const [showSheet, setShowSheet] = useState(false)
    return VStack([
        Button("Show", () => setShowSheet(true))
    ]).sheet({
        isPresented: showSheet,
        setIsPresented: setShowSheet,
        content: () => Text("Fixed height sheet").padding(16)
    }).presentationDetents([Detent.height(300), Detent.large])
}
```

<Note>
  Apply `.presentationDetents()` to the same component that has the `.sheet()` modifier. When multiple detents are provided, the user can drag between them. If only one detent is provided, the sheet snaps to that height and cannot be resized.
</Note>

## quickLookPreview

Presents a Quick Look preview for a file URL.

```typescript theme={null}
.quickLookPreview(props: {
    url?: string;
    setURL?: (url: string | null) => void;
    urls?: string[];
    onLoadingChanged?: (isLoading: boolean) => void;
}): Component
```

<ParamField path="props" type="object" required>
  <Expandable title="properties">
    <ParamField path="url" type="string" optional>
      The URL of the file to preview. When non-null, the Quick Look preview is presented. Set to `null` to dismiss.
    </ParamField>

    <ParamField path="setURL" type="(url: string | null) => void" optional>
      Callback to update the URL state, typically used to dismiss the preview by setting `null`.
    </ParamField>

    <ParamField path="urls" type="string[]" optional>
      An array of file URLs for multi-item preview with swipe navigation.
    </ParamField>

    <ParamField path="onLoadingChanged" type="(isLoading: boolean) => void" optional>
      Callback fired when the loading state of the preview changes.
    </ParamField>
  </Expandable>
</ParamField>

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

**Single file preview**

```typescript theme={null}
const body = () => {
    const [previewURL, setPreviewURL] = useState(null)
    return VStack([
        Button("Preview PDF", () => setPreviewURL("https://example.com/doc.pdf"))
    ]).quickLookPreview({
        url: previewURL,
        setURL: setPreviewURL,
    })
}
```

**Multi-file preview**

```typescript theme={null}
const body = () => {
    const [previewURL, setPreviewURL] = useState(null)
    const files = [
        "https://example.com/photo1.jpg",
        "https://example.com/photo2.jpg",
        "https://example.com/doc.pdf",
    ]
    return VStack([
        Button("View Files", () => setPreviewURL(files[0]))
    ]).quickLookPreview({
        url: previewURL,
        setURL: setPreviewURL,
        urls: files,
    })
}
```

<Note>
  Quick Look supports many file types including images, PDFs, 3D models, and documents. The preview is presented modally and dismissed by the user or by setting the URL to `null`.
</Note>

## See also

* [Navigation chrome](/bindjs/modifiers/navigation-chrome) — push views onto a navigation stack
* [Toolbar](/bindjs/modifiers/toolbar) — add toolbar items to presented content
* [NavigationStack](/bindjs/components/NavigationStack) — navigation container
