Index: uspace/app/calculator/calculator.c
===================================================================
--- uspace/app/calculator/calculator.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/app/calculator/calculator.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2016 Martin Decky
  * All rights reserved.
@@ -46,6 +46,7 @@
 #include <ui/entry.h>
 #include <ui/fixed.h>
+#include <ui/menu.h>
 #include <ui/menubar.h>
-#include <ui/menu.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
 #include <ui/pbutton.h>
@@ -896,5 +897,5 @@
 	}
 
-	rc = ui_menu_create(calc.menubar, "~F~ile", &mfile);
+	rc = ui_menu_dd_create(calc.menubar, "~F~ile", NULL, &mfile);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -910,5 +911,5 @@
 	ui_menu_entry_set_cb(mexit, calc_file_exit, (void *) &calc);
 
-	rc = ui_menu_create(calc.menubar, "~E~dit", &medit);
+	rc = ui_menu_dd_create(calc.menubar, "~E~dit", NULL, &medit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
Index: uspace/app/edit/edit.c
===================================================================
--- uspace/app/edit/edit.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/app/edit/edit.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2012 Martin Sucha
  * All rights reserved.
@@ -60,4 +60,5 @@
 #include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
 #include <ui/promptdialog.h>
@@ -430,5 +431,5 @@
 	}
 
-	rc = ui_menu_create(edit->menubar, "~F~ile", &mfile);
+	rc = ui_menu_dd_create(edit->menubar, "~F~ile", NULL, &mfile);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -466,5 +467,5 @@
 	ui_menu_entry_set_cb(mexit, edit_file_exit, (void *) edit);
 
-	rc = ui_menu_create(edit->menubar, "~E~dit", &medit);
+	rc = ui_menu_dd_create(edit->menubar, "~E~dit", NULL, &medit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -518,5 +519,5 @@
 	ui_menu_entry_set_cb(mselall, edit_edit_select_all, (void *) edit);
 
-	rc = ui_menu_create(edit->menubar, "~S~earch", &msearch);
+	rc = ui_menu_dd_create(edit->menubar, "~S~earch", NULL, &msearch);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
Index: uspace/app/nav/menu.c
===================================================================
--- uspace/app/nav/menu.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/app/nav/menu.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,4 +39,5 @@
 #include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
 #include "menu.h"
@@ -72,5 +73,5 @@
 		goto error;
 
-	rc = ui_menu_create(menu->menubar, "~F~ile", &mfile);
+	rc = ui_menu_dd_create(menu->menubar, "~F~ile", NULL, &mfile);
 	if (rc != EOK)
 		goto error;
Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/app/uidemo/uidemo.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -45,7 +45,8 @@
 #include <ui/label.h>
 #include <ui/list.h>
+#include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
-#include <ui/menu.h>
 #include <ui/msgdialog.h>
 #include <ui/pbutton.h>
@@ -764,5 +765,5 @@
 	}
 
-	rc = ui_menu_create(demo.mbar, "~F~ile", &demo.mfile);
+	rc = ui_menu_dd_create(demo.mbar, "~F~ile", NULL, &demo.mfile);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -818,5 +819,5 @@
 	ui_menu_entry_set_cb(mexit, uidemo_file_exit, (void *) &demo);
 
-	rc = ui_menu_create(demo.mbar, "~E~dit", &demo.medit);
+	rc = ui_menu_dd_create(demo.mbar, "~E~dit", NULL, &demo.medit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -842,5 +843,6 @@
 	    (void *) &demo);
 
-	rc = ui_menu_create(demo.mbar, "~P~references", &demo.mpreferences);
+	rc = ui_menu_dd_create(demo.mbar, "~P~references", NULL,
+	    &demo.mpreferences);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -848,5 +850,5 @@
 	}
 
-	rc = ui_menu_create(demo.mbar, "~H~elp", &demo.mhelp);
+	rc = ui_menu_dd_create(demo.mbar, "~H~elp", NULL, &demo.mhelp);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
Index: uspace/lib/ui/include/types/ui/menu.h
===================================================================
--- uspace/lib/ui/include/types/ui/menu.h	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/include/types/ui/menu.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,6 +37,20 @@
 #define _UI_TYPES_MENU_H
 
+#include <types/common.h>
+
 struct ui_menu;
 typedef struct ui_menu ui_menu_t;
+
+/** Menu callbacks */
+typedef struct ui_menu_cb {
+	/** Left arrow pressed */
+	void (*left)(ui_menu_t *, void *, sysarg_t);
+	/** Right arrow pressed */
+	void (*right)(ui_menu_t *, void *, sysarg_t);
+	/** Request menu closure */
+	void (*close_req)(ui_menu_t *, void *);
+	/** Accelerator key pressed */
+	void (*press_accel)(ui_menu_t *, void *, char32_t, sysarg_t);
+} ui_menu_cb_t;
 
 #endif
Index: uspace/lib/ui/include/types/ui/menudd.h
===================================================================
--- uspace/lib/ui/include/types/ui/menudd.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/include/types/ui/menudd.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Menu
+ */
+
+#ifndef _UI_TYPES_MENUDD_H
+#define _UI_TYPES_MENUDD_H
+
+struct ui_menu_dd;
+typedef struct ui_menu_dd ui_menu_dd_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/menu.h
===================================================================
--- uspace/lib/ui/include/ui/menu.h	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/include/ui/menu.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -43,18 +43,13 @@
 #include <stdbool.h>
 #include <types/common.h>
+#include <types/ui/event.h>
 #include <types/ui/menu.h>
 #include <types/ui/menubar.h>
-#include <types/ui/event.h>
+#include <types/ui/window.h>
 #include <uchar.h>
 
-extern errno_t ui_menu_create(ui_menu_bar_t *, const char *, ui_menu_t **);
+extern errno_t ui_menu_create(ui_window_t *, ui_menu_t **);
 extern void ui_menu_destroy(ui_menu_t *);
-extern ui_menu_t *ui_menu_first(ui_menu_bar_t *);
-extern ui_menu_t *ui_menu_next(ui_menu_t *);
-extern ui_menu_t *ui_menu_last(ui_menu_bar_t *);
-extern ui_menu_t *ui_menu_prev(ui_menu_t *);
-extern const char *ui_menu_caption(ui_menu_t *);
-extern void ui_menu_get_rect(ui_menu_t *, gfx_coord2_t *, gfx_rect_t *);
-extern char32_t ui_menu_get_accel(ui_menu_t *);
+extern void ui_menu_set_cb(ui_menu_t *, ui_menu_cb_t *, void *);
 extern errno_t ui_menu_open(ui_menu_t *, gfx_rect_t *, sysarg_t);
 extern void ui_menu_close(ui_menu_t *);
