diff --git a/lib/screens/account/me/settings.dart b/lib/screens/account/me/settings.dart
index 9e1e870..1a605ea 100644
--- a/lib/screens/account/me/settings.dart
+++ b/lib/screens/account/me/settings.dart
@@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
 import 'package:gap/gap.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:island/models/auth.dart';
@@ -339,7 +340,7 @@ class _AuthFactorSheet extends HookConsumerWidget {
     }
 
     Future<void> enableFactor() async {
-      final passwordController = TextEditingController();
+      String? password;
       final confirmed = await showDialog<bool>(
         context: context,
         builder:
@@ -350,13 +351,15 @@ class _AuthFactorSheet extends HookConsumerWidget {
                 children: [
                   Text('authFactorEnableHint').tr(),
                   const SizedBox(height: 16),
-                  TextField(
-                    controller: passwordController,
-                    obscureText: true,
-                    decoration: InputDecoration(
-                      labelText: 'password'.tr(),
-                      border: const OutlineInputBorder(),
-                    ),
+                  OtpTextField(
+                    numberOfFields: 6,
+                    obscureText: false,
+                    showFieldAsBox: true,
+                    focusedBorderColor: Theme.of(context).colorScheme.primary,
+                    onSubmit: (String verificationCode) {
+                      password = verificationCode;
+                    },
+                    textStyle: Theme.of(context).textTheme.titleLarge!,
                   ),
                 ],
               ),
@@ -372,11 +375,10 @@ class _AuthFactorSheet extends HookConsumerWidget {
               ],
             ),
       );
-      final password = passwordController.text;
-      if (confirmed == false || password.isEmpty || !context.mounted) {
-        WidgetsBinding.instance.addPostFrameCallback((_) {
-          passwordController.dispose();
-        });
+      if (confirmed == false ||
+          (password?.isEmpty ?? true) ||
+          !context.mounted) {
+        return;
       }
       try {
         final client = ref.read(apiClientProvider);
@@ -385,9 +387,6 @@ class _AuthFactorSheet extends HookConsumerWidget {
           data: jsonEncode(password),
         );
         if (context.mounted) Navigator.pop(context, true);
-        WidgetsBinding.instance.addPostFrameCallback((_) {
-          passwordController.dispose();
-        });
       } catch (err) {
         showErrorAlert(err);
       }
@@ -398,12 +397,37 @@ class _AuthFactorSheet extends HookConsumerWidget {
       child: Column(
         crossAxisAlignment: CrossAxisAlignment.stretch,
         children: [
-          ListTile(
-            title: Text(kFactorTypes[factor.type]!.$1).tr(),
-            subtitle: Text(kFactorTypes[factor.type]!.$2).tr(),
-            leading: Icon(kFactorTypes[factor.type]!.$3),
-            contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 8),
-          ),
+          Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Icon(kFactorTypes[factor.type]!.$3, size: 32),
+              const Gap(8),
+              Text(kFactorTypes[factor.type]!.$1).tr(),
+              const Gap(4),
+              Text(
+                kFactorTypes[factor.type]!.$2,
+                style: Theme.of(context).textTheme.bodySmall,
+              ).tr(),
+              const Gap(10),
+              Row(
+                children: [
+                  if (factor.enabledAt == null)
+                    Badge(
+                      label: Text('Disabled'),
+                      textColor: Theme.of(context).colorScheme.onSecondary,
+                      backgroundColor: Theme.of(context).colorScheme.secondary,
+                    )
+                  else
+                    Badge(
+                      label: Text('Enabled'),
+                      textColor: Theme.of(context).colorScheme.onPrimary,
+                      backgroundColor: Theme.of(context).colorScheme.primary,
+                    ),
+                ],
+              ),
+            ],
+          ).padding(all: 20),
           const Divider(height: 1),
           if (factor.enabledAt != null)
             ListTile(
diff --git a/pubspec.lock b/pubspec.lock
index 8054416..60fa47b 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -827,6 +827,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.4.6"
+  flutter_otp_text_field:
+    dependency: "direct main"
+    description:
+      name: flutter_otp_text_field
+      sha256: e7e589dc51cde120d63da6db55f3cef618f5d013d12adba76137ca1a51ce1390
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.5.1+1"
   flutter_platform_alert:
     dependency: "direct main"
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 042f37d..25bf4d3 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -107,6 +107,7 @@ dependencies:
   flutter_colorpicker: ^1.1.0
   record: ^6.0.0
   qr_flutter: ^4.1.0
+  flutter_otp_text_field: ^1.5.1+1
 
 dev_dependencies:
   flutter_test: