---
title: Onboarding
description: A five-step first run through sign-in, a workspace, preferences, and permissions.
category: TemplateApp
status: beta
since: 0.6.0
tags: [template, onboarding, sign-in, setup, permissions]
syncStatus: reviewed
verified: false
---

## Usage

### First-run setup
:::TuffDemoWrapper{demo="TemplateOnboardingDemo" code-lang="vue" description="Watch the flow plays the first three steps and stops at permissions, which only you grant. Sign-in and permissions are simulated."}
---
code: |
  <script setup lang="ts">
  import { computed, reactive, ref } from 'vue'
  import TemplateFrame from './TemplateFrame.vue'

  const step = ref(0)
  const entered = ref(false)
  const perms = reactive({ files: 'unchecked', accessibility: 'unchecked', screen: 'unchecked' })

  // TxBorderBeam is tuned for dark backgrounds by default, and auto follows the system, not the site: pass the site theme
  const colorMode = useColorMode()
  const beamTheme = computed(() => (colorMode.value === 'dark' ? 'dark' : 'light'))

  // The entrance effects play when they mount, so they mount once the stage is in view
  function onEnter() {
    entered.value = true
  }

  // Only the reader's click grants; the wait stands in for the system dialog
  function grant(key: keyof typeof perms) {
    perms[key] = 'pending'
    setTimeout(() => {
      perms[key] = 'granted'
    }, 1200)
  }
  </script>

  <template>
    <TemplateFrame title="First-run setup" :height="560" @enter="onEnter">
      <template #default="{ width }">
        <div class="onb" :class="{ 'is-narrow': width > 0 && width < 640 }">
          <aside class="onb__rail">
            <TxTuffLogoStroke v-if="entered" :size="56" mode="once" />
            <TxKeyframeStrokeText v-if="entered" text="Welcome to Tuff" :font-size="24" fill-color="var(--tx-text-color-primary)" />
            <TxSteps direction="vertical" size="small" :active="step">
              <!-- TxSteps emits nothing: going back is the host's call, in a native click -->
              <TxStep v-for="(item, i) in steps" :key="item.key" :step="i" :title="item.title" :clickable="i < step" @click="i < step && (step = i)" />
            </TxSteps>
          </aside>

          <main class="onb__main">
            <TxProgressBar :percentage="(step + 1) * 20" height="4px" aria-label="Setup progress" />
            <TxTransition preset="slide-fade" :appear="false">
              <section v-if="step === 0" key="signin">
                <TxBorderBeam size="sm" :theme="beamTheme" :active="!reducedMotion">
                  <TxButton block size="lg" :loading="pending === 'passkey'" @click="signIn('passkey')">
                    Sign in with Passkey
                  </TxButton>
                </TxBorderBeam>
                <TxForm :model="form" :rules="rules" label-position="top" @submit="sendMagicLink">
                  <TxFormItem label="Email" prop="email">
                    <template #default="{ id }">
                      <TxInput :id="id" v-model="form.email" type="email" />
                    </template>
                  </TxFormItem>
                  <TxButton native-type="submit" variant="primary" block>Continue with Email</TxButton>
                </TxForm>
              </section>
              <section v-else-if="step === 3" key="perms">
                <TxStatusBadge :text="statusText(perms.files)" :status="statusTone(perms.files)" size="sm" />
                <TxButton size="sm" :loading="perms.files === 'pending'" @click="grant('files')">Allow access</TxButton>
              </section>
              <!-- Workspace, preferences and done left out -->
            </TxTransition>
            <footer class="onb__footer">
              <TxButton variant="ghost" :disabled="step === 0" @click="step--">Back</TxButton>
              <TxButton variant="primary" :disabled="!canContinue" @click="step++">Continue</TxButton>
            </footer>
          </main>
        </div>
      </template>
    </TemplateFrame>
  </template>

  <style scoped>
  .onb {
    display: grid;
    grid-template-columns: 264px minmax(0, 1fr);
    height: 100%;
  }

  .onb.is-narrow {
    grid-template-columns: minmax(0, 1fr);
  }
  </style>
---
:::

## Overview

Tuff Desktop's first-run guide; every step is simulated, sends no request, and asks for no real permission.

