diff --git a/gajim/data/gui/change_password_dialog.ui b/gajim/data/gui/change_password_dialog.ui
deleted file mode 100644
index 292334a4fb9e931cb0e17c535a8ec0ef2f450b2b..0000000000000000000000000000000000000000
--- a/gajim/data/gui/change_password_dialog.ui
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1 -->
-<interface>
-  <requires lib="gtk+" version="3.12"/>
-  <object class="GtkBox" id="change_password_box">
-    <property name="width_request">220</property>
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="margin_bottom">12</property>
-    <property name="orientation">vertical</property>
-    <property name="spacing">6</property>
-    <child>
-      <object class="GtkEntry" id="password1_entry">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="visibility">False</property>
-        <property name="placeholder_text" translatable="yes">New Password</property>
-      </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">0</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkEntry" id="password2_entry">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="visibility">False</property>
-        <property name="activates_default">True</property>
-        <property name="placeholder_text" translatable="yes">Confirm New Password</property>
-      </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">1</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkLabel" id="error_label">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="wrap">True</property>
-        <property name="xalign">0</property>
-        <style>
-          <class name="error-color"/>
-        </style>
-      </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">2</property>
-      </packing>
-    </child>
-  </object>
-</interface>
diff --git a/gajim/gtk/change_password.py b/gajim/gtk/change_password.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee0d85d789c9518102fb51f232d36cc85232f106
--- /dev/null
+++ b/gajim/gtk/change_password.py
@@ -0,0 +1,242 @@
+# This file is part of Gajim.
+#
+# Gajim is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published
+# by the Free Software Foundation; version 3 only.
+#
+# Gajim is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+from gi.repository import Gtk
+
+from nbxmpp.util import is_error_result
+
+from gajim.common import app
+from gajim.common.i18n import _
+from gajim.common import passwords
+from gajim.common.helpers import to_user_string
+
+from gajim.gtk.assistant import Assistant
+from gajim.gtk.assistant import Page
+from gajim.gtk.dataform import DataFormWidget
+from gajim.gtk.util import ensure_not_destroyed
+
+log = logging.getLogger('gajim.gtk.change_password')
+
+
+class ChangePassword(Assistant):
+    def __init__(self, account):
+        Assistant.__init__(self)
+
+        self.account = account
+        self._con = app.connections.get(account)
+        self._destroyed = False
+
+        self.add_button('apply', _('Change'), 'suggested-action',
+                        complete=True)
+        self.add_button('close', _('Close'))
+        self.add_button('back', _('Back'))
+
+        self.add_pages({'password': EnterPassword(),
+                        'next_stage': NextStage()})
+
+        progress = self.add_default_page('progress')
+        progress.set_title(_('Changing Password...'))
+        progress.set_text(_('Trying to change password...'))
+
+        success = self.add_default_page('success')
+        success.set_title(_('Password Changed'))
+        success.set_heading(_('Password Changed'))
+        success.set_text(_('Your password has successfully been changed.'))
+
+        error = self.add_default_page('error')
+        error.set_title(_('Password Change Failed'))
+        error.set_heading(_('Password Change Failed'))
+        error.set_text(
+            _('An error occured while trying to change your password.'))
+
+        self.set_button_visible_func(self._visible_func)
+
+        self.connect('button-clicked', self._on_button_clicked)
+        self.connect('page-changed', self._on_page_changed)
+        self.connect('destroy', self._on_destroy)
+
+        self.show_all()
+
+    @staticmethod
+    def _visible_func(_assistant, page_name):
+        if page_name in ('password', 'next_stage'):
+            return ['apply']
+
+        if page_name == 'progress':
+            return None
+
+        if page_name == 'error':
+            return ['back']
+
+        if page_name == 'success':
+            return ['close']
+        raise ValueError('page %s unknown' % page_name)
+
+    def _on_button_clicked(self, _assistant, button_name):
+        page = self.get_current_page()
+        if button_name == 'apply':
+            self.show_page('progress', Gtk.StackTransitionType.SLIDE_LEFT)
+            self._on_apply(next_stage=page == 'next_stage')
+
+        elif button_name == 'back':
+            self.show_page('password', Gtk.StackTransitionType.SLIDE_RIGHT)
+
+        elif button_name == 'close':
+            self.destroy()
+
+    def _on_page_changed(self, _assistant, page_name):
+        if page_name in ('password', 'next_stage'):
+            self.set_default_button('apply')
+
+        elif page_name == 'success':
+            password = self.get_page('password').get_password()
+            passwords.save_password(self.account, password)
+            self.set_default_button('close')
+
+        elif page_name == 'error':
+            self.set_default_button('back')
+
+    def _on_apply(self, next_stage=False):
+        register = self._con.connection.get_module('Register')
+        if next_stage:
+            form = self.get_page('next_stage').get_submit_form()
+            register.change_password_with_form(
+                form, callback=self._on_change_password)
+        else:
+            password = self.get_page('password').get_password()
+            register.change_password(password,
+                                     callback=self._on_change_password)
+
+    @ensure_not_destroyed
+    def _on_change_password(self, result):
+        if is_error_result(result):
+            error_text = to_user_string(result)
+            self.get_page('error').set_text(error_text)
+            self.show_page('error', Gtk.StackTransitionType.SLIDE_LEFT)
+        elif result.successful:
+            self.show_page('success')
+        else:
+            self.get_page('next_stage').set_form(result.form)
+            self.show_page('next_stage', Gtk.StackTransitionType.SLIDE_LEFT)
+
+    def _on_destroy(self, *args):
+        self._destroyed = True
+
+
+class EnterPassword(Page):
+    def __init__(self):
+        Page.__init__(self)
+        self.complete = False
+        self.title = _('Change Password')
+
+        heading = Gtk.Label(label=_('Change Password'))
+        heading.get_style_context().add_class('large-header')
+        heading.set_max_width_chars(30)
+        heading.set_line_wrap(True)
+        heading.set_halign(Gtk.Align.CENTER)
+        heading.set_justify(Gtk.Justification.CENTER)
+
+        label = Gtk.Label(label=_('Please enter your new password.'))
+        label.set_max_width_chars(50)
+        label.set_line_wrap(True)
+        label.set_halign(Gtk.Align.CENTER)
+        label.set_justify(Gtk.Justification.CENTER)
+        label.set_margin_bottom(12)
+
+        self._password1_entry = Gtk.Entry()
+        self._password1_entry.set_input_purpose(Gtk.InputPurpose.PASSWORD)
+        self._password1_entry.set_visibility(False)
+        self._password1_entry.set_invisible_char('•')
+        self._password1_entry.set_valign(Gtk.Align.END)
+        self._password1_entry.set_placeholder_text(
+            _('Enter new password...'))
+        self._password1_entry.connect('changed', self._on_changed)
+        self._password2_entry = Gtk.Entry()
+        self._password2_entry.set_input_purpose(Gtk.InputPurpose.PASSWORD)
+        self._password2_entry.set_visibility(False)
+        self._password2_entry.set_invisible_char('•')
+        self._password2_entry.set_activates_default(True)
+        self._password2_entry.set_valign(Gtk.Align.START)
+        self._password2_entry.set_placeholder_text(
+            _('Confirm new password...'))
+        self._password2_entry.connect('changed', self._on_changed)
+
+        self.pack_start(heading, False, True, 0)
+        self.pack_start(label, False, True, 0)
+        self.pack_start(self._password1_entry, True, True, 0)
+        self.pack_start(self._password2_entry, True, True, 0)
+        self._show_icon(False)
+        self.show_all()
+
+    def _show_icon(self, show):
+        if show:
+            self._password2_entry.set_icon_from_icon_name(
+                Gtk.EntryIconPosition.SECONDARY, 'dialog-warning-symbolic')
+            self._password2_entry.set_icon_tooltip_text(
+                Gtk.EntryIconPosition.SECONDARY, _('Passwords do not match'))
+        else:
+            self._password2_entry.set_icon_from_icon_name(
+                Gtk.EntryIconPosition.SECONDARY, None)
+
+    def _on_changed(self, _entry):
+        password1 = self._password1_entry.get_text()
+        if not password1:
+            self._show_icon(True)
+            self._set_complete(False)
+            return
+
+        password2 = self._password2_entry.get_text()
+        if password1 != password2:
+            self._show_icon(True)
+            self._set_complete(False)
+            return
+        self._show_icon(False)
+        self._set_complete(True)
+
+    def _set_complete(self, state):
+        self.complete = state
+        self.get_toplevel().update_page_complete()
+
+    def get_password(self):
+        return self._password1_entry.get_text()
+
+
+class NextStage(Page):
+    def __init__(self):
+        Page.__init__(self)
+        self.set_valign(Gtk.Align.FILL)
+        self.complete = False
+        self.title = _('Change Password')
+        self._current_form = None
+
+        self.show_all()
+
+    def _on_is_valid(self, _widget, is_valid):
+        self.complete = is_valid
+        self.get_toplevel().update_page_complete()
+
+    def set_form(self, form):
+        if self._current_form is not None:
+            self.remove(self._current_form)
+            self._current_form.destroy()
+        self._current_form = DataFormWidget(form)
+        self._current_form.connect('is-valid', self._on_is_valid)
+        self._current_form.validate()
+        self.pack_start(self._current_form, True, True, 0)
+        self._current_form.show_all()
+
+    def get_submit_form(self):
+        return self._current_form.get_submit_form()
diff --git a/gajim/gtk/const.py b/gajim/gtk/const.py
index 3dd001eb12a1dea839b9bc0e1cdfab2de7d2987a..6eff760e93b2997cfb0d7cc291a363803e535b57 100644
--- a/gajim/gtk/const.py
+++ b/gajim/gtk/const.py
@@ -130,4 +130,5 @@ def __str__(self):
     'CertificateDialog': 'gajim.gtk.dialogs',
     'SubscriptionRequest': 'gajim.gtk.subscription_request',
     'RemoveAccount': 'gajim.gtk.remove_account',
