Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision 0d00e53ee0e707747c5d846467b5eddfa7a0e101)
+++ uspace/app/uidemo/uidemo.c	(revision 9a07ee3efa9f0bc51fee1cb5d2a8dd94cb95c6a0)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -108,4 +108,5 @@
 static void uidemo_file_load(ui_menu_entry_t *, void *);
 static void uidemo_file_message(ui_menu_entry_t *, void *);
+static void uidemo_file_confirmation(ui_menu_entry_t *, void *);
 static void uidemo_file_exit(ui_menu_entry_t *, void *);
 static void uidemo_edit_modify(ui_menu_entry_t *, void *);
@@ -340,5 +341,5 @@
 }
 
-/** Display a message window.
+/** Display a message window with OK button.
  *
  * @param demo UI demo
@@ -405,4 +406,30 @@
 	mdparams.caption = "Message For You";
 	mdparams.text = "Hello, world!";
+
+	rc = ui_msg_dialog_create(demo->ui, &mdparams, &dialog);
+	if (rc != EOK) {
+		printf("Error creating message dialog.\n");
+		return;
+	}
+
+	ui_msg_dialog_set_cb(dialog, &msg_dialog_cb, &demo);
+}
+
+/** File / Confirmation menu entry selected.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (demo)
+ */
+static void uidemo_file_confirmation(ui_menu_entry_t *mentry, void *arg)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+	ui_msg_dialog_params_t mdparams;
+	ui_msg_dialog_t *dialog;
+	errno_t rc;
+
+	ui_msg_dialog_params_init(&mdparams);
+	mdparams.caption = "Confirmation";
+	mdparams.text = "This will not actually do anything. Proceed?";
+	mdparams.choice = umdc_ok_cancel;
 
 	rc = ui_msg_dialog_create(demo->ui, &mdparams, &dialog);
@@ -782,4 +809,12 @@
 	ui_menu_entry_set_cb(mmsg, uidemo_file_message, (void *) &demo);
 
+	rc = ui_menu_entry_create(demo.mfile, "~C~onfirmation", "", &mmsg);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	ui_menu_entry_set_cb(mmsg, uidemo_file_confirmation, (void *) &demo);
+
 	rc = ui_menu_entry_create(demo.mfile, "~L~oad", "", &mload);
 	if (rc != EOK) {
Index: uspace/lib/ui/include/types/ui/msgdialog.h
===================================================================
--- uspace/lib/ui/include/types/ui/msgdialog.h	(revision 0d00e53ee0e707747c5d846467b5eddfa7a0e101)
+++ uspace/lib/ui/include/types/ui/msgdialog.h	(revision 9a07ee3efa9f0bc51fee1cb5d2a8dd94cb95c6a0)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,4 +45,17 @@
 typedef struct ui_msg_dialog ui_msg_dialog_t;
 
+enum {
+	/** Maximum number of buttons in message dialog. */
+	ui_msg_dialog_maxbtn = 2
+};
+
+/** Which choices the user can select from. */
+typedef enum {
+	/** OK (the default) */
+	umdc_ok,
+	/** OK, Cancel */
+	umdc_ok_cancel
+} ui_msg_dialog_choice_t;
+
 /** Message dialog parameters */
 typedef struct {
@@ -51,4 +64,6 @@
 	/** Message text */
 	const char *text;
+	/** The choice that the user is given */
+	ui_msg_dialog_choice_t choice;
 } ui_msg_dialog_params_t;
 
Index: uspace/lib/ui/private/msgdialog.h
===================================================================
--- uspace/lib/ui/private/msgdialog.h	(revision 0d00e53ee0e707747c5d846467b5eddfa7a0e101)
+++ uspace/lib/ui/private/msgdialog.h	(revision 9a07ee3efa9f0bc51fee1cb5d2a8dd94cb95c6a0)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,4 +38,8 @@
 #define _UI_PRIVATE_MSGDIALOG_H
 
+#include <types/ui/msgdialog.h>
+#include <ui/pbutton.h>
+#include <ui/window.h>
+
 /** Actual structure of message dialog.
  *
@@ -45,6 +49,6 @@
 	/** Dialog window */
 	struct ui_window *window;
-	/** OK button */
-	struct ui_pbutton *bok;
+	/** Buttons */
+	struct ui_pbutton *btn[ui_msg_dialog_maxbtn];
 	/** Message dialog callbacks */
 	struct ui_msg_dialog_cb *cb;
Index: uspace/lib/ui/src/msgdialog.c
===================================================================
--- uspace/lib/ui/src/msgdialog.c	(revision 0d00e53ee0e707747c5d846467b5eddfa7a0e101)
+++ uspace/lib/ui/src/msgdialog.c	(revision 9a07ee3efa9f0bc51fee1cb5d2a8dd94cb95c6a0)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -58,4 +58,9 @@
 };
 
