diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php
index e5ef007b62e06576ba403ac5e202a81b67ca188c..19d3ef93deeea923ef8a6698bcc57f161267cc62 100644
--- a/app/views/questionnaire/edit.php
+++ b/app/views/questionnaire/edit.php
@@ -50,3 +50,158 @@ $questionnaire_data = [
             'range-id'       => Request::get('range_id'),
             'range-type'     => Request::get('range_type'),
         ]) ?>
+=======
+<form action="<?= URLHelper::getLink('dispatch.php/questionnaire/edit/' . (!$questionnaire->isNew() ? $questionnaire->getId() : '')) ?>"
+      method="post"
+      enctype="multipart/form-data"
+      class="questionnaire_edit default"
+      data-questiontypes="<?= htmlReady(json_encode($questiontypes)) ?>"
+      data-questionnaire_data="<?= htmlReady(json_encode($questionnaire_data)) ?>"
+      data-questions_data="<?= htmlReady(json_encode($questions_data)) ?>"
+      data-range_type="<?= htmlReady(Request::get('range_type')) ?>"
+      data-range_id="<?= htmlReady(Request::get('range_id')) ?>"
+      <?= Request::isAjax() ? 'data-dialog' : '' ?>
+      :data-secure="activateFormSecure">
+
+    <div class="editor">
+        <div class="rightside" aria-live="polite" tabindex="0" ref="rightside">
+            <div class="admin" v-if="activeTab === 'admin'">
+
+                <article aria-live="assertive" class="validation_notes studip">
+                    <header>
+                        <h1>
+                            <?= Icon::create('info-circle', Icon::ROLE_INFO)->asImg(['class' => 'text-bottom validation_notes_icon']) ?>
+                            <?= _('Hinweise zum Ausfüllen des Formulars') ?>
+                        </h1>
+                    </header>
+                    <div class="required_note">
+                        <div aria-hidden="true">
+                            <?= _('Pflichtfelder sind mit Sternchen gekennzeichnet.') ?>
+                        </div>
+                        <div class="sr-only">
+                            <?= _('Dieses Formular enthält Pflichtfelder.') ?>
+                        </div>
+                    </div>
+                    <div v-if="validationNotice && !data.title">
+                        <?= _('Folgende Angaben müssen korrigiert werden, um das Formular abschicken zu können:') ?>
+                        <ul>
+                            <li aria-describedby="questionnaire_title"><?= _('Titel des Fragebogens') ?></li>
+                        </ul>
+                    </div>
+                </article>
+
+                <div class="formpart">
+                    <label class="studiprequired" for="questionnaire_title">
+                        <span class="textlabel"><?= _('Titel des Fragebogens') ?></span>
+                        <span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
+                    </label>
+                    <input type="text" id="questionnaire_title" v-model="data.title" v-autofocus>
+                </div>
+
+                <div class="hgroup">
+                    <label>
+                        <?= _('Startzeitpunkt') ?>
+                        <datetimepicker v-model="data.startdate"></datetimepicker>
+                    </label>
+                    <label>
+                        <?= _('Endzeitpunkt') ?>
+                        <datetimepicker v-model="data.stopdate"></datetimepicker>
+                    </label>
+                </div>
+                <label>
+                    <input type="checkbox" v-model="data.copyable" true-value="1" false-value="0">
+                    <?= _('Fragebogen zum Kopieren freigeben') ?>
+                </label>
+                <label>
+                    <input type="checkbox" v-model="data.anonymous" true-value="1" false-value="0">
+                    <?= _('Teilnehmende anonymisieren') ?>
+                </label>
+                <label>
+                    <input type="checkbox" v-model="data.editanswers" true-value="1" false-value="0">
+                    <?= _('Teilnehmende dürfen ihre Antworten revidieren') ?>
+                </label>
+                <label>
+                    <?= _('Ergebnisse einsehbar') ?>
+                    <select v-model="data.resultvisibility">
+                        <option value="always"><?= _('Immer') ?></option>
+                        <option value="afterending"><?= _('Nach Ende der Befragung') ?></option>
+                        <option value="afterparticipation"><?= _('Nach der Teilnahme') ?></option>
+                        <option value="never"><?= _('Niemals') ?></option>
+                    </select>
+                </label>
+            </div>
+            <div class="add_question file_select_possibilities" v-else-if="activeTab === 'add_question'">
+                <div>
+                    <button v-for="(questiontype, key) in questiontypes" :key="key"
+                       :ref="key == Object.keys(questiontypes)[0] ? 'autofocus' : ''"
+                       href=""
+                       @click.prevent="addQuestion(questiontype.type)">
+                        <studip-icon :shape="questiontype.icon" :size="40"></studip-icon>
+                        {{questiontype.name}}
+                    </button>
+                </div>
+            </div>
+            <div v-else>
+                <component :is="questiontypes[questions[indexForQuestion].questiontype].component[0]"
+                           v-model="questions[indexForQuestion].questiondata"
+                           :question_id="questions[indexForQuestion].id"
+                           :key="questions[indexForQuestion].id">
+                </component>
+            </div>
+        </div>
+        <aside>
+            <a class="admin"
+               :class="{active: activeTab === 'admin'}"
+               href="#"
+               @click.prevent="switchTab('admin')">
+                <span class="icon"><studip-icon shape="evaluation" :size="30" alt=""></studip-icon></span>
+                <?= _('Einstellungen') ?>
+            </a>
+            <draggable v-if="questions.length > 0" v-model="questions" handle=".drag-handle" group="questions" class="questions_container questions">
+                <div v-for="question in questions"
+                     :key="question.id"
+                     @mouseenter="hoverTab = question.id"
+                     @mouseleave="hoverTab = null"
+                     :class="(activeTab === question.id || activeTab === 'meta_' + question.id ? 'active' : '') + (hoverTab === question.id ? ' hovered' : '')">
+                    <a href="#"
+                       @click.prevent="switchTab(question.id)">
+                        <span class="drag-handle"></span>
+                        <span class="icon type">
+                            <studip-icon :shape="questiontypes[question.questiontype].icon" :size="30" alt=""></studip-icon>
+                        </span>
+
+                        <div v-if="editInternalName !== question.id">{{ question.internal_name || questiontypes[question.questiontype].name}}</div>
+                        <div v-else class="inline_editing">
+                            <input type="text" ref="editInternalName" v-model="tempInternalName" class="inlineediting_internal_name">
+                            <button @click="saveInternalName(question.id)">
+                                <studip-icon shape="accept" :size="20" title="<?= _('Internen Namen speichern') ?>"></studip-icon>
+                            </button>
+                            <button @click="editInternalName = null">
+                                <studip-icon shape="decline" :size="20" title="<?= _('Internen Namen nicht speichern') ?>"></studip-icon>
+                            </button>
+                        </div>
+                    </a>
+
+                    <studip-action-menu :items="[{label: '<?= _('Umbenennen') ?>', icon: 'edit', emit: 'rename'}, {label: '<?= _('Frage kopieren') ?>', icon: 'copy', emit: 'copy'}, {label: '<?= _('Frage nach oben verschieben') ?>', icon: 'arr_1up', emit: 'moveup'}, {label: '<?= _('Frage nach unten verschieben') ?>', icon: 'arr_1down', emit: 'movedown'}, {label: '<?= _('Frage löschen') ?>', icon: 'trash', emit: 'delete'}]"
+                                        @copy="duplicateQuestion(question.id)"
+                                        @rename="renameInternalName(question.id)"
+                                        @moveup="moveQuestionUp(question.id)"
+                                        @movedown="moveQuestionDown(question.id)"
+                                        @delete="deleteQuestion(question.id)"></studip-action-menu>
+                </div>
+            </draggable>
+            <a :class="activeTab === 'add_question' ? 'add_question active' : 'add_question'"
+               href="#"
+               @click.prevent="switchTab('add_question')">
+                <span class="icon"><studip-icon shape="add" :size="30" alt=""></studip-icon></span>
+                <?= _('Element hinzufügen') ?>
+            </a>
+        </aside>
+    </div>
+
+
+    <footer data-dialog-button>
+        <?= Studip\LinkButton::create(_('Speichern'), 'questionnaire_store', ['onclick' => 'STUDIP.Questionnaire.Editor.submit(); return false;']) ?>
+    </footer>
+</form>
+>>>>>>> 166e475f04 (introduce vue directive v-autofocus, fixes #3986)
diff --git a/resources/vue/base-directives.js b/resources/vue/base-directives.js
index a2b8ae126e2b157c1daa6b1678ffdc5526ce0d65..8f46b03f7f03645161abe14ccdba497f4532c3f0 100644
--- a/resources/vue/base-directives.js
+++ b/resources/vue/base-directives.js
@@ -1,4 +1,7 @@
+import autofocus from './directives/autofocus';
+
 const BaseDirectives = {
+    autofocus,
 };
 
 export default BaseDirectives;
diff --git a/resources/vue/components/questionnaires/FreetextEdit.vue b/resources/vue/components/questionnaires/FreetextEdit.vue
index 58848edd1d51e58c718734c1b24c41b7d921c06e..60852ffa858a249fa45821e5e87c683fc55aa273 100644
--- a/resources/vue/components/questionnaires/FreetextEdit.vue
+++ b/resources/vue/components/questionnaires/FreetextEdit.vue
@@ -1,6 +1,6 @@
 <template>
     <div>
-        <div class="formpart" tabindex="0" ref="autofocus">
+        <div class="formpart" tabindex="0">
             {{ $gettext('Frage') }}
             <StudipWysiwyg v-model="val_clone.description" />
         </div>
@@ -23,9 +23,6 @@ export default {
             description: '',
             mandatory: '0',
         });
