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.
| Step | Action |
|---|---|
| 1 | Sign in at the TryMellon dashboard. |
| 2 | Create an Application (or open an existing one) — Dashboard → Create app / Your application. |
| 3 | Copy App ID (UUID) and Client ID (starts with cli_). These are the app-id and publishable-key for the Web Components. |
| 4 | Add 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>
| Attribute | Type | Description |
|---|---|---|
app-id | string | Application ID (UUID) from the dashboard. Required. |
publishable-key | string | Client ID (cli_xxxx) from the dashboard. Required. |
mode | auto | login | register | Auth mode. auto = login by default. |
external-user-id | string | Your user identifier. Omit for anonymous registration (see below). |
theme | light | dark | Visual theme. |
action | open-modal | direct-auth | Default: button opens modal. direct-auth: direct ceremony without opening the modal. |
trigger-only | true | false | If 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
| Attribute | Type | Description |
|---|---|---|
app-id | string | Application ID (UUID). Required. |
publishable-key | string | Client ID (cli_xxxx). Required. |
open | true | false | Controls visibility. |
tab | login | register | Active tab. |
tab-labels | string | Custom labels, comma-separated (e.g. "Sign Up,Sign In"). |
mode | modal | inline | Presentation mode. |
theme | light | dark | Theme. |
dialog-title | string | Modal title. If not set, a default title is used (e.g. “TryMellon — Sign in or register” or app name + ” — Sign in or register”). |
dialog-description | string | Description below the title. If not set, SDK default text is used. |
session-id | string | Session ID for onboarding. |
onboarding-url | string | External URL to complete onboarding. |
is-mobile-override | true | false | Override mobile detection. |
fallback-type | email | qr | Preferred fallback channel. |
qr-load-timeout-ms | number | Timeout (ms) to show an error if no content is injected into the cross-device slot after opening. Default: 12000. |
external-user-id | string | Your 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-idattribute 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:successand send the session token to your backend. Your backend validates the token (e.g.GET /v1/sessions/validate); the validation response includesexternal_user_id. You can then create or link the user in your system and set a session cookie.
Flow summary:
- User opens the modal, goes to Register, and does not provide a user ID (attribute omitted).
- Backend creates an anonymous user and cross-device/registration session; returns
session_id,qr_url, andexternal_user_id. - User completes the passkey ceremony (same device or via QR).
- Frontend receives
mellon:successwithtoken; sends token to your backend. - Backend validates the token, reads
external_user_idfrom 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:
- Listen for
mellon:fallbackwhendetail.fallbackType === 'qr'. - Call the TryMellon client:
client.auth.crossDevice.init()(login) orinitRegistration({ externalUserId })(register;externalUserIdis optional for anonymous registration). - Get
qr_urlfrom the response and generate a QR image (e.g. with a library likeqrcode). - 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.
| Event | Detail | Description |
|---|---|---|
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
action | Effect |
|---|---|
open-modal (default) | Click → emits mellon:open-request and (if not trigger-only) opens the internal modal. |
direct-auth | Click → 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:
IDLE → EVALUATING_ENV → READY / READY_LOGIN / READY_REGISTER → AUTHENTICATING → SUCCESS / ERROR / FALLBACK
When to use Web Components vs hooks
| Scenario | Recommendation |
|---|---|
| You want auth UI with minimal code | Web Components |
| You need full control over the UI | React/Vue/Angular hooks + your own components |
| You’re building a static site or CMS | Web Components |
| You already have a design system | Framework 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.