+    'ChangePassword': 'gajim.gtk.change_password',
 }
diff --git a/gajim/gtk/dialogs.py b/gajim/gtk/dialogs.py
index af277ae5a985f3c94ef6a0ccb320fa9072c6b43c..10e9d0f854862e2fdf6bafe52050cf067b359952 100644
--- a/gajim/gtk/dialogs.py
+++ b/gajim/gtk/dialogs.py
@@ -258,59 +258,6 @@ def _on_copy_cert_info_button_clicked(self, widget):
         self._clipboard.set_text(clipboard_text, -1)
 
 
-class ChangePasswordDialog(Gtk.Dialog):
-    def __init__(self, account, success_cb, transient_for):
-        super().__init__(title=_('Change Password'),
-                         transient_for=transient_for,
-                         destroy_with_parent=True)
-
-        self._account = account
-        self._success_cb = success_cb
-
-        self._builder = get_builder('change_password_dialog.ui')
-        self.get_content_area().add(
-            self._builder.get_object('change_password_box'))
-        self._password1_entry = self._builder.get_object('password1_entry')
-        self._password2_entry = self._builder.get_object('password2_entry')
-        self._error_label = self._builder.get_object('error_label')
-
-        self.add_button(_('_OK'), Gtk.ResponseType.OK)
-        self.set_default_response(Gtk.ResponseType.OK)
-        self.get_style_context().add_class('dialog-margin')
-        self.connect('response', self._on_dialog_response)
-        self.show_all()
-
-    def _on_dialog_response(self, dialog, response):
-        if response != Gtk.ResponseType.OK:
-            self.destroy()
-            return
-
-        password1 = self._password1_entry.get_text()
-        if not password1:
-            self._error_label.set_text(_('You must enter a password'))
-            return
-        password2 = self._password2_entry.get_text()
-        if password1 != password2:
-            self._error_label.set_text(_('Passwords do not match'))
-            return
-
-        self._password1_entry.set_sensitive(False)
-        self._password2_entry.set_sensitive(False)
-
-        con = app.connections[self._account]
-        con.get_module('Register').change_password(
-            password1, self._on_success, self._on_error)
-
-    def _on_success(self):
-        self._success_cb(self._password1_entry.get_text())
-        self.destroy()
-
-    def _on_error(self, error_text):
-        self._error_label.set_text(error_text)
-        self._password1_entry.set_sensitive(True)
-        self._password2_entry.set_sensitive(True)
-
-
 class InvitationReceivedDialog(Gtk.ApplicationWindow):
     def __init__(self, account, event):
         Gtk.ApplicationWindow.__init__(self)