- Sign-in matches Tuff: Passkey, GitHub, LINUX DO, and an email Magic Link, with no password field; Continue offline skips the workspace step.
- Continue works only once the step is satisfied, and the footer says what is missing; Back and finished steps go back, and focus moves to the new step's heading.
- The summon shortcut is recordable: a combination must include ⌘, ⌃, or ⌥, and Esc cancels; combinations the browser keeps, such as ⌘W or ⌘T, are refused.
- Every permission waits for you to press Allow access; screen recording is refused the first time and offers Open System Settings.
- Once in view only the entrance plays; Watch the flow runs to the permissions step and stops; a click or key stops it, and reduced motion jumps straight to permissions.
- On the last step your summon shortcut opens a miniature CoreBox; ≥ 1200px adds a live preview, and narrow (< 640px) folds the brand rail into a top row.

## Customization

- For real sign-in, replace the timer in `signIn()` with an OAuth redirect or a WebAuthn call; don't add a password field, since Tuff has no password sign-in.
- For real permissions, read the state from the system and check again after Allow access raises the system dialog; grants start only from the user's click.
- The entrance effects (TxTuffLogoStroke with `once`, TxKeyframeStrokeText, TxGlowText with `:repeat="false"`) play on mount: mount them once visible, and change `key` to replay.
- TxBorderBeam's rotating family (`sm`, `md`, `line`) has no reduced-motion fallback, so pass `:active="false"` then; its `theme` defaults to `dark` and `auto` follows the system, not the site, so pass the site theme.
- TxSteps emits nothing: to go back, put a native `@click` on TxStep and give `clickable` to finished steps only.

## Related components

| Region | Components | Role |
|---|---|---|
| Brand rail | [TxTuffLogoStroke](/docs/dev/components/tuff-logo-stroke), [TxKeyframeStrokeText](/docs/dev/components/keyframe-stroke-text), [TxGlowText](/docs/dev/components/glow-text) | Entrance stroke and light sweep |
| Steps | [TxSteps / TxStep](/docs/dev/components/steps), [TxProgressBar](/docs/dev/components/progress-bar) | Five-step progress you can revisit |
| Sign in | [TxBorderBeam](/docs/dev/components/border-beam), [TxButton](/docs/dev/components/button), [TxBadge](/docs/dev/components/badge), [TxForm / TxFormItem](/docs/dev/components/form), [TxInput](/docs/dev/components/input) | Four sign-in methods and offline |
| Workspace | [TxRadioGroup / TxRadio](/docs/dev/components/radio), [TxFlatRadio](/docs/dev/components/flat-radio), [TxAvatar](/docs/dev/components/avatar), [TxTagInput](/docs/dev/components/tag-input), [TxCard](/docs/dev/components/card) | New space or team invitation |
| Preferences | [TxKbd](/docs/dev/components/kbd), [TxGradientBorder](/docs/dev/components/gradient-border), [TxFlatRadio](/docs/dev/components/flat-radio), [TxRadioGroup / TxRadio](/docs/dev/components/radio), [TuffSwitch](/docs/dev/components/switch) | Shortcut, theme, and startup options |
| Permissions | [TxIconChip](/docs/dev/components/icon-chip), [TxTag](/docs/dev/components/tag), [TxStatusBadge](/docs/dev/components/status-badge), [TxProgressBar](/docs/dev/components/progress-bar) | Three permissions and their states |
| Done | [TxKeyframeStrokeText](/docs/dev/components/keyframe-stroke-text), [TxGlowText](/docs/dev/components/glow-text), [TxKbd](/docs/dev/components/kbd), [TxBorderBeam](/docs/dev/components/border-beam), [TxGlassSurface](/docs/dev/components/glass-surface), [TxCardItem](/docs/dev/components/card-item) | Shortcut try-out and summary |
| Transitions and feedback | [TxTransition](/docs/dev/components/transition), [TxToastPanel](/docs/dev/components/toast-panel) | Step transitions and result notices |
| Live preview (wide) | [TxGlassSurface](/docs/dev/components/glass-surface), [TxStatusBadge](/docs/dev/components/status-badge) | Live preview of your choices |
