Skip to main content
Urbicon UI
Back to Recipes

Settings Page

Compact tabbed settings page — profile, notifications, and security as flat, co-equal sections with a single save. The small-scale shape of the settings-page pattern (larger or hierarchical settings use a sidebar).

Live Preview

JW

JPG, PNG or GIF. Max 2MB.

Brief description for your profile
51/280

Key Features

  • Tab navigation for Profile, Notifications, and Security
  • Textarea with character counter for bio
  • Select menu for timezone and language
  • Notification toggles with descriptions
  • Accordion for advanced security settings
  • Save action with success feedback

Code

Settings Page Recipe

<script lang="ts">
  import {
    Tab, TabItem, TabPanel, Input, Textarea, Select,
    Toggle, Accordion, AccordionItem, Card, Button,
    Avatar, Alert, Separator, Badge
  } from '@urbicon-ui/blocks';

  let activeTab = $state('profile');
  let fullName = $state('Jonas Weber');
  let email = $state('jonas@example.com');
  let bio = $state('Software developer passionate about design systems.');
  let timezone = $state('europe-berlin');
  let language = $state('en');
  let emailNotifications = $state(true);
  let pushNotifications = $state(false);
  let marketingEmails = $state(false);
  let twoFactorAuth = $state(true);
  let sessionTimeout = $state(true);
  let apiAccess = $state(false);
  let saved = $state(false);

  const timezones = [
    { label: 'UTC', value: 'utc' },
    { label: 'Europe/Berlin (CET)', value: 'europe-berlin' },
    { label: 'America/New York (EST)', value: 'america-new-york' },
    { label: 'America/Los Angeles (PST)', value: 'america-los-angeles' },
    { label: 'Asia/Tokyo (JST)', value: 'asia-tokyo' }
  ];

  const languages = [
    { label: 'English', value: 'en' },
    { label: 'Deutsch', value: 'de' },
    { label: 'Français', value: 'fr' },
    { label: 'Español', value: 'es' }
  ];

  function handleSave() {
    saved = true;
    setTimeout(() => (saved = false), 3000);
  }
</script>

<div class="mx-auto max-w-2xl p-8">
  {#if saved}
    <Alert intent="success" variant="soft" dismissible onDismiss={() => (saved = false)}>
      Settings saved successfully.
    </Alert>
  {/if}

  <Tab bind:value={activeTab} variant="line">
    {#snippet tabs()}
      <TabItem value="profile">Profile</TabItem>
      <TabItem value="notifications">Notifications</TabItem>
      <TabItem value="security">Security</TabItem>
    {/snippet}

    {#snippet panels()}
      <TabPanel value="profile">
        <div class="space-y-6 pt-6">
          <div class="flex items-center gap-4">
            <Avatar name={fullName} size="xl" />
            <div>
              <Button size="sm" variant="outlined" intent="neutral">Change Photo</Button>
              <p class="mt-1 text-xs text-text-tertiary">JPG, PNG or GIF. Max 2MB.</p>
            </div>
          </div>

          <Separator />

          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <Input label="Full Name" bind:value={fullName} />
            <Input label="Email" type="email" bind:value={email} />
          </div>

          <Textarea
            label="Bio"
            bind:value={bio}
            autoResize
            showCounter
            maxlength={280}
            helper="Brief description for your profile"
          />

          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <Select label="Timezone" options={timezones} bind:value={timezone} />
            <Select label="Language" options={languages} bind:value={language} />
          </div>
        </div>
      </TabPanel>

      <TabPanel value="notifications">
        <div class="pt-6">
          <Card class="border-border-subtle">
            <div class="divide-y divide-border-subtle">
              <div class="flex items-center justify-between p-4">
                <div>
                  <p class="text-sm font-medium text-text-primary">Email Notifications</p>
                  <p class="text-xs text-text-tertiary">Receive updates about your account via email.</p>
                </div>
                <Toggle checked={emailNotifications}
                  onCheckedChange={(v: boolean) => (emailNotifications = v)} />
              </div>
              <div class="flex items-center justify-between p-4">
                <div>
                  <p class="text-sm font-medium text-text-primary">Push Notifications</p>
                  <p class="text-xs text-text-tertiary">Receive push notifications on your devices.</p>
                </div>
                <Toggle checked={pushNotifications}
                  onCheckedChange={(v: boolean) => (pushNotifications = v)} />
              </div>
              <div class="flex items-center justify-between p-4">
                <div>
                  <p class="text-sm font-medium text-text-primary">Marketing Emails</p>
                  <p class="text-xs text-text-tertiary">Receive emails about new features and offers.</p>
                </div>
                <Toggle checked={marketingEmails}
                  onCheckedChange={(v: boolean) => (marketingEmails = v)} />
              </div>
            </div>
          </Card>
        </div>
      </TabPanel>

      <TabPanel value="security">
        <div class="pt-6">
          <Accordion variant="card">
            <AccordionItem value="2fa" title="Two-Factor Authentication">
              <div class="flex items-center justify-between">
                <div>
                  <p class="text-sm text-text-secondary">
                    Add an extra layer of security to your account.
                  </p>
                  <Badge variant="soft" intent={twoFactorAuth ? 'success' : 'warning'}
                    size="sm" class="mt-2">
                    {twoFactorAuth ? 'Enabled' : 'Disabled'}
                  </Badge>
                </div>
                <Toggle checked={twoFactorAuth}
                  onCheckedChange={(v: boolean) => (twoFactorAuth = v)} />
              </div>
            </AccordionItem>
            <AccordionItem value="sessions" title="Session Management">
              <div class="flex items-center justify-between">
                <div>
                  <p class="text-sm text-text-secondary">
                    Automatically log out after 30 minutes of inactivity.
                  </p>
                </div>
                <Toggle checked={sessionTimeout}
                  onCheckedChange={(v: boolean) => (sessionTimeout = v)} />
              </div>
            </AccordionItem>
            <AccordionItem value="api" title="API Access">
              <div class="flex items-center justify-between">
                <div>
                  <p class="text-sm text-text-secondary">
                    Enable API access for third-party integrations.
                  </p>
                </div>
                <Toggle checked={apiAccess}
                  onCheckedChange={(v: boolean) => (apiAccess = v)} />
              </div>
            </AccordionItem>
          </Accordion>
        </div>
      </TabPanel>
    {/snippet}
  </Tab>

  <div class="flex items-center justify-end gap-3 pt-6">
    <Button variant="ghost" intent="neutral">Cancel</Button>
    <Button intent="primary" onclick={handleSave}>Save Changes</Button>
  </div>
</div>