diff --git a/gajim/gtk/settings.py b/gajim/gtk/settings.py
index d34a7710739d2adc2f693b14d5f71e5730793be5..628981248b6af4517f32ce282a7091e600c588ec 100644
--- a/gajim/gtk/settings.py
+++ b/gajim/gtk/settings.py
@@ -28,9 +28,9 @@
 
 from gajim import gtkgui_helpers
 
-from gajim.gtk.dialogs import ChangePasswordDialog
 from gajim.gtk.util import get_image_button
 from gajim.gtk.util import MaxWidthComboBoxText
+from gajim.gtk.util import open_window
 from gajim.gtk.const import SettingKind
 from gajim.gtk.const import SettingType
 
@@ -635,18 +635,10 @@ def get_setting_value(self):
 class ChangePasswordSetting(DialogSetting):
     def __init__(self, *args, **kwargs):
         DialogSetting.__init__(self, *args, **kwargs)
-        self.change_dialog = None
 
     def show_dialog(self, parent):
-        try:
-            self.change_dialog = ChangePasswordDialog(
-                self.account, self.on_changed, parent)
-        except GajimGeneralException:
-            return
-        self.change_dialog.set_modal(True)
-
-    def on_changed(self, new_password):
-        self.set_value(new_password)
+        parent.destroy()
+        open_window('ChangePassword', account=self.account)
 
     def update_activatable(self, name, value):
         activatable = False
