Cloud|

@k2b/ui

Prompts

Alert, confirmation, search, form, and custom dialog flows.

prompts@k2b/ui

Browser-only alert, ordinary and typed confirmation, search, form, and custom-dialog flows, including a safe nested bare-surface example.

TSX

Copy
const confirmed = await prompts.confirm("Publish this release?", {
title: "Publish release",
confirmText: "Publish",
});
const deleted = await prompts.confirm("Delete Project Atlas?", {
title: "Delete project",
confirmText: "Delete project",
confirmationPhrase: "Project Atlas",
variant: "danger",
});
const selected = await prompts.search(resolveProjects, {
title: "Open project",
placeholder: "Search projects...",
});
const values = await prompts.form({
title: "New project",
confirmText: "Create",
fields: {
name: { type: "text", label: "Project name", required: true },
},
});
await prompts.dialog((close) => <MySurface close={close} />, {
surface: "bare",
header: false,
});

prompts opens modal alert, confirmation, input, search, form, and custom-dialog flows. Every method returns a promise, so the calling action can continue from the result.

The application owns the decision, validation rules, and side effect. The prompt owns modal stacking, focus, dismissal, and shared presentation.

Use prompts

Use a prompt when the user must acknowledge information, choose a result, or provide a small amount of data before work continues.

Use a toast for non-blocking feedback. Use PanelDialog for a persistent editor with tabs, sections, or a larger application workflow.

Import

tsx
import {
  Button,
  DialogHeader,
  openSpotlightSearch,
  prompts,
  SpotlightButton,
  type PromptSearchItem,
} from "@k2b/ui";

Properties

Choose a prompt

Method Result Use
prompts.alert(content, options) resolves when closed Information that requires acknowledgement.
prompts.success(content, options) resolves when closed Successful result that requires acknowledgement.
prompts.error(content, options) resolves when closed Failure details that must be read.
prompts.confirm(content, options) boolean | undefined One confirm-or-cancel decision.
prompts.prompt(content, defaultValue, options) string | null One text value.
prompts.promptNumber(content, defaultValue, options) number | null One numeric value with optional min and max.
prompts.form(config) typed values or null A small declarative form.
prompts.search(resolver, options) selected item or undefined An asynchronous picker.
prompts.dialog(component, options) caller-defined result Custom content and actions.

Cancellation returns false, null, or undefined according to the method. Handle it before starting a mutation.

Dialog options

Property Purpose
title, icon Name the dialog and add a Tabler icon.
confirmText, cancelText Override action labels. cancelText: false hides the cancel button in prompts.form.
confirmationPhrase Requires an exact typed phrase before prompts.confirm can confirm.
variant Selects primary, success, or danger action treatment.
size Selects small, medium, large, or wide.
surface Uses the standard panel or a caller-owned bare surface.
header Set to false when a custom dialog owns its header.
cancelBehavior prompts.alert can use "ignore" to keep Escape and backdrop clicks from closing it.

Use surface: "bare" with header: false only when the custom content supplies complete visible panel structure and a close control.

Typed confirmation

Use a non-empty, single-line confirmationPhrase for unusually consequential actions where an ordinary confirm-or-cancel decision does not provide enough friction. Empty, whitespace-only, multiline, and tab-containing phrases are rejected. The confirmation input receives focus, and the primary action remains disabled until the value matches the configured phrase exactly, including capitalization and spaces. Cancellation does not require entering the phrase.

confirmText continues to label the action button; it does not configure the phrase.

tsx
const confirmed = await prompts.confirm(
  "Delete Project Atlas and all stored data?",
  {
    title: "Delete project",
    confirmText: "Delete project",
    confirmationPhrase: "Project Atlas",
    variant: "danger",
  },
);

if (!confirmed) return;
await deleteProject("atlas");

Forms

prompts.form infers its return type from the field schema. Supported field types are:

  • text, including multiline, password, and markdown options;
  • number;
  • image;
  • pin;
  • select;
  • tags;
  • boolean;
  • datetime, with optional date-only mode;
  • info, which displays content and is excluded from the result.

Fields share label, description, placeholder, required, default, and a validate function where applicable. Form-state validation checks required values, text-length and tag-count constraints, and the custom validator. A required boolean field must be checked. Number bounds and PIN length configure their controls but do not create additional form-state error messages.

