Profile

Let signed-in users view and edit their profile. Compose your own form field-by-field with <u-profile> + <u-field>, or drop in <u-full-profile> for a complete UI in one tag. Custom attributes, SSO navigation to connected services, and account deletion round it out.

View this guide as Markdown

Composed profile form

u-profile loads and saves the data; each u-field renders one profile field with validation, labels and the right input type (date picker, phone, country select with flags). You own the layout — group fields in any markup you like.

  • Field-by-field composition — u-field per profile attribute, your layout
  • Typed inputs — dates, phone numbers, country select with icons
  • Validation + labels — render-default-label and built-in error handling
<u-signed-in>
  <!-- u-profile loads the user's data; each u-field binds one profile field.
       partial-validation limits validation to the fields rendered here. -->
  <u-profile partial-validation="true">
    <u-field field="salutation" render-default-label="true"></u-field>
    <div class="grid gap-x-4 sm:grid-cols-2">
      <u-field field="first_name" render-default-label="true"></u-field>
      <u-field field="last_name" render-default-label="true"></u-field>
      <u-field field="date_of_birth" required="true" render-default-label="true"></u-field>
      <u-field field="phone_number" render-default-label="true"></u-field>
    </div>
    <u-field field="country_code" country-code-display-option="icon" render-default-label="true"
    ></u-field>

    <u-submit-button
      class-name="mt-4 px-4 py-2 text-white bg-accent hover:bg-accent-hover rounded-lg disabled:opacity-50 w-full cursor-pointer">
      Save changes
    </u-submit-button>
  </u-profile>
</u-signed-in>

<u-signed-in not>
  <p class="text-text-light">
    Sign in on the <a href="/auth" class="text-primary underline">Auth page</a> to edit your profile.
  </p>
</u-signed-in>
Live demo
Save changes

Sign in on the Auth page to edit your profile.

Full profile in one tag

u-full-profile renders a complete, ready-made profile editor. Use the fields attribute to pick and order fields (including custom_attributes.*), or omit it to show everything the tenant defines. The fastest path to a working account page.

  • One component — complete profile UI with zero markup
  • Field selection — fields="first_name,last_name,…" picks and orders
<u-signed-in>
  <!-- One component, complete profile UI. Limit and order fields with the
       fields attribute — or omit it to render everything the tenant defines. -->
  <u-full-profile
    fields="first_name,last_name,date_of_birth,country_code"
    country-code-display-option="icon"></u-full-profile>
</u-signed-in>

<u-signed-in not>
  <p class="text-text-light">
    Sign in on the <a href="/auth" class="text-primary underline">Auth page</a> to see the full profile
    component.
  </p>
</u-signed-in>
Live demo

Sign in on the Auth page to see the full profile component.

Custom attributes

Tenant-defined custom attributes behave like normal fields via u-field — or use u-raw-field to build fully custom inputs. These sport chips are plain checkboxes bound to a multi-value attribute, styled with CSS only.

  • custom_attributes.* fields — text, select, multi-select, radio groups
  • u-raw-field escape hatch — any UI you can build with native inputs
<u-signed-in>
  <u-profile partial-validation="true">
    <u-field field="custom_attributes.fav_athlete" render-default-label="true"></u-field>

    <p class="mb-2 mt-4 text-sm font-medium">Favorite disciplines</p>
    <u-field field="custom_attributes.fav_discipline"></u-field>

    <u-submit-button
      class-name="mt-4 px-4 py-2 text-white bg-accent hover:bg-accent-hover rounded-lg disabled:opacity-50 w-full cursor-pointer">
      Save changes
    </u-submit-button>
  </u-profile>
</u-signed-in>

<u-signed-in not>
  <p class="text-text-light">
    Sign in on the <a href="/auth" class="text-primary underline">Auth page</a> to edit your profile.
  </p>
</u-signed-in>

