Skip to content

UI Components

The @latch/ui package provides drop-in Web Components that work in any HTML page — no React, Vue, or build step required. Each component uses Shadow DOM for style isolation and CSS custom properties for theming.

Initialize the SDK before importing the UI package. This ensures the branding configuration is fetched automatically when components load.

<script type="module">
// 1. Initialize the SDK first
const { init } = await import('@latch/sdk');
init({
apiKey: 'pk_...',
apiUrl: 'https://your-latch-api',
});
// 2. Then import UI components — triggers branding fetch
await import('@latch/ui');
</script>

Or import individual components:

import { LatchAuthModal, LatchUserMenu } from '@latch/ui';

Standalone email + password login form.

<latch-login-form
heading="Sign in"
button-text="Sign in"
show-register="true"
></latch-login-form>
AttributeDefaultDescription
heading"Sign in"Form heading text
button-text"Sign in"Submit button label
show-register"true"Show “Create account” link
EventDetailDescription
latch:login{ customer }Successful login
latch:login:error{ error }Login failure
latch:show-register“Create account” link clicked

Standalone registration form with optional name field.

<latch-register-form
heading="Create account"
show-name="true"
></latch-register-form>
AttributeDefaultDescription
heading"Create account"Form heading
button-text"Create account"Submit button label
show-login"true"Show “Sign in” link
show-name"true"Show the optional name field
EventDetailDescription
latch:register{ customer }Successful registration
latch:register:error{ error }Registration failure
latch:show-login“Sign in” link clicked

Combined login/register modal with tab switching. Show it by setting the open attribute or calling .show().

<latch-auth-modal id="auth"></latch-auth-modal>
<button onclick="document.getElementById('auth').show()">
Sign in
</button>
AttributeDefaultDescription
openIf present, the modal is visible
tab"login"Initial tab: "login" or "register"
headingAutoHeading (defaults based on active tab)
show-name"true"Show name field in register tab
MethodDescription
.show(tab?)Open the modal. Optionally set the starting tab.
.hide()Close the modal.
EventDetailDescription
latch:login{ customer }Successful login
latch:register{ customer }Successful registration
latch:auth-modal:closeModal was closed

Shows a “Sign in” button when logged out, or a user dropdown with avatar and sign-out when logged in. Automatically updates when auth state changes.

<latch-user-menu
login-text="Sign in"
show-account-link="true"
></latch-user-menu>
AttributeDefaultDescription
login-text"Sign in"Button text when logged out
show-account-link"true"Show “Account” link in dropdown
avatar"true"Show initials avatar
EventDetailDescription
latch:show-loginSign in button clicked
latch:show-accountAccount link clicked
latch:logoutLogout completed

A styled paywall overlay. Trigger it from an access check result or manually.

<latch-paywall-modal
message="Subscribe to continue reading"
description="Get unlimited access to all content."
cta-text="Subscribe"
product-ids="prod-uuid-1,prod-uuid-2"
></latch-paywall-modal>
AttributeDefaultDescription
openIf present, the modal is visible
message"Subscribe to continue reading"Paywall heading
description"Get unlimited access..."Secondary text
cta-text"Subscribe"CTA button label
dismiss-text"Maybe later"Dismiss button label
dismissible"true"Show dismiss button
lock-icon"true"Show lock icon
product-idsComma-separated product IDs
meter-remainingIf set, shows meter info
EventDetailDescription
latch:subscribe{ productIds }CTA button clicked
latch:dismissDismiss button clicked

Fetches active products and prices from the API and renders a pricing grid. Each price has a checkout button that automatically redirects authenticated customers to Stripe Checkout.

<latch-pricing-table
heading="Choose your plan"
description="Cancel anytime. No commitment."
></latch-pricing-table>
AttributeDefaultDescription
heading"Choose your plan"Section heading
descriptionSubheading text
columns"auto"Grid columns: "auto", "1", "2", "3"
show-free"false"Show free-tier prices
EventDetailDescription
latch:checkout{ priceId, productId, productName }A price was selected
latch:pricing:loaded{ products }Products loaded from API
latch:pricing:error{ error }Failed to load products

Shows the logged-in customer’s profile, subscription status, and a “Manage subscription” button that opens the Stripe Customer Portal. When logged out, shows a sign-in prompt.

<latch-account-panel heading="Your account"></latch-account-panel>
AttributeDefaultDescription
heading"Your account"Panel heading
show-portal"true"Show “Manage subscription” button
EventDetailDescription
latch:show-loginSign in prompt clicked
latch:logoutLogout completed

All components support theming via CSS custom properties. Set them on the component element or any ancestor:

/* Override on a specific component */
latch-auth-modal {
--latch-primary: #e74c3c;
--latch-primary-hover: #c0392b;
--latch-radius: 16px;
}
/* Override globally */
:root {
--latch-primary: #2ecc71;
--latch-font: 'Georgia', serif;
}
PropertyDefaultDescription
--latch-primary#6C5CE7Primary brand color
--latch-primary-hover#5A4BD1Primary hover color
--latch-primary-text#ffffffText color on primary backgrounds
--latch-bg#ffffffComponent background
--latch-bg-secondary#f8f9faSecondary background
--latch-bg-hover#f1f3f5Hover background
--latch-text#0f172aPrimary text color
--latch-text-secondary#64748bSecondary text color
--latch-text-muted#94a3b8Muted text color
--latch-border#e2e8f0Border color
--latch-border-focus#6C5CE7Focus ring color
--latch-error#ef4444Error color
--latch-success#22c55eSuccess color
--latch-radius8pxBorder radius
--latch-radius-lg12pxLarge border radius
--latch-fontsystem-uiFont family
--latch-shadowBox shadow
--latch-overlayrgba(0,0,0,0.5)Modal overlay color

A complete publisher page with auth, paywalls, and pricing:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My Publication</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 24px; }
header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; }
/* Theme the components to match your brand */
:root { --latch-primary: #2563eb; }
</style>
</head>
<body>
<header>
<h1>My Publication</h1>
<latch-user-menu></latch-user-menu>
</header>
<!-- Auth modal (hidden by default) -->
<latch-auth-modal id="auth-modal"></latch-auth-modal>
<!-- Article content -->
<article>
<h2>Premium Article Title</h2>
<p>Article content goes here...</p>
</article>
<!-- Pricing section -->
<latch-pricing-table heading="Subscribe today"></latch-pricing-table>
<!-- Account section -->
<latch-account-panel></latch-account-panel>
<script type="module">
import { init, checkAccess, onAuthChange } from '@latch/sdk';
import '@latch/ui';
init({ apiKey: 'pk_...', apiUrl: 'https://your-api' });
// Wire up user menu -> auth modal
document.addEventListener('latch:show-login', () => {
document.getElementById('auth-modal').show('login');
});
// Re-check access after login
document.addEventListener('latch:login', async () => {
const result = await checkAccess();
if (!result.granted) {
// Show paywall or redirect
}
});
// Wire up pricing table -> auth modal (if not logged in)
document.addEventListener('latch:checkout', (e) => {
// If not authenticated, the pricing table won't auto-redirect.
// Open the auth modal so the customer can log in first.
const { isAuthenticated } = await import('@latch/sdk');
if (!isAuthenticated()) {
document.getElementById('auth-modal').show('register');
}
});
</script>
</body>
</html>

The components use Web Components (Custom Elements v1 + Shadow DOM v1), supported in all modern browsers:

  • Chrome/Edge 54+
  • Firefox 63+
  • Safari 10.1+

No polyfills are needed for any browser released after 2018.