diff --git a/test/gtk/change_password.py b/test/gtk/change_password.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cabf9ae20a763f17f090f8c83209f106a0cdad0
--- /dev/null
+++ b/test/gtk/change_password.py
@@ -0,0 +1,37 @@
+from functools import partial
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+from nbxmpp.modules.dataforms import create_field
+from nbxmpp.modules.dataforms import SimpleDataForm
+
+from gajim.common.const import CSSPriority
+
+from gajim.gtk.change_password import ChangePassword
+
+from test.gtk import util
+util.load_style('gajim.css', CSSPriority.APPLICATION)
+
+fields = [
+    create_field(typ='text-single', label='Username', var='username'),
+    create_field(typ='text-single', label='Old Password', var='old_password'),
+    create_field(typ='text-single', label='Mothers name', var='mother', required=True),
+]
+
+form = SimpleDataForm(type_='form', fields=fields)
+
+def _apply(self, next_stage=False):
+    if next_stage:
+        print(self.get_page('next_stage').get_submit_form())
+    else:
+        self.get_page('next_stage').set_form(form)
+        self.show_page('next_stage', Gtk.StackTransitionType.SLIDE_LEFT)
+
+win = ChangePassword(None)
+win._on_apply = partial(_apply, win)
+
+win.connect('destroy', Gtk.main_quit)
+win.show_all()
+Gtk.main()