Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision faca61b865ace1f8e56cfe959875ce75b8aaad0d)
+++ uspace/app/uidemo/uidemo.c	(revision 8ef48ece717476a9d162affd18ffa54f6f946249)
@@ -54,4 +54,10 @@
 };
 
+static void pb_clicked(ui_pbutton_t *, void *);
+
+static ui_pbutton_cb_t pbutton_cb = {
+	.clicked = pb_clicked
+};
+
 static bool quit = false;
 
@@ -84,4 +90,20 @@
 	ui_pbutton_pos_event(demo->pb1, event);
 	ui_pbutton_pos_event(demo->pb2, event);
+}
+
+/** Push button was clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (demo)
+ */
+static void pb_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+
+	if (pbutton == demo->pb1) {
+		printf("Clicked 'Confirm' button\n");
+	} else {
+		printf("Clicked 'Cancel' button\n");
+	}
 }
 
@@ -140,4 +162,6 @@
 	}
 
+	ui_pbutton_set_cb(demo.pb1, &pbutton_cb, (void *) &demo);
+
 	rect.p0.x = 20;
 	rect.p0.y = 50;
@@ -153,4 +177,6 @@
 		return rc;
 	}
+
+	ui_pbutton_set_cb(demo.pb2, &pbutton_cb, (void *) &demo);
 
 	rect.p0.x = 120;
Index: uspace/lib/ui/include/types/ui/pbutton.h
===================================================================
--- uspace/lib/ui/include/types/ui/pbutton.h	(revision faca61b865ace1f8e56cfe959875ce75b8aaad0d)
+++ uspace/lib/ui/include/types/ui/pbutton.h	(revision 8ef48ece717476a9d162affd18ffa54f6f946249)
@@ -40,4 +40,9 @@
 typedef struct ui_pbutton ui_pbutton_t;
 
+/** Push button callbacks */
+typedef struct ui_pbutton_cb {
+	void (*clicked)(ui_pbutton_t *, void *);
+} ui_pbutton_cb_t;
+
 #endif
 
Index: uspace/lib/ui/include/ui/pbutton.h
===================================================================
--- uspace/lib/ui/include/ui/pbutton.h	(revision faca61b865ace1f8e56cfe959875ce75b8aaad0d)
+++ uspace/lib/ui/include/ui/pbutton.h	(revision 8ef48ece717476a9d162affd18ffa54f6f946249)
@@ -47,4 +47,5 @@
     ui_pbutton_t **);
 extern void ui_pbutton_destroy(ui_pbutton_t *);
+extern void ui_pbutton_set_cb(ui_pbutton_t *, ui_pbutton_cb_t *, void *);
 extern void ui_pbutton_set_rect(ui_pbutton_t *, gfx_rect_t *);
 extern void ui_pbutton_set_default(ui_pbutton_t *, bool);
@@ -52,4 +53,7 @@
 extern void ui_pbutton_press(ui_pbutton_t *);
 extern void ui_pbutton_release(ui_pbutton_t *);
+extern void ui_pbutton_enter(ui_pbutton_t *);
+extern void ui_pbutton_leave(ui_pbutton_t *);
+extern void ui_pbutton_clicked(ui_pbutton_t *);
 extern void ui_pbutton_pos_event(ui_pbutton_t *, pos_event_t *);
 
Index: uspace/lib/ui/private/pbutton.h
===================================================================
--- uspace/lib/ui/private/pbutton.h	(revision faca61b865ace1f8e56cfe959875ce75b8aaad0d)
+++ uspace/lib/ui/private/pbutton.h	(revision 8ef48ece717476a9d162affd18ffa54f6f946249)
@@ -49,4 +49,8 @@
 	/** UI resource */
 	struct ui_resource *res;
+	/** Callbacks */
+	struct ui_pbutton_cb *cb;
+	/** Callback argument */
+	void *arg;
 	/** Push button rectangle */
 	gfx_rect_t rect;
