TryMellon

Web Components

Drop-in auth UI with trymellon-auth and trymellon-auth-modal — usage, anonymous flow, and dashboard configuration.

Web Components

The SDK ships two pre-built Web Components in @trymellon/js/ui that provide a complete auth UI with zero framework code. They use Shadow DOM for style isolation and emit Custom Events for integration.

Early access — Web Components are at v0.1.0. The API is stable but visual customization options may expand in future releases.


Installation

<script type="module">
  import '@trymellon/js/ui';
</script>

Or in a bundler:

import '@trymellon/js/ui';

This registers both <trymellon-auth> and <trymellon-auth-modal> as custom elements.


Configuring from the Dashboard

Before using the Web Components in your app, you need credentials and origin settings from the TryMellon dashboard.

StepAction
1Sign in at the TryMellon dashboard.
2Create an Application (or open an existing one) — Dashboard → Create app / Your application.
3Copy App ID (UUID) and Client ID (starts with cli_). These are the app-id and publishable-key for the Web Components.
4Add your site’s origin to Allowed origins (e.g. https://yourdomain.com, http://localhost:5173). The API will reject requests from origins not listed here.
5(Optional) For cross-device QR: if you use the bridge domain, no extra configuration is needed. To use your own mobile-auth page, set Primary QR base URL in the app settings and add that origin to Allowed origins.

Where to set these: Dashboard → Your application → Edit (or Settings). Allowed origins and Primary QR base URL are in the application settings.


<trymellon-auth> — Button + modal

A single tag that renders an action button and, by default, an internal modal. Ideal for most integrations.

Default usage (button opens built-in modal)

<trymellon-auth
  app-id="your-app-id-uuid"
  publishable-key="cli_xxxx"
  mode="auto"
  external-user-id="user_123"
  theme="light"
></trymellon-auth>
AttributeTypeDescription
app-idstringApplication ID (UUID) from the dashboard. Required.
publishable-keystringClient ID (cli_xxxx) from the dashboard. Required.
modeauto | login | registerAuth mode. auto = login by default.
external-user-idstringYour user identifier. Omit for anonymous registration (see below).
themelight | darkVisual theme.
actionopen-modal | direct-authDefault: button opens modal. direct-auth: direct ceremony without opening the modal.
trigger-onlytrue | falseIf true, only the button is rendered; the element emits mellon:open-request and the host must open <trymellon-auth-modal> itself.

Button-only option (trigger-only="true")

Use this when you want to control where the modal is mounted (e.g. portal or custom overlay):

<trymellon-auth
  app-id="…"
  publishable-key="…"
  trigger-only="true"
></trymellon-auth>
<trymellon-auth-modal id="my-modal" app-id="…" publishable-key="…"></trymellon-auth-modal>
document.querySelector('trymellon-auth').addEventListener('mellon:open-request', () => {
  document.getElementById('my-modal').open = true;
});

If you mount the modal yourself, you must inject the auth core into it (see Core injection below).


<trymellon-auth-modal>

Modal with Login/Register tabs, onboarding, and open/close cycle.

Attributes

AttributeTypeDescription
app-idstringApplication ID (UUID). Required.
publishable-keystringClient ID (cli_xxxx). Required.
opentrue | falseControls visibility.
tablogin | registerActive tab.
tab-labelsstringCustom labels, comma-separated (e.g. "Sign Up,Sign In").
modemodal | inlinePresentation mode.
themelight | darkTheme.
dialog-titlestringModal title. If not set, a default title is used (e.g. “TryMellon — Sign in or register” or app name + ” — Sign in or register”).
dialog-descriptionstringDescription below the title. If not set, SDK default text is used.
session-idstringSession ID for onboarding.
onboarding-urlstringExternal URL to complete onboarding.
is-mobile-overridetrue | falseOverride mobile detection.
fallback-typeemail | qrPreferred fallback channel.
qr-load-timeout-msnumberTimeout (ms) to show an error if no content is injected into the cross-device slot after opening. Default: 12000.
external-user-idstringYour user identifier. Omit for anonymous registration (see below).

API from JavaScript

const modal = document.querySelector('trymellon-auth-modal');
modal.open = true;
modal.tab = 'register';
modal.theme = 'dark';
modal.reset(); // Reset to initial state before reopening

Core injection (when using trigger-only)

If you use trigger-only and mount <trymellon-auth-modal> yourself, you must attach the TryMellon client to the modal:

import { TryMellon } from '@trymellon/js';

const clientResult = TryMellon.create({ appId: '…', publishableKey: '…' });
if (!clientResult.ok) throw clientResult.error;

const modal = document.querySelector('trymellon-auth-modal');
modal.attachCore(clientResult.value);

Before reopening the modal, call modal.reset() for a clean state.


Anonymous flow (registration without user ID)

You can let users register a passkey without providing an external-user-id (anonymous registration). The backend creates an anonymous user and returns an external_user_id in the init response; you can persist it after the session is validated.

In the Web Component:

  • Omit the external-user-id attribute on <trymellon-auth> or <trymellon-auth-modal> when you want anonymous registration.
  • On the Register tab, the component calls the init-registration API without an external user ID. The backend generates a UUID and returns it (e.g. in the cross-device init response or after session validation).
  • After a successful authentication, listen for mellon:success and send the session token to your backend. Your backend validates the token (e.g. GET /v1/sessions/validate); the validation response includes external_user_id. You can then create or link the user in your system and set a session cookie.

Flow summary:

  1. User opens the modal, goes to Register, and does not provide a user ID (attribute omitted).
  2. Backend creates an anonymous user and cross-device/registration session; returns session_id, qr_url, and external_user_id.
  3. User completes the passkey ceremony (same device or via QR).
  4. Frontend receives mellon:success with token; sends token to your backend.
  5. Backend validates the token, reads external_user_id from the validation response, creates or links the account, and sets the session.

For login (authenticate), the QR flow remains discoverable: no external user ID is required to show the login QR. For registration, omitting external-user-id enables the anonymous flow; the backend assigns the ID.


Cross-device (QR) in the modal

The modal shows by default a button with QR icon (illustration), not a scannable QR. When the user clicks it, the element emits mellon:fallback with detail.fallbackType === 'qr'.

To show a scannable QR, the host must:

  1. Listen for mellon:fallback when detail.fallbackType === 'qr'.
  2. Call the TryMellon client: client.auth.crossDevice.init() (login) or initRegistration({ externalUserId }) (register; externalUserId is optional for anonymous registration).
  3. Get qr_url from the response and generate a QR image (e.g. with a library like qrcode).
  4. Inject the image into the modal’s cross-device slot: create a node with slot="cross-device" (e.g. <div slot="cross-device"><img src="..."></div>) and append it to the <trymellon-auth-modal> element.

Skeleton and timeout: When the modal opens, the cross-device area is in a “waiting” state and shows a minimal skeleton (“Loading QRs…”). If the host injects content into the cross-device slot before the timeout (qr-load-timeout-ms, default 12s), the modal shows that content. If nothing is injected in time, the modal shows an error (“QR could not be loaded. Try again.”). The host subscribes to mellon:open, performs the init requests, and when URLs are available injects the QRs into the slot.

QR image generation is the host’s responsibility; the SDK does not bundle a QR library.


Events

Listen on the Web Component element (not on document) so the token is not exposed to third-party scripts.

EventDetailDescription
mellon:open{}Modal opened.
mellon:close{ reason: 'success' | 'cancel' | 'error' | 'user' }Modal closed.
mellon:open-request{}Button clicked (when action="open-modal").
mellon:start{ operation }Auth operation started.
mellon:success{ token, user?, nonce?, operation?, redirectUrl? }Auth succeeded. token is the session token.
mellon:error{ error }Auth error.
mellon:cancelled{}Auth cancelled.
mellon:fallback{ operation?, fallbackType? }Fallback (email or QR) triggered.
mellon:tab-change{ tab }Tab changed.

Example: send token to backend

const modal = document.querySelector('trymellon-auth-modal');
modal.addEventListener('mellon:success', (e) => {
  const { token } = e.detail;
  fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ sessionToken: token }),
  }).then(() => { /* redirect or close */ });
});

The backend must always validate the token (TryMellon validation endpoint); do not create a session based only on the string sent from the client.


action behavior

actionEffect
open-modal (default)Click → emits mellon:open-request and (if not trigger-only) opens the internal modal.
direct-authClick → direct ceremony in the component; no modal opens. Listen for mellon:success or mellon:error / mellon:cancelled on the element.

FSM states

The Web Components use an internal Finite State Machine. You don’t interact with it directly, but the states help with debugging:

IDLEEVALUATING_ENVREADY / READY_LOGIN / READY_REGISTERAUTHENTICATINGSUCCESS / ERROR / FALLBACK


When to use Web Components vs hooks

ScenarioRecommendation
You want auth UI with minimal codeWeb Components
You need full control over the UIReact/Vue/Angular hooks + your own components
You’re building a static site or CMSWeb Components
You already have a design systemFramework hooks

CDN usage

For non-bundler environments, load the UI bundle via a script tag:

<script type="module" src="https://cdn.jsdelivr.net/npm/@trymellon/js/dist/ui/index.js"></script>
<trymellon-auth app-id="YOUR_APP_ID" publishable-key="cli_xxxx"></trymellon-auth>

Use SRI when loading from a CDN.