Index: uspace/app/nav/panel.c
===================================================================
--- uspace/app/nav/panel.c	(revision be1d74c1177b171bc7d692a94141566e1359937f)
+++ uspace/app/nav/panel.c	(revision 8c72f533956df49c241cb19ffbeceb0d3722ad4c)
@@ -137,6 +137,5 @@
 
 	gfx_text_fmt_init(&fmt);
-
-	rows = panel->rect.p1.y - panel->rect.p0.y - 2;
+	rows = panel_page_size(panel);
 
 	/* Do not display entry outside of current page */
@@ -201,5 +200,5 @@
 		return rc;
 
-	lines = panel->rect.p1.y - panel->rect.p0.y - 2;
+	lines = panel_page_size(panel);
 	i = 0;
 
@@ -244,4 +243,10 @@
 				panel_cursor_bottom(panel);
 				break;
+			case KC_PAGE_UP:
+				panel_page_up(panel);
+				break;
+			case KC_PAGE_DOWN:
+				panel_page_down(panel);
+				break;
 			default:
 				break;
@@ -282,4 +287,14 @@
 {
 	panel->rect = *rect;
+}
+
+/** Get panel page size.
+ *
+ * @param panel Panel
+ * @return Number of entries that fit in panel at the same time.
+ */
+unsigned panel_page_size(panel_t *panel)
+{
+	return panel->rect.p1.y - panel->rect.p0.y - 2;
 }
 
@@ -494,4 +509,10 @@
 }
 
+/** Move cursor to a new position, possibly scrolling.
+ *
+ * @param panel Panel
+ * @param entry New entry under cursor
+ * @param entry_idx Index of new entry under cursor
+ */
 void panel_cursor_move(panel_t *panel, panel_entry_t *entry, size_t entry_idx)
 {
@@ -503,5 +524,5 @@
 	size_t i;
 
-	rows = panel->rect.p1.y - panel->rect.p0.y - 2;
+	rows = panel_page_size(panel);
 
 	old_cursor = panel->cursor;
@@ -604,4 +625,109 @@
 }
 
+/** Move one page up.
+ *
+ * @param panel Panel
+ */
+void panel_page_up(panel_t *panel)
+{
+	gfx_context_t *gc = ui_window_get_gc(panel->window);
+	panel_entry_t *old_page;
+	panel_entry_t *old_cursor;
+	size_t old_idx;
+	size_t rows;
+	panel_entry_t *entry;
+	size_t i;
+
+	rows = panel_page_size(panel);
+
+	old_page = panel->page;
+	old_cursor = panel->cursor;
+	old_idx = panel->cursor_idx;
+
+	/* Move page by rows entries up (if possible) */
+	for (i = 0; i < rows; i++) {
+		entry = panel_prev(panel->page);
+		if (entry != NULL) {
+			panel->page = entry;
+			--panel->page_idx;
+		}
+	}
+
+	/* Move cursor by rows entries up (if possible) */
+
+	for (i = 0; i < rows; i++) {
+		entry = panel_prev(panel->cursor);
+		if (entry != NULL) {
+			panel->cursor = entry;
+			--panel->cursor_idx;
+		}
+	}
+
+	if (panel->page != old_page) {
+		/* We have scrolled. Need to repaint all entries */
+		(void) panel_paint(panel);
+	} else if (panel->cursor != old_cursor) {
+		/* No scrolling, but cursor has moved */
+		panel_entry_paint(old_cursor, old_idx);
+		panel_entry_paint(panel->cursor, panel->cursor_idx);
+
+		(void) gfx_update(gc);
+	}
+}
+
+/** Move one page down.
+ *
+ * @param panel Panel
+ */
+void panel_page_down(panel_t *panel)
+{
+	gfx_context_t *gc = ui_window_get_gc(panel->window);
+	panel_entry_t *old_page;
+	panel_entry_t *old_cursor;
+	size_t old_idx;
+	size_t max_idx;
+	size_t rows;
+	panel_entry_t *entry;
+	size_t i;
+
+	rows = panel_page_size(panel);
+
+	old_page = panel->page;
+	old_cursor = panel->cursor;
+	old_idx = panel->cursor_idx;
+	max_idx = panel->entries_cnt - rows;
+
+	/* Move page by rows entries down (if possible) */
+	for (i = 0; i < rows; i++) {
+		entry = panel_next(panel->page);
+		/* Do not scroll that results in a short page */
+		if (entry != NULL && panel->page_idx < max_idx) {
+			panel->page = entry;
+			++panel->page_idx;
+		}
+	}
+
+	/* Move cursor by rows entries down (if possible) */
+
+	for (i = 0; i < rows; i++) {
+		entry = panel_next(panel->cursor);
+		if (entry != NULL) {
+			panel->cursor = entry;
+			++panel->cursor_idx;
+		}
+	}
+
+	if (panel->page != old_page) {
+		/* We have scrolled. Need to repaint all entries */
+		(void) panel_paint(panel);
+	} else if (panel->cursor != old_cursor) {
+		/* No scrolling, but cursor has moved */
+		panel_entry_paint(old_cursor, old_idx);
+		panel_entry_paint(panel->cursor, panel->cursor_idx);
+
+		(void) gfx_update(gc);
+	}
+}
+
 /** @}
  */