tsx
const values = await prompts.form({
  title: "Add member",
  icon: "ti ti-user-plus",
  fields: {
    name: {
      type: "text",
      label: "Name",
      required: true,
    },
    role: {
      type: "select",
      label: "Role",
      options: [
        { id: "admin", label: "Admin" },
        { id: "user", label: "User" },
      ],
    },
    active: {
      type: "boolean",
      label: "Active",
      default: true,
    },
  },
});

if (!values) return;

The search resolver receives the current query and an AbortSignal. It returns items with a label and optional desc, icon, root-relative previewUrl, or value. Results without desc use a compact single-line row; adding desc expands only that result to the two-line layout. Handle the selected value after the returned promise resolves; result items do not own application side effects.

Search options include placeholder, initialQuery, minQueryLength, debounceMs, emptyText, and noResultsText.

Honor the abort signal when the resolver calls a remote API:

tsx
const selected = await prompts.search(
  async ({ query, abortSignal }) => {
    const response = await fetch(`/api/users?q=${encodeURIComponent(query)}`, {
      signal: abortSignal,
    });
    const users = await response.json();

    return users.map((user) => ({
      label: user.name,
      desc: user.email,
      value: user,
      icon: "ti ti-user",
    }));
  },
  {
    title: "Assign owner",
    minQueryLength: 1,
    noResultsText: "No matching people.",
  },
);

Use openSpotlightSearch for application or workspace navigation. It supplies search-oriented defaults around prompts.search. SpotlightButton provides matching triggers for ordinary, compact, chip, sidebar, mobile-sidebar, and icon placements.

The shortcut is one contract shared by every trigger, exported so the application can bind the same keys it advertises: SPOTLIGHT_SHORTCUT ("mod+shift+k", the binding), SPOTLIGHT_SHORTCUT_LABEL ("⇧⌘K", the chip text, overridable per button through shortcutLabel), and SPOTLIGHT_SHORTCUT_TITLE ("Mod+Shift+K", used in the default title).

Custom dialogs

prompts.dialog passes a typed close(result) callback to the component. Use it when the standard methods cannot express the content or actions.

The standard surface can provide the title and close row:

tsx
const result = await prompts.dialog<"publish" | "draft">(
  (close) => (
    <div class="flex justify-end gap-2">
      <Button variant="secondary" onClick={() => close("draft")}>
        Save draft
      </Button>
      <Button onClick={() => close("publish")}>
        Publish
      </Button>
    </div>
  ),
  { title: "Publish changes", icon: "ti ti-rocket" },
);

For a bare surface, the caller owns all visible panel structure. DialogHeader remains available for that structure. A prompt opened from inside another prompt is stacked above it; the underlying Solid state stays mounted until the nested prompt closes.

tsx
await prompts.dialog<void>(
  (close) => (
    <section class="my-dialog-surface">
      <DialogHeader
        title="Review changes"
        icon="ti ti-file-check"
        close={() => close()}
      />
      <p>The caller owns this panel background, spacing, and boundary.</p>
      <Button
        variant="secondary"
        onClick={() => void prompts.confirm("Return to the review?")}
      >
        Open nested confirmation
      </Button>
    </section>
  ),
  { surface: "bare", header: false },
);

Accessibility

Use specific titles and action labels. Destructive confirmations should name the object and consequence. Do not hide the cancel action when cancellation is a valid path.

The shared dialog core focuses the first input, textarea, select, or button by default and owns Escape handling. Custom bodies still need semantic headings, labelled controls, native buttons, and an explicit close path.

Search results need unique, descriptive labels. A preview or icon may supplement the label but cannot replace it.

Runtime

Prompts are browser interactions and must be called from hydrated code. Their content is mounted through the shared @k2b/ui portal into one modal dialog stack at runtime.

Do not call a prompt during server rendering. Await it from an event handler, then start the mutation only after a confirmed result.

Example

tsx
const removeProject = async () => {
  const confirmed = await prompts.confirm(
    "Delete Project Atlas and its stored data?",
    {
      title: "Delete project",
      confirmText: "Delete project",
      variant: "danger",
    },
  );

  if (!confirmed) return;
  await deleteProject("atlas");
};

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.