> ## Documentation Index
> Fetch the complete documentation index at: https://www.logo.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Personalize onboarding from a work email

> Cut signup friction. Greet new users with their own brand the moment they type a work email, with logos served from one Logo.dev image URL.

export const OnboardingAutofillDemo = () => {
  const TOKEN = "live_6a1a28fd-6420-4492-aeb0-b297461d9de2";
  const [email, setEmail] = useState("");
  const domain = useMemo(() => {
    const at = email.indexOf("@");
    if (at === -1) {
      return null;
    }
    const candidate = email.slice(at + 1).trim().toLowerCase();
    return candidate.includes(".") ? candidate : null;
  }, [email]);
  const company = useMemo(() => {
    if (!domain) {
      return null;
    }
    const name = domain.split(".")[0];
    return name.charAt(0).toUpperCase() + name.slice(1);
  }, [domain]);
  return <div className="not-prose my-8 rounded-2xl border border-zinc-950/10 bg-gradient-to-br from-zinc-50 to-white p-6 shadow-sm sm:p-8 dark:border-white/10 dark:from-zinc-900 dark:to-zinc-950">
      <div className="mx-auto grid max-w-2xl items-center gap-6 sm:grid-cols-2">
        <div>
          <label className="mb-2 block text-sm font-medium text-zinc-700 dark:text-zinc-300" htmlFor="onboarding-email-input">
            Work email
          </label>
          <input autoComplete="off" className="w-full rounded-xl border border-zinc-950/10 bg-white px-4 py-3 text-base text-zinc-950 placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-zinc-950/20 dark:border-white/20 dark:bg-zinc-900 dark:text-white dark:focus:ring-white/20" id="onboarding-email-input" onChange={e => setEmail(e.target.value)} placeholder="you@stripe.com" type="email" value={email} />
        </div>
        <div className="rounded-xl border border-zinc-950/10 bg-white p-4 dark:border-white/10 dark:bg-zinc-900">
          <div className="flex items-center gap-3">
            {domain ? <img alt={`${company} logo`} className="h-9 w-9 rounded-lg object-contain" height="36" key={domain} src={`https://img.logo.dev/${domain}?token=${TOKEN}&format=webp&retina=true&size=72`} width="36" /> : <div className="h-9 w-9 rounded-lg bg-zinc-200 dark:bg-zinc-700" />}
            <div className="min-w-0">
              <div className="truncate text-sm font-semibold text-zinc-950 dark:text-white">
                {company ? `${company} workspace` : "Your workspace"}
              </div>
              <div className="truncate text-xs text-zinc-400">
                {domain ?? "yourcompany.com"}
              </div>
            </div>
          </div>
          <div className="mt-4 space-y-2">
            <div className="h-2 w-3/4 rounded bg-zinc-100 dark:bg-zinc-800" />
            <div className="h-2 w-1/2 rounded bg-zinc-100 dark:bg-zinc-800" />
            <div className="h-2 w-2/3 rounded bg-zinc-100 dark:bg-zinc-800" />
          </div>
        </div>
      </div>
    </div>;
};

The strongest moment to show a logo is during signup: a new user types their work email, and your product greets them with their own brand. That flash of recognition turns a generic form into something that already feels like their workspace.

As the user types, the preview pane flips from a grey placeholder to their company's logo and name. Every logo is a [Logo API](/docs/logo-images/introduction) image URL built from the domain after the `@`, so there is nothing to look up or store.

## Demo

Type any work email:

<OnboardingAutofillDemo />

The preview brands itself live from `img.logo.dev` as you type; try a domain nobody has heard of and you still get a monogram.

## Build it with AI

