diff --git a/assets/script.js b/assets/script.js
index bdc498376c06a66018a05f92d9fcdcb16632c3b6..4168011dcafc3e831d5af22d46cf25a38729dfa9 100644
--- a/assets/script.js
+++ b/assets/script.js
@@ -73,6 +73,7 @@
                 createApp({
                     data () {
                         return {
+                            needle: '',
                             issues,
                             qmLabels,
                             filters
@@ -81,6 +82,13 @@
                     methods: {
                         getStateForIssueAndQmLabel(issue, qm) {
                             return issue.qm_states[qm];
+                        },
+                        valueMatchesNeedle(what) {
+                            if (this.needle.length === 0) {
+                                return false;
+                            }
+
+                            return what.toLowerCase().includes(this.needle.toLowerCase());
                         }
                     },
                     computed: {
@@ -88,7 +96,7 @@
                             return 8 + Object.values(qmLabels).length;
                         },
                         filteredIssues() {
-                            return this.issues.filter(issue => {
+                            let filtered = this.issues.filter(issue => {
                                 for (const [key, value] of Object.entries(this.filters)) {
                                     if (value === null) {
                                         continue;
@@ -99,6 +107,19 @@
                                 }
                                 return true;
                             });
+
+                            if (this.needle.length > 0) {
+                                filtered = filtered.filter(issue => {
+                                    const ciNeedle = this.needle.toLowerCase();
+                                    return issue.iid.toString().includes(this.needle)
+                                        || issue.title.toLowerCase().includes(ciNeedle)
+                                        || (issue.assignee ?? '').toLowerCase().includes(ciNeedle)
+                                        || (issue.author ?? '').toLowerCase().includes(ciNeedle)
+                                        || issue.reviewers.some(reviewer => reviewer.toLowerCase().includes(ciNeedle));
+                                });
+                            }
+
+                            return filtered;
                         }
                     },
                     watch: {
diff --git a/assets/style.scss b/assets/style.scss
index 965afa51078f2c97c9cd36a5df90a9ad86a0b055..98e4050b72993d4a71bc61f14de1d005e32b5232 100644
--- a/assets/style.scss
+++ b/assets/style.scss
@@ -23,3 +23,7 @@
         padding: 0 0.5em;
     }
 }
+
+td.filter-match {
+    background-color: var(--yellow-20);
+}
diff --git a/views/dashboard/index.php b/views/dashboard/index.php
index 2359f1d2a7770d9e95f1e4cdbfc1b24f1ea55d86..1e98d18f9738ba59d0fa66155bda80b1dad19717 100644
--- a/views/dashboard/index.php
+++ b/views/dashboard/index.php
@@ -48,7 +48,7 @@ $attributes = [
                 </td>
             </tr>
             <tr v-for="issue in filteredIssues">
-                <td>
+                <td :class="{'filter-match': valueMatchesNeedle(issue.iid.toString())}">
                     <a :href="issue.web_url" target="_blank">
                         #{{ issue.iid }}
                     </a>
@@ -70,14 +70,18 @@ $attributes = [
                         <?= Icon::create('date', Icon::ROLE_STATUS_YELLOW) ?>
                     </abbr>
                 </td>
-                <td>
+                <td :class="{'filter-match': valueMatchesNeedle(issue.title)}">
                     <a :href="issue.web_url" target="_blank">
                         {{ issue.title }}
                     </a>
                 </td>
-                <td>{{ issue.author }}</td>
-                <td>{{ issue.assignee }}</td>
-                <td>
+                <td :class="{'filter-match': valueMatchesNeedle(issue.author)}">
+                    {{ issue.author }}
+                </td>
+                <td :class="{'filter-match': valueMatchesNeedle(issue.assignee)}">
+                    {{ issue.assignee }}
+                </td>
+                <td :class="{'filter-match': valueMatchesNeedle(issue.reviewers.join('||'))}">
                     <ul v-if="issue.reviewers.length > 0" class="list-csv">
                         <li v-for="username in issue.reviewers" :key="`reviewer-${issue.iid}-${username}`">{{ username }}</li>
                     </ul>
@@ -92,8 +96,26 @@ $attributes = [
     </table>
 
     <mounting-portal mount-to="#layout-sidebar .sidebar" name="sidebar-filter" append>
+        <div class="sidebar-widget sidebar-search">
+            <div class="sidebar-widget-header"><?= _('Suche') ?></div>
+            <div class="sidebar-widget-content">
+                <form action="#" @submit="event => event.preventDefault()">
+                    <ul class="needles">
+                        <li>
+                            <div class="input-group files-search">
+                                <input type="text" v-model.trim="needle" placeholder="<?= _('Suchwort') ?>">
+                                <button class="submit-search" type="submit" title="<?= _('Suche auführen') ?>">
+                                    <?= Icon::create('search')->asImg(20) ?>
+                                </button>
+                            </div>
+                        </li>
+                    </ul>
+                </form>
+            </div>
+        </div>
+
         <div class="sidebar-widget">
-            <div class="sidebar-widget-header">Filter</div>
+            <div class="sidebar-widget-header"><?= _('Filter') ?></div>
             <div class="sidebar-widget-content">
                 <label v-for="(abbr, label) in qmLabels" style="display: block;">
                     {{ label }}