diff --git a/app/controllers/registration.php b/app/controllers/registration.php
new file mode 100644
index 0000000000000000000000000000000000000000..edb6b42d0ed31decc5a84d9194ebd9089552192e
--- /dev/null
+++ b/app/controllers/registration.php
@@ -0,0 +1,133 @@
+<?php
+class RegistrationController extends AuthenticatedController
+{
+    protected $allow_nobody = true;
+
+    public function before_filter(&$action, &$args)
+    {
+        parent::before_filter($action, $args);
+
+        PageLayout::setTitle(_('Registrierung'));
+    }
+
+    public function index_action()
+    {
+        $new_user = new User();
+
+        $this->registrationform = \Studip\Forms\Form::fromSORM(
+            $new_user,
+            [
+                'legend' => _('Herzlich willkommen!'),
+                'fields' => [
+                    'username' => [
+                        'label' => _('Benutzername'),
+                        'required' => true,
+                        'maxlength' => '63',
+                        'validate' => function ($value, $input) {
+                            if (!preg_match(Config::get()->USERNAME_REGULAR_EXPRESSION, $value)) {
+                                return Config::get()->getMetadata('USERNAME_REGULAR_EXPRESSION')['comment'] ?:
+                                    _('Benutzername muss mindestens 4 Zeichen lang sein und darf nur aus Buchstaben, '
+                                    . 'Ziffern, Unterstrich, @, Punkt und Minus bestehen.');
+                            }
+                            $user = User::findByUsername($value);
+                            $context = $input->getContextObject();
+                            if ($user && ($user->id !== $context->getId())) {
+                                return _('Benutzername ist schon vergeben.');
+                            }
+                            return true;
+                        }
+                    ],
+                    'password' => [
+                        'label' => _('Passwort'),
+                        'type' => 'password',
+                        'required' => true,
+                        'maxlength' => '31',
+                        'minlength' =>  '8',
+                        'mapper' => function($value) {
+                            $hasher = UserManagement::getPwdHasher();
+                            return $hasher->HashPassword($value);
+                        }
+                    ],
+                    'confirm_password' => [
+                        'label' => _('Passwortbestätigung'),
+                        'type' => 'password',
+                        'required' => true,
+                        'maxlength' => '31',
+                        'minlength' =>  '8',
+                        ':pattern'    => "password.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')", //mask special chars
+                        'data-validation_requirement' => _('Passwörter stimmen nicht überein.'),
+                        'store' => function() {}
+                    ],
+                    'title_front' => [
+                        'label' => _('Titel'),
+                        'type'  => 'datalist',
+                        'options' => $GLOBALS['TITLE_FRONT_TEMPLATE']
+                    ],
+                    'title_rear' => [
+                        'label' => _('Titel nachgestellt'),
+                        'type'  => 'datalist',
+                        'options' => $GLOBALS['TITLE_REAR_TEMPLATE'],
+                    ],
+                    'vorname' => [
+                        'label' => _('Vorname'),
+                        'required' => true
+                    ],
+                    'nachname' => [
+                        'label' => _('Nachname'),
+                        'required' => true
+                    ],
+                    'geschlecht' => [
+                        'name' => 'geschlecht',
+                        'label' => _('Geschlecht'),
+                        'type' => 'radio',
+                        'orientation' => 'horizontal',
+                        'options' => [
+                            '0' => _('keine Angabe'),
+                            '1' => _('männlich'),
+                            '2' => _('weiblich'),
+                            '3' => _('divers'),
+                        ],
+                    ],
+                    'email' => [
+                        'label' => _('E-Mail'),
+                        'required' => true,
+                        'validate' => function ($value, $input) {
+                            $user = User::findOneByEmail($value);
+                            $context = $input->getContextObject();
+                            if ($user && ($user->id !== $context->getId())) {
+                                return _('Diese Emailadresse ist bereits registriert.');
+                            }
+                            return true;
+                        }
+                    ],
+                ]
+            ]
+        );
+        $this->registrationform->setSaveButtonText(_('Registrierung abschließen'));
+        $this->registrationform->setCancelButtonText(_('Abbrechen'));
+        $this->registrationform->setCancelButtonName(URLHelper::getURL('index.php?cancel_login=1'));
+
+        $this->registrationform->addStoreCallback(
+            function ($form) {
+                $new_user = $form->getLastPart()->getContextObject();
+
+                $GLOBALS['sess']->regenerate_session_id(['auth']);
+                $GLOBALS['auth']->unauth();
+                $GLOBALS['auth']->auth['jscript'] = true;
+                $GLOBALS['auth']->auth['perm']  = $new_user['perms'];
+                $GLOBALS['auth']->auth['uname'] = $new_user['username'];
+                $GLOBALS['auth']->auth['auth_plugin']  = $new_user['auth_plugin'];
+                $GLOBALS['auth']->auth_set_user_settings($new_user->user_id);
+                $GLOBALS['auth']->auth['uid'] = $new_user['user_id'];
+                $GLOBALS['auth']->auth['exp'] = time() + (60 * $GLOBALS['auth']->lifetime);
+                $GLOBALS['auth']->auth['refresh'] = time() + (60 * $GLOBALS['auth']->refresh);
+
+                Seminar_Register_Auth::sendValidationMail($new_user);
+
+                return 1;
+            }
+        );
+
+        $this->registrationform->autoStore()->setURL(URLHelper::getURL('dispatch.php/start'));
+    }
+}
diff --git a/app/views/registration/index.php b/app/views/registration/index.php
new file mode 100644
index 0000000000000000000000000000000000000000..b253987c6c8c462ff6fefd40491cb083270badd9
--- /dev/null
+++ b/app/views/registration/index.php
@@ -0,0 +1 @@
+<?= $registrationform->render() ?>
diff --git a/templates/register/success.php b/app/views/registration/save.php
similarity index 100%
rename from templates/register/success.php
rename to app/views/registration/save.php
diff --git a/db/migrations/5.5.19_update_username_regular_expression.php b/db/migrations/5.5.19_update_username_regular_expression.php
new file mode 100644
index 0000000000000000000000000000000000000000..cdcbc9d40bc810742e2c4585b55b5ca3032c4679
--- /dev/null
+++ b/db/migrations/5.5.19_update_username_regular_expression.php
@@ -0,0 +1,27 @@
+<?php
+class UpdateUsernameRegularExpression extends Migration
+{
+    public function description()
+    {
+        return 'Alters configuration description to make clearer that it is used for creating new users';
+    }
+
+    public function up()
+    {
+        $db = DBManager::get();
+        $stmt = $db->prepare("UPDATE `config`
+            SET `description` = 'Regulärer Ausdruck für erlaubte Zeichen in Benutzernamen. Das Kommentarfeld kann genutzt werden, um eine Fehlermeldung anzugeben, die zum Beispiel im Registrierungsformular ausgegeben wird, wenn der Ausdruck nicht erfüllt wird.'
+            WHERE `field` = 'USERNAME_REGULAR_EXPRESSION'");
+        $stmt->execute();
+    }
+
+    public function down()
+    {
+        $db = DBManager::get();
+        $stmt = $db->prepare("UPDATE `config`
+            SET `description` = 'Regex for allowed characters in usernames'
+            WHERE `field` = 'USERNAME_REGULAR_EXPRESSION'");
+        $stmt->execute();
+
+    }
+}
diff --git a/lib/classes/forms/ConfirmInput.php b/lib/classes/forms/ConfirmInput.php
new file mode 100644
index 0000000000000000000000000000000000000000..13e36348e4a362e650d1b9cb12979d7d446ede66
--- /dev/null
+++ b/lib/classes/forms/ConfirmInput.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Studip\Forms;
+
+class ConfirmInput extends Input
+{
+    public function render()
+    {
+        $template = $GLOBALS['template_factory']->open('forms/confirm_password_input');
+        $template->title = $this->title;
+        $template->name = $this->name;
+        $template->value = $this->value;
+        $template->id = md5(uniqid());
+        $template->required = $this->required;
+        $template->attributes = arrayToHtmlAttributes($this->attributes);
+        return $template->render();
+    }
+}
diff --git a/lib/classes/forms/DatalistInput.php b/lib/classes/forms/DatalistInput.php
new file mode 100644
index 0000000000000000000000000000000000000000..06cc9e030962f2feb9fa6b29c49bdbf97fb61623
--- /dev/null
+++ b/lib/classes/forms/DatalistInput.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Studip\Forms;
+
+class DatalistInput extends Input
+{
+    public function render()
+    {
+        $options = $this->extractOptionsFromAttributes($this->attributes);
+
+        $template = $GLOBALS['template_factory']->open('forms/datalist_input');
+        $template->title = $this->title;
+        $template->name = $this->name;
+        $template->value = $this->value;
+        $template->id = md5(uniqid());
+        $template->required = $this->required;
+        $template->attributes = arrayToHtmlAttributes($this->attributes);
+        $template->options = $options;
+        return $template->render();
+    }
+}
diff --git a/lib/classes/forms/Form.php b/lib/classes/forms/Form.php
index 49713b73db321f1a1e64202a674ad10b13abc26d..3b295524d1b95f52a639267a8206ad41c6c81235 100644
--- a/lib/classes/forms/Form.php
+++ b/lib/classes/forms/Form.php
@@ -17,6 +17,8 @@ class Form extends Part
     protected $save_button_text = '';
     protected $save_button_name = '';
 
+    protected $cancel_button_text = '';
+    protected $cancel_button_name = '';
     protected $autoStore = false;
     protected $debugmode = false;
     protected $success_message = '';
@@ -207,6 +209,31 @@ class Form extends Part
         return $this->save_button_name ?: $this->getSaveButtonText();
     }
 
+    public function setCancelButtonText(string $text): Form
+    {
+        $this->cancel_button_text = $text;
+        return $this;
+    }
+
+    /**
+     * @return string The text for the "save" button in the form.
+     */
+    public function getCancelButtonText() : string
+    {
+        return $this->cancel_button_text ?: _('Abbrechen');
+    }
+
+    public function setCancelButtonName(string $name): Form
+    {
+        $this->cancel_button_name = $name;
+        return $this;
+    }
+
+    public function getCancelButtonName() : string
+    {
+        return $this->cancel_button_name ?: $this->getCancelButtonText();
+    }
+
     public function setSuccessMessage(string $success_message): Form
     {
         $this->success_message = $success_message;
@@ -250,12 +277,35 @@ class Form extends Part
     {
         $this->autoStore = true;
         if (\Request::isPost() && \Request::isAjax() && !\Request::isDialog()) {
-            $this->store();
-            if ($this->success_message) {
-                \PageLayout::postSuccess($this->success_message);
+            if (\Request::submitted('STUDIPFORM_SERVERVALIDATION')) {
+                //verify the user input:
+                $output = [];
+                foreach ($this->getAllInputs() as $input) {
+                    if ($input->validate) {
+                        $callback = $input->getValidationCallback();
+                        $value = $this->getStorableValueFromRequest($input);
+                        $valid = $callback($value, $input);
+                        if ($valid !== true) {
+                            $output[$input->getName()] = [
+                                'name' => $input->getName(),
+                                'label' => $input->getTitle(),
+                                'error' => $callback($value, $input)
+                            ];
+                        }
+                    }
+                }
+                echo json_encode($output);
+                page_close();
+                die();
+            } else {
+                //storing the input
+                $this->store();
+                if ($this->success_message) {
+                    \PageLayout::postSuccess($this->success_message);
+                }
+                page_close();
+                die();
             }
-            page_close();
-            die();
         }
         return $this;
     }
@@ -325,6 +375,17 @@ class Form extends Part
 
         $stored = 0;
 
+        foreach ($this->getAllInputs() as $input) {
+            if ($input->validate) {
+                $callback = $input->getValidationCallback();
+                $value = $this->getStorableValueFromRequest($input);
+                $valid = $callback($value, $input);
+                if ($valid !== true) {
+                    return $stored;
+                }
+            }
+        }
+
         //store by each input
         $all_values = [];
         foreach ($this->getAllInputs() as $input) {
diff --git a/lib/classes/forms/Input.php b/lib/classes/forms/Input.php
index aa3069b70b1224c560d9337a1d0eb8c562158b93..9d2ad327caef281ab4924d2cc8c5f8f64857b8e1 100644
--- a/lib/classes/forms/Input.php
+++ b/lib/classes/forms/Input.php
@@ -11,6 +11,7 @@ abstract class Input
     protected $parent = null;
     public $mapper = null;
     public $store = null;
+    public $validate = null;
     public $if = null;
     public $permission = true;
     public $required = false;
@@ -133,6 +134,21 @@ abstract class Input
         return $this->name;
     }
 
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    public function hasValidation()
+    {
+        return $this->validate !== null;
+    }
+
+    public function getValidationCallback()
+    {
+        return $this->validate;
+    }
+
     /**
      * Returns the value of this input.
      * @return null
@@ -215,6 +231,18 @@ abstract class Input
         return $this;
     }
 
+    /**
+     * Sets the server-side verify function of this input. The callable returns true if the given value is okay, or
+     * false or a textstring representing the error.
+     * @param callable $verify
+     * @return $this
+     */
+    public function setValidationFunction(Callable $validate)
+    {
+        $this->validate = $validate;
+        return $this;
+    }
+
     /**
      * Sets a condition to display this input. The condition is a javascript condition which is used by vue to
      * hide the input if the condition is not satisfies.
@@ -261,7 +289,7 @@ abstract class Input
 
     protected function extractOptionsFromAttributes(array &$attributes)
     {
-        $options = null;
+        $options = [];
         if (isset($attributes['options'])) {
             $options = $attributes['options'];
             unset($attributes['options']);
diff --git a/lib/classes/forms/Part.php b/lib/classes/forms/Part.php
index 3609eb4fad39be7756c2f501fe5c6a483fba8866..fdca8f5395af8e88dd99b8b5f05bba4735709360 100644
--- a/lib/classes/forms/Part.php
+++ b/lib/classes/forms/Part.php
@@ -235,6 +235,7 @@ abstract class Part
             $attributes['type'],
             $attributes['mapper'],
             $attributes['store'],
+            $attributes['validate'],
             $attributes['if'],
             $attributes['permission'],
             $attributes['required'],
@@ -257,6 +258,9 @@ abstract class Part
         if (isset($data['store']) && is_callable($data['store'])) {
             $input->store = $data['store'];
         }
+        if (isset($data['validate']) && is_callable($data['validate'])) {
+            $input->validate = $data['validate'];
+        }
         if (!empty($data['if'])) {
             $input->if = $data['if'];
         }
diff --git a/lib/classes/forms/PasswordInput.php b/lib/classes/forms/PasswordInput.php
new file mode 100644
index 0000000000000000000000000000000000000000..68e4718ed037ea5b611ad202cf26f33ecbb5b4f5
--- /dev/null
+++ b/lib/classes/forms/PasswordInput.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Studip\Forms;
+
+class PasswordInput extends Input
+{
+    public function render()
+    {
+        $template = $GLOBALS['template_factory']->open('forms/password_input');
+        $template->title = $this->title;
+        $template->name = $this->name;
+        $template->value = $this->value;
+        $template->id = md5(uniqid());
+        $template->required = $this->required;
+        $template->attributes = arrayToHtmlAttributes($this->attributes);
+        return $template->render();
+    }
+}
diff --git a/lib/classes/forms/RadioInput.php b/lib/classes/forms/RadioInput.php
new file mode 100644
index 0000000000000000000000000000000000000000..1945d87cbb868f9fd139bde12750ff9552247833
--- /dev/null
+++ b/lib/classes/forms/RadioInput.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Studip\Forms;
+
+class RadioInput extends Input
+{
+    public function render()
+    {
+        $options = $this->extractOptionsFromAttributes($this->attributes);
+        $template = $GLOBALS['template_factory']->open('forms/radio_input');
+        $template->title = $this->title;
+        $template->name = $this->name;
+        $template->value = $this->value;
+        $template->id = md5(uniqid());
+        $template->required = $this->required;
+        $template->options = $options;
+        $template->attributes = arrayToHtmlAttributes($this->attributes);
+        $template->orientation = $this->attributes['orientation'];
+        return $template->render();
+
+    }
+}
diff --git a/lib/classes/forms/RangeInput.php b/lib/classes/forms/RangeInput.php
index 9f99d592929655fc7a0074fcf71179ccf836c4b8..7a69e9eacfb6a88a1ea66561164b4d2d8f97291c 100644
--- a/lib/classes/forms/RangeInput.php
+++ b/lib/classes/forms/RangeInput.php
@@ -11,6 +11,9 @@ class RangeInput extends Input
         $template->name = $this->name;
         $template->value = $this->value;
         $template->id = md5(uniqid());
+        $template->min = $this->attributes['min'];
+        $template->max = $this->attributes['max'];
+        $template->step = $this->attributes['step'];
         $template->required = $this->required;
         $template->attributes = arrayToHtmlAttributes($this->attributes);
         return $template->render();
diff --git a/lib/navigation/LoginNavigation.php b/lib/navigation/LoginNavigation.php
index 96ec90ed8979128d05027795ec5a8c7c4844bc84..bb63a03e97f19f655fdb4614630013e9643b6931 100644
--- a/lib/navigation/LoginNavigation.php
+++ b/lib/navigation/LoginNavigation.php
@@ -43,9 +43,9 @@ class LoginNavigation extends Navigation
         }
 
         if (Config::get()->ENABLE_SELF_REGISTRATION) {
-            $navigation = new Navigation(_('Registrieren'), 'register1.php');
+            $navigation = new Navigation(_('Registrieren'), 'dispatch.php/registration');
             $navigation->setDescription(_('um das System erstmalig zu nutzen'));
-            $this->addSubNavigation('register', $navigation);
+            $this->addSubNavigation('registration', $navigation);
         }
 
         if (Config::get()->ENABLE_FREE_ACCESS) {
diff --git a/public/register1.php b/public/register1.php
deleted file mode 100644
index 7b4740f931e7710fce5cc9535f69cbe32e7494cb..0000000000000000000000000000000000000000
--- a/public/register1.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/**
- * register1.php - Benutzerregistrierung in Stud.IP, Part I
- *
- * @author    Stefan Suchi <suchi@gmx.de>
- * @author    Oliver Brakel <obrakel@gwdg.de>
- * @copyright 2000 authors
- * @license   GPL2 or any later version
- */
-
-require '../lib/bootstrap.php';
-
-page_open([
-    'sess' => 'Seminar_Session',
-    'auth' => 'Seminar_Default_Auth',
-    'perm' => 'Seminar_Perm',
-    'user' => 'Seminar_User',
-]);
-
-include 'lib/seminar_open.php'; // initialise Stud.IP-Session
-
-if (!Config::get()->ENABLE_SELF_REGISTRATION) {
-    PageLayout::postError(_('Registrierung ausgeschaltet'), [
-        _('In dieser Installation ist die Möglichkeit zur Registrierung ausgeschaltet.'),
-        sprintf(
-            '<a href="%s">%s</a>',
-            URLHelper::getLink('index.php'),
-            _('Hier geht es zur Startseite.')
-        )
-    ]);
-
-    echo $GLOBALS['template_factory']->render('layouts/base.php', [
-        'content_for_layout' => '',
-    ]);
-} elseif (Config::get()->SHOW_TERMS_ON_FIRST_LOGIN) {
-    header('Location: ' . URLHelper::getURL('register2.php'));
-} elseif ($GLOBALS['auth']->is_authenticated() && $GLOBALS['user']->id !== 'nobody') {
-    PageLayout::postError(_('Sie sind schon als BenutzerIn am System angemeldet!'), [
-        sprintf(
-            '<a href="%s">%s</a>',
-            URLHelper::getLink('index.php'),
-            _('Hier geht es zur Startseite.')
-        )
-    ]);
-    echo $GLOBALS['template_factory']->render('layouts/base.php', [
-        'content_for_layout' => '',
-    ]);
-} else {
-    PageLayout::setHelpKeyword('Basis.AnmeldungRegistrierung');
-    PageLayout::setTitle(_('Nutzungsbedingungen'));
-    echo $GLOBALS['template_factory']->render(
-        'register/step1.php',
-        [],
-        $GLOBALS['template_factory']->open('layouts/base.php')
-    );
-    $auth->logout();
-}
-
-page_close();
diff --git a/public/register2.php b/public/register2.php
deleted file mode 100644
index 8d73c81ee6952012fcf547ac65fd16ab9ca9d06f..0000000000000000000000000000000000000000
--- a/public/register2.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-/**
- * register2.php - Benutzerregistrierung in Stud.IP, Part II
- *
- * @author    Stefan Suchi <suchi@gmx.de>
- * @author    Oliver Brakel <obrakel@gwdg.de>
- * @copyright 2000 authors
- * @license   GPL2 or any later version
- */
-
-require '../lib/bootstrap.php';
-
-page_open([
-    'sess' => 'Seminar_Session',
-    'auth' => Config::get()->ENABLE_SELF_REGISTRATION ? 'Seminar_Register_Auth' : 'Seminar_Default_Auth',
-    'perm' => 'Seminar_Perm',
-    'user' => 'Seminar_User',
-]);
-
-if (!Config::get()->ENABLE_SELF_REGISTRATION){
-    PageLayout::postError(_('Registrierung ausgeschaltet'), [
-        _('In dieser Installation ist die Möglichkeit zur Registrierung ausgeschaltet.'),
-        sprintf(
-            '<a href="%s">%s</a>',
-            URLHelper::getLink('index.php'),
-            _('Hier geht es zur Startseite.')
-        )
-    ]);
-
-    echo $GLOBALS['template_factory']->render('layouts/base.php', [
-        'content_for_layout' => '',
-    ]);
-} elseif ($GLOBALS['auth']->auth['uid'] === 'nobody') {
-    $GLOBALS['auth']->logout();
-    header('Location: ' . URLHelper::getURL('register2.php'));
-} else {
-    include 'lib/seminar_open.php'; // initialise Stud.IP-Session
-
-    PageLayout::setHelpKeyword('Basis.AnmeldungRegistrierung');
-    PageLayout::setTitle(_('Registrierung erfolgreich'));
-    echo $GLOBALS['template_factory']->render(
-        'register/success.php',
-        [],
-        $GLOBALS['template_factory']->open('layouts/base.php')
-    );
-    $GLOBALS['auth']->logout();
-}
-
-page_close();
diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js
index b0e66d31c10e448eedf54a059dd699e9bb3cd3ce..2cbac9ea08dc59ceecad899b923499c8697b9eab 100644
--- a/resources/assets/javascripts/bootstrap/forms.js
+++ b/resources/assets/javascripts/bootstrap/forms.js
@@ -250,12 +250,15 @@ STUDIP.ready(function () {
                     data() {
                         let params = JSON.parse(f.dataset.inputs);
                         params.STUDIPFORM_REQUIRED = f.dataset.required ? JSON.parse(f.dataset.required) : [];
+                        params.STUDIPFORM_SERVERVALIDATION = f.dataset.server_validation > 0;
                         params.STUDIPFORM_DISPLAYVALIDATION = false;
                         params.STUDIPFORM_VALIDATIONNOTES = [];
                         params.STUDIPFORM_AUTOSAVEURL = f.dataset.autosave;
                         params.STUDIPFORM_REDIRECTURL = f.dataset.url;
-                        params.STUDIPFORM_SELECTEDLANGUAGES = {};
-                        params.STUDIPFORM_DEBUGMODE = JSON.parse(f.dataset.debugmode);
+                        params.STUDIPFORM_INPUTS_ORDER = [];
+                        for (let i in JSON.parse(f.dataset.inputs)) {
+                            params.STUDIPFORM_INPUTS_ORDER.push(i);
+                        }
                         return params;
                     },
                     methods: {
@@ -265,31 +268,31 @@ STUDIP.ready(function () {
                             this.STUDIPFORM_DISPLAYVALIDATION = true;
 
                             //validation:
-                            let validated = this.validate();
-
-                            if (!validated) {
-                                e.preventDefault();
-                                v.$el.scrollIntoView({
-                                    "behavior": "smooth"
-                                });
-                                return;
-                            }
-
-                            if (this.STUDIPFORM_AUTOSAVEURL) {
-                                let params = this.getFormValues();
+                            let validation_promise = this.validate();
+                            validation_promise.then(function (validated) {
+                                if (!validated) {
+                                    v.$el.scrollIntoView({
+                                        behavior: 'smooth'
+                                    });
+                                    return;
+                                }
 
-                                $.ajax({
-                                    url: this.STUDIPFORM_AUTOSAVEURL,
-                                    data: params,
-                                    type: 'post',
-                                    success() {
-                                        if (v.STUDIPFORM_REDIRECTURL && !v.STUDIPFORM_DEBUGMODE) {
-                                            window.location.href = v.STUDIPFORM_REDIRECTURL;
+                                if (v.STUDIPFORM_AUTOSAVEURL) {
+                                    let params = v.getFormValues();
+
+                                    $.ajax({
+                                        url: v.STUDIPFORM_AUTOSAVEURL,
+                                        data: params,
+                                        type: 'post',
+                                        success() {
+                                            if (v.STUDIPFORM_REDIRECTURL) {
+                                                window.location.href = v.STUDIPFORM_REDIRECTURL
+                                            }
                                         }
-                                    }
-                                });
-                                e.preventDefault();
-                            }
+                                    });
+                                }
+                            });
+                            e.preventDefault();
                         },
                         getFormValues() {
                             let v = this;
@@ -311,32 +314,74 @@ STUDIP.ready(function () {
                             let v = this;
                             this.STUDIPFORM_VALIDATIONNOTES = [];
 
-                            let validated = this.$el.checkValidity();
-
-                            $(this.$el).find('input, select, textarea').each(function () {
-                                if (!this.validity.valid) {
-                                    let note = {
-                                        name: $(this.labels[0]).find('.textlabel').text(),
-                                        description: $gettext('Fehler!'),
-                                        describedby: this.id
-                                    };
-                                    if (this.validity.tooShort) {
-                                        note.description = $gettextInterpolate(
-                                            $gettext('Geben Sie mindestens %{min} Zeichen ein.'),
-                                            {min: this.minLength}
-                                        );
-                                    }
-                                    if (this.validity.valueMissing) {
-                                        if (this.type === 'checkbox') {
-                                            note.description = $gettext('Dieses Feld muss ausgewählt sein.');
-                                        } else {
-                                            note.description = $gettext('Hier muss ein Wert eingetragen werden.');
+                            let validation_promise = new Promise(function (resolve, reject) {
+                                let validated = v.$el.checkValidity();
+
+                                $(v.$el).find('input, select, textarea').each(function () {
+                                    let name = $(this).attr('name');
+                                    if (!this.validity.valid) {
+                                        let note = {
+                                            name: this.name,
+                                            label: $(this.labels[0]).find('.textlabel').text(),
+                                            description: $gettext('Fehler!'),
+                                            describedby: this.id
+                                        };
+                                        if ($(this).data('validation_requirement')) {
+                                            note.description = $(this).data('validation_requirement');
+                                        }
+                                        if (this.validity.tooShort) {
+                                            note.description = $gettextInterpolate(
+                                                $gettext('Geben Sie mindestens %{min} Zeichen ein.'),
+                                                {min: this.minLength}
+                                            );
+                                        }
+                                        if (this.validity.valueMissing) {
+                                            if (this.type === 'checkbox') {
+                                                note.description = $gettext('Dieses Feld muss ausgewählt sein.');
+                                            } else {
+                                                if (this.minLength > 0) {
+                                                    note.description = $gettextInterpolate(
+                                                        $gettext('Hier muss ein Wert mit mindestens %{min} Zeichen eingetragen werden.'),
+                                                        {min: this.minLength}
+                                                    );
+                                                } else {
+                                                    note.description = $gettext('Hier muss ein Wert eingetragen werden.');
+                                                }
+
+                                            }
                                         }
+                                        v.STUDIPFORM_VALIDATIONNOTES.push(note);
                                     }
-                                    v.STUDIPFORM_VALIDATIONNOTES.push(note);
+                                });
+                                if (v.STUDIPFORM_SERVERVALIDATION) {
+
+                                    let params = v.getFormValues();
+                                    params.STUDIPFORM_SERVERVALIDATION = 1;
+
+                                    $.ajax({
+                                        url: v.STUDIPFORM_AUTOSAVEURL,
+                                        data: params,
+                                        type: 'post',
+                                        dataType: 'json',
+                                        success(output) {
+                                            for (let i in output) {
+                                                let note = {
+                                                    name: output[i].name,
+                                                    label: output[i].label,
+                                                    description: output[i].error,
+                                                    describedby: null
+                                                };
+                                                v.STUDIPFORM_VALIDATIONNOTES.push(note);
+                                            }
+                                            validated = v.STUDIPFORM_VALIDATIONNOTES.length < 1;
+                                            resolve(validated);
+                                        }
+                                    });
+                                } else {
+                                    resolve(validated);
                                 }
                             });
-                            return validated;
+                            return validation_promise;
                         },
                         setInputs(inputs) {
                             for (const [key, value] of Object.entries(inputs)) {
@@ -353,6 +398,19 @@ STUDIP.ready(function () {
                             this.STUDIPFORM_SELECTEDLANGUAGES = languages;
                         }
                     },
+                    computed: {
+                        ordererValidationNotes: function () {
+                            let orderedNotes = [];
+                            for (let i in this.STUDIPFORM_INPUTS_ORDER) {
+                                for (let k in this.STUDIPFORM_VALIDATIONNOTES) {
+                                    if (this.STUDIPFORM_VALIDATIONNOTES[k].name === this.STUDIPFORM_INPUTS_ORDER[i]) {
+                                        orderedNotes.push(this.STUDIPFORM_VALIDATIONNOTES[k]);
+                                    }
+                                }
+                            }
+                            return orderedNotes;
+                        }
+                    },
                     mounted () {
                         $(this.$el).addClass("vueified");
                     }
@@ -362,7 +420,7 @@ STUDIP.ready(function () {
     }
 
     // Well, this is really nasty: Select2 can't determine the select
-    // element's width if it is hidden (by itself or by it's parent).
+    // element's width if it is hidden (by itself or by its parent).
     // This is due to the fact that elements are not rendered when hidden
     // (which seems pretty obvious when you think about it) but elements
     // only have a width when they are rendered (pretty obvious as well).
diff --git a/resources/assets/stylesheets/scss/forms.scss b/resources/assets/stylesheets/scss/forms.scss
index 72e2d26acdc6e86e99026cb8f80452644cf5f22b..0b43f044afae4f571df093bacc3b504bacdf7ddd 100644
--- a/resources/assets/stylesheets/scss/forms.scss
+++ b/resources/assets/stylesheets/scss/forms.scss
@@ -70,6 +70,14 @@ form.default {
         }
     }
 
+    input[list] {
+        @include background-icon(arr_1down, clickable);
+
+        background-repeat: no-repeat;
+        background-position: center right 4px;
+        padding-right: 24px
+    }
+
     textarea:not(.size-l) + .ck-editor {
         max-width: $max-width-m;
     }
diff --git a/templates/forms/confirm_password_input.php b/templates/forms/confirm_password_input.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2e2583fb9f4f75749e5b33fca4fc2303b694a76
--- /dev/null
+++ b/templates/forms/confirm_password_input.php
@@ -0,0 +1,16 @@
+<div class="formpart">
+    <label <?= $this->required ? 'class="studiprequired"' : '' ?> for="<?= $id ?>">
+        <span class="textlabel">
+            <?= htmlReady($this->title) ?>
+        </span>
+        <? if ($this->required) : ?>
+            <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
+        <? endif ?>
+        <input type="password"
+               v-model="<?= htmlReady($this->name) ?>"
+               name="<?= htmlReady($this->name) ?>"
+               value="<?= htmlReady($this->value) ?>"
+               id="<?= $id ?>" <?= $this->required ? 'required aria-required="true"' : '' ?>
+            <?= $attributes ?>>
+    </label>
+</div>
diff --git a/templates/forms/datalist_input.php b/templates/forms/datalist_input.php
new file mode 100644
index 0000000000000000000000000000000000000000..7958c332875ee1d8cb667ea00135c4790d14f7e9
--- /dev/null
+++ b/templates/forms/datalist_input.php
@@ -0,0 +1,22 @@
+<div class="formpart">
+    <label <?= $this->required ? 'class="studiprequired"' : '' ?> for="<?= $id ?>">
+        <span class="textlabel">
+            <?= htmlReady($this->title) ?>
+        </span>
+        <? if ($this->required) : ?>
+            <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
+        <? endif ?>
+
+        <input type="text" list="<?= $this->title ?>" id="" v-model="<?= htmlReady($this->name) ?>" <?= $this->required ? 'required aria-required="true"' : '' ?> />
+
+        <datalist class="" id="<?= $this->title ?>" <?= $attributes ?>>
+            <? foreach ($options as $key => $option) : ?>
+                <option value="<?= htmlReady($option) ?>"<?= ($option == $value ? " selected" : "") ?>>
+                </option>
+            <? endforeach ?>
+        </datalist>
+    </label>
+
+</div>
+
+
diff --git a/templates/forms/form.php b/templates/forms/form.php
index 05b0b302fe8e38231c7fc12cc90dd3ca8266ae1c..96cd2c055d9cb9ac20c2e0fb6589224e97c60291 100644
--- a/templates/forms/form.php
+++ b/templates/forms/form.php
@@ -2,14 +2,17 @@
 $inputs = [];
 $allinputs = $form->getAllInputs();
 $required_inputs = [];
+$server_validation = false;
 foreach ($allinputs as $input) {
     foreach ($input->getAllInputNames() as $name) {
         $inputs[$name] = $input->getValue();
     }
-
     if ($input->required) {
         $required_inputs[] = $input->getName();
     }
+    if ($input->hasValidation()) {
+        $server_validation = true;
+    }
 }
 $form_id = md5(uniqid());
 ?><form v-cloak
@@ -21,12 +24,14 @@ $form_id = md5(uniqid());
           data-url="<?= htmlReady($form->getURL()) ?>"
       <? endif ?>
       @submit="submit"
+      @cancel=""
       novalidate
       <?= $form->getDataSecure() ? 'data-secure' : '' ?>
       id="<?= htmlReady($form_id) ?>"
       data-inputs="<?= htmlReady(json_encode($inputs)) ?>"
       data-debugmode="<?= htmlReady(json_encode($form->getDebugMode())) ?>"
       data-required="<?= htmlReady(json_encode($required_inputs)) ?>"
+      data-server_validation="<?= $server_validation ? 1 : 0?>"
       class="default studipform<?= $form->isCollapsable() ? ' collapsable' : '' ?>">
 
     <?= CSRFProtection::tokenTag(['ref' => 'securityToken']) ?>
@@ -52,7 +57,7 @@ $form_id = md5(uniqid());
         <div v-if="STUDIPFORM_DISPLAYVALIDATION && (STUDIPFORM_VALIDATIONNOTES.length > 0)">
             <?= _('Folgende Angaben müssen korrigiert werden, um das Formular abschicken zu können:') ?>
             <ul>
-                <li v-for="note in STUDIPFORM_VALIDATIONNOTES" :aria-describedby="note.describedby">{{ note.name + ": " + note.description }}</li>
+                <li v-for="note in ordererValidationNotes" :aria-describedby="note.describedby">{{ note.label.trim() + ": " + note.description }}</li>
             </ul>
         </div>
     </article>
@@ -64,7 +69,8 @@ $form_id = md5(uniqid());
     </div>
     <? if (!Request::isDialog()) : ?>
         <footer>
-            <?= \Studip\Button::create($form->getSaveButtonText(), $form->getSaveButtonName(), ['form' => $form_id]) ?>
+            <?= \Studip\Button::createAccept($form->getSaveButtonText(), $form->getSaveButtonName(), ['form' => $form_id]) ?>
+            <?= \Studip\LinkButton::createCancel($form->getCancelButtonText(), $form->getCancelButtonName()) ?>
         </footer>
     <? endif ?>
 </form>
diff --git a/templates/forms/password_input.php b/templates/forms/password_input.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2e2583fb9f4f75749e5b33fca4fc2303b694a76
--- /dev/null
+++ b/templates/forms/password_input.php
@@ -0,0 +1,16 @@
+<div class="formpart">
+    <label <?= $this->required ? 'class="studiprequired"' : '' ?> for="<?= $id ?>">
+        <span class="textlabel">
+            <?= htmlReady($this->title) ?>
+        </span>
+        <? if ($this->required) : ?>
+            <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
+        <? endif ?>
+        <input type="password"
+               v-model="<?= htmlReady($this->name) ?>"
+               name="<?= htmlReady($this->name) ?>"
+               value="<?= htmlReady($this->value) ?>"
+               id="<?= $id ?>" <?= $this->required ? 'required aria-required="true"' : '' ?>
+            <?= $attributes ?>>
+    </label>
+</div>
diff --git a/templates/forms/radio_input.php b/templates/forms/radio_input.php
new file mode 100644
index 0000000000000000000000000000000000000000..37170c99e4960a7781ec0778b3fa140e542229d9
--- /dev/null
+++ b/templates/forms/radio_input.php
@@ -0,0 +1,16 @@
+<div class="formpart">
+    <section <?= $this->orientation == 'horizontal' ? 'class="hgroup"' : '' ?> for="<?= $id ?>">
+    <span class="textlabel">
+        <?= htmlReady($this->title) ?>
+    </span>
+
+    <? foreach ($options as $key => $option) : ?>
+        <label class="" <?= $attributes ?>>
+                <input type="radio"
+                       v-model="<?= htmlReady($this->name) ?>"
+                       value="<?= htmlReady($key) ?>" <?= $key == $value ? 'checked' : '' ?>>
+                    <?= htmlReady($option) ?>
+        </label>
+    <? endforeach ?>
+</section>
+</div>
diff --git a/templates/forms/range_input.php b/templates/forms/range_input.php
index c85b02d3a398d3bd3f76e2a25a5f23d0a64eb1f5..07bd934926ec9f97ef5765b888da89e970397a3f 100644
--- a/templates/forms/range_input.php
+++ b/templates/forms/range_input.php
@@ -10,4 +10,7 @@
              name="<?= htmlReady($name) ?>"
              value="<?= htmlReady($value) ?>"
              id="<?= $id ?>"
-             <?= $attributes ?>></range-input>
+             min="<?= $min ?>"
+             max="<?= $max ?>"
+             step="<?= $step ?>"
+    <?= $attributes ?>></range-input>
diff --git a/templates/forms/text_input.php b/templates/forms/text_input.php
index ae93758d14bff316d87ccf95510fbd147af43008..3104ee843269aaea1a0b81c59c68a1d8cd3cc779 100644
--- a/templates/forms/text_input.php
+++ b/templates/forms/text_input.php
@@ -6,11 +6,12 @@
         <? if ($this->required) : ?>
             <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
         <? endif ?>
+        <input type="text"
+               v-model="<?= htmlReady($this->name) ?>"
+               name="<?= htmlReady($this->name) ?>"
+               value="<?= htmlReady($this->value) ?>"
+               id="<?= $id ?>" <?= ($this->required ? 'required aria-required="true"' : '') ?>
+            <?= $attributes ?>>
     </label>
-    <input type="text"
-           v-model="<?= htmlReady($this->name) ?>"
-           name="<?= htmlReady($this->name) ?>"
-           value="<?= htmlReady($this->value) ?>"
-           id="<?= $id ?>" <?= ($this->required ? 'required aria-required="true"' : '') ?>
-           <?= $attributes ?>>
+
 </div>
diff --git a/templates/register/form.php b/templates/register/form.php
deleted file mode 100644
index 137cd05e69cd7b80b5c4a4fd1c9d32e965b436b1..0000000000000000000000000000000000000000
--- a/templates/register/form.php
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-use Studip\Button, Studip\LinkButton;
-
-$email_restriction = Config::get()->EMAIL_DOMAIN_RESTRICTION;
-?>
-<script type="text/javascript" language="javaScript">
-jQuery(document).ready(function() {
-    STUDIP.register.re_username = <?= $validator->username_regular_expression ?>;
-    STUDIP.register.re_name = <?= $validator->name_regular_expression ?>;
-
-    $('form[name=login]').submit(function () {
-        return STUDIP.register.checkdata();
-    });
-});
-</script>
-
-<? if (isset($username)): ?>
-    <?= MessageBox::error(_("Bei der Registrierung ist ein Fehler aufgetreten!"), [$error_msg, _("Bitte korrigieren Sie Ihre Eingaben und versuchen Sie es erneut")]) ?>
-<? endif; ?>
-
-<h1><?= _('Stud.IP - Registrierung') ?></h1>
-
-<form name="login" action="<?= URLHelper::getLink() ?>" method="post" class="default">
-    <?= CSRFProtection::tokenTag() ?>
-    <input type="hidden" name="login_ticket" value="<?= Seminar_Session::get_ticket() ?>">
-
-    <fieldset>
-        <legend><?= _('Herzlich willkommen!') ?></legend>
-
-        <p><?= _('Bitte füllen Sie zur Anmeldung das Formular aus:') ?></p>
-
-        <label for="username">
-            <em class="required"><?= _('Benutzername') ?></em>
-            <input type="text" name="username" id="username"
-                   onchange="STUDIP.register.checkusername()"
-                   value="<?= htmlReady($username) ?>"
-                   autofocus
-                   required maxlength="63"
-                   autocapitalize="off" autocorrect="off">
-        </label>
-
-        <label for="password">
-            <em class="required"><?= _('Passwort') ?></em>
-            <input type="password" name="password" id="password"
-                   onchange="STUDIP.register.checkpassword()"
-                   required maxlength="31">
-        </label>
-
-        <label for="password2">
-            <em class="required"><?= _('Passwortbestätigung') ?></em>
-            <input type="password" name="password2" id="password2"
-                   onchange="STUDIP.register.checkpassword2()"
-                   required maxlength="31">
-        </label>
-
-        <label for="title_front">
-            <?= _('Titel') ?>
-        </label>
-        <section class="hgroup size-m">
-            <select name="title_chooser_front" data-copy-to="#title_front" class="size-s">
-            <? foreach ($GLOBALS['TITLE_FRONT_TEMPLATE'] as $template): ?>
-                <option <? if ($template === $title_front) echo 'selected'; ?>>
-                    <?= htmlReady($template) ?>
-                </option>
-            <? endforeach; ?>
-            </select>
-
-            <input type="text" name="title_front" id="title_front"
-                   value="<?= htmlReady($title_front) ?>"
-                   maxlength="63" class="no-hint">
-        </section>
-
-        <label for="title_rear">
-            <?= _('Titel nachgestellt') ?>
-        </label>
-        <section class="hgroup size-m">
-            <select name="title_chooser_rear" data-copy-to="#title_rear" class="size-s">
-            <? foreach ($GLOBALS['TITLE_REAR_TEMPLATE'] as $template): ?>
-                <option <? if ($template === $title_rear) echo 'selected'; ?>>
-                    <?= htmlReady($template) ?>
-                </option>
-            <? endforeach; ?>
-            </select>
-
-            <input type="text" name="title_rear" id="title_rear"
-                   value="<?= htmlReady($title_rear) ?>"
-                   maxlength="63" class="no-hint">
-        </section>
-
-        <label for="first_name">
-            <em class="required"><?= _('Vorname') ?></em>
-
-            <input type="text" name="Vorname" id="first_name"
-                   onchange="STUDIP.register.checkVorname()"
-                   value="<?= htmlReady($Vorname) ?>"
-                   required maxlength="63">
-        </label>
-
-        <label for="last_name">
-            <em class="required"><?= _('Nachname') ?></em>
-
-            <input type="text" name="Nachname" id="last_name"
-                   onchange="STUDIP.register.checkNachname()"
-                   value="<?= htmlReady($Nachname) ?>"
-                   required maxlength="63">
-        </label>
-
-        <div>
-            <?= _('Geschlecht') ?>
-        </div>
-
-        <section class="hgroup" id="gender">
-            <label>
-                <input type="radio" name="geschlecht" value="0"
-                       <? if (!$geschlecht) echo 'checked' ?>>
-                <?= _('unbekannt') ?>
-            </label>
-
-            <label>
-                <input type="radio" name="geschlecht" value="1"
-                       <? if ($geschlecht == 1) echo "checked" ?>>
-                <?= _('männlich') ?>
-            </label>
-
-            <label>
-                <input type="radio" name="geschlecht" value="2"
-                       <? if ($geschlecht == 2) echo "checked" ?>>
-                <?= _('weiblich') ?>
-            </label>
-
-            <label>
-                <input type="radio" name="geschlecht" value="3"
-                       <? if ($geschlecht == 3) echo "checked" ?>>
-                <?= _('divers') ?>
-            </label>
-        </section>
-
-
-        <label for="email">
-            <em class="required"><?= _('E-Mail') ?></em>
-        <? if (!trim($email_restriction)): ?>
-            <input type="email" name="Email" id="email"
-                   onchange="STUDIP.register.checkEmail()"
-                   value="<?= htmlReady(trim($Email)) ?>"
-                   required maxlength="63">
-        <? endif; ?>
-        </label>
-
-    <? if (trim($email_restriction)): ?>
-        <section class="hgroup size-m">
-            <input type="text" name="Email" id="email"
-                   onchange="STUDIP.register.checkEmail()"
-                   value="<?= htmlReady(preg_replace('/@.*$/', '', trim($Email ?: ''))) ?>"
-                   required maxlength="63"
-                   class="no-hint">
-            <select name="emaildomain">
-            <? foreach (explode(',', $email_restriction) as $domain): ?>
-                <option value="<?= trim($domain) ?>"
-                        <? if (trim($domain) == Request::get('emaildomain')) echo 'selected'; ?>>
-                    @<?= trim($domain) ?>
-                </option>
-            <? endforeach; ?>
-            </select>
-        </section>
-    <? endif; ?>
-    </fieldset>
-
-    <footer>
-        <?= Button::createAccept(_('Registrieren'))?>
-        <?= LinkButton::createCancel(
-            _('Registrierung abbrechen'),
-            URLHelper::getURL('index.php?cancel_login=1')
-        ) ?>
-    </footer>
-</form>
diff --git a/templates/register/step1.php b/templates/register/step1.php
deleted file mode 100644
index b67116e113bdce4933a7a4f9513e8c633d7be557..0000000000000000000000000000000000000000
--- a/templates/register/step1.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?= $GLOBALS['template_factory']->render('terms.php') ?>
-
-<footer style="text-align: center">
-    <?= Studip\LinkButton::createAccept(_('Ich erkenne die Nutzungsbedingungen an'), URLHelper::getLink('register2.php')) ?>
-    <?= Studip\LinkButton::createCancel(_('Registrierung abbrechen'), URLHelper::getLink('index.php')) ?>
-</footer>