Index: uspace/lib/ui/include/ui/menudd.h
===================================================================
--- uspace/lib/ui/include/ui/menudd.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/include/ui/menudd.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Menu drop-down
+ */
+
+#ifndef _UI_MENUDD_H
+#define _UI_MENUDD_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <stdbool.h>
+#include <types/common.h>
+#include <types/ui/menu.h>
+#include <types/ui/menudd.h>
+#include <types/ui/menubar.h>
+#include <types/ui/event.h>
+#include <uchar.h>
+
+extern errno_t ui_menu_dd_create(ui_menu_bar_t *, const char *, ui_menu_dd_t **,
+    ui_menu_t **);
+extern void ui_menu_dd_destroy(ui_menu_dd_t *);
+extern ui_menu_dd_t *ui_menu_dd_first(ui_menu_bar_t *);
+extern ui_menu_dd_t *ui_menu_dd_next(ui_menu_dd_t *);
+extern ui_menu_dd_t *ui_menu_dd_last(ui_menu_bar_t *);
+extern ui_menu_dd_t *ui_menu_dd_prev(ui_menu_dd_t *);
+extern const char *ui_menu_dd_caption(ui_menu_dd_t *);
+extern void ui_menu_dd_get_rect(ui_menu_dd_t *, gfx_coord2_t *, gfx_rect_t *);
+extern char32_t ui_menu_dd_get_accel(ui_menu_dd_t *);
+extern errno_t ui_menu_dd_open(ui_menu_dd_t *, gfx_rect_t *, sysarg_t);
+extern void ui_menu_dd_close(ui_menu_dd_t *);
+extern bool ui_menu_dd_is_open(ui_menu_dd_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/meson.build
===================================================================
--- uspace/lib/ui/meson.build	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/meson.build	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -43,4 +43,5 @@
 	'src/menu.c',
 	'src/menubar.c',
+	'src/menudd.c',
 	'src/menuentry.c',
 	'src/msgdialog.c',
@@ -77,4 +78,5 @@
 	'test/menu.c',
 	'test/menubar.c',
+	'test/menudd.c',
 	'test/menuentry.c',
 	'test/msgdialog.c',
Index: uspace/lib/ui/private/menu.h
===================================================================
--- uspace/lib/ui/private/menu.h	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/private/menu.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,4 +41,5 @@
 #include <gfx/coord.h>
 #include <stdbool.h>
+#include <types/common.h>
 #include <types/ui/menu.h>
 #include <types/ui/resource.h>
@@ -49,8 +50,6 @@
  */
 struct ui_menu {
-	/** Containing menu bar */
-	struct ui_menu_bar *mbar;
-	/** Link to @c bar->menus */
-	link_t lmenus;
+	/** Parent window */
+	struct ui_window *parent;
 	/** Caption */
 	char *caption;
@@ -67,4 +66,8 @@
 	/** Menu entries (ui_menu_entry_t) */
 	list_t entries;
+	/** Callbacks */
+	struct ui_menu_cb *cb;
+	/** Callback argument */
+	void *arg;
 };
 
@@ -86,4 +89,8 @@
 extern void ui_menu_up(ui_menu_t *);
 extern void ui_menu_down(ui_menu_t *);
+extern void ui_menu_left(ui_menu_t *, sysarg_t);
+extern void ui_menu_right(ui_menu_t *, sysarg_t);
+extern void ui_menu_close_req(ui_menu_t *);
+extern void ui_menu_press_accel(ui_menu_t *, char32_t, sysarg_t);
 
 #endif
Index: uspace/lib/ui/private/menubar.h
===================================================================
--- uspace/lib/ui/private/menubar.h	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/private/menubar.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -42,6 +42,6 @@
 #include <stdbool.h>
 #include <types/common.h>
-#include <types/ui/menu.h>
 #include <types/ui/menubar.h>
+#include <types/ui/menudd.h>
 
 /** Actual structure of menu bar.
@@ -60,15 +60,16 @@
 	/** Menu bar is active */
 	bool active;
-	/** Selected menu or @c NULL */
-	struct ui_menu *selected;
-	/** List of menus (ui_menu_t) */
-	list_t menus;
+	/** Selected menu drop-down or @c NULL */
+	struct ui_menu_dd *selected;
+	/** List of menu drop-downs (ui_menu_dd_t) */
+	list_t menudds;
 };
 
-extern void ui_menu_bar_select(ui_menu_bar_t *, ui_menu_t *, bool, sysarg_t);
+extern void ui_menu_bar_select(ui_menu_bar_t *, ui_menu_dd_t *, bool, sysarg_t);
 extern void ui_menu_bar_left(ui_menu_bar_t *, sysarg_t);
 extern void ui_menu_bar_right(ui_menu_bar_t *, sysarg_t);
 extern ui_evclaim_t ui_menu_bar_key_press_unmod(ui_menu_bar_t *, kbd_event_t *);
-extern void ui_menu_bar_entry_rect(ui_menu_bar_t *, ui_menu_t *, gfx_rect_t *);
+extern void ui_menu_bar_entry_rect(ui_menu_bar_t *, ui_menu_dd_t *,
+    gfx_rect_t *);
 
 #endif
Index: uspace/lib/ui/private/menudd.h
===================================================================
--- uspace/lib/ui/private/menudd.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/private/menudd.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Menu drop-down structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_MENUDD_H
+#define _UI_PRIVATE_MENUDD_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <types/ui/menudd.h>
+#include <types/ui/resource.h>
+
+/** Actual structure of menu drop-down.
+ *
+ * This is private to libui.
+ */
+struct ui_menu_dd {
+	/** Containing menu bar */
+	struct ui_menu_bar *mbar;
+	/** Link to @c bar->menudds */
+	link_t lmenudds;
+	/** Caption */
+	char *caption;
+	/** Popup window or @c NULL if drop-down is not currently open */
+	struct ui_popup *popup;
+	/** Actual menu */
+	struct ui_menu *menu;
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/src/menu.c
===================================================================
--- uspace/lib/ui/src/menu.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/src/menu.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -44,4 +44,5 @@
 #include <str.h>
 #include <uchar.h>
+#include <ui/ui.h>
 #include <ui/accel.h>
 #include <ui/control.h>
@@ -49,9 +50,7 @@
 #include <ui/popup.h>
 #include <ui/menu.h>
-#include <ui/menubar.h>
 #include <ui/menuentry.h>
 #include <ui/resource.h>
 #include <ui/window.h>
-#include "../private/menubar.h"
 #include "../private/menu.h"
 #include "../private/resource.h"
@@ -78,11 +77,10 @@
 /** Create new menu.
  *
+ * @param parent Parent window
  * @param mbar Menu bar
- * @param caption Caption
  * @param rmenu Place to store pointer to new menu
  * @return EOK on success, ENOMEM if out of memory
  */
-errno_t ui_menu_create(ui_menu_bar_t *mbar, const char *caption,
-    ui_menu_t **rmenu)
+errno_t ui_menu_create(ui_window_t *parent, ui_menu_t **rmenu)
 {
 	ui_menu_t *menu;
@@ -92,12 +90,5 @@
 		return ENOMEM;
 
-	menu->caption = str_dup(caption);
-	if (menu->caption == NULL) {
-		free(menu);
-		return ENOMEM;
-	}
-
-	menu->mbar = mbar;
-	list_append(&menu->lmenus, &mbar->menus);
+	menu->parent = parent;
 	list_initialize(&menu->entries);
 
@@ -124,81 +115,18 @@
 	}
 
-	list_remove(&menu->lmenus);
 	free(menu->caption);
 	free(menu);
 }
 
