Two-factor setup dialog
A two-step authenticator enrollment: scan or copy the key, then type the six-digit code.
<script setup lang="ts">
import TwoFactorSetupDialog from '@shared/components/common/TwoFactorSetupDialog.vue'
import { Button } from '@shared/components/ui/button'
import { ref } from 'vue'
const open = ref(false)
const enabled = ref(false)
</script>
<template>
<div class="space-y-3">
<Button :variant="enabled ? 'outline' : 'default'" @click="open = true">
{{ enabled ? 'Set it up again' : 'Turn on two-factor authentication' }}
</Button>
<TwoFactorSetupDialog v-model:open="open" @confirmed="enabled = true" />
<p class="text-sm text-muted-foreground">
Two-factor authentication is {{ enabled ? 'on' : 'off' }}
</p>
</div>
</template>Usage
One prop and one event. v-model:open is the dialog, and confirmed fires once the six digits are in, so the caller flips its own flag.
It walks both halves of a real enrollment: the QR and the setup key on step one, the code on step two. The code boxes are the OTP input, and finishing them confirms without a second press.
Nothing is verified
The dialog says so on both steps, and it is worth saying here too: any six digits confirm. No request is made and nothing is enrolled.
The QR is drawn as SVG rectangles from a fixed 21 by 21 pattern. It is deliberately not a real code encoding a real secret: a scannable code that enrolls nothing is worse than an obvious placeholder. It also ships no image file, fires no request, and takes the theme's colors like everything else.
To make it real, replace the pattern with your provider's otpauth:// URI rendered as a QR, and verify the code server-side before treating confirmed as success.
The setup key is printed grouped in fours, the way every authenticator app shows one, with a copy button beside it.
API
Source: src/shared/components/common/TwoFactorSetupDialog.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | required | You own it. Opening resets to step one and clears the code. |
Emits
| Event | Payload | Description |
|---|---|---|
update:open | value: boolean | From Cancel, Escape and the overlay. The dialog also closes itself on confirm. |
confirmed | — | Once six digits are in, by typing the last one or by pressing Confirm. |
Slots
None.
Accessibility
- The QR is
role="img"named "Demo QR code placeholder", so it is not announced as a real code. - The setup key is
dir="ltr"in every language. A base32 secret is not language text and must not reorder in a right-to-left layout. - The copy button's name changes to "Setup key copied" when it has been pressed, so the result is announced rather than only shown as a tick.
- Confirm stays
disableduntil all six digits are typed. - The footer's first button is Cancel on step one and Back on step two, so there is always a way out that is not Escape.