Index: uspace/app/nav/panel.h
===================================================================
--- uspace/app/nav/panel.h	(revision be1d74c1177b171bc7d692a94141566e1359937f)
+++ uspace/app/nav/panel.h	(revision 8c72f533956df49c241cb19ffbeceb0d3722ad4c)
@@ -108,4 +108,5 @@
 extern ui_control_t *panel_ctl(panel_t *);
 extern void panel_set_rect(panel_t *, gfx_rect_t *);
+extern unsigned panel_page_size(panel_t *);
 extern errno_t panel_entry_append(panel_t *, const char *, uint64_t);
 extern void panel_entry_delete(panel_entry_t *);
@@ -121,4 +122,6 @@
 extern void panel_cursor_top(panel_t *);
 extern void panel_cursor_bottom(panel_t *);
+extern void panel_page_up(panel_t *);
+extern void panel_page_down(panel_t *);
 
 #endif
Index: uspace/app/nav/test/panel.c
===================================================================
--- uspace/app/nav/test/panel.c	(revision be1d74c1177b171bc7d692a94141566e1359937f)
+++ uspace/app/nav/test/panel.c	(revision 8c72f533956df49c241cb19ffbeceb0d3722ad4c)
@@ -209,4 +209,31 @@
 }
 
+/** panel_page_size() returns correct size */
+PCUT_TEST(page_size)
+{
+	panel_t *panel;
+	ui_control_t *control;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = panel_create(NULL, &panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	control = panel_ctl(panel);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 30;
+	rect.p1.y = 40;
+
+	panel_set_rect(panel, &rect);
+
+	/* NOTE If page size changes, we have problems elsewhere in the tests */
+	PCUT_ASSERT_INT_EQUALS(18, panel_page_size(panel));
+
+	panel_destroy(panel);
+}
+
 /** panel_entry_append() appends new entry */
 PCUT_TEST(entry_append)
@@ -518,4 +545,6 @@
 	rect.p1.y = 4; // XXX Assuming this makes page size 2
 	panel_set_rect(panel, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, panel_page_size(panel));
 
 	/* Add tree entries (more than page size, which is 2) */
@@ -597,4 +626,6 @@
 	rect.p1.y = 4; // XXX Assuming this makes page size 2
 	panel_set_rect(panel, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, panel_page_size(panel));
 
 	/* Add tree entries (more than page size, which is 2) */
@@ -679,4 +710,6 @@
 	panel_set_rect(panel, &rect);
 
+	PCUT_ASSERT_INT_EQUALS(2, panel_page_size(panel));
+
 	/* Add tree entries (more than page size, which is 2) */
 	rc = panel_entry_append(panel, "a", 1);
@@ -737,4 +770,6 @@
 	rect.p1.y = 4; // XXX Assuming this makes page size 2
 	panel_set_rect(panel, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, panel_page_size(panel));
 
 	/* Add tree entries (more than page size, which is 2) */
@@ -770,3 +805,181 @@
 }
 
