diff --git a/examples/pdf/multipage/resources/go-down-search.svg b/examples/pdf/multipage/resources/go-down-search.svg
new file mode 100644
index 0000000000000000000000000000000000000000..ae17ab93b5f5481b8cd19e5135fac2a8ea98987c
--- /dev/null
+++ b/examples/pdf/multipage/resources/go-down-search.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+     d="M 4.7070312 8 L 4 8.7070312 L 10.125 14.832031 L 12 16.707031 L 13.875 14.832031 L 20 8.7070312 L 19.292969 8 L 13.167969 14.125 L 12 15.292969 L 10.832031 14.125 L 4.7070312 8 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/examples/pdf/multipage/resources/go-up-search.svg b/examples/pdf/multipage/resources/go-up-search.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5cc1558731a56fbd07f192d703e57027ac1ace21
--- /dev/null
+++ b/examples/pdf/multipage/resources/go-up-search.svg
@@ -0,0 +1,8 @@
+<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
+    <style type="text/css" id="current-color-scheme">
+        .ColorScheme-Text {
+            color:#232629;
+        }
+    </style>
+    <path d="M4.707 16L4 15.293l8-8 8 8-.707.707L12 8.707" class="ColorScheme-Text" fill="currentColor"/>
+</svg>
diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml
index bbc28cd8dbacd727e8890644661a9c9920064caf..3d9cd371ad86ad531c47157a43bea97117e32777 100644
--- a/examples/pdf/multipage/viewer.qml
+++ b/examples/pdf/multipage/viewer.qml
@@ -167,30 +167,6 @@ ApplicationWindow {
                     onTriggered: view.copySelectionToClipboard()
                 }
             }
