Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/app/uidemo/uidemo.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -43,4 +43,7 @@
 #include <ui/image.h>
 #include <ui/label.h>
+#include <ui/menubar.h>
+#include <ui/menuentry.h>
+#include <ui/menu.h>
 #include <ui/pbutton.h>
 #include <ui/resource.h>
@@ -80,4 +83,6 @@
 	.moved = slider_moved
 };
+
+static void uidemo_file_exit(ui_menu_entry_t *, void *);
 
 /** Window close button was clicked.
@@ -185,4 +190,16 @@
 }
 
+/** File/exit menu entry selected.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (demo)
+ */
+static void uidemo_file_exit(ui_menu_entry_t *mentry, void *arg)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+
+	ui_quit(demo->ui);
+}
+
 /** Run UI demo on display server. */
 static errno_t ui_demo(const char *display_spec)
@@ -198,4 +215,9 @@
 	gfx_bitmap_t *bitmap;
 	gfx_coord2_t off;
+	ui_menu_entry_t *mfoo;
+	ui_menu_entry_t *mbar;
+	ui_menu_entry_t *mfoobar;
+	ui_menu_entry_t *mexit;
+	ui_menu_entry_t *mabout;
 	errno_t rc;
 
@@ -212,5 +234,5 @@
 	params.rect.p0.y = 0;
 	params.rect.p1.x = 220;
-	params.rect.p1.y = 340;
+	params.rect.p1.y = 350;
 
 	memset((void *) &demo, 0, sizeof(demo));
@@ -235,4 +257,97 @@
 	}
 