@@ -57,4 +61,6 @@
 	/** Button is currently held down */
 	bool held;
+	/** Pointer is currently inside */
+	bool inside;
 };
 
Index: uspace/lib/ui/src/pbutton.c
===================================================================
--- uspace/lib/ui/src/pbutton.c	(revision faca61b865ace1f8e56cfe959875ce75b8aaad0d)
+++ uspace/lib/ui/src/pbutton.c	(revision 8ef48ece717476a9d162affd18ffa54f6f946249)
@@ -91,4 +91,16 @@
 }
 
+/** Set push button callbacks.
+ *
+ * @param pbutton Push button
+ * @param cb Push button callbacks
+ * @param arg Callback argument
+ */
+void ui_pbutton_set_cb(ui_pbutton_t *pbutton, ui_pbutton_cb_t *cb, void *arg)
+{
+	pbutton->cb = cb;
+	pbutton->arg = arg;
+}
+
 /** Set button rectangle.
  *
@@ -314,7 +326,9 @@
 	gfx_rect_t rect;
 	gfx_coord_t thickness;
+	bool depressed;
 	errno_t rc;
 
 	thickness = pbutton->isdefault ? 2 : 1;
+	depressed = pbutton->held && pbutton->inside;
 
 	rect.p0.x = pbutton->rect.p0.x + thickness;
@@ -350,5 +364,5 @@
 	pos.y = (rect.p0.y + rect.p1.y) / 2;
 
-	if (pbutton->held) {
+	if (depressed) {
 		pos.x += ui_pb_press_dx;
 		pos.y += ui_pb_press_dy;
@@ -370,5 +384,5 @@
 		goto error;
 
-	if (pbutton->held) {
+	if (depressed) {
 		rc = ui_pbutton_paint_inset(pbutton, &rect);
 		if (rc != EOK)
@@ -389,22 +403,69 @@
 /** Press down button.
  *
- * This does not automatically repaint the button.
- *
  * @param pbutton Push button
  */
 void ui_pbutton_press(ui_pbutton_t *pbutton)
 {
+	if (pbutton->held)
+		return;
+
+	pbutton->inside = true;
 	pbutton->held = true;
+	(void) ui_pbutton_paint(pbutton);
 }
 
 /** Release button.
  *
- * This does not automatically repaint the button.
- *
  * @param pbutton Push button
  */
 void ui_pbutton_release(ui_pbutton_t *pbutton)
 {
+	if (!pbutton->held)
+		return;
+
 	pbutton->held = false;
+
+	if (pbutton->inside) {
+		(void) ui_pbutton_paint(pbutton);
+		ui_pbutton_clicked(pbutton);
+	}
+}
+
+/** Pointer entered button.
+ *
+ * @param pbutton Push button
+ */
+void ui_pbutton_enter(ui_pbutton_t *pbutton)
+{
+	if (pbutton->inside)
+		return;
+
+	pbutton->inside = true;
+	if (pbutton->held)
+		(void) ui_pbutton_paint(pbutton);
+}
+
+/** Pointer left button.
+ *
+ * @param pbutton Push button
+ */
+void ui_pbutton_leave(ui_pbutton_t *pbutton)
+{
+	if (!pbutton->inside)
+		return;
+
+	pbutton->inside = false;
+	if (pbutton->held)
+		(void) ui_pbutton_paint(pbutton);
+}
+
+/** Button was clicked.
+ *
+ * @param pbutton Push button
+ */
+void ui_pbutton_clicked(ui_pbutton_t *pbutton)
+{
+	if (pbutton->cb != NULL && pbutton->cb->clicked != NULL)
+		pbutton->cb->clicked(pbutton, pbutton->arg);
 }
 