-    },
-    mounted() {
-        this.$refs.autofocus.focus();
     }
 }
 </script>
diff --git a/resources/vue/components/questionnaires/LikertEdit.vue b/resources/vue/components/questionnaires/LikertEdit.vue
index 736be6b93fd586911e4a87661c931e0b9db75513..311bf6d4109b7b80392a9d67f67231df5cfcaa58 100644
--- a/resources/vue/components/questionnaires/LikertEdit.vue
+++ b/resources/vue/components/questionnaires/LikertEdit.vue
@@ -1,6 +1,7 @@
 <template>
     <div class="likert_edit">
-        <div class="formpart" tabindex="0" ref="autofocus">
+
+        <div class="formpart" tabindex="0">
             {{ $gettext('Einleitungstext' )}}
             <StudipWysiwyg v-model="val_clone.description" />
         </div>
@@ -65,9 +66,6 @@ export default {
     mixins: [ QuestionnaireComponent ],
     created() {
         this.setDefaultValues(default_values());
-    },
-    mounted() {
-        this.$refs.autofocus.focus();
     }
 }
 </script>
diff --git a/resources/vue/components/questionnaires/QuestionnaireEditor.vue b/resources/vue/components/questionnaires/QuestionnaireEditor.vue
index d87305a2b7a019193f5d430d3ade88121554bcb6..089353bbfcae65fb0a172a60a0dc6da8c6bc58e8 100644
--- a/resources/vue/components/questionnaires/QuestionnaireEditor.vue
+++ b/resources/vue/components/questionnaires/QuestionnaireEditor.vue
@@ -39,7 +39,7 @@
                             <span class="textlabel">{{ $gettext('Titel des Fragebogens') }}</span>
                             <span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
                         </label>