<style is:inline>
  /* Chip-style for the multi-select discipline field */
  u-field[field="custom_attributes.fav_discipline"]::part(multi-select-group_field) {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0;
    border: none;
    background: transparent;
  }
  u-field[field="custom_attributes.fav_discipline"]::part(multi-select-item_label) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 90px;
    height: 2.5rem;
    padding: 0 0.875rem;
    border: 1px solid var(--color-border);
    border-radius: 9999px;
    background: var(--color-white);
    font-size: 0.875rem;
    cursor: pointer;
    transition: all var(--transition-normal);
    gap: 0;
  }
  u-field[field="custom_attributes.fav_discipline"]::part(multi-select-item_label):hover {
    border-color: var(--color-primary);
    background: var(--color-primary-soft);
  }
  u-field[field="custom_attributes.fav_discipline"]::part(multi-select-item_checkbox) {
    display: none;
  }
  u-field[field="custom_attributes.fav_discipline"]::part(multi-select-item_checked) {
    background: var(--color-primary-soft);
    border-color: var(--color-primary);
    color: var(--color-primary-dark);
    font-weight: 600;
  }
  u-field[field="custom_attributes.fav_discipline"]::part(field_label) {
    display: none;
  }
</style>
Live demo

Favorite disciplines

Save changes

Sign in on the Auth page to edit your profile.

Header profile icon

The classic header pattern: u-signed-in swaps a sign-in link for an avatar menu with profile and logout entries. Pure CSS hover dropdown — no JS required.

  • Signed-in/out switch — u-signed-in / u-signed-in not
  • u-logout-button anywhere — style it like any menu item
<!-- A header user-menu: avatar button with a hover dropdown.
     u-signed-in / u-signed-in not switch between menu and sign-in link. -->
<div class="flex justify-end">
  <u-signed-in>
    <div class="group relative inline-block">
      <button
        class="flex items-center justify-center rounded-full border border-border bg-white p-2 shadow-sm hover:bg-background-light"
        aria-label="User menu">
        <svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <circle cx="12" cy="8" r="4"></circle>
          <path d="M4 20c0-3.5 3.5-6 8-6s8 2.5 8 6"></path>
        </svg>
      </button>

      <!-- hover bridge so the menu doesn't close between button and panel -->
      <div class="absolute left-0 right-0 top-full h-2"></div>

      <div
        class="absolute right-0 top-full z-20 mt-2 hidden w-48 rounded-lg border border-border bg-white shadow-xl group-hover:block">
        <a href="/profile" class="block px-4 py-2 text-sm hover:bg-background-light"
          >Profile settings</a
        >
        <div class="border-t border-border"></div>
        <u-logout-button
          class-name="block w-full text-left px-4 py-2 text-sm hover:bg-background-light cursor-pointer">
          Sign out
        </u-logout-button>
      </div>
    </div>
  </u-signed-in>

  <u-signed-in not>
    <a href="/auth" class="btn btn-primary !min-h-0 !px-4 !py-2 text-sm">Sign in</a>
  </u-signed-in>
</div>
Live demo

SSO navigation & account deletion

u-jump-to-service signs the user into another OAuth app of your tenant; u-jump-to-unidy generates a one-time login token and opens Unidy's hosted pages — including the profile page where account deletion lives. no-auth skips the token for public pages.

  • Service SSO — jump into connected apps without re-login
  • Hosted Unidy pages — subscriptions, tickets, terms, profile
  • Account deletion — deep-link to /profile#logindata
<u-signed-in>
  <div class="flex flex-col gap-3">
    <!-- SSO jump to the FIS Fan Portal (service-id = 6) -->
    <u-jump-to-service
      service-id="6"
      newtab
      class-name="px-4 py-2 bg-accent hover:bg-accent-hover text-white text-sm font-medium rounded-lg cursor-pointer disabled:opacity-50">
      Open FIS Fan Portal
    </u-jump-to-service>

    <!-- One-time-token jump into Unidy's hosted pages -->
    <u-jump-to-unidy
      path="/subscriptions"
      newtab
      class-name="px-4 py-2 border border-border text-sm font-medium rounded-lg cursor-pointer hover:bg-background-light disabled:opacity-50">
      Manage subscriptions in Unidy
    </u-jump-to-unidy>

    <!-- Pages that need no authentication: add no-auth -->
    <u-jump-to-unidy
      path="/terms_of_use"
      no-auth
      newtab
      class-name="text-sm text-text-light underline cursor-pointer hover:text-text">
      Terms of use (no auth)
    </u-jump-to-unidy>

    <!-- Account deletion lives on Unidy's profile page — deep-link straight to it -->
    <u-jump-to-unidy
      path="/profile#logindata"
      newtab
      class-name="px-4 py-2 bg-danger hover:bg-danger-hover text-white text-sm font-medium rounded-lg cursor-pointer disabled:opacity-50">
      Delete my account
    </u-jump-to-unidy>
  </div>
