Decision Menu
@cloudflare/kumo

Decision Menu

A card-based menu for presenting AI-driven decisions to the user. Options are selectable via click or keyboard — press 19 to jump to an option by position, Enter to confirm, or Esc to cancel. Clicking an already-selected option also confirms it.

Update the DNS record example.com to point to the new origin server?


import { useRef, useState } from "react";
import { Button } from "@cloudflare/kumo";
import { ArrowCounterClockwiseIcon } from "@phosphor-icons/react";
import { DecisionMenu } from "../kumo/decision-menu";

/** Full-featured decision menu with options, keyboard shortcuts, and footer. */
export function DecisionMenuDemo() {
  const [status, setStatus] = useState<DemoStatus>("pending");
  const [selected, setSelected] = useState("update");
  const timerRef = useRef<ReturnType<typeof setTimeout>>();

  const handleSubmit = (value: string) => {
    setSelected(value);
    setStatus("applying");
    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => setStatus("done"), 1500);
  };

  const handleCancel = () => {
    clearTimeout(timerRef.current);
    setSelected("");
    setStatus("cancelled");
  };

  const isPending = status === "pending";

  return (
    <div className="w-full max-w-lg">
      <DecisionMenu onCancel={handleCancel}>
        <DecisionMenu.Description>
          <p>
            Update the DNS record <strong>example.com</strong> to point to the
            new origin server?
          </p>
        </DecisionMenu.Description>
        <DecisionMenu.Actions
          onSubmit={handleSubmit}
          value={selected}
          disabled={!isPending}
        >
          <DecisionMenu.Option label="Update existing record" value="update" />
          <DecisionMenu.Option label="Create new record" value="create" />
          <DecisionMenu.Option label="Skip this change" value="skip" />
        </DecisionMenu.Actions>
        <DecisionMenu.Footer>
          {isPending ? (
            <>
              <DecisionMenu.ShortcutButton type="submit" shortcut="↵">
                Confirm
              </DecisionMenu.ShortcutButton>
              <DecisionMenu.ShortcutButton type="cancel" shortcut="Esc">
                Cancel
              </DecisionMenu.ShortcutButton>
            </>
          ) : (
            <DecisionMenu.StatusIndicator
              status={
                status === "applying"
                  ? "loading"
                  : status === "done"
                    ? "done"
                    : status === "cancelled"
                      ? "cancelled"
                      : "failed"
              }
              action={
                status !== "applying" && (
                  <Button
                    variant="secondary"
                    size="sm"
                    shape="square"
                    icon={ArrowCounterClockwiseIcon}
                    aria-label="Retry"
                    onClick={() => {
                      clearTimeout(timerRef.current);
                      setStatus("pending");
                      setSelected("update");
                    }}
                  />
                )
              }
            />
          )}
        </DecisionMenu.Footer>
      </DecisionMenu>
    </div>
  );
}

Status Indicators

After a decision is submitted, the footer transitions to a status indicator showing the outcome. Four states are supported: loading, done, cancelled, and failed. The failed state supports an expandable description for error details.

done

cancelled

Working on it...

failed

import { DecisionMenu } from "../kumo/decision-menu";

/** All four status indicator states shown side by side. */
export function DecisionMenuStatusDemo() {
  return (
    <div className="w-full max-w-lg flex flex-col gap-3">
      {STATUSES.map((status) => (
        <DecisionMenu key={status}>
          <DecisionMenu.StatusIndicator
            status={status}
            description={
              status === "failed"
                ? "DNS validation failed: CNAME record conflicts with an existing A record for example.com. Remove the A record first, then retry."
                : undefined
            }
          />
        </DecisionMenu>
      ))}
    </div>
  );
}

Keyboard Controls

KeyAction
19Jump to option by position
EnterConfirm the selected option
EscCancel — deselects all options
Navigate between options (native radio behavior)

Installation

DecisionMenu is an AI block — a CLI-installed component that you own and can customize.

1. Initialize Kumo config (first time only)

npx @cloudflare/kumo init

2. Install the block

npx @cloudflare/kumo add DecisionMenu

3. Import from your local path

import { DecisionMenu } from "./components/kumo/decision-menu/decision-menu";

API Reference

PropTypeDefaultDescription
size"base" | "lg""base"-
childrenReactNode--
classNamestring--