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.
// 1. Initialize the SDK first
const { init } = await import ( ' @latch/sdk ' );
apiUrl: ' https://your-latch-api ' ,
// 2. Then import UI components — triggers branding fetch
await import ( ' @latch/ui ' );
Or import individual components:
import { LatchAuthModal, LatchUserMenu } from ' @latch/ui ' ;
Tip
Automatic branding : When @latch/ui is imported, it fetches your publication’s branding configuration from the API. Colors, logo, fonts, and custom text configured in Settings > Branding in the dashboard are applied automatically. See the Branding docs for details.
Standalone email + password login form.
Attribute Default Description heading"Sign in"Form heading text button-text"Sign in"Submit button label show-register"true"Show “Create account” link
Event Detail Description 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.
Attribute Default Description 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
Event Detail Description 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 () " >
Attribute Default Description open— If present, the modal is visible tab"login"Initial tab: "login" or "register" headingAuto Heading (defaults based on active tab) show-name"true"Show name field in register tab
Method Description .show(tab?)Open the modal. Optionally set the starting tab. .hide()Close the modal.
Event Detail Description latch:login{ customer }Successful login latch:register{ customer }Successful registration latch:auth-modal:close— Modal 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.
Attribute Default Description login-text"Sign in"Button text when logged out show-account-link"true"Show “Account” link in dropdown avatar"true"Show initials avatar
Event Detail Description latch:show-login— Sign in button clicked latch:show-account— Account link clicked latch:logout— Logout completed
A styled paywall overlay. Trigger it from an access check result or manually.
message = " Subscribe to continue reading "
description = " Get unlimited access to all content. "
product-ids = " prod-uuid-1,prod-uuid-2 "
Attribute Default Description open— If 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-ids— Comma-separated product IDs meter-remaining— If set, shows meter info
Event Detail Description latch:subscribe{ productIds }CTA button clicked latch:dismiss— Dismiss 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.
heading = " Choose your plan "
description = " Cancel anytime. No commitment. "
Attribute Default Description heading"Choose your plan"Section heading description— Subheading text columns"auto"Grid columns: "auto", "1", "2", "3" show-free"false"Show free-tier prices
Event Detail Description 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 >
Attribute Default Description heading"Your account"Panel heading show-portal"true"Show “Manage subscription” button
Event Detail Description latch:show-login— Sign in prompt clicked latch:logout— Logout completed
All components support theming via CSS custom properties. Set them on the component element or any ancestor:
/* Override on a specific component */
--latch-primary : # e74c3c ;
--latch-primary-hover : # c0392b ;
--latch-primary : # 2ecc71 ;
--latch-font : ' Georgia ' , serif ;
Property Default Description --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-shadow— Box shadow --latch-overlayrgba(0,0,0,0.5)Modal overlay color
A complete publisher page with auth, paywalls, and pricing:
< title > My Publication </ title >
body { font-family : system-ui , sans-serif ; max-width : 800 px ; margin : 0 auto ; padding : 24 px ; }
header { display : flex ; justify-content : space-between ; align-items : center ; margin-bottom : 32 px ; }
/* Theme the components to match your brand */
:root { --latch-primary : # 2563eb ; }
< latch-user-menu ></ latch-user-menu >
<!-- Auth modal (hidden by default) -->
< latch-auth-modal id = " auth-modal " ></ latch-auth-modal >
< h2 > Premium Article Title </ h2 >
< p > Article content goes here... </ p >
< latch-pricing-table heading = " Subscribe today " ></ latch-pricing-table >
< latch-account-panel ></ latch-account-panel >
import { init, checkAccess, onAuthChange } from ' @latch/sdk ' ;
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 ();
// 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 ' );
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.