@@ -417,18 +478,26 @@
 {
 	gfx_coord2_t pos;
+	bool inside;
 
 	pos.x = event->hpos;
 	pos.y = event->vpos;
 
-	if (gfx_pix_inside_rect(&pos, &pbutton->rect)) {
-		if (event->type == POS_PRESS) {
+	inside = gfx_pix_inside_rect(&pos, &pbutton->rect);
+
+	switch (event->type) {
+	case POS_PRESS:
+		if (inside)
 			ui_pbutton_press(pbutton);
-			(void) ui_pbutton_paint(pbutton);
+		break;
+	case POS_RELEASE:
+		ui_pbutton_release(pbutton);
+		break;
+	case POS_UPDATE:
+		if (inside && !pbutton->inside) {
+			ui_pbutton_enter(pbutton);
+		} else if (!inside && pbutton->inside) {
+			ui_pbutton_leave(pbutton);
 		}
-	}
-
-	if (event->type == POS_RELEASE && pbutton->held) {
-		ui_pbutton_release(pbutton);
-		(void) ui_pbutton_paint(pbutton);
+		break;
 	}
 }
Index: uspace/lib/ui/test/pbutton.c
===================================================================
--- uspace/lib/ui/test/pbutton.c	(revision faca61b865ace1f8e56cfe959875ce75b8aaad0d)
+++ uspace/lib/ui/test/pbutton.c	(revision 8ef48ece717476a9d162affd18ffa54f6f946249)
@@ -57,4 +57,13 @@
 };
 
+static void test_pbutton_clicked(ui_pbutton_t *, void *);
+
+static ui_pbutton_cb_t test_pbutton_cb = {
+	.clicked = test_pbutton_clicked
+};
+
+static ui_pbutton_cb_t dummy_pbutton_cb = {
+};
+
 typedef struct {
 	bool bm_created;
@@ -74,4 +83,8 @@
 } testgc_bitmap_t;
 
+typedef struct {
+	bool clicked;
+} test_cb_resp_t;
+
 /** Create and destroy button */
 PCUT_TEST(create_destroy)
@@ -165,22 +178,175 @@
 }
 