-            TextField {
-                id: searchField
-                placeholderText: "search"
-                Layout.minimumWidth: 200
-                Layout.fillWidth: true
-                Image {
-                    visible: searchField.text !== ""
-                    source: "resources/edit-clear.svg"
-                    anchors {
-                        right: parent.right
-                        top: parent.top
-                        bottom: parent.bottom
-                        margins: 3
-                        rightMargin: 5
-                    }
-                    TapHandler {
-                        onTapped: searchField.clear()
-                    }
-                }
-            }
-            Shortcut {
-                sequence: StandardKey.Find
-                onActivated: searchField.forceActiveFocus()
-            }
             Shortcut {
                 sequence: StandardKey.Quit
                 onActivated: Qt.quit()
@@ -259,6 +235,63 @@ ApplicationWindow {
         onCurrentPageChanged: currentPageSB.value = view.currentPage + 1
     }
 
+    Drawer {
+        id: searchDrawer
+        edge: Qt.BottomEdge
+        x: 20
+        width: searchLayout.implicitWidth
+        height: searchLayout.implicitHeight
+        dim: false
+        Shortcut {
+            sequence: StandardKey.Find
+            onActivated: {
+                searchDrawer.open()
+                searchField.forceActiveFocus()
+            }
+        }
+        RowLayout {
+            id: searchLayout
+            ToolButton {
+                action: Action {
+                    icon.source: "resources/go-up-search.svg"
+                    onTriggered: view.searchBack()
+                }
+                ToolTip.visible: enabled && hovered
+                ToolTip.delay: 2000
+                ToolTip.text: "find previous"
+            }
+            TextField {
+                id: searchField
+                placeholderText: "search"
+                Layout.minimumWidth: 200
+                Layout.fillWidth: true
+                Image {
+                    visible: searchField.text !== ""
+                    source: "resources/edit-clear.svg"
+                    anchors {
+                        right: parent.right
+                        top: parent.top
+                        bottom: parent.bottom
+                        margins: 3
+                        rightMargin: 5
+                    }
+                    TapHandler {
+                        onTapped: searchField.clear()
+                    }
+                }
+            }
+            ToolButton {
+                action: Action {
+                    icon.source: "resources/go-down-search.svg"
+                    onTriggered: view.searchForward()
+                }
+                ToolTip.visible: enabled && hovered
+                ToolTip.delay: 2000
+                ToolTip.text: "find next"
+            }
+        }
+    }
+
     footer: ToolBar {
         height: statusLabel.implicitHeight * 1.5
         Label {
diff --git a/examples/pdf/multipage/viewer.qrc b/examples/pdf/multipage/viewer.qrc
index fa3561caf0a857175c02157a2262294c43c32b61..9698a2689322a587a90bf65f5909d5ed708e68f3 100644
--- a/examples/pdf/multipage/viewer.qrc
+++ b/examples/pdf/multipage/viewer.qrc
@@ -3,8 +3,10 @@
         <file>viewer.qml</file>
         <file>resources/edit-clear.svg</file>
         <file>resources/edit-copy.svg</file>
+        <file>resources/go-down-search.svg</file>
         <file>resources/go-next-view-page.svg</file>
         <file>resources/go-previous-view-page.svg</file>
+        <file>resources/go-up-search.svg</file>
         <file>resources/rotate-left.svg</file>
         <file>resources/rotate-right.svg</file>
         <file>resources/zoom-in.svg</file>
diff --git a/src/pdf/quick/qml/PdfMultiPageView.qml b/src/pdf/quick/qml/PdfMultiPageView.qml
index acef9fbea789f593514f28e076c4a85aea5eab74..bc5134267bcdb539ac7a6022f5d3a6de902ff51c 100644
--- a/src/pdf/quick/qml/PdfMultiPageView.qml
+++ b/src/pdf/quick/qml/PdfMultiPageView.qml
@@ -58,32 +58,33 @@ Item {
     // public API
     // TODO 5.15: required property
     property var document: undefined
-    property real renderScale: 1
-    property real pageRotation: 0
-    property string searchString
+
     property string selectedText
-    property alias currentPage: navigationStack.currentPage
     function copySelectionToClipboard() {
         if (listView.currentItem !== null)
             listView.currentItem.selection.copyToClipboard()
     }
+
+    // page navigation
+    property alias currentPage: navigationStack.currentPage
     property alias backEnabled: navigationStack.backAvailable
     property alias forwardEnabled: navigationStack.forwardAvailable
-    function back() {
-        navigationStack.back()
-    }
-    function forward() {
-        navigationStack.forward()
-    }
-
-    function resetScale() {
-        root.renderScale = 1
+    function back() { navigationStack.back() }
+    function forward() { navigationStack.forward() }
+    function goToPage(page) { goToLocation(page, Qt.point(0, 0), 0) }
+    function goToLocation(page, location, zoom) {
+        if (zoom > 0)
+            root.renderScale = zoom
+        navigationStack.push(page, location, zoom)
     }
 
+    // page scaling
+    property real renderScale: 1
+    property real pageRotation: 0
+    function resetScale() { root.renderScale = 1 }
     function scaleToWidth(width, height) {
         root.renderScale = width / (listView.rot90 ? listView.firstPagePointSize.height : listView.firstPagePointSize.width)
     }
-
     function scaleToPage(width, height) {
         var windowAspect = width / height
         var pageAspect = listView.firstPagePointSize.width / listView.firstPagePointSize.height
@@ -102,14 +103,39 @@ Item {
         }
     }
 
-    function goToPage(page) {
-        goToLocation(page, Qt.point(0, 0), 0)
+    // text search
+    property alias searchString: searchModel.searchString
+    property bool searchBackEnabled: searchModel.currentResult > 0
+    property bool searchForwardEnabled: searchModel.currentResult < searchModel.matchGeometry.length - 1
+    function searchBack() {
+        if (searchModel.currentResult > 0) {
+            --searchModel.currentResult
+        } else {
+            searchModel.deferRendering = true // save time while we are searching
+            while (searchModel.currentResult <= 0) {
+                if (navigationStack.currentPage > 0)
+                    goToPage(navigationStack.currentPage - 1)
+                else
+                    goToPage(document.pageCount - 1)
+                searchModel.currentResult = searchModel.matchGeometry.length - 1
+            }
+            searchModel.deferRendering = false
+        }
     }
-
-    function goToLocation(page, location, zoom) {
-        if (zoom > 0)
-            root.renderScale = zoom
-        navigationStack.push(page, location, zoom)
+    function searchForward() {
+        if (searchModel.currentResult < searchModel.matchGeometry.length - 1) {
+            ++searchModel.currentResult
+        } else {
+            searchModel.deferRendering = true // save time while we are searching
+            while (searchModel.currentResult >= searchModel.matchGeometry.length - 1) {
+                searchModel.currentResult = 0
+                if (navigationStack.currentPage < document.pageCount - 1)
+                    goToPage(navigationStack.currentPage + 1)
+                else
+                    goToPage(0)
+            }
+            searchModel.deferRendering = false
+        }
     }
 
     id: root
@@ -133,7 +159,7 @@ Item {
             property real pageScale: image.paintedWidth / pagePointSize.width
             Image {
                 id: image
-                source: document.source
+                source: searchModel.deferRendering ? "" : document.source
                 currentFrame: index
                 asynchronous: true
                 fillMode: Image.PreserveAspectFit
@@ -152,17 +178,25 @@ Item {
             Shape {
                 anchors.fill: parent
                 opacity: 0.25
-                visible: image.status === Image.Ready
+                visible: image.status === Image.Ready && searchModel.page == index
                 ShapePath {
                     strokeWidth: 1
-                    strokeColor: "blue"
-                    fillColor: "cyan"
+                    strokeColor: "steelblue"
+                    fillColor: "lightsteelblue"
                     scale: Qt.size(paper.pageScale, paper.pageScale)
                     PathMultiline {
-                        id: searchResultBoundaries
                         paths: searchModel.matchGeometry
                     }
                 }
+                ShapePath {
+                    strokeWidth: 1
+                    strokeColor: "blue"
+                    fillColor: "cyan"
+                    scale: Qt.size(paper.pageScale, paper.pageScale)
+                    PathPolyline {
+                        path: searchModel.matchGeometry[searchModel.currentResult]
+                    }
+                }
                 ShapePath {
                     fillColor: "orange"
                     scale: Qt.size(paper.pageScale, paper.pageScale)
@@ -172,12 +206,6 @@ Item {
                     }
                 }
             }
-            PdfSearchModel {
-                id: searchModel
-                document: root.document
-                page: image.currentFrame
-                searchString: root.searchString
-            }
             PdfSelection {
                 id: selection
                 document: root.document
@@ -274,4 +302,12 @@ Item {
         onCurrentZoomChanged: root.renderScale = currentZoom
         // TODO deal with horizontal location (need another Flickable probably)
     }
+    PdfSearchModel {
+        id: searchModel
+        document: root.document === undefined ? null : root.document
+        page: navigationStack.currentPage
+        searchString: root.searchString
+        property int currentResult: 0
+        property bool deferRendering: false
+    }
 }