</u-signed-in>

<u-signed-in not>
  <p class="text-text-light">
    Sign in on the <a href="/auth" class="text-primary underline">Auth page</a> to use SSO navigation.
  </p>
</u-signed-in>
Live demo
Open FIS Fan Portal Manage subscriptions in Unidy Terms of use (no auth) Delete my account

Sign in on the Auth page to use SSO navigation.

Connected services

Show which third-party services a user has connected, and let them connect or disconnect in place. The connect flow runs on the /partner page (which holds the u-oauth-provider); this UI reads the result from sessionStorage and shows a connected or disconnected state accordingly.

  • Connect / disconnect — visual state card with one-click actions
  • sessionStorage handoff — state set on /partner, read here
  • u-signed-in gating — only shown to authenticated users
{clientId ? (
  <>
    <u-signed-in>
      <div id="pfProvider" class="space-y-3">

        <!-- Disconnected state -->
        <div id="pfLocked" class="flex items-center justify-between p-4 rounded-xl border border-border bg-background-light">
          <div class="flex items-center gap-3">
            <div class="w-9 h-9 rounded-lg bg-[#1a1f2e] flex items-center justify-center flex-shrink-0">
              <svg width="18" height="18" viewBox="0 0 28 28" fill="none">
                <path d="M14 2v24M2 14h24M5.5 5.5l17 17M22.5 5.5l-17 17" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
              </svg>
            </div>
            <div>
              <p class="font-semibold text-sm">FIS Fan Portal</p>
              <p class="text-xs text-text-muted">Get personalized race content</p>
            </div>
          </div>
          <a
            href="/partner"
            class="text-xs font-medium px-3 py-1.5 rounded-lg bg-primary text-white hover:bg-primary-dark transition-colors cursor-pointer no-underline"
          >
            Connect
          </a>
        </div>

        <!-- Connected state -->
        <div id="pfConnected" class="flex items-center justify-between p-4 rounded-xl border border-green-200 bg-green-50">
          <div class="flex items-center gap-3">
            <div class="w-9 h-9 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
              <svg class="w-4 h-4 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>
              </svg>
            </div>
            <div>
              <p class="font-semibold text-sm text-green-900">FIS Fan Portal</p>
              <p class="text-xs text-green-700">Connected</p>
            </div>
          </div>
          <button
            id="pfDisconnectBtn"
            class="text-xs font-medium cursor-pointer px-3 py-1.5 rounded-lg border border-error-border text-danger hover:bg-error-bg transition-colors"
          >
            Disconnect
          </button>
        </div>

      </div>
    </u-signed-in>

    <u-signed-in not>
      <p class="text-text-muted text-sm">Sign in to manage connected services.</p>
    </u-signed-in>
  </>
) : (
  <p class="text-text-muted text-sm">No partner service configured for this demo.</p>
)}

<style>
  #pfConnected { display: none; }
  #pfProvider.pf-connected #pfLocked    { display: none; }
  #pfProvider.pf-connected #pfConnected { display: flex; }
</style>

<script is:inline>
  (function () {
    var provider = document.getElementById('pfProvider');
    if (!provider) return;

    function showConnected() {
      provider.classList.add('pf-connected');
      try { sessionStorage.setItem('fis_connected', '1'); } catch (_) {}
    }

    function showDisconnected() {
      provider.classList.remove('pf-connected');
      try { sessionStorage.removeItem('fis_connected'); } catch (_) {}
    }

    // Restore from sessionStorage (set by /partner page after successful connect)
    try { if (sessionStorage.getItem('fis_connected') === '1') showConnected(); } catch (_) {}

    var btn = document.getElementById('pfDisconnectBtn');
    if (btn) btn.addEventListener('click', showDisconnected);
  })();
</script>
Live demo

FIS Fan Portal

Get personalized race content

Connect

FIS Fan Portal

Connected

Sign in to manage connected services.