<Prompt description="Want a branded signup preview in your app? This prompt gives your AI coding tool everything it needs to build it in your framework." actions={["copy", "cursor"]}>
  Build a signup email field with a live branded workspace preview using Logo.dev.

  * Take the domain from everything after the @ in the email input, lowercased; treat it as valid once it contains a dot.
  * The logo is a plain image URL: [https://img.logo.dev/:domain?token=LOGO\_DEV\_PUBLISHABLE\_KEY\&format=webp\&retina=true\&size=72](https://img.logo.dev/:domain?token=LOGO_DEV_PUBLISHABLE_KEY\&format=webp\&retina=true\&size=72), rendered at 36px.
  * Use a Logo.dev publishable key (safe in client code).
  * Unknown domains return a generated monogram from Logo.dev, so the preview always shows something branded.
  * Keep the logo slot a fixed 36px square with a grey placeholder so the preview never jumps, derive a display name by capitalizing the domain's first label, and add a few skeleton lines below to suggest the workspace.
  * Reference implementation: [https://www.logo.dev/docs/use-cases/onboarding-personalization](https://www.logo.dev/docs/use-cases/onboarding-personalization)
</Prompt>

## Code

This example is in React, but you can get the same result in any frontend framework: the preview is one image URL rebuilt as the input changes. `OnboardingPreview.tsx` is the whole component, and the Usage tab renders it with your [publishable key](/docs/platform/api-keys).

<CodeGroup>
  ```tsx OnboardingPreview.tsx expandable theme={null}
  import { useMemo, useState } from "react";

  export function OnboardingPreview({ token }: { token: string }) {
    const [email, setEmail] = useState("");

    const domain = useMemo(() => {
      const at = email.indexOf("@");
      if (at === -1) {
        return null;
      }
      const candidate = email.slice(at + 1).trim().toLowerCase();
      return candidate.includes(".") ? candidate : null;
    }, [email]);

    const company = useMemo(() => {
      if (!domain) {
        return null;
      }
      const name = domain.split(".")[0];
      return name.charAt(0).toUpperCase() + name.slice(1);
    }, [domain]);

    return (
      <div className="grid max-w-2xl items-center gap-6 sm:grid-cols-2">
        <div>
          <label
            className="mb-2 block text-sm font-medium"
            htmlFor="signup-email"
          >
            Work email
          </label>
          <input
            className="w-full rounded-xl border border-zinc-950/10 bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-zinc-950/20 dark:border-white/20 dark:bg-zinc-900 dark:text-white"
            id="signup-email"
            onChange={(e) => setEmail(e.target.value)}
            placeholder="you@stripe.com"
            type="email"
            value={email}
          />
        </div>
        <div className="rounded-xl border border-zinc-950/10 bg-white p-4 dark:border-white/10 dark:bg-zinc-900">
          <div className="flex items-center gap-3">
            {domain ? (
              <img
                alt={`${company} logo`}
                className="h-9 w-9 rounded-lg object-contain"
                height={36}
                key={domain}
                src={`https://img.logo.dev/${domain}?token=${token}&format=webp&retina=true&size=72`}
                width={36}
              />
            ) : (
              <div className="h-9 w-9 rounded-lg bg-zinc-200 dark:bg-zinc-700" />
            )}
            <div className="min-w-0">
              <div className="truncate text-sm font-semibold">
                {company ? `${company} workspace` : "Your workspace"}
              </div>
              <div className="truncate text-xs text-zinc-400">
                {domain ?? "yourcompany.com"}
              </div>
            </div>
          </div>
          <div className="mt-4 space-y-2">
            <div className="h-2 w-3/4 rounded bg-zinc-100 dark:bg-zinc-800" />
            <div className="h-2 w-1/2 rounded bg-zinc-100 dark:bg-zinc-800" />
            <div className="h-2 w-2/3 rounded bg-zinc-100 dark:bg-zinc-800" />
          </div>
        </div>
      </div>
    );
  }
  ```

  ```tsx Usage theme={null}
  import { OnboardingPreview } from "./OnboardingPreview";

  export function SignupStep() {
    return <OnboardingPreview token="LOGO_DEV_PUBLISHABLE_KEY" />;
  }
  ```
</CodeGroup>

<Note>
  Your [publishable key](/docs/platform/api-keys) is built for client-side code, so
  you can ship it in the browser as-is.
</Note>

## How it works

* **One image URL returns the logo.** `img.logo.dev/:domain` serves it, and `size`, `retina`, and `format` parameters keep the preview sharp and light. See all [image parameters](/docs/logo-images/introduction#parameters).
* **The domain comes from the email.** Everything after the `@` is the lookup key, so identifying the company costs zero API calls.
* **Unknown domains still look intentional.** Logo.dev serves a generated monogram when it has no logo for a domain, so a startup nobody has heard of still gets a branded preview. See [fallback images](/docs/logo-images/introduction#fallback-images).
* **The preview holds still.** The logo slot keeps one fixed size whether it shows the placeholder, a monogram, or the real logo.

## Make it your own

* **Skip free email providers.** Keep a small denylist for domains like `gmail.com` and show the neutral placeholder instead, the way the [logo from a domain](/docs/use-cases/logo-from-domain) example handles missing logos.
* **Pre-fill the company name.** Resolve the domain with the [Search API](/docs/brand-search/introduction) on the server to get the proper name instead of a capitalized domain.
* **Theme the whole page.** The [Brand API](/docs/brand/introduction) returns colors and other assets to brand more than the logo slot.

## Next steps

<CardGroup cols={2}>
  <Card title="Brand API" icon="swatchbook" href="/docs/brand/introduction">
    Get full brand profiles: logo, brandmark, banners, and colors.
  </Card>

  <Card title="Logo from a domain" icon="shield" href="/docs/use-cases/logo-from-domain">
    Render your own fallback instead of the monogram.
  </Card>
</CardGroup>
