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.