diff --git a/templates/allauth/elements/panel.html b/templates/allauth/elements/panel.html
index f295669..44c3931 100644
--- a/templates/allauth/elements/panel.html
+++ b/templates/allauth/elements/panel.html
@@ -1,8 +1,9 @@
{% load allauth %}
-
+
{% if slots.title %}
{% slot title %}{% endslot %}
{% endif %}
{% slot body %}{% endslot %}
- {% if slots.actions %}
{% slot actions %}{% endslot %}
{% endif %}
+ {% if slots.actions %}
+
{% slot actions %}{% endslot %}
{% endif %}
diff --git a/templates/mfa/index.html b/templates/mfa/index.html
new file mode 100644
index 0000000..704b91b
--- /dev/null
+++ b/templates/mfa/index.html
@@ -0,0 +1,128 @@
+{% extends "mfa/base_manage.html" %}
+{% load allauth i18n %}
+
+{% comment %}
+ allauth's own index, with an icon on every button and the recovery-code actions given
+ the ranking they deserve: View is the primary action, Download and Generate are outline
+ (Generate silently invalidates the codes you already have, so it must not look like the
+ obvious thing to click).
+{% endcomment %}
+
+{% block head_title %}
+ {% trans "Two-Factor Authentication" %}
+{% endblock head_title %}
+
+{% block content %}
+ {% element h1 tags="mfa,index" %}
+ {% trans "Two-Factor Authentication" %}
+ {% endelement %}
+
+ {% if "totp" in MFA_SUPPORTED_TYPES %}
+ {% element panel %}
+ {% slot title %}
+ {% translate "Authenticator App" %}
+ {% endslot %}
+ {% slot body %}
+ {% if authenticators.totp %}
+ {% element p %}
+ {% translate "Authentication using an authenticator app is active." %}
+ {% endelement %}
+ {% else %}
+ {% element p %}
+ {% translate "An authenticator app is not active." %}
+ {% endelement %}
+ {% endif %}
+ {% endslot %}
+ {% slot actions %}
+ {% url 'mfa_deactivate_totp' as deactivate_url %}
+ {% url 'mfa_activate_totp' as activate_url %}
+ {% if authenticators.totp %}
+ {% element button href=deactivate_url icon="power" tags="danger,delete,panel" %}
+ {% translate "Deactivate" %}
+ {% endelement %}
+ {% else %}
+ {% element button href=activate_url icon="smartphone" tags="panel" %}
+ {% translate "Activate" %}
+ {% endelement %}
+ {% endif %}
+ {% endslot %}
+ {% endelement %}
+ {% endif %}
+
+ {% if "webauthn" in MFA_SUPPORTED_TYPES %}
+ {% element panel %}
+ {% slot title %}
+ {% translate "Security Keys" %}
+ {% endslot %}
+ {% slot body %}
+ {% if authenticators.webauthn|length %}
+ {% element p %}
+ {% blocktranslate count count=authenticators.webauthn|length %}You have added {{ count }} security key.{% plural %}You have added {{ count }} security keys.{% endblocktranslate %}
+ {% endelement %}
+ {% else %}
+ {% element p %}
+ {% translate "No security keys have been added." %}
+ {% endelement %}
+ {% endif %}
+ {% endslot %}
+ {% slot actions %}
+ {% if authenticators.webauthn|length %}
+ {% url 'mfa_list_webauthn' as webauthn_list_url %}
+ {% element button href=webauthn_list_url icon="settings" %}
+ {% translate "Manage" %}
+ {% endelement %}
+ {% else %}
+ {% url 'mfa_add_webauthn' as webauthn_add_url %}
+ {% element button href=webauthn_add_url icon="usb" %}
+ {% translate "Add" %}
+ {% endelement %}
+ {% endif %}
+ {% endslot %}
+ {% endelement %}
+ {% endif %}
+
+ {% if "recovery_codes" in MFA_SUPPORTED_TYPES %}
+ {% with total_count=authenticators.recovery_codes.generate_codes|length unused_count=authenticators.recovery_codes.get_unused_codes|length %}
+ {% element panel %}
+ {% slot title %}
+ {% translate "Recovery Codes" %}
+ {% endslot %}
+ {% slot body %}
+ {% if authenticators.recovery_codes %}
+ {% element p %}
+ {% blocktranslate count unused_count=unused_count %}There is {{ unused_count }} out of {{ total_count }} recovery codes available.{% plural %}There are {{ unused_count }} out of {{ total_count }} recovery codes available.{% endblocktranslate %}
+ {% endelement %}
+ {% else %}
+ {% element p %}
+ {% translate "No recovery codes set up." %}
+ {% endelement %}
+ {% endif %}
+ {% endslot %}
+ {% if is_mfa_enabled %}
+ {% if authenticators.recovery_codes %}
+ {% if unused_count > 0 and not MFA_RECOVERY_CODES_SHOW_ONCE %}
+ {% slot actions %}
+ {% url 'mfa_view_recovery_codes' as view_url %}
+ {% element button href=view_url icon="eye" tags="panel" %}
+ {% translate "View" %}
+ {% endelement %}
+ {% endslot %}
+ {% slot actions %}
+ {% url 'mfa_download_recovery_codes' as download_url %}
+ {% element button href=download_url icon="download" tags="outline,panel" %}
+ {% translate "Download" %}
+ {% endelement %}
+ {% endslot %}
+ {% endif %}
+ {% endif %}
+ {% slot actions %}
+ {% url 'mfa_generate_recovery_codes' as generate_url %}
+ {% element button href=generate_url icon="refresh-cw" tags="outline,panel" %}
+ {% translate "Generate" %}
+ {% endelement %}
+ {% endslot %}
+ {% endif %}
+ {% endelement %}
+ {% endwith %}
+ {% endif %}
+{% endblock content %}
diff --git a/templates/mfa/recovery_codes/generate.html b/templates/mfa/recovery_codes/generate.html
new file mode 100644
index 0000000..25c8dfd
--- /dev/null
+++ b/templates/mfa/recovery_codes/generate.html
@@ -0,0 +1,35 @@
+{% extends "mfa/recovery_codes/base.html" %}
+{% load i18n allauth %}
+
+{% block content %}
+ {% element h1 %}
+ {% translate "Recovery Codes" %}
+ {% endelement %}
+ {% element p %}
+ {% blocktranslate %}You are about to generate a new set of recovery codes for your account.{% endblocktranslate %}
+ {% if unused_code_count %}
+ {% blocktranslate %}This action will invalidate your existing codes.{% endblocktranslate %}
+ {% endif %}
+ {% blocktranslate %}Are you sure?{% endblocktranslate %}
+ {% endelement %}
+
+ {% url 'mfa_generate_recovery_codes' as action_url %}
+ {% element form method="post" action=action_url no_visible_fields=True %}
+ {% slot body %}
+ {% csrf_token %}
+ {{ form.as_p }}
+ {% endslot %}
+ {% slot actions %}
+ {# Destructive when codes already exist: generating throws the old ones away. #}
+ {% setvar tags %}
+ {% if unused_code_count %}
+ danger
+ {% else %}
+ {% endif %}
+ {% endsetvar %}
+ {% element button type="submit" icon="refresh-cw" tags=tags %}
+ {% trans "Generate" %}
+ {% endelement %}
+ {% endslot %}
+ {% endelement %}
+{% endblock content %}
diff --git a/templates/mfa/recovery_codes/index.html b/templates/mfa/recovery_codes/index.html
new file mode 100644
index 0000000..f17d8c3
--- /dev/null
+++ b/templates/mfa/recovery_codes/index.html
@@ -0,0 +1,65 @@
+{% extends "mfa/recovery_codes/base.html" %}
+{% load i18n allauth static %}
+
+{% comment %}
+ Overridden for layout: Download and Generate sit side by side under the codes rather
+ than stacking. Generate is outline-only — it silently invalidates the codes on screen,
+ so it must not be the button your eye lands on.
+{% endcomment %}
+
+{% block content %}
+ {% element h1 %}
+ {% translate "Recovery Codes" %}
+ {% endelement %}
+ {% element p %}
+ {% blocktranslate count unused_count=unused_codes|length %}There is {{ unused_count }} out of {{ total_count }} recovery codes available.{% plural %}There are {{ unused_count }} out of {{ total_count }} recovery codes available.{% endblocktranslate %}
+ {% endelement %}
+
+ {% if can_view_codes %}
+ {% element field id="recovery_codes" type="textarea" rows=unused_codes|length readonly=True %}
+ {% slot label %}
+ {% translate "Unused codes" %}
+ {% endslot %}
+ {# djlint:off #}
+{% slot value %}{% for code in unused_codes %}{% if forloop.counter0 %}
+{% endif %}{{ code }}{% endfor %}{% endslot %}
+ {# djlint:on #}
+ {% endelement %}
+ {% endif %}
+
+
+ {% if can_download_codes %}
+ {% url 'mfa_download_recovery_codes' as download_url %}
+ {% element button href=download_url icon="download" %}
+ {% translate "Download codes" %}
+ {% endelement %}
+ {% endif %}
+ {% if can_generate_codes %}
+ {% url 'mfa_generate_recovery_codes' as generate_url %}
+ {% element button href=generate_url icon="refresh-cw" tags="outline" %}
+ {% translate "Generate new codes" %}
+ {% endelement %}
+ {% endif %}
+
+
+ {% if MFA_RECOVERY_CODES_SHOW_ONCE and can_view_codes %}
+ {% element field type="checkbox" id="codes_saved" %}
+ {% slot label %}
+ {% translate "I have saved my recovery codes" %}
+ {% endslot %}
+ {% endelement %}
+ {% endif %}
+{% endblock content %}
+
+{% block extra_body %}
+ {{ block.super }}
+
+
+
+{% endblock extra_body %}
diff --git a/templates/mfa/totp/activate_form.html b/templates/mfa/totp/activate_form.html
new file mode 100644
index 0000000..c81c39a
--- /dev/null
+++ b/templates/mfa/totp/activate_form.html
@@ -0,0 +1,45 @@
+{% extends "mfa/totp/base.html" %}
+{% load allauth i18n %}
+
+{% comment %}
+ The code field is boxed and label-less (the `fields` element does that for any `code`
+ field), and the secret sits in a mono field with room around it — it is copied by hand,
+ character by character.
+{% endcomment %}
+
+{% block head_title %}
+ {% translate "Activate Authenticator App" %}
+{% endblock head_title %}
+
+{% block content %}
+ {% element h1 %}
+ {% translate "Activate Authenticator App" %}
+ {% endelement %}
+ {% element p %}
+ {% blocktranslate %}To protect your account with two-factor authentication, scan the QR code below with your authenticator app. Then, input the verification code generated by the app below.{% endblocktranslate %}
+ {% endelement %}
+
+ {% url 'mfa_activate_totp' as action_url %}
+ {% element form form=form method="post" action=action_url %}
+ {% slot body %}
+ {% element img src=totp_svg_data_uri alt=form.secret tags="mfa,totp,qr" %}
+ {% endelement %}
+ {% csrf_token %}
+ {% element field id="authenticator_secret" type="text" value=form.secret disabled=True %}
+ {% slot label %}
+ {% translate "Authenticator secret" %}
+ {% endslot %}
+ {% slot help_text %}
+ {% translate "You can store this secret and use it to reinstall your authenticator app at a later time." %}
+ {% endslot %}
+ {% endelement %}
+ {% element fields form=form %}
+ {% endelement %}
+ {% endslot %}
+ {% slot actions %}
+ {% element button type="submit" icon="check" %}
+ {% trans "Activate" %}
+ {% endelement %}
+ {% endslot %}
+ {% endelement %}
+{% endblock content %}
diff --git a/templates/mfa/totp/deactivate_form.html b/templates/mfa/totp/deactivate_form.html
new file mode 100644
index 0000000..36736bb
--- /dev/null
+++ b/templates/mfa/totp/deactivate_form.html
@@ -0,0 +1,30 @@
+{% extends "mfa/totp/base.html" %}
+{% load allauth i18n %}
+
+{% block head_title %}
+ {% trans "Deactivate Authenticator App" %}
+{% endblock head_title %}
+
+{% block content %}
+ {% element h1 %}
+ {% trans "Deactivate Authenticator App" %}
+ {% endelement %}
+ {% element p %}
+ {% blocktranslate %}You are about to deactivate authenticator app based authentication. Are you sure?{% endblocktranslate %}
+ {% endelement %}
+
+ {% url 'mfa_deactivate_totp' as action_url %}
+ {% element form form=form method="post" action=action_url no_visible_fields=True %}
+ {% slot body %}
+ {% csrf_token %}
+ {% element fields form=form %}
+ {{ form.as_p }}
+ {% endelement %}
+ {% endslot %}
+ {% slot actions %}
+ {% element button type="submit" icon="power" tags="danger,delete" %}
+ {% trans "Deactivate" %}
+ {% endelement %}
+ {% endslot %}
+ {% endelement %}
+{% endblock content %}
diff --git a/templates/mfa/webauthn/add_form.html b/templates/mfa/webauthn/add_form.html
new file mode 100644
index 0000000..68af5c6
--- /dev/null
+++ b/templates/mfa/webauthn/add_form.html
@@ -0,0 +1,39 @@
+{% extends "mfa/webauthn/base.html" %}
+{% load allauth i18n static %}
+
+{% block head_title %}
+ {% trans "Add Security Key" %}
+{% endblock head_title %}
+
+{% block content %}
+ {% element h1 %}
+ {% trans "Add Security Key" %}
+ {% endelement %}
+
+ {% url 'mfa_add_webauthn' as action_url %}
+ {% element form form=form method="post" action=action_url %}
+ {% slot body %}
+ {% csrf_token %}
+ {% element fields form=form %}
+ {% endelement %}
+ {% endslot %}
+ {% slot actions %}
+ {# type="button": the webauthn script takes over the click and posts the form itself. #}
+ {% element button id="mfa_webauthn_add" type="button" icon="usb" %}
+ {% trans "Add" %}
+ {% endelement %}
+ {% endslot %}
+ {% endelement %}
+
+ {% include "mfa/webauthn/snippets/scripts.html" %}
+ {{ js_data|json_script:"js_data" }}
+
+{% endblock content %}
diff --git a/templates/mfa/webauthn/authenticator_confirm_delete.html b/templates/mfa/webauthn/authenticator_confirm_delete.html
new file mode 100644
index 0000000..82e9945
--- /dev/null
+++ b/templates/mfa/webauthn/authenticator_confirm_delete.html
@@ -0,0 +1,25 @@
+{% extends "mfa/webauthn/base.html" %}
+{% load allauth i18n %}
+
+{% block head_title %}
+ {% trans "Remove Security Key" %}
+{% endblock head_title %}
+
+{% block content %}
+ {% element h1 %}
+ {% trans "Remove Security Key" %}
+ {% endelement %}
+ {% element p %}
+ {% blocktranslate %}Are you sure you want to remove this security key?{% endblocktranslate %}
+ {% endelement %}
+
+ {% url 'mfa_remove_webauthn' pk=authenticator.pk as action_url %}
+ {% element form method="post" action=action_url no_visible_fields=True %}
+ {% slot actions %}
+ {% csrf_token %}
+ {% element button type="submit" icon="trash-2" tags="danger" %}
+ {% translate "Remove" %}
+ {% endelement %}
+ {% endslot %}
+ {% endelement %}
+{% endblock content %}
diff --git a/templates/mfa/webauthn/authenticator_list.html b/templates/mfa/webauthn/authenticator_list.html
new file mode 100644
index 0000000..872afbf
--- /dev/null
+++ b/templates/mfa/webauthn/authenticator_list.html
@@ -0,0 +1,72 @@
+{% extends "mfa/webauthn/base.html" %}
+{% load i18n %}
+{% load static %}
+{% load allauth %}
+{% load humanize %}
+{% block content %}
+ {% element h1 %}
+ {% trans "Security Keys" %}
+ {% endelement %}
+ {% if authenticators|length == 0 %}
+ {% element p %}
+ {% blocktranslate %}No security keys have been added.{% endblocktranslate %}
+ {% endelement %}
+ {% else %}
+ {% element table %}
+ {% element thead %}
+ {% element th %}
+ {% translate "Key" %}
+ {% endelement %}
+ {% element th %}
+ {% translate "Usage" %}
+ {% endelement %}
+ {% element th %}
+ {% endelement %}
+ {% endelement %}
+ {% element tbody %}
+ {% for authenticator in authenticators %}
+ {% element tr %}
+ {% element td %}
+ {{ authenticator }}
+ {% if authenticator.wrap.is_passwordless is True %}
+ {% element badge tags="mfa,key,primary" %}
+ {% translate "Passkey" %}
+ {% endelement %}
+ {% elif authenticator.wrap.is_passwordless is False %}
+ {% element badge tags="mfa,key,secondary" %}
+ {% translate "Security key" %}
+ {% endelement %}
+ {% else %}
+ {% element badge title=_("This key does not indicate whether it is a passkey.") tags="mfa,key,warning" %}
+ {% translate "Unspecified" %}
+ {% endelement %}
+ {% endif %}
+ {% endelement %}
+ {% element td %}
+ {% blocktranslate with created_at=authenticator.created_at|date:"SHORT_DATE_FORMAT" %}Added on {{ created_at }}{% endblocktranslate %}.
+ {% if authenticator.last_used_at %}
+ {% blocktranslate with last_used=authenticator.last_used_at|naturaltime %}Last used {{ last_used }}{% endblocktranslate %}
+ {% else %}
+ Not used.
+ {% endif %}
+ {% endelement %}
+ {% element td align="right" %}
+ {% url 'mfa_edit_webauthn' pk=authenticator.pk as edit_url %}
+ {% element button href=edit_url icon="pencil" tags="mfa,authenticator,edit,tool" %}
+ {% translate "Edit" %}
+ {% endelement %}
+ {% url 'mfa_remove_webauthn' pk=authenticator.pk as remove_url %}
+ {% element button href=remove_url icon="trash-2" tags="mfa,authenticator,danger,delete,tool" %}
+ {% translate "Remove" %}
+ {% endelement %}
+ {% endelement %}
+ {% endelement %}
+ {% endfor %}
+ {% endelement %}
+ {% endelement %}
+ {% endif %}
+ {% url 'mfa_add_webauthn' as add_url %}
+ {% element button href=add_url icon="usb" %}
+ {% translate "Add" %}
+ {% endelement %}
+{% endblock %}