+/** panel_page_up() moves one page up */
+PCUT_TEST(page_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	panel_t *panel;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, &panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 4; // XXX Assuming this makes page size 2
+	panel_set_rect(panel, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, panel_page_size(panel));
+
+	/* Add five entries (2 full pages, one partial) */
+	rc = panel_entry_append(panel, "a", 1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "b", 2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "c", 3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "d", 4);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "e", 5);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	panel->cursor = panel_last(panel);
+	panel->cursor_idx = 4;
+	panel->page = panel_prev(panel->cursor);
+	panel->page_idx = 3;
+
+	/* Move one page up */
+	panel_page_up(panel);
+
+	/* Page should now start at second entry and cursor at third */
+	PCUT_ASSERT_STR_EQUALS("c", panel->cursor->name);
+	PCUT_ASSERT_INT_EQUALS(3, panel->cursor->size);
+	PCUT_ASSERT_INT_EQUALS(2, panel->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", panel->page->name);
+	PCUT_ASSERT_INT_EQUALS(2, panel->page->size);
+	PCUT_ASSERT_INT_EQUALS(1, panel->page_idx);
+
+	/* Move one page up again. */
+	panel_page_up(panel);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", panel->cursor->name);
+	PCUT_ASSERT_INT_EQUALS(1, panel->cursor->size);
+	PCUT_ASSERT_INT_EQUALS(0, panel->cursor_idx);
+	PCUT_ASSERT_EQUALS(panel->cursor, panel->page);
+	PCUT_ASSERT_INT_EQUALS(0, panel->page_idx);
+
+	/* Moving further up should do nothing (we are at the top). */
+	panel_page_up(panel);
+
+	/* Cursor and page start should still be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", panel->cursor->name);
+	PCUT_ASSERT_INT_EQUALS(1, panel->cursor->size);
+	PCUT_ASSERT_INT_EQUALS(0, panel->cursor_idx);
+	PCUT_ASSERT_EQUALS(panel->cursor, panel->page);
+	PCUT_ASSERT_INT_EQUALS(0, panel->page_idx);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** panel_page_up() moves one page down */
+PCUT_TEST(page_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	panel_t *panel;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, &panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 4; // XXX Assuming this makes page size 2
+	panel_set_rect(panel, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, panel_page_size(panel));
+
+	/* Add five entries (2 full pages, one partial) */
+	rc = panel_entry_append(panel, "a", 1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "b", 2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "c", 3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "d", 4);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_entry_append(panel, "e", 5);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page to the first entry */
+	panel->cursor = panel_first(panel);
+	panel->cursor_idx = 0;
+	panel->page = panel->cursor;
+	panel->page_idx = 0;
+
+	/* Move one page down */
+	panel_page_down(panel);
+
+	/* Page and cursor should point to the third entry */
+	PCUT_ASSERT_STR_EQUALS("c", panel->cursor->name);
+	PCUT_ASSERT_INT_EQUALS(3, panel->cursor->size);
+	PCUT_ASSERT_INT_EQUALS(2, panel->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("c", panel->page->name);
+	PCUT_ASSERT_INT_EQUALS(3, panel->page->size);
+	PCUT_ASSERT_INT_EQUALS(2, panel->page_idx);
+
+	/* Move one page down again. */
+	panel_page_down(panel);
+
+	/* Cursor should point to last and page to next-to-last entry */
+	PCUT_ASSERT_STR_EQUALS("e", panel->cursor->name);
+	PCUT_ASSERT_INT_EQUALS(5, panel->cursor->size);
+	PCUT_ASSERT_INT_EQUALS(4, panel->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", panel->page->name);
+	PCUT_ASSERT_INT_EQUALS(4, panel->page->size);
+	PCUT_ASSERT_INT_EQUALS(3, panel->page_idx);
+
+	/* Moving further down should do nothing (we are at the bottom). */
+	panel_page_down(panel);
+
+	/* Cursor should still point to last and page to next-to-last entry */
+	PCUT_ASSERT_STR_EQUALS("e", panel->cursor->name);
+	PCUT_ASSERT_INT_EQUALS(5, panel->cursor->size);
+	PCUT_ASSERT_INT_EQUALS(4, panel->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", panel->page->name);
+	PCUT_ASSERT_INT_EQUALS(4, panel->page->size);
+	PCUT_ASSERT_INT_EQUALS(3, panel->page_idx);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
 PCUT_EXPORT(panel);
