All examples

Auth, onboarding and settings layouts

Authentication split, onboarding wizard and settings shell — responsive and RTL. Fictional Harbor Labs copy.

Auth, onboarding and settings layouts

layouts-auth-settings · texuli/layouts/auth-settings

texuli layouts
Props
  • form
  • mountScreen
  • sections
  • steps

This is a full-page layout example. It keeps its own landmarks in an isolated preview so it does not collide with the site shell.

Open the live example
Source (layouts-auth-settings/LayoutsAuthSettingsDemo.svelte)
<script lang="ts">
import { Button } from 'texuli/button';
import {
  AuthSplitLayout,
  SettingsLayout,
  type SettingsNavItem,
  WizardLayout,
} from 'texuli/layouts/auth-settings';
import type { StepperStep } from 'texuli/stepper';

const steps: StepperStep[] = [
  { id: 'profile', label: 'Profile' },
  { id: 'workspace', label: 'Workspace' },
  { id: 'invite', label: 'Invite' },
];

let wizardStep = $state(0);
let settingsActive = $state('profile');
let rtlActive = $state('profile');

const sections: SettingsNavItem[] = [
  { id: 'profile', label: 'Profile', description: 'Name and avatar' },
  { id: 'security', label: 'Security', description: 'Password and sessions' },
  { id: 'notifications', label: 'Notifications' },
];
</script>

<section
  class="demo"
  data-example="layouts-auth-settings"
  aria-labelledby="layouts-auth-title"
>
  <h2 id="layouts-auth-title">Auth, onboarding and settings</h2>
  <p>
    Authentication split, onboarding wizard and settings shell. Responsive and RTL-ready.
    Fictional Harbor Labs copy only.
  </p>

  <h3>Auth split</h3>
  <div class="frame">
    <AuthSplitLayout
      brandTitle="Harbor Labs"
      brandDescription="A clearer way for the team to ship prototypes together."
      brandEyebrow="Workspace"
    >
      {#snippet brand()}Harbor{/snippet}
      {#snippet form()}
        <div class="form">
          <label class="field">
            <span>Email</span>
            <input type="email" autocomplete="username" value="[email protected]" readonly />
          </label>
          <Button type="button">Continue</Button>
        </div>
      {/snippet}
    </AuthSplitLayout>
  </div>

  <h3>Onboarding wizard</h3>
  <div class="frame frame--muted">
    <WizardLayout
      bind:current={wizardStep}
      title="Set up Harbor"
      description="Three short steps to open the Harbor Labs workspace."
      {steps}
      finishLabel="Open workspace"
      onFinish={() => {
        wizardStep = 0;
      }}
    >
      {#snippet content({ step })}
        <p class="step-body">You are on <strong>{step.label}</strong>.</p>
      {/snippet}
    </WizardLayout>
  </div>

  <h3>Settings</h3>
  <div class="frame frame--pad">
    <SettingsLayout
      bind:active={settingsActive}
      title="Settings"
      description="Harbor workspace preferences."
      {sections}
      startWithList={false}
    >
      {#snippet children(id)}
        <p class="step-body">
          {#if id === 'profile'}
            Profile for Ada Park at Harbor Labs.
          {:else if id === 'security'}
            Password, MFA and active sessions.
          {:else}
            Email and in-app notification preferences.
          {/if}
        </p>
      {/snippet}
    </SettingsLayout>
  </div>

  <h3>Settings (RTL)</h3>
  <div class="frame frame--pad" dir="rtl" lang="ar">
    <SettingsLayout
      bind:active={rtlActive}
      title="الإعدادات"
      description="تفضيلات مساحة عمل هاربور."
      navLabel="أقسام الإعدادات"
      backLabel="الإعدادات"
      sections={[
        { id: 'profile', label: 'الملف الشخصي' },
        { id: 'security', label: 'الأمان' },
      ]}
      startWithList={false}
    >
      {#snippet children(id)}
        <p class="step-body">{id === 'profile' ? 'الملف الشخصي لأدا.' : 'كلمة المرور والجلسات.'}</p>
      {/snippet}
    </SettingsLayout>
  </div>
</section>

<style>
  .demo {
    display: grid;
    gap: 1.25rem;
    padding: 1.5rem;
    color: var(--tx-foreground, CanvasText);
    background: var(--tx-background, Canvas);
  }
  h2,
  h3 {
    margin: 0;
  }
  h2 {
    font-size: 1.25rem;
  }
  h3 {
    font-size: 1rem;
  }
  p {
    margin: 0;
    color: var(--tx-muted-foreground, GrayText);
  }
  .frame {
    border: 1px solid var(--tx-border, ButtonBorder);
    border-radius: 0.75rem;
    overflow: hidden;
    min-block-size: 18rem;
  }
  .frame--muted {
    background: color-mix(in srgb, var(--tx-muted, GrayText) 8%, transparent);
  }
  .frame--pad {
    padding: 0;
  }
  .form {
    display: grid;
    gap: 1rem;
  }
  .field {
    display: grid;
    gap: 0.35rem;
    font-size: 0.875rem;
  }
  .field input {
    min-block-size: 2.25rem;
    padding-inline: 0.75rem;
    border: 1px solid var(--tx-input, currentColor);
    border-radius: 0.5rem;
    background: var(--tx-surface, Field);
    color: inherit;
    font: inherit;
  }
  .step-body {
    margin: 0;
    color: var(--tx-foreground, CanvasText);
  }
</style>