+	rc = ui_menu_bar_create(ui_res, &demo.mbar);
+	if (rc != EOK) {
+		printf("Error creating menu bar.\n");
+		return rc;
+	}
+
+	rc = ui_menu_create(demo.mbar, "File", &demo.mfile);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_entry_create(demo.mfile, "Foo", &mfoo);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_entry_create(demo.mfile, "Bar", &mbar);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_entry_create(demo.mfile, "Foobar", &mfoobar);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_entry_create(demo.mfile, "Exit", &mexit);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	ui_menu_entry_set_cb(mexit, uidemo_file_exit, (void *) &demo);
+
+	rc = ui_menu_create(demo.mbar, "Edit", &demo.medit);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_create(demo.mbar, "Preferences", &demo.mpreferences);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_create(demo.mbar, "Help", &demo.mhelp);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_entry_create(demo.mhelp, "About", &mabout);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rect.p0.x = 4;
+	rect.p0.y = 30;
+	rect.p1.x = 216;
+	rect.p1.y = 52;
+	ui_menu_bar_set_rect(demo.mbar, &rect);
+
+	rc = ui_fixed_add(demo.fixed, ui_menu_bar_ctl(demo.mbar));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	rc = ui_entry_create(ui_res, "", &demo.entry);
+	if (rc != EOK) {
+		printf("Error creating entry.\n");
+		return rc;
+	}
+
+	rect.p0.x = 15;
+	rect.p0.y = 53;
+	rect.p1.x = 205;
+	rect.p1.y = 78;
+	ui_entry_set_rect(demo.entry, &rect);
+	ui_entry_set_halign(demo.entry, gfx_halign_center);
+
+	rc = ui_fixed_add(demo.fixed, ui_entry_ctl(demo.entry));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
 	rc = ui_label_create(ui_res, "Text label", &demo.label);
 	if (rc != EOK) {
@@ -242,7 +357,7 @@
 
 	rect.p0.x = 60;
-	rect.p0.y = 37;
+	rect.p0.y = 88;
 	rect.p1.x = 160;
-	rect.p1.y = 50;
+	rect.p1.y = 101;
 	ui_label_set_rect(demo.label, &rect);
 	ui_label_set_halign(demo.label, gfx_halign_center);
@@ -263,7 +378,7 @@
 
 	rect.p0.x = 15;
-	rect.p0.y = 70;
+	rect.p0.y = 111;
 	rect.p1.x = 105;
-	rect.p1.y = 98;
+	rect.p1.y = 139;
 	ui_pbutton_set_rect(demo.pb1, &rect);
 
@@ -285,29 +400,10 @@
 
 	rect.p0.x = 115;
-	rect.p0.y = 70;
+	rect.p0.y = 111;
 	rect.p1.x = 205;
-	rect.p1.y = 98;
+	rect.p1.y = 139;
 	ui_pbutton_set_rect(demo.pb2, &rect);
 
 	rc = ui_fixed_add(demo.fixed, ui_pbutton_ctl(demo.pb2));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	rc = ui_entry_create(ui_res, "", &demo.entry);
-	if (rc != EOK) {
-		printf("Error creating entry.\n");
-		return rc;
-	}
-
-	rect.p0.x = 15;
-	rect.p0.y = 110;
-	rect.p1.x = 205;
-	rect.p1.y = 135;
-	ui_entry_set_rect(demo.entry, &rect);
-	ui_entry_set_halign(demo.entry, gfx_halign_center);
-
-	rc = ui_fixed_add(demo.fixed, ui_entry_ctl(demo.entry));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -336,5 +432,5 @@
 
 	off.x = 15;
-	off.y = 145;
+	off.y = 155;
 	gfx_rect_translate(&off, &bparams.rect, &rect);
 
@@ -360,7 +456,7 @@
 
 	rect.p0.x = 15;
-	rect.p0.y = 180;
+	rect.p0.y = 190;
 	rect.p1.x = 140;
-	rect.p1.y = 200;
+	rect.p1.y = 210;
 	ui_checkbox_set_rect(demo.checkbox, &rect);
 
@@ -388,7 +484,7 @@
 
 	rect.p0.x = 15;
-	rect.p0.y = 210;
+	rect.p0.y = 220;
 	rect.p1.x = 140;
-	rect.p1.y = 230;
+	rect.p1.y = 240;
 	ui_rbutton_set_rect(demo.rb1, &rect);
 
@@ -407,7 +503,7 @@
 
 	rect.p0.x = 15;
-	rect.p0.y = 240;
+	rect.p0.y = 250;
 	rect.p1.x = 140;
-	rect.p1.y = 260;
+	rect.p1.y = 270;
 	ui_rbutton_set_rect(demo.rb2, &rect);
 
@@ -426,7 +522,7 @@
 
 	rect.p0.x = 15;
-	rect.p0.y = 270;
+	rect.p0.y = 280;
 	rect.p1.x = 140;
-	rect.p1.y = 290;
+	rect.p1.y = 300;
 	ui_rbutton_set_rect(demo.rb3, &rect);
 
@@ -446,7 +542,7 @@
 
 	rect.p0.x = 15;
-	rect.p0.y = 300;
+	rect.p0.y = 310;
 	rect.p1.x = 130;
-	rect.p1.y = 320;
+	rect.p1.y = 330;
 	ui_slider_set_rect(demo.slider, &rect);
 
Index: uspace/app/uidemo/uidemo.h
===================================================================
--- uspace/app/uidemo/uidemo.h	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/app/uidemo/uidemo.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -42,4 +42,6 @@
 #include <ui/fixed.h>
 #include <ui/label.h>
+#include <ui/menu.h>
+#include <ui/menubar.h>
 #include <ui/pbutton.h>
 #include <ui/rbutton.h>
@@ -53,4 +55,9 @@
 	ui_window_t *window;
 	ui_fixed_t *fixed;
+	ui_menu_bar_t *mbar;
+	ui_menu_t *mfile;
+	ui_menu_t *medit;
+	ui_menu_t *mpreferences;
+	ui_menu_t *mhelp;
 	ui_entry_t *entry;
 	ui_image_t *image;
Index: uspace/lib/ui/include/types/ui/menu.h
===================================================================
--- uspace/lib/ui/include/types/ui/menu.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/include/types/ui/menu.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2021 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_MENU_H
+#define _UI_TYPES_MENU_H
+
+struct ui_menu;
+typedef struct ui_menu ui_menu_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/menubar.h
===================================================================
--- uspace/lib/ui/include/types/ui/menubar.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/include/types/ui/menubar.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2021 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 bar
+ */
+
+#ifndef _UI_TYPES_MENUBAR_H
+#define _UI_TYPES_MENUBAR_H
+
+struct ui_menu_bar;
+typedef struct ui_menu_bar ui_menu_bar_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/menuentry.h
===================================================================
--- uspace/lib/ui/include/types/ui/menuentry.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/include/types/ui/menuentry.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021 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 entry
+ */
+
+#ifndef _UI_TYPES_MENUENTRY_H
+#define _UI_TYPES_MENUENTRY_H
+
+struct ui_menu_entry;
+typedef struct ui_menu_entry ui_menu_entry_t;
+
+/** Menu entry callback */
+typedef void (*ui_menu_entry_cb_t)(ui_menu_entry_t *, void *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/rbutton.h
===================================================================
--- uspace/lib/ui/include/types/ui/rbutton.h	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/include/types/ui/rbutton.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -37,6 +37,4 @@
 #define _UI_TYPES_RBUTTON_H
 
-#include <stdbool.h>
-
 struct ui_rbutton_group;
 typedef struct ui_rbutton_group ui_rbutton_group_t;
Index: uspace/lib/ui/include/types/ui/resource.h
===================================================================
--- uspace/lib/ui/include/types/ui/resource.h	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/include/types/ui/resource.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -40,4 +40,6 @@
 typedef struct ui_resource ui_resource_t;
 
+typedef void (*ui_expose_cb_t)(void *);
+
 #endif
 
Index: uspace/lib/ui/include/ui/menu.h
===================================================================
--- uspace/lib/ui/include/ui/menu.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/include/ui/menu.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021 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_MENU_H
+#define _UI_MENU_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/pos_event.h>
+#include <types/ui/menu.h>
+#include <types/ui/menubar.h>
+#include <types/ui/event.h>
+
+extern errno_t ui_menu_create(ui_menu_bar_t *, const char *, 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 const char *ui_menu_caption(ui_menu_t *);
+extern void ui_menu_get_rect(ui_menu_t *, gfx_coord2_t *, gfx_rect_t *);
+extern errno_t ui_menu_paint(ui_menu_t *, gfx_coord2_t *);
+extern errno_t ui_menu_unpaint(ui_menu_t *);
+extern void ui_menu_press(ui_menu_t *, gfx_coord2_t *, gfx_coord2_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/menubar.h
===================================================================
--- uspace/lib/ui/include/ui/menubar.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/include/ui/menubar.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2021 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 bar
+ */
+
+#ifndef _UI_MENUBAR_H
+#define _UI_MENUBAR_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/pos_event.h>
+#include <types/ui/menubar.h>
+#include <types/ui/control.h>
+#include <types/ui/event.h>
+#include <types/ui/resource.h>
+
+extern errno_t ui_menu_bar_create(ui_resource_t *,
+    ui_menu_bar_t **);
+extern void ui_menu_bar_destroy(ui_menu_bar_t *);
+extern ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *);
+extern void ui_menu_bar_set_rect(ui_menu_bar_t *, gfx_rect_t *);
+extern errno_t ui_menu_bar_paint(ui_menu_bar_t *);
+extern ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *, pos_event_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/menuentry.h
===================================================================
--- uspace/lib/ui/include/ui/menuentry.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/include/ui/menuentry.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2021 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 entry
+ */
+
+#ifndef _UI_MENUENTRY_H
+#define _UI_MENUENTRY_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <types/ui/menu.h>
+#include <types/ui/menuentry.h>
+#include <types/ui/event.h>
+
+extern errno_t ui_menu_entry_create(ui_menu_t *, const char *,
+    ui_menu_entry_t **);
+extern void ui_menu_entry_destroy(ui_menu_entry_t *);
+extern void ui_menu_entry_set_cb(ui_menu_entry_t *, ui_menu_entry_cb_t,
+    void *);
+extern ui_menu_entry_t *ui_menu_entry_first(ui_menu_t *);
+extern ui_menu_entry_t *ui_menu_entry_next(ui_menu_entry_t *);
+extern gfx_coord_t ui_menu_entry_width(ui_menu_entry_t *);
+extern gfx_coord_t ui_menu_entry_height(ui_menu_entry_t *);
+extern errno_t ui_menu_entry_paint(ui_menu_entry_t *, gfx_coord2_t *);
+extern void ui_menu_entry_press(ui_menu_entry_t *, gfx_coord2_t *,
+    gfx_coord2_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/paint.h
===================================================================
--- uspace/lib/ui/include/ui/paint.h	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/include/ui/paint.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -47,4 +47,6 @@
 extern errno_t ui_paint_inset_frame(ui_resource_t *, gfx_rect_t *,
     gfx_rect_t *);
+extern errno_t ui_paint_outset_frame(ui_resource_t *, gfx_rect_t *,
+    gfx_rect_t *);
 extern errno_t ui_paint_filled_circle(gfx_context_t *, gfx_coord2_t *,
     gfx_coord_t, ui_fcircle_part_t);
Index: uspace/lib/ui/include/ui/resource.h
===================================================================
--- uspace/lib/ui/include/ui/resource.h	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/include/ui/resource.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -44,4 +44,7 @@
 extern errno_t ui_resource_create(gfx_context_t *, bool, ui_resource_t **);
 extern void ui_resource_destroy(ui_resource_t *);
+extern void ui_resource_set_expose_cb(ui_resource_t *, ui_expose_cb_t,
+    void *);
+extern void ui_resource_expose(ui_resource_t *);
 
 #endif
Index: uspace/lib/ui/meson.build
===================================================================
--- uspace/lib/ui/meson.build	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/meson.build	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -36,4 +36,7 @@
 	'src/image.c',
 	'src/label.c',
+	'src/menu.c',
+	'src/menubar.c',
+	'src/menuentry.c',
 	'src/paint.c',
 	'src/pbutton.c',
Index: uspace/lib/ui/private/menu.h
===================================================================
--- uspace/lib/ui/private/menu.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/private/menu.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2021 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 structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_MENU_H
+#define _UI_PRIVATE_MENU_H
+
+#include <adt/list.h>
+#include <stdbool.h>
+
+/** Actual structure of menu.
+ *
+ * This is private to libui.
+ */
+struct ui_menu {
+	/** Containing menu bar */
+	struct ui_menu_bar *mbar;
+	/** Link to @c bar->menus */
+	link_t lmenus;
+	/** Caption */
+	char *caption;
+	/** Menu is currently open */
+	bool open;
+	/** Selected menu entry or @c NULL */
+	struct ui_menu_entry *selected;
+	/** Menu entries (ui_menu_entry_t) */
+	list_t entries;
+};
+
+/** Menu geometry.
+ *
+ * Computed rectangles of menu elements.
+ */
+typedef struct {
+	/** Outer rectangle */
+	gfx_rect_t outer_rect;
+	/** Entries rectangle */
+	gfx_rect_t entries_rect;
+} ui_menu_geom_t;
+
+extern void ui_menu_get_geom(ui_menu_t *, gfx_coord2_t *, ui_menu_geom_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/menubar.h
===================================================================
--- uspace/lib/ui/private/menubar.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/private/menubar.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021 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 bar structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_MENUBAR_H
+#define _UI_PRIVATE_MENUBAR_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <types/ui/menubar.h>
+
+/** Actual structure of menu bar.
+ *
+ * This is private to libui.
+ */
+struct ui_menu_bar {
+	/** Base control object */
+	struct ui_control *control;
+	/** UI resource */
+	struct ui_resource *res;
+	/** Menu bar rectangle */
+	gfx_rect_t rect;
+	/** Selected menu or @c NULL */
+	struct ui_menu *selected;
+	/** Position of selected entry */
+	gfx_coord2_t sel_pos;
+	/** List of menus (ui_menu_t) */
+	list_t menus;
+};
+
+extern void ui_menu_bar_select(ui_menu_bar_t *, gfx_coord2_t *, ui_menu_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/menuentry.h
===================================================================
--- uspace/lib/ui/private/menuentry.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/private/menuentry.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2021 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 entry structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_MENUENTRY_H
+#define _UI_PRIVATE_MENUENTRY_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <types/ui/menuentry.h>
+
+/** Actual structure of menu entry.
+ *
+ * This is private to libui.
+ */
+struct ui_menu_entry {
+	/** Containing menu */
+	struct ui_menu *menu;
+	/** Link to @c menu->entries */
+	link_t lentries;
+	/** Callbacks */
+	ui_menu_entry_cb_t cb;
+	/** Callback argument */
+	void *arg;
+	/** Caption */
+	char *caption;
+};
+
+/** Menu entry geometry.
+ *
+ * Computed positions of menu entry elements.
+ */
+typedef struct {
+	/** Outer rectangle */
+	gfx_rect_t outer_rect;
+	/** Text position */
+	gfx_coord2_t text_pos;
+} ui_menu_entry_geom_t;
+
+extern void ui_menu_entry_get_geom(ui_menu_entry_t *, gfx_coord2_t *,
+    ui_menu_entry_geom_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/resource.h
===================================================================
--- uspace/lib/ui/private/resource.h	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/private/resource.h	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -43,4 +43,5 @@
 #include <gfx/typeface.h>
 #include <stdbool.h>
+#include <types/ui/resource.h>
 
 /** Actual structure of UI resources.
@@ -73,4 +74,8 @@
 	/** Window text color */
 	gfx_color_t *wnd_text_color;
+	/** Window selected text color */
+	gfx_color_t *wnd_sel_text_color;
+	/** Window selected text background color */
+	gfx_color_t *wnd_sel_text_bg_color;
 	/** Window frame hightlight color */
 	gfx_color_t *wnd_frame_hi_color;
@@ -97,4 +102,9 @@
 	/** Entry (text entry, checkbox, raido button) active background color */
 	gfx_color_t *entry_act_bg_color;
+
+	/** Expose callback or @c NULL */
+	ui_expose_cb_t expose_cb;
+	/** Expose callback argument */
+	void *expose_arg;
 };
 
Index: uspace/lib/ui/src/menu.c
===================================================================
--- uspace/lib/ui/src/menu.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/src/menu.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2021 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
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <gfx/color.h>
+#include <gfx/context.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <io/pos_event.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/paint.h>
+#include <ui/menu.h>
+#include <ui/menuentry.h>
+#include <ui/resource.h>
+#include "../private/menubar.h"
+#include "../private/menu.h"
+#include "../private/resource.h"
+
+enum {
+	menu_frame_w = 4,
+	menu_frame_h = 4,
+	menu_frame_w_text = 2,
+	menu_frame_h_text = 1
+};
+
+/** Create new menu.
+ *
+ * @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)
+{
+	ui_menu_t *menu;
+
+	menu = calloc(1, sizeof(ui_menu_t));
+	if (menu == NULL)
+		return ENOMEM;
+
+	menu->caption = str_dup(caption);
+	if (menu->caption == NULL) {
+		free(menu);
+		return ENOMEM;
+	}
+
+	menu->mbar = mbar;
+	list_append(&menu->lmenus, &mbar->menus);
+	list_initialize(&menu->entries);
+
+	*rmenu = menu;
+	return EOK;
+}
+
+/** Destroy menu.
+ *
+ * @param menu Menu or @c NULL
+ */
+void ui_menu_destroy(ui_menu_t *menu)
+{
+	ui_menu_entry_t *mentry;
+
+	if (menu == NULL)
+		return;
+
+	/* Destroy entries */
+	mentry = ui_menu_entry_first(menu);
+	while (mentry != NULL) {
+		ui_menu_entry_destroy(mentry);
+		mentry = ui_menu_entry_first(menu);
+	}
+
+	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 menu caption.
+ *
+ * @param menu Menu
+ * @return Caption (owned by @a menu)
+ */
+const char *ui_menu_caption(ui_menu_t *menu)
+{
+	return menu->caption;
+}
+
+/** Get menu geometry.
+ *
+ * @param menu Menu
+ * @param spos Starting position
+ * @param geom Structure to fill in with computed geometry
+ */
+void ui_menu_get_geom(ui_menu_t *menu, gfx_coord2_t *spos,
+    ui_menu_geom_t *geom)
+{
+	ui_menu_entry_t *mentry;
+	gfx_coord2_t edim;
+	gfx_coord_t frame_w;
+	gfx_coord_t frame_h;
+	gfx_coord_t w;
+	gfx_coord_t h;
+
+	if (menu->mbar->res->textmode) {
+		frame_w = menu_frame_w_text;
+		frame_h = menu_frame_h_text;
+	} else {
+		frame_w = menu_frame_w;
+		frame_h = menu_frame_h;
+	}
+
+	edim.x = 0;
+	edim.y = 0;
+
+	mentry = ui_menu_entry_first(menu);
+	while (mentry != NULL) {
+		w = ui_menu_entry_width(mentry);
+		h = ui_menu_entry_height(mentry);
+
+		if (w > edim.x)
+			edim.x = w;
+		edim.y += h;
+
+		mentry = ui_menu_entry_next(mentry);
+	}
+
+	geom->outer_rect.p0 = *spos;
+	geom->outer_rect.p1.x = spos->x + edim.x + 2 * frame_w;
+	geom->outer_rect.p1.y = spos->y + edim.y + 2 * frame_h;
+
+	geom->entries_rect.p0.x = spos->x + frame_w;
+	geom->entries_rect.p0.y = spos->y + frame_h;
+	geom->entries_rect.p1.x = geom->entries_rect.p0.x + edim.x;
+	geom->entries_rect.p1.y = geom->entries_rect.p0.x + edim.y;
+}
+
+/** 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;
+}
+
+/** Paint menu.
+ *
+ * @param menu Menu
+ * @param spos Starting position (top-left corner)
+ * @return EOK on success or an error code
+ */
+errno_t ui_menu_paint(ui_menu_t *menu, gfx_coord2_t *spos)
+{
+	ui_resource_t *res;
+	gfx_coord2_t pos;
+	ui_menu_entry_t *mentry;
+	ui_menu_geom_t geom;
+	gfx_rect_t bg_rect;
+	errno_t rc;
+
+	res = menu->mbar->res;
+	ui_menu_get_geom(menu, spos, &geom);
+
+	/* Paint menu frame */
+
+	rc = gfx_set_color(res->gc, res->wnd_face_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_paint_outset_frame(res, &geom.outer_rect, &bg_rect);
+	if (rc != EOK)
+		goto error;
+
+	/* Paint menu background */
+
+	rc = gfx_set_color(res->gc, res->wnd_face_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(res->gc, &bg_rect);
+	if (rc != EOK)
+		goto error;
+
+	/* Paint entries */
+	pos = geom.entries_rect.p0;
+
+	mentry = ui_menu_entry_first(menu);
+	while (mentry != NULL) {
+		rc = ui_menu_entry_paint(mentry, &pos);
+		if (rc != EOK)
+			goto error;
+
+		pos.y += ui_menu_entry_height(mentry);
+		mentry = ui_menu_entry_next(mentry);
+	}
+
+	rc = gfx_update(res->gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Unpaint menu.
+ *
+ * @param menu Menu
+ * @return EOK on success or an error code
+ */
+errno_t ui_menu_unpaint(ui_menu_t *menu)
+{
+	ui_resource_expose(menu->mbar->res);
+	return EOK;
+}
+
+/** Handle button press in menu.
+ *
+ * @param menu Menu
+ * @param spos Starting position (top-left corner)
+ * @param ppos Press position
+ */
+void ui_menu_press(ui_menu_t *menu, gfx_coord2_t *spos, gfx_coord2_t *ppos)
+{
+	ui_menu_geom_t geom;
+	ui_menu_entry_t *mentry;
+	gfx_coord2_t pos;
+
+	ui_menu_get_geom(menu, spos, &geom);
+
+	pos = geom.entries_rect.p0;
+
+	mentry = ui_menu_entry_first(menu);
+	while (mentry != NULL) {
+		ui_menu_entry_press(mentry, &pos, ppos);
+
+		pos.y += ui_menu_entry_height(mentry);
+		mentry = ui_menu_entry_next(mentry);
+	}
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/menubar.c
===================================================================
--- uspace/lib/ui/src/menubar.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/src/menubar.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2021 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 bar
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <gfx/color.h>
+#include <gfx/context.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <io/pos_event.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/paint.h>
+#include <ui/menu.h>
+#include <ui/menubar.h>
+#include "../private/menubar.h"
+#include "../private/resource.h"
+
+enum {
+	menubar_hpad = 4,
+	menubar_vpad = 4,
+	menubar_hpad_text = 1,
+	menubar_vpad_text = 0
+};
+
+static void ui_menu_bar_ctl_destroy(void *);
+static errno_t ui_menu_bar_ctl_paint(void *);
+static ui_evclaim_t ui_menu_bar_ctl_pos_event(void *, pos_event_t *);
+
+/** Menu bar control ops */
+ui_control_ops_t ui_menu_bar_ops = {
+	.destroy = ui_menu_bar_ctl_destroy,
+	.paint = ui_menu_bar_ctl_paint,
+	.pos_event = ui_menu_bar_ctl_pos_event
+};
+
+/** Create new menu bar.
+ *
+ * @param res UI resource
+ * @param rmbar Place to store pointer to new menu bar
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_menu_bar_create(ui_resource_t *res, ui_menu_bar_t **rmbar)
+{
+	ui_menu_bar_t *mbar;
+	errno_t rc;
+
+	mbar = calloc(1, sizeof(ui_menu_bar_t));
+	if (mbar == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&ui_menu_bar_ops, (void *) mbar, &mbar->control);
+	if (rc != EOK) {
+		free(mbar);
+		return rc;
+	}
+
+	mbar->res = res;
+	list_initialize(&mbar->menus);
+	*rmbar = mbar;
+	return EOK;
+}
+
+/** Destroy menu bar
+ *
+ * @param mbar Menu bar or @c NULL
+ */
+void ui_menu_bar_destroy(ui_menu_bar_t *mbar)
+{
+	ui_menu_t *menu;
+
+	if (mbar == NULL)
+		return;
+
+	/* Destroy menus */
+	menu = ui_menu_first(mbar);
+	while (menu != NULL) {
+		ui_menu_destroy(menu);
+		menu = ui_menu_first(mbar);
+	}
+
+	ui_control_delete(mbar->control);
+	free(mbar);
+}
+
+/** Get base control from menu bar.
+ *
+ * @param mbar Menu bar
+ * @return Control
+ */
+ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *mbar)
+{
+	return mbar->control;
+}
+
+/** Set menu bar rectangle.
+ *
+ * @param mbar Menu bar
+ * @param rect New menu bar rectangle
+ */
+void ui_menu_bar_set_rect(ui_menu_bar_t *mbar, gfx_rect_t *rect)
+{
+	mbar->rect = *rect;
+}
+
+/** Paint menu bar.
+ *
+ * @param mbar Menu bar
+ * @return EOK on success or an error code
+ */
+errno_t ui_menu_bar_paint(ui_menu_bar_t *mbar)
+{
+	gfx_text_fmt_t fmt;
+	gfx_coord2_t pos;
+	gfx_coord2_t tpos;
+	gfx_rect_t rect;
+	gfx_color_t *bg_color;
+	ui_menu_t *menu;
+	const char *caption;
+	gfx_coord_t width;
+	gfx_coord_t hpad;
+	gfx_coord_t vpad;
+	errno_t rc;
+
+	/* Paint menu bar background */
+
+	rc = gfx_set_color(mbar->res->gc, mbar->res->wnd_face_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(mbar->res->gc, &mbar->rect);
+	if (rc != EOK)
+		goto error;
+
+	if (mbar->res->textmode) {
+		hpad = menubar_hpad_text;
+		vpad = menubar_vpad_text;
+	} else {
+		hpad = menubar_hpad;
+		vpad = menubar_vpad;
+	}
+
+	pos = mbar->rect.p0;
+
+	gfx_text_fmt_init(&fmt);
+	fmt.halign = gfx_halign_left;
+	fmt.valign = gfx_valign_top;
+
+	menu = ui_menu_first(mbar);
+	while (menu != NULL) {
+		caption = ui_menu_caption(menu);
+		width = gfx_text_width(mbar->res->font, caption) + 2 * hpad;
+		tpos.x = pos.x + hpad;
+		tpos.y = pos.y + vpad;
+
+		rect.p0 = pos;
+		rect.p1.x = rect.p0.x + width;
+		rect.p1.y = mbar->rect.p1.y;
+
+		if (menu == mbar->selected) {
+			fmt.color = mbar->res->wnd_sel_text_color;
+			bg_color = mbar->res->wnd_sel_text_bg_color;
+		} else {
+			fmt.color = mbar->res->wnd_text_color;
+			bg_color = mbar->res->wnd_face_color;
+		}
+
+		rc = gfx_set_color(mbar->res->gc, bg_color);
+		if (rc != EOK)
+			goto error;
+
+		rc = gfx_fill_rect(mbar->res->gc, &rect);
+		if (rc != EOK)
+			goto error;
+
+		rc = gfx_puttext(mbar->res->font, &tpos, &fmt, caption);
+		if (rc != EOK)
+			goto error;
+
+		pos.x += width;
+		menu = ui_menu_next(menu);
+	}
+
+	rc = gfx_update(mbar->res->gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Select or deselect menu from menu bar.
+ *
+ * Select @a menu. If @a menu is @c NULL or it is already selected,
+ * then select none.
+ *
+ * @param mbar Menu bar
+ * @param pos Position (top-left corner) of menu bar entry
+ * @param menu Menu to select (or deselect if selected) or @c NULL
+ */
+void ui_menu_bar_select(ui_menu_bar_t *mbar, gfx_coord2_t *pos,
+    ui_menu_t *menu)
+{
+	gfx_coord2_t spos;
+	ui_menu_t *old_menu;
+
+	old_menu = mbar->selected;
+
+	if (mbar->selected != menu)
+		mbar->selected = menu;
+	else
+		mbar->selected = NULL;
+
+	/* Need to clear the menu has just been closed */
+	if (old_menu != NULL)
+		(void) ui_menu_unpaint(old_menu);
+
+	(void) ui_menu_bar_paint(mbar);
+
+	if (mbar->selected != NULL) {
+		/* Cache position of selected entry */
+		mbar->sel_pos = *pos;
+
+		/* Position menu under selected menu bar entry */
+		spos.x = pos->x;
+		spos.y = mbar->rect.p1.y;
+
+		(void) ui_menu_paint(mbar->selected, &spos);
+	}
+}
+
+/** Handle menu bar position event.
+ *
+ * @param mbar Menu bar
+ * @param ppos Press position
+ * @return @c ui_claimed iff the event is claimed
+ */
+static ui_evclaim_t ui_menu_bar_press(ui_menu_bar_t *mbar, gfx_coord2_t *ppos)
+{
+	gfx_coord2_t pos;
+	gfx_coord2_t spos;
+	gfx_rect_t rect;
+	ui_menu_t *menu;
+	const char *caption;
+	gfx_coord_t width;
+	gfx_coord_t hpad;
+
+	if (mbar->res->textmode) {
+		hpad = menubar_hpad_text;
+	} else {
+		hpad = menubar_hpad;
+	}
+
+	pos = mbar->rect.p0;
+
+	menu = ui_menu_first(mbar);
+	while (menu != NULL) {
+		caption = ui_menu_caption(menu);
+		width = gfx_text_width(mbar->res->font, caption) + 2 * hpad;
+
+		rect.p0 = pos;
+		rect.p1.x = rect.p0.x + width;
+		rect.p1.y = mbar->rect.p1.y;
+
+		/* Check if press is inside menu bar entry */
+		if (gfx_pix_inside_rect(ppos, &rect)) {
+			ui_menu_bar_select(mbar, &pos, menu);
+			return ui_claimed;
+		}
+
+		if (menu == mbar->selected) {
+			/* Open menu is positioned below menu bar entry */
+			spos.x = pos.x;
+			spos.y = mbar->rect.p1.y;
+
+			ui_menu_get_rect(menu, &spos, &rect);
+
+			/* Check if press is inside open menu */
+			if (gfx_pix_inside_rect(ppos, &rect)) {
+				ui_menu_press(menu, &spos, ppos);
+				return ui_claimed;
+			}
+		}
+
+		pos.x += width;
+		menu = ui_menu_next(menu);
+	}
+
+	return ui_unclaimed;
+}
+
+/** Handle menu bar position event.
+ *
+ * @param mbar Menu bar
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *mbar, pos_event_t *event)
+{
+	gfx_coord2_t pos;
+
+	pos.x = event->hpos;
+	pos.y = event->vpos;
+
+	switch (event->type) {
+	case POS_PRESS:
+		return ui_menu_bar_press(mbar, &pos);
+	default:
+		break;
+	}
+
+	return ui_unclaimed;
+}
+
+/** Destroy menu bar control.
+ *
+ * @param arg Argument (ui_menu_bar_t *)
+ */
+void ui_menu_bar_ctl_destroy(void *arg)
+{
+	ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
+
+	ui_menu_bar_destroy(mbar);
+}
+
+/** Paint menu bar control.
+ *
+ * @param arg Argument (ui_menu_bar_t *)
+ * @return EOK on success or an error code
+ */
+errno_t ui_menu_bar_ctl_paint(void *arg)
+{
+	ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
+
+	return ui_menu_bar_paint(mbar);
+}
+
+/** Handle menu bar control position event.
+ *
+ * @param arg Argument (ui_menu_bar_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_menu_bar_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
+
+	return ui_menu_bar_pos_event(mbar, event);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/menuentry.c
===================================================================
--- uspace/lib/ui/src/menuentry.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
+++ uspace/lib/ui/src/menuentry.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2021 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 entry
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <gfx/color.h>
+#include <gfx/context.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <io/pos_event.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/paint.h>
+#include <ui/menuentry.h>
+#include "../private/menubar.h"
+#include "../private/menuentry.h"
+#include "../private/menu.h"
+#include "../private/resource.h"
+
+enum {
+	menu_entry_hpad = 4,
+	menu_entry_vpad = 4,
+	menu_entry_hpad_text = 1,
+	menu_entry_vpad_text = 0
+};
+
+/** Create new menu entry.
+ *
+ * @param menu Menu
+ * @param caption Caption
+ * @param rmentry Place to store pointer to new menu entry
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_menu_entry_create(ui_menu_t *menu, const char *caption,
+    ui_menu_entry_t **rmentry)
+{
+	ui_menu_entry_t *mentry;
+
+	mentry = calloc(1, sizeof(ui_menu_entry_t));
+	if (mentry == NULL)
+		return ENOMEM;
+
+	mentry->caption = str_dup(caption);
+	if (mentry->caption == NULL) {
+		free(mentry);
+		return ENOMEM;
+	}
+
+	mentry->menu = menu;
+	list_append(&mentry->lentries, &menu->entries);
+
+	*rmentry = mentry;
+	return EOK;
+}
+
+/** Destroy menu entry.
+ *
+ * @param mentry Menu entry or @c NULL
+ */
+void ui_menu_entry_destroy(ui_menu_entry_t *mentry)
+{
+	if (mentry == NULL)
+		return;
+
+	list_remove(&mentry->lentries);
+	free(mentry->caption);
+	free(mentry);
+}
+
+/** Set menu entry callback.
+ *
+ * @param mentry Menu entry
+ * @param cb Menu entry callback
+ * @param arg Callback argument
+ */
+void ui_menu_entry_set_cb(ui_menu_entry_t *mentry, ui_menu_entry_cb_t cb,
+    void *arg)
+{
+	mentry->cb = cb;
+	mentry->arg = arg;
+}
+
+/** Get first menu entry in menu.
+ *
+ * @param menu Menu
+ * @return First menu entry or @c NULL if there is none
+ */
+ui_menu_entry_t *ui_menu_entry_first(ui_menu_t *menu)
+{
+	link_t *link;
+
+	link = list_first(&menu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_entry_t, lentries);
+}
+
+/** Get next menu entry in menu.
+ *
+ * @param cur Current menu entry
+ * @return Next menu entry or @c NULL if @a cur is the last one
+ */
+ui_menu_entry_t *ui_menu_entry_next(ui_menu_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lentries, &cur->menu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_entry_t, lentries);
+}
+
+/** Get width of menu entry.
+ *
+ * @param mentry Menu entry
+ * @return Width in pixels
+ */
+gfx_coord_t ui_menu_entry_width(ui_menu_entry_t *mentry)
+{
+	ui_resource_t *res;
+	gfx_coord_t hpad;
+
+	res = mentry->menu->mbar->res;
+
+	if (res->textmode) {
+		hpad = menu_entry_hpad_text;
+	} else {
+		hpad = menu_entry_hpad;
+	}
+
+	return gfx_text_width(res->font, mentry->caption) + 2 * hpad;
+}
+
+/** Get height of menu entry.
+ *
+ * @param mentry Menu entry
+ * @return Width in pixels
+ */
+gfx_coord_t ui_menu_entry_height(ui_menu_entry_t *mentry)
+{
+	ui_resource_t *res;
+	gfx_font_metrics_t metrics;
+	gfx_coord_t height;
+	gfx_coord_t vpad;
+
+	res = mentry->menu->mbar->res;
+
+	if (res->textmode) {
+		vpad = menu_entry_vpad_text;
+	} else {
+		vpad = menu_entry_vpad;
+	}
+
+	gfx_font_get_metrics(res->font, &metrics);
+	height = metrics.ascent + metrics.descent;
+	return height + 2 * vpad;
+}
+
+/** Paint menu entry.
+ *
+ * @param mentry Menu entry
+ * @param pos Position where to paint entry
+ * @return EOK on success or an error code
+ */
+errno_t ui_menu_entry_paint(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
+{
+	ui_resource_t *res;
+	gfx_text_fmt_t fmt;
+	gfx_color_t *bg_color;
+	const char *caption;
+	ui_menu_entry_geom_t geom;
+	errno_t rc;
+
+	res = mentry->menu->mbar->res;
+
+	ui_menu_entry_get_geom(mentry, pos, &geom);
+
+	gfx_text_fmt_init(&fmt);
+	fmt.halign = gfx_halign_left;
+	fmt.valign = gfx_valign_top;
+
+	caption = mentry->caption;
+
+	if (mentry == mentry->menu->selected) {
+		fmt.color = res->wnd_sel_text_color;
+		bg_color = res->wnd_sel_text_bg_color;
+	} else {
+		fmt.color = res->wnd_text_color;
+		bg_color = res->wnd_face_color;
+	}
+
+	rc = gfx_set_color(res->gc, bg_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(res->gc, &geom.outer_rect);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_puttext(res->font, &geom.text_pos, &fmt, caption);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_update(res->gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Handle button press in menu entry.
+ *
+ * @param mentry Menu entry
+ * @param pos Position (top-left corner)
+ * @param ppos Press position
+ */
+void ui_menu_entry_press(ui_menu_entry_t *mentry, gfx_coord2_t *pos,
+    gfx_coord2_t *ppos)
+{
+	ui_menu_entry_geom_t geom;
+
+	ui_menu_entry_get_geom(mentry, pos, &geom);
+
+	if (gfx_pix_inside_rect(ppos, &geom.outer_rect)) {
+		/* Press inside menu entry */
+
+		/* Close menu */
+		ui_menu_bar_select(mentry->menu->mbar,
+		    &mentry->menu->mbar->sel_pos, NULL);
+
+		/* Call back */
+		if (mentry->cb != NULL)
+			mentry->cb(mentry, mentry->arg);
+	}
+}
+
+/** Get menu entry geometry.
+ *
+ * @param mentry Menu entry
+ * @param spos Entry position
+ * @param geom Structure to fill in with computed geometry
+ */
+void ui_menu_entry_get_geom(ui_menu_entry_t *mentry, gfx_coord2_t *pos,
+    ui_menu_entry_geom_t *geom)
+{
+	ui_resource_t *res;
+	gfx_coord_t hpad;
+	gfx_coord_t vpad;
+	const char *caption;
+	gfx_coord_t width;
+
+	res = mentry->menu->mbar->res;
+
+	if (res->textmode) {
+		hpad = menu_entry_hpad_text;
+		vpad = menu_entry_vpad_text;
+	} else {
+		hpad = menu_entry_hpad;
+		vpad = menu_entry_vpad;
+	}
+
+	caption = mentry->caption;
+	width = gfx_text_width(res->font, caption) + 2 * hpad;
+	geom->text_pos.x = pos->x + hpad;
+	geom->text_pos.y = pos->y + vpad;
+
+	geom->outer_rect.p0 = *pos;
+	geom->outer_rect.p1.x = geom->outer_rect.p0.x + width;
+	geom->outer_rect.p1.y = geom->outer_rect.p0.y +
+	    ui_menu_entry_height(mentry);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/paint.c
===================================================================
--- uspace/lib/ui/src/paint.c	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/src/paint.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -149,4 +149,34 @@
 }
 
+/** Paint outset frame.
+ *
+ * @param resource UI resource
+ * @param rect Rectangle to paint onto
+ * @param inside Place to store inside rectangle or @c NULL
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_outset_frame(ui_resource_t *resource, gfx_rect_t *rect,
+    gfx_rect_t *inside)
+{
+	gfx_rect_t frame;
+	errno_t rc;
+
+	rc = ui_paint_bevel(resource->gc, rect,
+	    resource->wnd_frame_hi_color, resource->wnd_frame_sh_color,
+	    1, &frame);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_paint_bevel(resource->gc, &frame,
+	    resource->wnd_highlight_color, resource->wnd_shadow_color,
+	    1, inside);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
 /** Paint filled circle vertical scanline.
  *
Index: uspace/lib/ui/src/resource.c
===================================================================
--- uspace/lib/ui/src/resource.c	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/src/resource.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -68,4 +68,6 @@
 	gfx_color_t *wnd_face_color = NULL;
 	gfx_color_t *wnd_text_color = NULL;
+	gfx_color_t *wnd_sel_text_color = NULL;
+	gfx_color_t *wnd_sel_text_bg_color = NULL;
 	gfx_color_t *wnd_frame_hi_color = NULL;
 	gfx_color_t *wnd_frame_sh_color = NULL;
@@ -139,4 +141,13 @@
 		goto error;
 
+	rc = gfx_color_new_rgb_i16(0xffff, 0xffff, 0xffff, &wnd_sel_text_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_rgb_i16(0x5858, 0x6a6a, 0xc4c4,
+	    &wnd_sel_text_bg_color);
+	if (rc != EOK)
+		goto error;
+
 	rc = gfx_color_new_rgb_i16(0x8888, 0x8888, 0x8888, &wnd_frame_hi_color);
 	if (rc != EOK)
@@ -200,4 +211,6 @@
 	resource->wnd_face_color = wnd_face_color;
 	resource->wnd_text_color = wnd_text_color;
+	resource->wnd_sel_text_color = wnd_sel_text_color;
+	resource->wnd_sel_text_bg_color = wnd_sel_text_bg_color;
 	resource->wnd_frame_hi_color = wnd_frame_hi_color;
 	resource->wnd_frame_sh_color = wnd_frame_sh_color;
@@ -232,4 +245,8 @@
 	if (wnd_text_color != NULL)
 		gfx_color_delete(wnd_text_color);
+	if (wnd_sel_text_color != NULL)
+		gfx_color_delete(wnd_sel_text_color);
+	if (wnd_sel_text_bg_color != NULL)
+		gfx_color_delete(wnd_sel_text_bg_color);
 	if (wnd_frame_hi_color != NULL)
 		gfx_color_delete(wnd_frame_hi_color);
@@ -280,4 +297,6 @@
 	gfx_color_delete(resource->wnd_face_color);
 	gfx_color_delete(resource->wnd_text_color);
+	gfx_color_delete(resource->wnd_sel_text_color);
+	gfx_color_delete(resource->wnd_sel_text_bg_color);
 	gfx_color_delete(resource->wnd_frame_hi_color);
 	gfx_color_delete(resource->wnd_frame_sh_color);
@@ -299,4 +318,32 @@
 }
 
+/** Set UI resource expose callback.
+ *
+ * @param resource Resource
+ * @param cb Callback
+ * @param arg Callback argument
+ */
+void ui_resource_set_expose_cb(ui_resource_t *resource,
+    ui_expose_cb_t cb, void *arg)
+{
+	resource->expose_cb = cb;
+	resource->expose_arg = arg;
+}
+
+/** Force UI repaint after an area has been exposed.
+ *
+ * This is called when a popup disappears, which could have exposed some
+ * other UI elements. It causes complete repaint of the UI.
+ *
+ * NOTE Ideally we could specify the exposed rectangle and then limit
+ * the repaint to just that. That would, however, require means of
+ * actually clipping the repaint operation.
+ */
+void ui_resource_expose(ui_resource_t *resource)
+{
+	if (resource->expose_cb != NULL)
+		resource->expose_cb(resource->expose_arg);
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/wdecor.c
===================================================================
--- uspace/lib/ui/src/wdecor.c	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/src/wdecor.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -178,14 +178,14 @@
 
 	if ((wdecor->style & ui_wds_frame) != 0) {
-		rc = ui_paint_bevel(wdecor->res->gc, &rect,
-		    wdecor->res->wnd_frame_hi_color,
-		    wdecor->res->wnd_frame_sh_color, 1, &rect);
-		if (rc != EOK)
-			return rc;
-
-		if (wdecor->res->textmode == false) {
+
+		if (wdecor->res->textmode != false) {
 			rc = ui_paint_bevel(wdecor->res->gc, &rect,
-			    wdecor->res->wnd_highlight_color,
-			    wdecor->res->wnd_shadow_color, 1, &rect);
+			    wdecor->res->wnd_frame_hi_color,
+			    wdecor->res->wnd_frame_sh_color, 1, &rect);
+			if (rc != EOK)
+				return rc;
+		} else {
+			rc = ui_paint_outset_frame(wdecor->res, &rect,
+			    &rect);
 			if (rc != EOK)
 				return rc;
Index: uspace/lib/ui/src/window.c
===================================================================
--- uspace/lib/ui/src/window.c	(revision dbf1be5be4d7ced56dbc0ea7ab6a77eb022a09fa)
+++ uspace/lib/ui/src/window.c	(revision bfb055b5d8b4b517c36ae0332c76d2f90f85bd12)
@@ -90,4 +90,5 @@
 static void ui_window_app_invalidate(void *, gfx_rect_t *);
 static void ui_window_app_update(void *);
+static void ui_window_expose_cb(void *);
 
 /** Initialize window parameters structure.
@@ -270,4 +271,6 @@
 	ui_wdecor_paint(wdecor);
 
+	ui_resource_set_expose_cb(res, ui_window_expose_cb, (void *) window);
+
 	window->ui = ui;
 	window->dwindow = dwindow;
@@ -922,4 +925,12 @@
 }
 
+/** Window expose callback. */
+static void ui_window_expose_cb(void *arg)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	ui_window_paint(window);
+}
+
 /** @}
  */
