Imperative Surface Imperative Surface

App Router

Imperative Surface can be used from Next.js App Router applications.

Because Surface.Dialog.present() and Surface.Sheet.present() access browser APIs, call them from client-side code.

Client component

'use client';

import { Surface } from 'imperative-surface';

export function DeleteButton() {
  async function handleDelete() {
    const confirmed = await Surface.Dialog.present<boolean>({
      body: ConfirmDialog,
    });

    if (!confirmed) return;

    // Perform deletion.
  }

  return (
    <button onClick={handleDelete}>
      Delete
    </button>
  );
}

Keeping the body client-side

The component passed to body should be usable in the browser because the surface is mounted with createRoot().

A practical pattern is to keep both the trigger and surface body in a client module:

'use client';

function ConfirmDialog({ pop }: SurfaceContext<boolean>) {
  // ...
}

export function Trigger() {
  // ...
}

Server actions

You can still call a server action after receiving a result from the surface:

'use client';

const confirmed = await Surface.Dialog.present<boolean>({
  body: ConfirmDialog,
});

if (confirmed) {
  await deleteItem();
}

The surface handles the client-side interaction; the server action handles server-side work.

On this page