-                        <input type="text" id="questionnaire_title" v-model="data.title" ref="autofocus">
+                        <input type="text" id="questionnaire_title" v-model="data.title" v-autofocus>
                     </div>
 
                     <div class="hgroup">
@@ -77,7 +77,6 @@
                 <div class="add_question file_select_possibilities" v-else-if="activeTab === 'add_question'">
                     <div>
                         <button v-for="(questiontype, key) in questionTypes" :key="key"
-                                :ref="key == Object.keys(questionTypes)[0] ? 'autofocus' : ''"
                                 href=""
                                 @click.prevent="addQuestion(questiontype.type)"
                         >
@@ -283,17 +282,6 @@ export default {
         },
         switchTab(tab_id) {
             this.activeTab = tab_id;
-            this.$nextTick(function () {
-                if (this.$refs.autofocus !== undefined) {
-                    if (Array.isArray(this.$refs.autofocus)) {
-                        if (typeof this.$refs.autofocus[0] !== "undefined") {
-                            this.$refs.autofocus[0].focus();
-                        }
-                    } else {
-                        this.$refs.autofocus.focus();
-                    }
-                }
-            });
         },
         objectsEqual(obj1, obj2) {
             return _.isEqual(obj1, obj2);
@@ -347,8 +335,5 @@ export default {
             return this.getIndexForQuestion(this.activeTab);
         },
     },