-/** ui_pbutton_press()/release() sets/clears internal held flag */
+/** Test ui_pbutton_clicked() */
+PCUT_TEST(clicked)
+{
+	errno_t rc;
+	ui_pbutton_t *pbutton;
+	test_cb_resp_t resp;
+
+	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Clicked with no callbacks set */
+	ui_pbutton_clicked(pbutton);
+
+	/* Clicked with callback not implementing clicked */
+	ui_pbutton_set_cb(pbutton, &dummy_pbutton_cb, NULL);
+	ui_pbutton_clicked(pbutton);
+
+	/* Clicked with real callback set */
+	resp.clicked = false;
+	ui_pbutton_set_cb(pbutton, &test_pbutton_cb, &resp);
+	ui_pbutton_clicked(pbutton);
+	PCUT_ASSERT_TRUE(resp.clicked);
+
+	ui_pbutton_destroy(pbutton);
+}
+
+/** Press and release button */
 PCUT_TEST(press_release)
 {
-	ui_pbutton_t *pbutton;
-	errno_t rc;
-
-	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	PCUT_ASSERT_FALSE(pbutton->held);
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	ui_pbutton_t *pbutton;
+	test_cb_resp_t resp;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_pbutton_create(resource, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	resp.clicked = false;
+	ui_pbutton_set_cb(pbutton, &test_pbutton_cb, &resp);
+
+	PCUT_ASSERT_FALSE(pbutton->held);
+	PCUT_ASSERT_FALSE(pbutton->inside);
 
 	ui_pbutton_press(pbutton);
 	PCUT_ASSERT_TRUE(pbutton->held);
+	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_FALSE(resp.clicked);
 
 	ui_pbutton_release(pbutton);
 	PCUT_ASSERT_FALSE(pbutton->held);
-
-	ui_pbutton_destroy(pbutton);
+	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_TRUE(resp.clicked);
+
+	ui_pbutton_destroy(pbutton);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Press, leave and release button */
+PCUT_TEST(press_leave_release)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	ui_pbutton_t *pbutton;
+	test_cb_resp_t resp;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_pbutton_create(resource, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	resp.clicked = false;
+	ui_pbutton_set_cb(pbutton, &test_pbutton_cb, &resp);
+
+	PCUT_ASSERT_FALSE(pbutton->held);
+	PCUT_ASSERT_FALSE(pbutton->inside);
+
+	ui_pbutton_press(pbutton);
+	PCUT_ASSERT_TRUE(pbutton->held);
+	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_FALSE(resp.clicked);
+
+	ui_pbutton_leave(pbutton);
+	PCUT_ASSERT_TRUE(pbutton->held);
+	PCUT_ASSERT_FALSE(pbutton->inside);
+	PCUT_ASSERT_FALSE(resp.clicked);
+
+	ui_pbutton_release(pbutton);
+	PCUT_ASSERT_FALSE(pbutton->held);
+	PCUT_ASSERT_FALSE(pbutton->inside);
+	PCUT_ASSERT_FALSE(resp.clicked);
+
+	ui_pbutton_destroy(pbutton);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Press, leave, enter and release button */
+PCUT_TEST(press_leave_enter_release)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	ui_pbutton_t *pbutton;
+	test_cb_resp_t resp;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_pbutton_create(resource, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	resp.clicked = false;
+	ui_pbutton_set_cb(pbutton, &test_pbutton_cb, &resp);
+
+	PCUT_ASSERT_FALSE(pbutton->held);
+	PCUT_ASSERT_FALSE(pbutton->inside);
+
+	ui_pbutton_press(pbutton);
+	PCUT_ASSERT_TRUE(pbutton->held);
+	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_FALSE(resp.clicked);
+
+	ui_pbutton_leave(pbutton);
+	PCUT_ASSERT_TRUE(pbutton->held);
+	PCUT_ASSERT_FALSE(pbutton->inside);
+	PCUT_ASSERT_FALSE(resp.clicked);
+
+	ui_pbutton_enter(pbutton);
+	PCUT_ASSERT_TRUE(pbutton->held);
+	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_FALSE(resp.clicked);
+
+	ui_pbutton_release(pbutton);
+	PCUT_ASSERT_FALSE(pbutton->held);
+	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_TRUE(resp.clicked);
+
+	ui_pbutton_destroy(pbutton);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 }
 
@@ -188,10 +354,21 @@
 PCUT_TEST(pos_event_press_release)
 {
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
 	ui_pbutton_t *pbutton;
 	pos_event_t event;
 	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_pbutton_create(resource, "Hello", &pbutton);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -226,4 +403,66 @@
 
 	ui_pbutton_destroy(pbutton);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** ui_pos_event() correctly translates POS_UPDATE to enter/leave */
+PCUT_TEST(pos_event_enter_leave)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	ui_pbutton_t *pbutton;
+	pos_event_t event;
+	gfx_rect_t rect;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_pbutton_create(resource, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(pbutton->inside);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 30;
+	rect.p1.y = 40;
+	ui_pbutton_set_rect(pbutton, &rect);
+
+	/* Moving outside does nothing */
+	event.type = POS_UPDATE;
+	event.hpos = 9;
+	event.vpos = 20;
+	ui_pbutton_pos_event(pbutton, &event);
+	PCUT_ASSERT_FALSE(pbutton->inside);
+
+	/* Moving inside sets inside flag */
+	event.type = POS_UPDATE;
+	event.hpos = 10;
+	event.vpos = 20;
+	ui_pbutton_pos_event(pbutton, &event);
+	PCUT_ASSERT_TRUE(pbutton->inside);
+
+	/* Moving outside clears inside flag */
+	event.type = POS_UPDATE;
+	event.hpos = 9;
+	event.vpos = 20;
+	ui_pbutton_pos_event(pbutton, &event);
+	PCUT_ASSERT_FALSE(pbutton->inside);
+
+	ui_pbutton_destroy(pbutton);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 }
 
@@ -304,3 +543,10 @@
 }
 
+static void test_pbutton_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->clicked = true;
+}
+
 PCUT_EXPORT(pbutton);