-/** Get first menu in menu bar.
- *
- * @param mbar Menu bar
- * @return First menu or @c NULL if there is none
- */
-ui_menu_t *ui_menu_first(ui_menu_bar_t *mbar)
-{
-	link_t *link;
-
-	link = list_first(&mbar->menus);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, ui_menu_t, lmenus);
-}
-
-/** Get next menu in menu bar.
- *
- * @param cur Current menu
- * @return Next menu or @c NULL if @a cur is the last one
- */
-ui_menu_t *ui_menu_next(ui_menu_t *cur)
-{
-	link_t *link;
-
-	link = list_next(&cur->lmenus, &cur->mbar->menus);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, ui_menu_t, lmenus);
-}
-
-/** Get last menu in menu bar.
- *
- * @param mbar Menu bar
- * @return Last menu or @c NULL if there is none
- */
-ui_menu_t *ui_menu_last(ui_menu_bar_t *mbar)
-{
-	link_t *link;
-
-	link = list_last(&mbar->menus);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, ui_menu_t, lmenus);
-}
-
-/** Get previous menu in menu bar.
- *
- * @param cur Current menu
- * @return Previous menu or @c NULL if @a cur is the fist one
- */
-ui_menu_t *ui_menu_prev(ui_menu_t *cur)
-{
-	link_t *link;
-
-	link = list_prev(&cur->lmenus, &cur->mbar->menus);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, ui_menu_t, lmenus);
-}
-
-/** Get menu caption.
- *
- * @param menu Menu
- * @return Caption (owned by @a menu)
- */
-const char *ui_menu_caption(ui_menu_t *menu)
-{
-	return menu->caption;
+/** Set menu callbacks.
+ *
+ * @param menu Menu
+ * @param cb Callbacks
+ * @param arg Callback argument
+ */
+void ui_menu_set_cb(ui_menu_t *menu, ui_menu_cb_t *cb, void *arg)
+{
+	menu->cb = cb;
+	menu->arg = arg;
 }
 
@@ -212,10 +140,10 @@
     ui_menu_geom_t *geom)
 {
-	ui_resource_t *res;
 	gfx_coord2_t edim;
 	gfx_coord_t frame_w;
 	gfx_coord_t frame_h;
-
-	res = ui_window_get_res(menu->mbar->window);
+	ui_resource_t *res;
+
+	res = ui_window_get_res(menu->parent);
 
 	if (res->textmode) {
@@ -241,29 +169,4 @@
 }
 
-/** Get menu rectangle.
- *
- * @param menu Menu
- * @param spos Starting position (top-left corner)
- * @param rect Place to store menu rectangle
- */
-void ui_menu_get_rect(ui_menu_t *menu, gfx_coord2_t *spos, gfx_rect_t *rect)
-{
-	ui_menu_geom_t geom;
-
-	ui_menu_get_geom(menu, spos, &geom);
-	*rect = geom.outer_rect;
-}
-
-/** Get menu accelerator character.
- *
- * @param menu Menu
- * @return Accelerator character (lowercase) or the null character if
- *         the menu has no accelerator.
- */
-char32_t ui_menu_get_accel(ui_menu_t *menu)
-{
-	return ui_accel_get(menu->caption);
-}
-
 /** Get UI resource from menu.
  *
@@ -304,6 +207,6 @@
 	params.idev_id = idev_id;
 
-	rc = ui_popup_create(menu->mbar->ui, menu->mbar->window, &params,
-	    &popup);
+	rc = ui_popup_create(ui_window_get_ui(menu->parent), menu->parent,
+	    &params, &popup);
 	if (rc != EOK)
 		return rc;
@@ -503,5 +406,5 @@
 		/* Press outside menu - close it */
 		if (event->type == POS_PRESS)
-			ui_menu_bar_deactivate(menu->mbar);
+			ui_menu_close_req(menu);
 	}
 