-    mounted() {
-        this.$refs.autofocus.focus();
-    },
 }
 </script>
diff --git a/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue b/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
index 83d5fa2d5e877b95c9b8d38523086d1c7cd518c8..57452d49dd531e73517b9f4477ea7d02136b9154 100644
--- a/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
+++ b/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
@@ -2,7 +2,7 @@
     <div class="vote_edit">
         <label>
             {{ $gettext('Link eines Videos oder einer anderen Informationsseite (optional)') }}
-            <input type="url" v-model="val_clone.url" ref="infoUrl"
+            <input type="url" v-model="val_clone.url" v-autofocus ref="infoUrl"
                    @input="checkValidity()">
         </label>
 
@@ -26,7 +26,6 @@ export default {
         });
     },
     mounted() {
-        this.$refs.infoUrl.focus();
         this.checkValidity();
     },
     methods: {
diff --git a/resources/vue/components/questionnaires/RangescaleEdit.vue b/resources/vue/components/questionnaires/RangescaleEdit.vue
index cd7ce3b215745987e0e4b1e7123c07a5520996a2..044c921cd349681ef431916573ea93e76fb83efe 100644
--- a/resources/vue/components/questionnaires/RangescaleEdit.vue
+++ b/resources/vue/components/questionnaires/RangescaleEdit.vue
@@ -1,7 +1,7 @@
 <template>
     <div class="rangescale_edit">
 
-        <div class="formpart" tabindex="0" ref="autofocus">
+        <div class="formpart" tabindex="0">
             {{ $gettext('Einleitungstext') }}
             <StudipWysiwyg v-model="val_clone.description" />
         </div>
@@ -68,9 +68,6 @@ export default {
             statements: ['', '', '', '']
         });
     },
-    mounted() {
-        this.$refs.autofocus.focus();
-    },
     computed: {
         options() {
             let result = [];
diff --git a/resources/vue/components/questionnaires/VoteEdit.vue b/resources/vue/components/questionnaires/VoteEdit.vue
index 1d6d9cf7c066977535535e4c3b87cfcc52e714ec..56dd160f4446d683aaa0b3a02bab7ab178fb4f77 100644
--- a/resources/vue/components/questionnaires/VoteEdit.vue
+++ b/resources/vue/components/questionnaires/VoteEdit.vue
@@ -1,6 +1,6 @@
 <template>
     <div class="vote_edit">
-        <div class="formpart" tabindex="0" ref="autofocus">
+        <div class="formpart" tabindex="0">
             {{ $gettext('Frage') }}
             <StudipWysiwyg v-model="val_clone.description" />
         </div>
@@ -39,9 +39,6 @@ export default {
             options: ['', '', '', ''],
             randomize: '0',
         });
-    },
-    mounted() {
-        this.$refs.autofocus.focus();
     }
 }
 </script>
diff --git a/resources/vue/directives/autofocus.ts b/resources/vue/directives/autofocus.ts
new file mode 100644
index 0000000000000000000000000000000000000000..554dc5a7889614d9567cb9ea29370a52aef11e0f
--- /dev/null
+++ b/resources/vue/directives/autofocus.ts
@@ -0,0 +1,23 @@
+// Shamelessly copied from https://github.com/byteboomers/vue-autofocus-directive
+
+function focusElement(el: HTMLElement, binding: any) : void {
+    if (binding.value && !binding.value) {
+        return;
+    }
+
+    el.focus()
+}
+
+export default {
+    bind(el: HTMLElement, binding: any, vnode: any) {
+        if (vnode.componentInstance?.focus instanceof Function) {
+            // When the component itself has a focus method
+            vnode.componentInstance.focus();
+        } else {
+            // When the component of the element gets activated
+            vnode.context.$on('hook:activated', () => focusElement(el, binding));
+        }
+
+    },
+    inserted: focusElement
+}