+static const char *ui_msg_dialog_captions[][ui_msg_dialog_maxbtn + 1] = {
+	[umdc_ok] = { "OK", NULL },
+	[umdc_ok_cancel] = { "OK", "Cancel", NULL }
+};
+
 /** Initialize message dialog parameters structure.
  *
@@ -86,7 +91,13 @@
 	ui_fixed_t *fixed = NULL;
 	ui_label_t *label = NULL;
-	ui_pbutton_t *bok = NULL;
+	ui_pbutton_t *btn[ui_msg_dialog_maxbtn];
+	const char **cp;
+	unsigned i, nb;
 	gfx_rect_t rect;
+	gfx_coord_t bw, bpad, btnsw, bp0x;
 	ui_resource_t *ui_res;
+
+	for (i = 0; i < ui_msg_dialog_maxbtn; i++)
+		btn[i] = NULL;
 
 	dialog = calloc(1, sizeof(ui_msg_dialog_t));
@@ -103,10 +114,10 @@
 		wparams.rect.p0.x = 0;
 		wparams.rect.p0.y = 0;
-		wparams.rect.p1.x = 40;
+		wparams.rect.p1.x = 60;
 		wparams.rect.p1.y = 7;
 	} else {
 		wparams.rect.p0.x = 0;
 		wparams.rect.p0.y = 0;
-		wparams.rect.p1.x = 200;
+		wparams.rect.p1.x = 400;
 		wparams.rect.p1.y = 110;
 	}
@@ -132,10 +143,10 @@
 		rect.p0.x = 3;
 		rect.p0.y = 2;
-		rect.p1.x = 17;
+		rect.p1.x = 57;
 		rect.p1.y = 3;
 	} else {
 		rect.p0.x = 10;
 		rect.p0.y = 35;
-		rect.p1.x = 190;
+		rect.p1.x = 390;
 		rect.p1.y = 50;
 	}
@@ -150,33 +161,58 @@
 	label = NULL;
 
-	rc = ui_pbutton_create(ui_res, "OK", &bok);
-	if (rc != EOK)
-		goto error;
-
-	ui_pbutton_set_cb(bok, &ui_msg_dialog_btn_cb, dialog);
-
-	/* FIXME: Auto layout */
+	i = 0;
+	assert(params->choice == umdc_ok || params->choice == umdc_ok_cancel);
+	cp = ui_msg_dialog_captions[params->choice];
+
+	while (*cp != NULL) {
+		rc = ui_pbutton_create(ui_res, *cp, &btn[i]);
+		if (rc != EOK)
+			goto error;
+
+		ui_pbutton_set_cb(btn[i], &ui_msg_dialog_btn_cb, dialog);
+		++cp;
+		++i;
+	}
+
+	nb = i;
+
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 8;
-		rect.p0.y = 4;
-		rect.p1.x = 12;
-		rect.p1.y = 5;
+		bw = 12;
+		bpad = 2;
 	} else {
-		rect.p0.x = 55;
-		rect.p0.y = 60;
-		rect.p1.x = 145;
-		rect.p1.y = 88;
-	}
-
-	ui_pbutton_set_rect(bok, &rect);
-
-	ui_pbutton_set_default(bok, true);
-
-	rc = ui_fixed_add(fixed, ui_pbutton_ctl(bok));
-	if (rc != EOK)
-		goto error;
-
-	dialog->bok = bok;
-	bok = NULL;
+		bw = 90;
+		bpad = 10;
+	}
+
+	btnsw = (nb - 1) * (bw + bpad) + bw;
+	bp0x = (wparams.rect.p0.x + wparams.rect.p1.x - btnsw) / 2;
+
+	for (i = 0; i < nb; i++) {
+		/* FIXME: Auto layout */
+		if (ui_is_textmode(ui)) {
+			rect.p0.x = bp0x + i * (bw + bpad);
+			rect.p0.y = 4;
+			rect.p1.x = bp0x + bw + i * (bw + bpad);
+			rect.p1.y = 5;
+		} else {
+			rect.p0.x = bp0x + i * (bw + bpad);
+			rect.p0.y = 60;
+			rect.p1.x = bp0x + bw + i * (bw + bpad);
+			rect.p1.y = 88;
+		}
+
+		ui_pbutton_set_rect(btn[i], &rect);
+
+		rc = ui_fixed_add(fixed, ui_pbutton_ctl(btn[i]));
+		if (rc != EOK)
+			goto error;
+	}
+
+	ui_pbutton_set_default(btn[0], true);
+
+	for (i = 0; i < ui_msg_dialog_maxbtn; i++) {
+		dialog->btn[i] = btn[i];
+		btn[i] = NULL;
+	}
 
 	ui_window_add(window, ui_fixed_ctl(fixed));