@@ -524,5 +427,5 @@
 	if (event->type == KEY_PRESS && (event->mods & KM_ALT) != 0 &&
 	    (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0')
-		ui_menu_bar_press_accel(menu->mbar, event->c, event->kbd_id);
+		ui_menu_press_accel(menu, event->c, event->kbd_id);
 
 	return ui_claimed;
@@ -615,11 +518,11 @@
 	switch (event->key) {
 	case KC_ESCAPE:
-		ui_menu_bar_deactivate(menu->mbar);
+		ui_menu_close_req(menu);
 		break;
 	case KC_LEFT:
-		ui_menu_bar_left(menu->mbar, event->kbd_id);
+		ui_menu_left(menu, event->kbd_id);
 		break;
 	case KC_RIGHT:
-		ui_menu_bar_right(menu->mbar, event->kbd_id);
+		ui_menu_right(menu, event->kbd_id);
 		break;
 	case KC_UP:
@@ -658,6 +561,6 @@
 	ui_menu_t *menu = (ui_menu_t *)arg;
 
-	/* Deactivate menu bar, close menu */
-	ui_menu_bar_deactivate(menu->mbar);
+	/* Forward close request to caller */
+	ui_menu_close_req(menu);
 }
 
@@ -691,4 +594,48 @@
 }
 
+/** Send menu left event.
+ *
+ * @param menu Menu
+ * @param idev_id Input device ID
+ */
+void ui_menu_left(ui_menu_t *menu, sysarg_t idev_id)
+{
+	if (menu->cb != NULL && menu->cb->left != NULL)
+		menu->cb->left(menu, menu->arg, idev_id);
+}
+
+/** Send menu right event.
+ *
+ * @param menu Menu
+ * @param idev_id Input device ID
+ */
+void ui_menu_right(ui_menu_t *menu, sysarg_t idev_id)
+{
+	if (menu->cb != NULL && menu->cb->right != NULL)
+		menu->cb->right(menu, menu->arg, idev_id);
+}
+
+/** Send menu close request event.
+ *
+ * @param menu Menu
+ */
+void ui_menu_close_req(ui_menu_t *menu)
+{
+	if (menu->cb != NULL && menu->cb->close_req != NULL)
+		menu->cb->close_req(menu, menu->arg);
+}
+
+/** Send menu accelerator key press event.
+ *
+ * @param menu Menu
+ * @param c Character
+ * @param kbd_id Keyboard ID
+ */
+void ui_menu_press_accel(ui_menu_t *menu, char32_t c, sysarg_t kbd_id)
+{
+	if (menu->cb != NULL && menu->cb->press_accel != NULL)
+		menu->cb->press_accel(menu, menu->arg, c, kbd_id);
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/menubar.c
===================================================================
--- uspace/lib/ui/src/menubar.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/src/menubar.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -45,6 +45,6 @@
 #include <ui/control.h>
 #include <ui/paint.h>
-#include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/window.h>
 #include "../private/menubar.h"
@@ -95,5 +95,5 @@
 	mbar->ui = ui;
 	mbar->window = window;
-	list_initialize(&mbar->menus);
+	list_initialize(&mbar->menudds);
 	*rmbar = mbar;
 	return EOK;
@@ -106,14 +106,14 @@
 void ui_menu_bar_destroy(ui_menu_bar_t *mbar)
 {
-	ui_menu_t *menu;
+	ui_menu_dd_t *mdd;
 
 	if (mbar == NULL)
 		return;
 
-	/* Destroy menus */
-	menu = ui_menu_first(mbar);
-	while (menu != NULL) {
-		ui_menu_destroy(menu);
-		menu = ui_menu_first(mbar);
+	/* Destroy menu drop-downs */
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		ui_menu_dd_destroy(mdd);
+		mdd = ui_menu_dd_first(mbar);
 	}
 
@@ -155,5 +155,5 @@
 	gfx_rect_t rect;
 	gfx_color_t *bg_color;
-	ui_menu_t *menu;
+	ui_menu_dd_t *mdd;
 	const char *caption;
 	gfx_coord_t width;
@@ -189,7 +189,7 @@
 	fmt.valign = gfx_valign_top;
 
-	menu = ui_menu_first(mbar);
-	while (menu != NULL) {
-		caption = ui_menu_caption(menu);
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		caption = ui_menu_dd_caption(mdd);
 		width = ui_text_width(res->font, caption) + 2 * hpad;
 		tpos.x = pos.x + hpad;
@@ -200,5 +200,5 @@
 		rect.p1.y = mbar->rect.p1.y;
 
-		if (menu == mbar->selected) {
+		if (mdd == mbar->selected) {
 			fmt.color = res->wnd_sel_text_color;
 			fmt.hgl_color = res->wnd_sel_text_hgl_color;
@@ -223,5 +223,5 @@
 
 		pos.x += width;
-		menu = ui_menu_next(menu);
+		mdd = ui_menu_dd_next(mdd);
 	}
 
@@ -241,23 +241,23 @@
  *
  * @param mbar Menu bar
- * @param menu Menu to select (or deselect if selected) or @c NULL
+ * @param mdd Menu drop-down to select (or deselect if selected) or @c NULL
  * @param openup Open menu even if not currently open
  * @param idev_id Input device ID associated with the selecting seat
  */
-void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_t *menu, bool openup,
+void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd, bool openup,
     sysarg_t idev_id)
 {
-	ui_menu_t *old_menu;
+	ui_menu_dd_t *old_mdd;
 	gfx_rect_t rect;
 	bool was_open;
 
-	old_menu = mbar->selected;
-
-	mbar->selected = menu;
-
-	/* Close previously open menu */
-	if (old_menu != NULL && ui_menu_is_open(old_menu)) {
+	old_mdd = mbar->selected;
+
+	mbar->selected = mdd;
+
+	/* Close previously open menu drop-down */
+	if (old_mdd != NULL && ui_menu_dd_is_open(old_mdd)) {
 		was_open = true;
-		(void) ui_menu_close(old_menu);
+		(void) ui_menu_dd_close(old_mdd);
 	} else {
 		was_open = false;
@@ -270,9 +270,9 @@
 		if (openup || was_open) {
 			/*
-			 * Open the newly selected menu if either
-			 * the old menu was open or @a openup was
+			 * Open the newly selected menu drop-down if either
+			 * the old menu drop-down was open or @a openup was
 			 * specified.
 			 */
-			(void) ui_menu_open(mbar->selected, &rect, idev_id);
+			(void) ui_menu_dd_open(mbar->selected, &rect, idev_id);
 		}
 	}
@@ -289,15 +289,15 @@
 void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
 {
-	ui_menu_t *nmenu;
+	ui_menu_dd_t *nmdd;
 
 	if (mbar->selected == NULL)
 		return;
 
-	nmenu = ui_menu_prev(mbar->selected);
-	if (nmenu == NULL)
-		nmenu = ui_menu_last(mbar);
-
-	if (nmenu != mbar->selected)
-		ui_menu_bar_select(mbar, nmenu, false, idev_id);
+	nmdd = ui_menu_dd_prev(mbar->selected);
+	if (nmdd == NULL)
+		nmdd = ui_menu_dd_last(mbar);
+
+	if (nmdd != mbar->selected)
+		ui_menu_bar_select(mbar, nmdd, false, idev_id);
 }
 
@@ -312,15 +312,15 @@
 void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
 {
-	ui_menu_t *nmenu;
+	ui_menu_dd_t *nmdd;
 
 	if (mbar->selected == NULL)
 		return;
 
-	nmenu = ui_menu_next(mbar->selected);
-	if (nmenu == NULL)
-		nmenu = ui_menu_first(mbar);
-
-	if (nmenu != mbar->selected)
-		ui_menu_bar_select(mbar, nmenu, false, idev_id);
+	nmdd = ui_menu_dd_next(mbar->selected);
+	if (nmdd == NULL)
+		nmdd = ui_menu_dd_first(mbar);
+
+	if (nmdd != mbar->selected)
+		ui_menu_bar_select(mbar, nmdd, false, idev_id);
 }
 
@@ -355,8 +355,9 @@
 
 	if (event->key == KC_ENTER || event->key == KC_DOWN) {
-		if (mbar->selected != NULL && !ui_menu_is_open(mbar->selected)) {
+		if (mbar->selected != NULL &&
+		    !ui_menu_dd_is_open(mbar->selected)) {
 			ui_menu_bar_entry_rect(mbar, mbar->selected,
 			    &rect);
-			ui_menu_open(mbar->selected, &rect, event->kbd_id);
+			ui_menu_dd_open(mbar->selected, &rect, event->kbd_id);
 		}
 
@@ -364,5 +365,5 @@
 	}
 
-	if (event->c != '\0' && !ui_menu_is_open(mbar->selected)) {
+	if (event->c != '\0' && !ui_menu_dd_is_open(mbar->selected)) {
 		/* Check if it is an accelerator. */
 		ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
@@ -407,16 +408,16 @@
 void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
 {
-	ui_menu_t *menu;
+	ui_menu_dd_t *mdd;
 	char32_t maccel;
 
-	menu = ui_menu_first(mbar);
-	while (menu != NULL) {
-		maccel = ui_menu_get_accel(menu);
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		maccel = ui_menu_dd_get_accel(mdd);
 		if (c == maccel) {
-			ui_menu_bar_select(mbar, menu, true, kbd_id);
+			ui_menu_bar_select(mbar, mdd, true, kbd_id);
 			return;
 		}
 
-		menu = ui_menu_next(menu);
+		mdd = ui_menu_dd_next(mdd);
 	}
 }
@@ -433,5 +434,5 @@
 	gfx_coord2_t pos;
 	gfx_rect_t rect;
-	ui_menu_t *menu;
+	ui_menu_dd_t *mdd;
 	const char *caption;
 	gfx_coord_t width;
@@ -454,7 +455,7 @@
 	pos_id = event->pos_id;
 
-	menu = ui_menu_first(mbar);
-	while (menu != NULL) {
-		caption = ui_menu_caption(menu);
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		caption = ui_menu_dd_caption(mdd);
 		width = ui_text_width(res->font, caption) + 2 * hpad;
 
@@ -469,8 +470,8 @@
 
 			/* Open the menu, close if already open. */
-			if (menu == mbar->selected)
+			if (mdd == mbar->selected)
 				ui_menu_bar_select(mbar, NULL, false, pos_id);
 			else
-				ui_menu_bar_select(mbar, menu, true, pos_id);
+				ui_menu_bar_select(mbar, mdd, true, pos_id);
 
 			return ui_claimed;
@@ -478,5 +479,5 @@
 
 		pos.x += width;
-		menu = ui_menu_next(menu);
+		mdd = ui_menu_dd_next(mdd);
 	}
 
@@ -487,8 +488,8 @@
  *
  * @param mbar Menu bar
- * @param menu Menu whose entry's rectangle is to be returned
+ * @param mdd Menu drop-down whose entry's rectangle is to be returned
  * @param rrect Place to store entry rectangle
  */
-void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_t *menu,
+void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd,
     gfx_rect_t *rrect)
 {
@@ -496,5 +497,5 @@
 	gfx_coord2_t pos;
 	gfx_rect_t rect;
-	ui_menu_t *cur;
+	ui_menu_dd_t *cur;
 	const char *caption;
 	gfx_coord_t width;
@@ -511,7 +512,7 @@
 	pos = mbar->rect.p0;
 
-	cur = ui_menu_first(mbar);
+	cur = ui_menu_dd_first(mbar);
 	while (cur != NULL) {
-		caption = ui_menu_caption(cur);
+		caption = ui_menu_dd_caption(cur);
 		width = ui_text_width(res->font, caption) + 2 * hpad;
 
@@ -520,5 +521,5 @@
 		rect.p1.y = mbar->rect.p1.y;
 
-		if (cur == menu) {
+		if (cur == mdd) {
 			*rrect = rect;
 			return;
@@ -526,5 +527,5 @@
 
 		pos.x += width;
-		cur = ui_menu_next(cur);
+		cur = ui_menu_dd_next(cur);
 	}
 
@@ -544,5 +545,5 @@
 	mbar->active = true;
 	if (mbar->selected == NULL)
-		mbar->selected = ui_menu_first(mbar);
+		mbar->selected = ui_menu_dd_first(mbar);
 
 	(void) ui_menu_bar_paint(mbar);
Index: uspace/lib/ui/src/menudd.c
===================================================================
--- uspace/lib/ui/src/menudd.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/src/menudd.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Menu drop-down
+ *
+ * One of the drop-down menus of a menu bar. This class takes the generic
+ * ui_menu and ties it to the menu bar.
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/accel.h>
+#include <ui/menu.h>
+#include <ui/menudd.h>
+#include <ui/menubar.h>
+#include <ui/resource.h>
+#include "../private/menubar.h"
+#include "../private/menudd.h"
+
+static void ui_menu_dd_left(ui_menu_t *, void *, sysarg_t);
+static void ui_menu_dd_right(ui_menu_t *, void *, sysarg_t);
+static void ui_menu_dd_close_req(ui_menu_t *, void *);
+static void ui_menu_dd_press_accel(ui_menu_t *, void *, char32_t, sysarg_t);
+
+static ui_menu_cb_t ui_menu_dd_menu_cb = {
+	.left = ui_menu_dd_left,
+	.right = ui_menu_dd_right,
+	.close_req = ui_menu_dd_close_req,
+	.press_accel = ui_menu_dd_press_accel
+};
+
+/** Create new menu drop-down.
+ *
+ * @param mbar Menu bar
+ * @param caption Caption
+ * @param rmdd Place to store pointer to new menu drop-down or @c NULL
+ * @param rmenu Place to store pointer to new menu or @c NULL
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_menu_dd_create(ui_menu_bar_t *mbar, const char *caption,
+    ui_menu_dd_t **rmdd, ui_menu_t **rmenu)
+{
+	errno_t rc;
+	ui_menu_dd_t *mdd;
+
+	mdd = calloc(1, sizeof(ui_menu_dd_t));
+	if (mdd == NULL)
+		return ENOMEM;
+
+	mdd->caption = str_dup(caption);
+	if (mdd->caption == NULL) {
+		free(mdd);
+		return ENOMEM;
+	}
+
+	/* Create menu */
+	rc = ui_menu_create(mbar->window, &mdd->menu);
+	if (rc != EOK) {
+		free(mdd->caption);
+		free(mdd);
+		return rc;
+	}
+
+	mdd->mbar = mbar;
+	list_append(&mdd->lmenudds, &mbar->menudds);
+
+	ui_menu_set_cb(mdd->menu, &ui_menu_dd_menu_cb, (void *)mdd);
+
+	if (rmdd != NULL)
+		*rmdd = mdd;
+	if (rmenu != NULL)
+		*rmenu = mdd->menu;
+	return EOK;
+}
+
+/** Destroy menu drop-down.
+ *
+ * @param menu Menu or @c NULL
+ */
+void ui_menu_dd_destroy(ui_menu_dd_t *mdd)
+{
+	if (mdd == NULL)
+		return;
+
+	/* Destroy menu */
+	ui_menu_destroy(mdd->menu);
+
+	list_remove(&mdd->lmenudds);
+	free(mdd->caption);
+	free(mdd);
+}
+
+/** Get first menu drop-down in menu bar.
+ *
+ * @param mbar Menu bar
+ * @return First menu or @c NULL if there is none
+ */
+ui_menu_dd_t *ui_menu_dd_first(ui_menu_bar_t *mbar)
+{
+	link_t *link;
+
+	link = list_first(&mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get next menu drop-down in menu bar.
+ *
+ * @param cur Current menu drop-down
+ * @return Next menu drop-down or @c NULL if @a cur is the last one
+ */
+ui_menu_dd_t *ui_menu_dd_next(ui_menu_dd_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lmenudds, &cur->mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get last menu drop-down in menu bar.
+ *
+ * @param mbar Menu bar
+ * @return Last menu drop-down or @c NULL if there is none
+ */
+ui_menu_dd_t *ui_menu_dd_last(ui_menu_bar_t *mbar)
+{
+	link_t *link;
+
+	link = list_last(&mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get previous menu drop-down in menu bar.
+ *
+ * @param cur Current menu drop-down
+ * @return Previous menu drop-down or @c NULL if @a cur is the fist one
+ */
+ui_menu_dd_t *ui_menu_dd_prev(ui_menu_dd_t *cur)
+{
+	link_t *link;
+
+	link = list_prev(&cur->lmenudds, &cur->mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get menu drop-down caption.
+ *
+ * @param mdd Menu drop-down
+ * @return Caption (owned by @a menu)
+ */
+const char *ui_menu_dd_caption(ui_menu_dd_t *mdd)
+{
+	return mdd->caption;
+}
+
+/** Get menu drop-down accelerator character.
+ *
+ * @param mdd Menu drop-down
+ * @return Accelerator character (lowercase) or the null character if
+ *         the menu has no accelerator.
+ */
+char32_t ui_menu_dd_get_accel(ui_menu_dd_t *mdd)
+{
+	return ui_accel_get(mdd->caption);
+}
+
+/** Open menu drop-down.
+ *
+ * @param mdd Menu drop-down
+ * @param prect Parent rectangle around which the drop-down should be placed
+ * @param idev_id Input device associated with the drop-down's seat
+ */
+errno_t ui_menu_dd_open(ui_menu_dd_t *mdd, gfx_rect_t *prect, sysarg_t idev_id)
+{
+	return ui_menu_open(mdd->menu, prect, idev_id);
+}
+
+/** Close menu drop-down.
+ *
+ * @param mdd Menu drop-down
+ */
+void ui_menu_dd_close(ui_menu_dd_t *mdd)
+{
+	ui_menu_close(mdd->menu);
+}
+
+/** Determine if menu drop-down is open.
+ *
+ * @param mdd Menu drop-down
+ * @return @c true iff menu drop-down is open
+ */
+bool ui_menu_dd_is_open(ui_menu_dd_t *mdd)
+{
+	return ui_menu_is_open(mdd->menu);
+}
+
+/** Handle menu left event.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ * @param idev_id Input device ID
+ */
+static void ui_menu_dd_left(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+
+	ui_menu_bar_left(mdd->mbar, idev_id);
+}
+
+/** Handle menu right event.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ * @param idev_id Input device ID
+ */
+static void ui_menu_dd_right(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+
+	ui_menu_bar_right(mdd->mbar, idev_id);
+}
+
+/** Handle menu close request.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ */
+static void ui_menu_dd_close_req(ui_menu_t *menu, void *arg)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+	ui_menu_bar_deactivate(mdd->mbar);
+}
+
+/** Handle menu accelerator key press event.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ * @param c Character
+ * @param kbd_id Keyboard ID
+ */
+static void ui_menu_dd_press_accel(ui_menu_t *menu, void *arg, char32_t c,
+    sysarg_t kbd_id)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+	ui_menu_bar_press_accel(mdd->mbar, c, kbd_id);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/menuentry.c
===================================================================
--- uspace/lib/ui/src/menuentry.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/src/menuentry.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -241,8 +241,8 @@
 	 * This needs to work even if the menu is not open, so we cannot
 	 * use the menu's resource, which is only created after the menu
-	 * is open (and its window is created). Use the menu bar's
+	 * is open (and its window is created). Use the parent window's
 	 * resource instead.
 	 */
-	res = ui_window_get_res(mentry->menu->mbar->window);
+	res = ui_window_get_res(mentry->menu->parent);
 
 	*caption_w = ui_text_width(res->font, mentry->caption);
@@ -267,8 +267,8 @@
 	 * This needs to work even if the menu is not open, so we cannot
 	 * use the menu's resource, which is only created after the menu
-	 * is open (and its window is created). Use the menu bar's
+	 * is open (and its window is created). Use the parent window's
 	 * resource instead.
 	 */
-	res = ui_window_get_res(menu->mbar->window);
+	res = ui_window_get_res(menu->parent);
 
 	if (res->textmode)
@@ -306,8 +306,8 @@
 	 * This needs to work even if the menu is not open, so we cannot
 	 * use the menu's resource, which is only created after the menu
-	 * is open (and its window is created). Use the menu bar's
+	 * is open (and its window is created). Use the parent window's
 	 * resource instead.
 	 */
-	res = ui_window_get_res(mentry->menu->mbar->window);
+	res = ui_window_get_res(mentry->menu->parent);
 
 	if (res->textmode) {
@@ -474,6 +474,6 @@
 void ui_menu_entry_activate(ui_menu_entry_t *mentry)
 {
-	/* Deactivate menu bar, close menu */
-	ui_menu_bar_deactivate(mentry->menu->mbar);
+	/* Close menu */
+	ui_menu_close_req(mentry->menu);
 
 	/* Call back */
Index: uspace/lib/ui/test/main.c
===================================================================
--- uspace/lib/ui/test/main.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/test/main.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -44,4 +44,5 @@
 PCUT_IMPORT(menu);
 PCUT_IMPORT(menubar);
+PCUT_IMPORT(menudd);
 PCUT_IMPORT(menuentry);
 PCUT_IMPORT(msg_dialog);
Index: uspace/lib/ui/test/menu.c
===================================================================
--- uspace/lib/ui/test/menu.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/test/menu.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -46,25 +46,40 @@
 PCUT_TEST_SUITE(menu);
 
+typedef struct {
+	bool left_called;
+	bool right_called;
+	bool close_req_called;
+	bool press_accel_called;
+	ui_menu_t *menu;
+	sysarg_t idev_id;
+	char32_t c;
+} test_resp_t;
+
+static void testmenu_left(ui_menu_t *, void *, sysarg_t);
+static void testmenu_right(ui_menu_t *, void *, sysarg_t);
+static void testmenu_close_req(ui_menu_t *, void *);
+static void testmenu_press_accel(ui_menu_t *, void *, char32_t, sysarg_t);
+
+ui_menu_cb_t testmenu_cb = {
+	.left = testmenu_left,
+	.right = testmenu_right,
+	.close_req = testmenu_close_req,
+	.press_accel = testmenu_press_accel
+};
+
+ui_menu_cb_t dummy_cb = {
+};
+
 /** Create and destroy menu */
 PCUT_TEST(create_destroy)
 {
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	errno_t rc;
-
-	rc = ui_menu_bar_create(NULL, NULL, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	/*
-	 * Normally we don't need to destroy a menu explicitly, it will
-	 * be destroyed along with menu bar, but here we'll test destroying
-	 * it explicitly.
-	 */
-	ui_menu_destroy(menu);
-	ui_menu_bar_destroy(mbar);
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_destroy(menu);
 }
 
@@ -75,707 +90,21 @@
 }
 
-/** ui_menu_first() / ui_menu_next() iterate over menus */
-PCUT_TEST(first_next)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu1 = NULL;
-	ui_menu_t *menu2 = NULL;
-	ui_menu_t *m;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu1);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu2);
-
-	m = ui_menu_first(mbar);
-	PCUT_ASSERT_EQUALS(menu1, m);
-
-	m = ui_menu_next(m);
-	PCUT_ASSERT_EQUALS(menu2, m);
-
-	m = ui_menu_next(m);
-	PCUT_ASSERT_NULL(m);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_last() / ui_menu_prev() iterate over menus in reverse */
-PCUT_TEST(last_prev)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu1 = NULL;
-	ui_menu_t *menu2 = NULL;
-	ui_menu_t *m;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu1);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu2);
-
-	m = ui_menu_last(mbar);
-	PCUT_ASSERT_EQUALS(menu2, m);
-
-	m = ui_menu_prev(m);
-	PCUT_ASSERT_EQUALS(menu1, m);
-
-	m = ui_menu_prev(m);
-	PCUT_ASSERT_NULL(m);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_caption() returns the menu's caption */
-PCUT_TEST(caption)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	const char *caption;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	caption = ui_menu_caption(menu);
-	PCUT_ASSERT_NOT_NULL(caption);
-
-	PCUT_ASSERT_INT_EQUALS(0, str_cmp(caption, "Test"));
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_get_rect() returns outer menu rectangle */
-PCUT_TEST(get_rect)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_coord2_t pos;
-	gfx_rect_t rect;
-	const char *caption;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	caption = ui_menu_caption(menu);
-	PCUT_ASSERT_NOT_NULL(caption);
-
-	pos.x = 0;
-	pos.y = 0;
-	ui_menu_get_rect(menu, &pos, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(0, rect.p0.x);
-	PCUT_ASSERT_INT_EQUALS(0, rect.p0.y);
-	PCUT_ASSERT_INT_EQUALS(16, rect.p1.x);
-	PCUT_ASSERT_INT_EQUALS(8, rect.p1.y);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Open and close menu with ui_menu_open() / ui_menu_close() */
-PCUT_TEST(open_close)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Open and close */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_close(menu);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_is_open() correctly returns menu state */
-PCUT_TEST(is_open)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	bool open;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	open = ui_menu_is_open(menu);
-	PCUT_ASSERT_FALSE(open);
-
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	open = ui_menu_is_open(menu);
-	PCUT_ASSERT_TRUE(open);
-
-	ui_menu_close(menu);
-
-	open = ui_menu_is_open(menu);
-	PCUT_ASSERT_FALSE(open);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Paint background in graphics mode */
-PCUT_TEST(paint_bg_gfx)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to paint it */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	pos.x = 0;
-	pos.y = 0;
-	rc = ui_menu_paint_bg_gfx(menu, &pos);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Paint background in text mode */
-PCUT_TEST(paint_bg_text)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to paint it */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	pos.x = 0;
-	pos.y = 0;
-	rc = ui_menu_paint_bg_text(menu, &pos);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Paint menu */
-PCUT_TEST(paint)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to paint it */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	pos.x = 0;
-	pos.y = 0;
-	rc = ui_menu_paint(menu, &pos);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_up() with empty menu does nothing */
-PCUT_TEST(up_empty)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to move around it */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_up(menu);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_up() moves one entry up, skips separators, wraps around */
-PCUT_TEST(up)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	ui_menu_entry_t *mentry1 = NULL;
-	ui_menu_entry_t *mentry2 = NULL;
-	ui_menu_entry_t *mentry3 = NULL;
-	gfx_rect_t prect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	rc = ui_menu_entry_create(menu, "Foo", "F1", &mentry1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mentry1);
-
-	rc = ui_menu_entry_sep_create(menu, &mentry2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mentry2);
-
-	rc = ui_menu_entry_create(menu, "Bar", "F2", &mentry3);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mentry3);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to move around it */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* When menu is open, the first entry is selected */
-	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
-
-	ui_menu_up(menu);
-
-	/* Now we've wrapped around to the last entry */
-	PCUT_ASSERT_EQUALS(mentry3, menu->selected);
-
-	ui_menu_up(menu);
-
-	/* mentry2 is a separator and was skipped */
-	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_down() with empty menu does nothing */
-PCUT_TEST(down_empty)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to move around it */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_down(menu);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_down() moves one entry down, skips separators, wraps around */
-PCUT_TEST(down)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	ui_menu_entry_t *mentry1 = NULL;
-	ui_menu_entry_t *mentry2 = NULL;
-	ui_menu_entry_t *mentry3 = NULL;
-	gfx_rect_t prect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	rc = ui_menu_entry_create(menu, "Foo", "F1", &mentry1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mentry1);
-
-	rc = ui_menu_entry_sep_create(menu, &mentry2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mentry2);
-
-	rc = ui_menu_entry_create(menu, "Bar", "F2", &mentry3);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mentry3);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to move around it */
-	rc = ui_menu_open(menu, &prect, 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* When menu is open, the first entry is selected */
-	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
-
-	ui_menu_down(menu);
-
-	/* mentry2 is a separator and was skipped */
-	PCUT_ASSERT_EQUALS(mentry3, menu->selected);
-
-	ui_menu_up(menu);
-
-	/* Now we've wrapped around to the first entry */
-	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_pos_event() inside menu is claimed */
-PCUT_TEST(pos_event_inside)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	ui_evclaim_t claimed;
-	gfx_coord2_t pos;
-	pos_event_t event;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	pos.x = 0;
-	pos.y = 0;
-	event.type = POS_PRESS;
-	event.hpos = 0;
-	event.vpos = 0;
-	claimed = ui_menu_pos_event(menu, &pos, &event);
-	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
+/** ui_menu_set_cb() sets the internal fields */
+PCUT_TEST(set_cb)
+{
+	ui_menu_t *menu = NULL;
+	ui_menu_cb_t cb;
+	int obj;
+	errno_t rc;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &cb, (void *)&obj);
+	PCUT_ASSERT_EQUALS(&cb, menu->cb);
+	PCUT_ASSERT_EQUALS((void *)&obj, menu->arg);
+
+	ui_menu_destroy(menu);
 }
 
@@ -786,5 +115,4 @@
 	ui_window_t *window = NULL;
 	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
 	ui_menu_t *menu = NULL;
 	ui_menu_geom_t geom;
@@ -802,9 +130,5 @@
 	PCUT_ASSERT_NOT_NULL(window);
 
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_create(window, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -823,7 +147,695 @@
 	PCUT_ASSERT_INT_EQUALS(4, geom.entries_rect.p1.y);
 
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
+	ui_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_get_res() gets the menu's resource */
+PCUT_TEST(get_res)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_resource_t *res;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* The menu must be open first */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	res = ui_menu_get_res(menu);
+	PCUT_ASSERT_NOT_NULL(res);
+
+	ui_menu_destroy(menu);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Open and close menu with ui_menu_open() / ui_menu_close() */
+PCUT_TEST(open_close)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Open and close */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_close(menu);
+
+	ui_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_is_open() correctly returns menu state */
+PCUT_TEST(is_open)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	bool open;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	open = ui_menu_is_open(menu);
+	PCUT_ASSERT_FALSE(open);
+
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	open = ui_menu_is_open(menu);
+	PCUT_ASSERT_TRUE(open);
+
+	ui_menu_close(menu);
+
+	open = ui_menu_is_open(menu);
+	PCUT_ASSERT_FALSE(open);
+
+	ui_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint background in graphics mode */
+PCUT_TEST(paint_bg_gfx)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	gfx_coord2_t pos;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to paint it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pos.x = 0;
+	pos.y = 0;
+	rc = ui_menu_paint_bg_gfx(menu, &pos);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint background in text mode */
+PCUT_TEST(paint_bg_text)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	gfx_coord2_t pos;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to paint it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pos.x = 0;
+	pos.y = 0;
+	rc = ui_menu_paint_bg_text(menu, &pos);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint menu */
+PCUT_TEST(paint)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	gfx_coord2_t pos;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to paint it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pos.x = 0;
+	pos.y = 0;
+	rc = ui_menu_paint(menu, &pos);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_pos_event() inside menu is claimed */
+PCUT_TEST(pos_event_inside)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_evclaim_t claimed;
+	gfx_coord2_t pos;
+	pos_event_t event;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	pos.x = 0;
+	pos.y = 0;
+	event.type = POS_PRESS;
+	event.hpos = 0;
+	event.vpos = 0;
+	claimed = ui_menu_pos_event(menu, &pos, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_up() with empty menu does nothing */
+PCUT_TEST(up_empty)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_up(menu);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_up() moves one entry up, skips separators, wraps around */
+PCUT_TEST(up)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *mentry1 = NULL;
+	ui_menu_entry_t *mentry2 = NULL;
+	ui_menu_entry_t *mentry3 = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	rc = ui_menu_entry_create(menu, "Foo", "F1", &mentry1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry1);
+
+	rc = ui_menu_entry_sep_create(menu, &mentry2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry2);
+
+	rc = ui_menu_entry_create(menu, "Bar", "F2", &mentry3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry3);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* When menu is open, the first entry is selected */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_menu_up(menu);
+
+	/* Now we've wrapped around to the last entry */
+	PCUT_ASSERT_EQUALS(mentry3, menu->selected);
+
+	ui_menu_up(menu);
+
+	/* mentry2 is a separator and was skipped */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_down() with empty menu does nothing */
+PCUT_TEST(down_empty)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_down(menu);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_down() moves one entry down, skips separators, wraps around */
+PCUT_TEST(down)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *mentry1 = NULL;
+	ui_menu_entry_t *mentry2 = NULL;
+	ui_menu_entry_t *mentry3 = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	rc = ui_menu_entry_create(menu, "Foo", "F1", &mentry1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry1);
+
+	rc = ui_menu_entry_sep_create(menu, &mentry2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry2);
+
+	rc = ui_menu_entry_create(menu, "Bar", "F2", &mentry3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry3);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* When menu is open, the first entry is selected */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_menu_down(menu);
+
+	/* mentry2 is a separator and was skipped */
+	PCUT_ASSERT_EQUALS(mentry3, menu->selected);
+
+	ui_menu_up(menu);
+
+	/* Now we've wrapped around to the first entry */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Sending an unhandled event does nothing. */
+PCUT_TEST(send_unhandled)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	sysarg_t idev_id;
+	char32_t c;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	/* Send events without setting callback */
+	c = 'A';
+	idev_id = 42;
+	ui_menu_left(menu, idev_id);
+	ui_menu_right(menu, idev_id);
+	ui_menu_close_req(menu);
+	ui_menu_press_accel(menu, c, idev_id);
+
+	/* Set dummy callback structure */
+	ui_menu_set_cb(menu, &dummy_cb, NULL);
+
+	/* Send unhandled events */
+	ui_menu_left(menu, idev_id);
+	ui_menu_right(menu, idev_id);
+	ui_menu_close_req(menu);
+	ui_menu_press_accel(menu, c, idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_left() sends left event */
+PCUT_TEST(left)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+	sysarg_t idev_id;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.left_called);
+
+	idev_id = 42;
+	ui_menu_left(menu, idev_id);
+
+	PCUT_ASSERT_TRUE(resp.left_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+	PCUT_ASSERT_INT_EQUALS(idev_id, resp.idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_right() sends right event */
+PCUT_TEST(right)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+	sysarg_t idev_id;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.right_called);
+
+	idev_id = 42;
+	ui_menu_right(menu, idev_id);
+
+	PCUT_ASSERT_TRUE(resp.right_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+	PCUT_ASSERT_INT_EQUALS(idev_id, resp.idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_close_req() sends close_req event */
+PCUT_TEST(close_req)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.close_req_called);
+
+	ui_menu_close_req(menu);
+
+	PCUT_ASSERT_TRUE(resp.close_req_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_press_accel() sends press_accel event */
+PCUT_TEST(press_accel)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+	char32_t c;
+	sysarg_t idev_id;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.press_accel_called);
+
+	c = 'A';
+	idev_id = 42;
+	ui_menu_press_accel(menu, c, idev_id);
+
+	PCUT_ASSERT_TRUE(resp.press_accel_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+	PCUT_ASSERT_EQUALS(c, resp.c);
+	PCUT_ASSERT_INT_EQUALS(idev_id, resp.idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** Test menu left callback */
+static void testmenu_left(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->left_called = true;
+	resp->menu = menu;
+	resp->idev_id = idev_id;
+}
+
+/** Test menu right callback */
+static void testmenu_right(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->right_called = true;
+	resp->menu = menu;
+	resp->idev_id = idev_id;
+}
+
+/** Test menu close callback */
+static void testmenu_close_req(ui_menu_t *menu, void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->close_req_called = true;
+	resp->menu = menu;
+}
+
+/** Test menu press accel callback */
+static void testmenu_press_accel(ui_menu_t *menu, void *arg,
+    char32_t c, sysarg_t kbd_id)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->press_accel_called = true;
+	resp->menu = menu;
+	resp->c = c;
+	resp->idev_id = kbd_id;
 }
 
Index: uspace/lib/ui/test/menubar.c
===================================================================
--- uspace/lib/ui/test/menubar.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/test/menubar.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -32,6 +32,6 @@
 #include <stdbool.h>
 #include <ui/control.h>
-#include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/ui.h>
 #include <ui/window.h>
@@ -182,5 +182,5 @@
 	ui_menu_bar_set_rect(mbar, &rect);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -206,4 +206,5 @@
 	ui_menu_bar_t *mbar = NULL;
 	ui_menu_t *menu = NULL;
+	ui_menu_dd_t *mdd = NULL;
 	gfx_rect_t rect;
 	errno_t rc;
@@ -229,13 +230,14 @@
 	ui_menu_bar_set_rect(mbar, &rect);
 
-	rc = ui_menu_create(mbar, "~T~est", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = ui_menu_dd_create(mbar, "~T~est", &mdd, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
 	PCUT_ASSERT_NOT_NULL(menu);
 
-	PCUT_ASSERT_FALSE(ui_menu_is_open(menu));
+	PCUT_ASSERT_FALSE(ui_menu_dd_is_open(mdd));
 
 	ui_menu_bar_press_accel(mbar, 't', 0);
 
-	PCUT_ASSERT_TRUE(ui_menu_is_open(menu));
+	PCUT_ASSERT_TRUE(ui_menu_dd_is_open(mdd));
 
 	ui_menu_bar_destroy(mbar);
@@ -252,4 +254,5 @@
 	ui_menu_bar_t *mbar = NULL;
 	ui_menu_t *menu = NULL;
+	ui_menu_dd_t *mdd = NULL;
 	ui_evclaim_t claimed;
 	pos_event_t event;
@@ -277,5 +280,5 @@
 	ui_menu_bar_set_rect(mbar, &rect);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -288,6 +291,6 @@
 	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
 
-	/* Clicking the menu bar entry should select menu */
-	PCUT_ASSERT_EQUALS(menu, mbar->selected);
+	/* Clicking the menu bar entry should select menu drop-down */
+	PCUT_ASSERT_EQUALS(mdd, mbar->selected);
 
 	ui_menu_bar_destroy(mbar);
@@ -303,36 +306,36 @@
 	ui_wnd_params_t params;
 	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu1 = NULL;
-	ui_menu_t *menu2 = NULL;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu1);
-
-	rc = ui_menu_create(mbar, "Test 2", &menu2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu2);
-
-	ui_menu_bar_select(mbar, menu1, true, 0);
-	PCUT_ASSERT_EQUALS(menu1, mbar->selected);
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 2", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	ui_menu_bar_select(mbar, mdd1, true, 0);
+	PCUT_ASSERT_EQUALS(mdd1, mbar->selected);
 
 	/* Selecting different menu should select it */
-	ui_menu_bar_select(mbar, menu2, true, 0);
-	PCUT_ASSERT_EQUALS(menu2, mbar->selected);
+	ui_menu_bar_select(mbar, mdd2, true, 0);
+	PCUT_ASSERT_EQUALS(mdd2, mbar->selected);
 
 	ui_menu_bar_destroy(mbar);
@@ -348,27 +351,27 @@
 	ui_wnd_params_t params;
 	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
+	ui_menu_dd_t *mdd = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
 
 	ui_menu_bar_activate(mbar);
-	PCUT_ASSERT_EQUALS(menu, mbar->selected);
+	PCUT_ASSERT_EQUALS(mdd, mbar->selected);
 
 	ui_menu_bar_deactivate(mbar);
Index: uspace/lib/ui/test/menudd.c
===================================================================
--- uspace/lib/ui/test/menudd.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/test/menudd.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gfx/context.h>
+#include <gfx/coord.h>
+#include <mem.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/menudd.h>
+#include <ui/menubar.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/menu.h"
+#include "../private/menubar.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(menudd);
+
+/** Create and destroy menu drop-down */
+PCUT_TEST(create_destroy)
+{
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+
+	rc = ui_menu_bar_create(NULL, NULL, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	/*
+	 * Normally we don't need to destroy a menu drop-down explicitly,
+	 * it will be destroyed along with menu bar, but here we'll test
+	 * destroying it explicitly.
+	 */
+	ui_menu_dd_destroy(mdd);
+	ui_menu_bar_destroy(mbar);
+}
+
+/** ui_menu_dd_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_menu_dd_destroy(NULL);
+}
+
+/** ui_menu_dd_first() / ui_menu_dd_next() iterate over menu drop-downs */
+PCUT_TEST(first_next)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	ui_menu_dd_t *m;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	m = ui_menu_dd_first(mbar);
+	PCUT_ASSERT_EQUALS(mdd1, m);
+
+	m = ui_menu_dd_next(m);
+	PCUT_ASSERT_EQUALS(mdd2, m);
+
+	m = ui_menu_dd_next(m);
+	PCUT_ASSERT_NULL(m);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_last() / ui_menu_dd_prev() iterate over menu drop-downs
+ * in reverse.
+ */
+PCUT_TEST(last_prev)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	ui_menu_dd_t *m;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	m = ui_menu_dd_last(mbar);
+	PCUT_ASSERT_EQUALS(mdd2, m);
+
+	m = ui_menu_dd_prev(m);
+	PCUT_ASSERT_EQUALS(mdd1, m);
+
+	m = ui_menu_dd_prev(m);
+	PCUT_ASSERT_NULL(m);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_caption() returns the drop down's caption */
+PCUT_TEST(caption)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	const char *caption;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	caption = ui_menu_dd_caption(mdd);
+	PCUT_ASSERT_NOT_NULL(caption);
+
+	PCUT_ASSERT_INT_EQUALS(0, str_cmp(caption, "Test"));
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_get_accel() returns the accelerator character */
+PCUT_TEST(get_accel)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	char32_t accel;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "~T~est", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	accel = ui_menu_dd_get_accel(mdd);
+	printf("accel='%c' (%d)\n", (char)accel, (int)accel);
+	PCUT_ASSERT_EQUALS((char32_t)'t', accel);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Open and close menu drop-down with ui_menu_dd_open() / ui_menu_dd_close() */
+PCUT_TEST(open_close)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Open and close */
+	rc = ui_menu_dd_open(mdd, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_dd_close(mdd);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_is_open() correctly returns menu drop-down state */
+PCUT_TEST(is_open)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	gfx_rect_t prect;
+	bool open;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	open = ui_menu_dd_is_open(mdd);
+	PCUT_ASSERT_FALSE(open);
+
+	rc = ui_menu_dd_open(mdd, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	open = ui_menu_dd_is_open(mdd);
+	PCUT_ASSERT_TRUE(open);
+
+	ui_menu_dd_close(mdd);
+
+	open = ui_menu_dd_is_open(mdd);
+	PCUT_ASSERT_FALSE(open);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+PCUT_EXPORT(menudd);
Index: uspace/lib/ui/test/menuentry.c
===================================================================
--- uspace/lib/ui/test/menuentry.c	(revision 0b6fad98b01843ea564e7d4184663eef63f3a047)
+++ uspace/lib/ui/test/menuentry.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
@@ -35,4 +35,5 @@
 #include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
 #include <ui/ui.h>
@@ -75,5 +76,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -116,5 +117,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -164,5 +165,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -210,5 +211,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -263,5 +264,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -318,5 +319,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -368,5 +369,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -419,5 +420,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -476,5 +477,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -539,5 +540,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -607,5 +608,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -679,5 +680,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -737,5 +738,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -797,5 +798,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -857,5 +858,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -920,5 +921,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
