# Image



import ImageWithActions from "@/components/nexus-ui/examples/image/with-actions";
import ImageDefault from "@/components/nexus-ui/examples/image/default";
import ImageNoSourcePlaceholder from "@/components/nexus-ui/examples/image/no-source-placeholder";
import ImageExternalSrc from "@/components/nexus-ui/examples/image/external-src";
import ImageLightboxExample from "@/components/nexus-ui/examples/image/lightbox";

Composable image primitives for multimodal UIs. `Image` accepts AI-generated payloads (`base64` or `uint8Array`) and exposes `ImagePreview`, `ImageLoader`, and optional action slots.

<DemoWithCode src="components/nexus-ui/examples/image/default.tsx">
  <ImageDefault />
</DemoWithCode>

Installation [#installation]

<Tabs items={["CLI", "Manual"]} framed={false}>
  <Tab value="CLI">
    <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
      <Tab value="npm">
        ```bash
        npx shadcn@latest add @nexus-ui/image
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm dlx shadcn@latest add @nexus-ui/image
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn dlx shadcn@latest add @nexus-ui/image
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx shadcn@latest add @nexus-ui/image
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab value="Manual">
    <Steps>
      <Step>
        <h3>
          Install the following dependencies:
        </h3>

        <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
          <Tab value="npm">
            ```bash
            npx shadcn@latest add tooltip kbd && npm install @radix-ui/react-slot @radix-ui/react-dialog
            ```
          </Tab>

          <Tab value="pnpm">
            ```bash
            pnpm dlx shadcn@latest add tooltip kbd && pnpm add @radix-ui/react-slot @radix-ui/react-dialog
            ```
          </Tab>

          <Tab value="yarn">
            ```bash
            yarn dlx shadcn@latest add tooltip kbd && yarn add @radix-ui/react-slot @radix-ui/react-dialog
            ```
          </Tab>

          <Tab value="bun">
            ```bash
            bunx shadcn@latest add tooltip kbd && bun add @radix-ui/react-slot @radix-ui/react-dialog
            ```
          </Tab>
        </Tabs>
      </Step>

      <Step>
        <h3>
          Copy and paste the following code into your project.
        </h3>

        <ComponentSource src="components/nexus-ui/image.tsx" title="components/nexus-ui/image.tsx" />
      </Step>

      <Step>
        <h3>
          Update import paths to match your project setup.
        </h3>
      </Step>
    </Steps>
  </Tab>
</Tabs>

Usage [#usage]

```tsx keepBackground
import {
  Image,
  ImagePreview,
  ImageLightbox,
  ImageLightboxOverlay,
  ImageLightboxPreview,
  ImageLightboxClose,
  ImageLoader,
  ImageActions,
  ImageActionGroup,
  ImageAction,
} from "@/components/nexus-ui/image";
```

```tsx keepBackground
<Image
  base64={base64Image}
  mediaType="image/png"
  uint8Array={new Uint8Array([])}
  alt="Generated image"
/>
```

Examples [#examples]

With Overlay Actions [#with-overlay-actions]

Compose `ImagePreview` with `ImageActions`, `ImageActionGroup`, and `ImageAction` for top/bottom/side controls. `ImageAction` supports built-in tooltips via `tooltip` as a string or object (`content`, optional `side`, optional `shortcut`).

<DemoWithCode src="components/nexus-ui/examples/image/with-actions.tsx" previewClassName="h-auto sm:h-[412px]">
  <ImageWithActions />
</DemoWithCode>

External URL [#external-url]

Use `src` for regular image URLs when you want `Image` to behave like a standard `<img>` source renderer.

<DemoWithCode src="components/nexus-ui/examples/image/external-src.tsx">
  <ImageExternalSrc />
</DemoWithCode>

No Source Placeholder [#no-source-placeholder]

Render `Image` without `base64` or `uint8Array` source to show the built-in placeholder loader.

<DemoWithCode src="components/nexus-ui/examples/image/no-source-placeholder.tsx">
  <ImageNoSourcePlaceholder />
</DemoWithCode>

Lightbox Preview [#lightbox-preview]

`ImagePreview` acts as the trigger, while `ImageLightbox` renders portal primitives (`ImageLightboxOverlay`, `ImageLightboxPreview`) and `ImageLightboxClose` can be placed externally in overlay actions.

<DemoWithCode src="components/nexus-ui/examples/image/lightbox.tsx">
  <ImageLightboxExample />
</DemoWithCode>

Vercel AI SDK Integration [#vercel-ai-sdk-integration]

Use `Image` with OpenAI image generation by returning `base64` and `mediaType` from your API route, then passing them directly to `Image`.

<Steps>
  <Step>
    <h3>
      Install the AI SDK
    </h3>

    ```bash
    npm install ai @ai-sdk/openai
    ```
  </Step>

  <Step>
    <h3>
      Create your chat route
    </h3>

    ```ts title="app/api/chat/route.ts"
    import { openai } from "@ai-sdk/openai";
    import { generateImage } from "ai";

    export async function POST(req: Request) {
      const { prompt }: { prompt: string } = await req.json();

      const { image } = await generateImage({
        model: openai.image("dall-e-3"),
        prompt: prompt,
        size: "1024x1024",
      });

      return Response.json({
        base64: image.base64,
        uint8Array: image.uint8Array ? Array.from(image.uint8Array) : undefined,
        mediaType: image.mediaType,
      });
    }
    ```
  </Step>

  <Step>
    <h3>
      Call the route and render the generated image
    </h3>

    ```tsx
    "use client";

    import { useState } from "react";
    import { Image } from "@/components/nexus-ui/image";

    type GeneratedImage = {
      base64?: string;
      uint8Array?: number[];
      mediaType?: string;
    };

    export function ChatWithImages() {
      const [prompt, setPrompt] = useState("A futuristic city skyline at sunset");
      const [generatedImage, setGeneratedImage] = useState<GeneratedImage | null>(null);
      const binaryImage = generatedImage?.uint8Array
        ? new Uint8Array(generatedImage.uint8Array)
        : undefined;

      async function onGenerate() {
        const response = await fetch("/api/chat", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ prompt }),
        });
        const data = (await response.json()) as GeneratedImage;
        setGeneratedImage(data);
      }

      return (
        <div className="space-y-3">
          <input
            value={prompt}
            onChange={(e) => setPrompt(e.target.value)}
            className="w-full rounded-md border px-3 py-2"
          />
          <button className="rounded-md border px-3 py-2" onClick={onGenerate}>
            Generate
          </button>

          {generatedImage?.base64 || binaryImage ? (
            <Image
              base64={generatedImage.base64}
              uint8Array={binaryImage}
              mediaType={generatedImage.mediaType ?? "image/png"}
              alt="Generated image"
              className="h-64 w-full"
            />
          ) : null}
        </div>
      );
    }
    ```
  </Step>
</Steps>

API Reference [#api-reference]

Image [#image]

Root container that resolves image source from `base64` or `uint8Array`, manages shared context, and wraps [Dialog Root](https://www.radix-ui.com/primitives/docs/components/dialog#root) for lightbox state.

<TypeTable
  type={{
  src: {
    type: "string",
    description:
      "Standard image URL source (e.g. external or local path). Used when `base64` and `uint8Array` are not provided.",
  },
  base64: {
    type: "string",
    description:
      "Raw base64 payload or a full data URL (`data:image/...;base64,...`).",
  },
  uint8Array: {
    type: "Uint8Array",
    description: "Binary image payload used when `base64` is not provided.",
  },
  mediaType: {
    type: "string",
    default: '"image/png"',
    description:
      "Preferred MIME type when creating the data URL/blob. Ignored when `base64` is a full `data:` URL.",
  },
  open: {
    type: "boolean",
    description: "Controlled open state for the internal dialog root.",
  },
  defaultOpen: {
    type: "boolean",
    description: "Initial uncontrolled open state for the internal dialog root.",
  },
  onOpenChange: {
    type: "(open: boolean) => void",
    description: "Callback fired when dialog open state changes.",
  },
  modal: {
    type: "boolean",
    default: "true",
    description: "Whether the dialog should behave as a modal.",
  },
  alt: {
    type: "string",
    default: '""',
    description: "Default alt text used by `ImagePreview`.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes on the image root container.",
  },
  children: {
    type: "React.ReactNode",
    description:
      "Optional custom composition. When omitted, `<ImagePreview />` is rendered.",
  },
}}
/>

ImagePreview [#imagepreview]

Renders the `<img>` element using source/alt from `Image` context and wraps [Dialog Trigger](https://www.radix-ui.com/primitives/docs/components/dialog#trigger). If there is no resolved source, it renders the loader placeholder.

<TypeTable
  type={{
  src: {
    type: "string",
    description:
      "Optional `img` source override for this preview instance. Falls back to `Image` root source when omitted.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes on the preview image/wrapper.",
  },
  alt: {
    type: "string",
    description:
      "Optional alt override. Falls back to `Image` root `alt` when omitted.",
  },
  onLoad: {
    type: "React.ReactEventHandler<HTMLImageElement>",
    description: "Called when the image successfully loads.",
  },
  onError: {
    type: "React.ReactEventHandler<HTMLImageElement>",
    description: "Called when image loading fails.",
  },
}}
/>

Also extends standard `img` props.

ImageLightbox [#imagelightbox]

Portal root for lightbox content. Wraps [Dialog Portal](https://www.radix-ui.com/primitives/docs/components/dialog#portal).

<TypeTable
  type={{
  container: {
    type: "HTMLElement",
    description: "Optional custom portal container element.",
  },
  forceMount: {
    type: "boolean",
    default: "false",
    description: "Forces portal subtree to stay mounted.",
  },
}}
/>

ImageLightboxOverlay [#imagelightboxoverlay]

Backdrop layer for the lightbox. Wraps [Dialog Overlay](https://www.radix-ui.com/primitives/docs/components/dialog#overlay).

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional CSS classes for the overlay.",
  },
  onClick: {
    type: "React.MouseEventHandler<HTMLDivElement>",
    description: "Optional click handler for the overlay element.",
  },
}}
/>

ImageLightboxPreview [#imagelightboxpreview]

Centered lightbox content surface that renders the image and optional children. Wraps [Dialog Content](https://www.radix-ui.com/primitives/docs/components/dialog#content).

<TypeTable
  type={{
  src: {
    type: "string",
    description:
      "Optional source override for the lightbox image. Falls back to `Image` context source.",
  },
  alt: {
    type: "string",
    description:
      "Optional alt override for the lightbox image. Falls back to `Image` context alt.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes for the content surface.",
  },
  children: {
    type: "React.ReactNode",
    description:
      "Optional overlay content rendered inside the preview content (e.g. actions).",
  },
  onInteractOutside: {
    type: "(event) => void",
    description:
      "Called when interacting outside content. Built-in handling keeps `ImageActions` interactions from dismissing.",
  },
}}
/>

ImageLightboxClose [#imagelightboxclose]

Close control primitive for the lightbox. Wraps [Dialog Close](https://www.radix-ui.com/primitives/docs/components/dialog#close).

<TypeTable
  type={{
  asChild: {
    type: "boolean",
    default: "false",
    description:
      "When true, composes close behavior onto the child element instead of rendering a button.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes on the close trigger.",
  },
}}
/>

ImageLoader [#imageloader]

Pulsing loader surface used by `ImagePreview` (both as background while image paints and as placeholder when no source exists).

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional CSS classes on the loader element.",
  },
}}
/>

ImageActions [#imageactions]

Absolute-positioned actions container for overlay controls.

<TypeTable
  type={{
  align: {
    type: '"inline-start" | "inline-end" | "block-start" | "block-end"',
    default: '"block-end"',
    description: "Placement of the action bar around the image frame.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes on the actions container.",
  },
}}
/>

ImageActionGroup [#imageactiongroup]

Horizontal grouping wrapper for related overlay actions.

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional CSS classes on the group.",
  },
}}
/>

ImageAction [#imageaction]

Wrapper for one action item. Supports polymorphism via `asChild` and optional built-in tooltip rendering.

<TypeTable
  type={{
  asChild: {
    type: "boolean",
    default: "false",
    description:
      "When true, merges props onto the child element instead of rendering a div.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes on the action wrapper.",
  },
  tooltip: {
    type: 'string | { content?: string; side?: "top" | "right" | "bottom" | "left"; shortcut?: string }',
    description:
      "Tooltip config. String form maps to tooltip content. Object form supports content, side, and keyboard shortcut. If content is omitted, no tooltip is rendered.",
  },
}}
/>