@@ -191,6 +227,8 @@
 	return EOK;
 error:
-	if (bok != NULL)
-		ui_pbutton_destroy(bok);
+	for (i = 0; i < ui_msg_dialog_maxbtn; i++) {
+		if (btn[i] != NULL)
+			ui_pbutton_destroy(btn[i]);
+	}
 	if (label != NULL)
 		ui_label_destroy(label);
@@ -251,7 +289,12 @@
 {
 	ui_msg_dialog_t *dialog = (ui_msg_dialog_t *) arg;
-
-	if (dialog->cb != NULL && dialog->cb->button != NULL)
-		dialog->cb->button(dialog, dialog->arg, 0);
+	unsigned i;
+
+	if (dialog->cb != NULL && dialog->cb->button != NULL) {
+		for (i = 0; i < ui_msg_dialog_maxbtn; i++) {
+			if (dialog->btn[i] == pbutton)
+				dialog->cb->button(dialog, dialog->arg, i);
+		}
+	}
 }
 
Index: uspace/lib/ui/test/msgdialog.c
===================================================================
--- uspace/lib/ui/test/msgdialog.c	(revision 0d00e53ee0e707747c5d846467b5eddfa7a0e101)
+++ uspace/lib/ui/test/msgdialog.c	(revision 9a07ee3efa9f0bc51fee1cb5d2a8dd94cb95c6a0)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -92,4 +92,5 @@
 	ui_msg_dialog_params_t params;
 	ui_msg_dialog_t *dialog = NULL;
+	unsigned i;
 	test_cb_resp_t resp;
 
@@ -100,4 +101,5 @@
 	params.caption = "Message";
 	params.text = "Hello";
+	params.choice = umdc_ok_cancel;
 
 	rc = ui_msg_dialog_create(ui, &params, &dialog);
@@ -106,17 +108,19 @@
 
 	/* Button callback with no callbacks set */
-	ui_pbutton_clicked(dialog->bok);
+	ui_pbutton_clicked(dialog->btn[0]);
 
 	/* Button callback with callback not implemented */
 	ui_msg_dialog_set_cb(dialog, &dummy_msg_dialog_cb, NULL);
-	ui_pbutton_clicked(dialog->bok);
+	ui_pbutton_clicked(dialog->btn[0]);
 
-	/* Button callback with real callback set */
-	resp.button = false;
-	resp.bnum = 123;
-	ui_msg_dialog_set_cb(dialog, &test_msg_dialog_cb, &resp);
-	ui_pbutton_clicked(dialog->bok);
-	PCUT_ASSERT_TRUE(resp.button);
-	PCUT_ASSERT_INT_EQUALS(0, resp.bnum);
+	for (i = 0; i < 2; i++) {
+		/* Button callback with real callback set */
+		resp.button = false;
+		resp.bnum = 123;
+		ui_msg_dialog_set_cb(dialog, &test_msg_dialog_cb, &resp);
+		ui_pbutton_clicked(dialog->btn[i]);
+		PCUT_ASSERT_TRUE(resp.button);
+		PCUT_ASSERT_INT_EQUALS(i, resp.bnum);
+	}
 
 	ui_msg_dialog_destroy(dialog);
