Playground · every component is running

SDK component playground

All 33 components below are live instances of the real bundle. Connect a sandbox key and everything that can run live flips to LIVE — components that need an id get a guided picker fed by the same documented API call you'd make yourself. Source for every sample in vanilla, Lit, React, and Vue.

Start here

Install → hello world → go live

1 · Install. One script tag from the CDN — version-pinned, SRI-locked. This is the exact tag this page runs, so it is always current:

<script type="module" crossorigin="anonymous"
  integrity="sha384-C9MnCHscJp/a4a4nFVumGM98in+lDZ4/iftR/6rzLd17/wnoBtAQbMKbd/rcRO3N"
  src="https://cdn.ledrapay.com/v1.6.0/ledrapay-components.js"></script>

Or from the Ledra Pay npm registry:

# .npmrc
@ledrapay:registry=https://npm.ledrapay.com/

npm install @ledrapay/components

Full install guides: CDN → · npm →

2 · Hello world. Drop a tag on the page — that's the whole integration:

<ledrapay-payslip-viewer country="AU"></ledrapay-payslip-viewer>
<ledrapay-payslip-viewer country="AU">RUNNING · SAMPLE

No credentials → sample data, marked by the chip.

3 · Go live. Three steps: ① your backend mints an embed token — POST /v1/tenants/{id}/embed/token; ② the page sets it as a property, not an attribute — el.embedToken = token (keeps it out of the DOM); ③ provide el.tokenProvider = () => refresh() for silent renewal on expiry. Details: security model · embedding guide →

Need a key? sandbox.ledrapay.com issues sandbox keys.

↳ or try the DE governed demo — real German payroll from payrollengine.ch (uses your sandbox key)
sandbox keys only · the country is read from each tenant's jurisdiction (AU/DE) — components render the right locale automatically

Pattern

Component linking — list → detail

Components announce intent through bubbling, composed DOM events and never navigate for you. Click View on a run: the listener mounts ledrapay-pay-run-detail with the id from e.detail.

ledrapay-pay-runs-list → ledrapay-pay-run-detailRUNNING · SAMPLE
container.addEventListener('ledrapay-view-pay-run', (e) => {
  const detail = document.createElement('ledrapay-pay-run-detail');
  detail.setAttribute('pay-run-id', e.detail.id);     // { id } from the event
  detail.setAttribute('tenant-id', tenantId);          // same credentials flow through
  detail.embedToken = token;                           // property — stays out of the DOM
  container.appendChild(detail);
});
Other linking pairs — all live in this playground: employees-list emits ledrapay-view-employee and pay-run-detail emits ledrapay-employee-select → both mount ledrapay-employee-detail with employee-id; pay-runs-list / pay-run-detail emit ledrapay-view-pay-run → mount ledrapay-pay-run-detail. Click View on any list above and the linked detail mounts inline. Write actions (ledrapay-complete from onboarding, ledrapay-save) emit to your backend with a governance_reason — shown in the event console below.

Pattern

Leave flow — request → approve → booked

The request→approval lifecycle lives in LedraPay, above the engines — the payroll engine only ever sees leave taken. An employee applies (ledrapay-leave-request); a manager approves (ledrapay-leave-approvals), which books the leave to the bound engine with a cev_; the booked leave then surfaces in the review (ledrapay-leave-management). The three compose — submit a request below and watch it flow through.

leave-request → leave-approvals → leave-managementRUNNING · SAMPLE
How they link: ledrapay-leave-requested from the form refreshes the approvals queue — the new pending appears instantly (it's a control-plane read, no engine). ledrapay-leave-approved books the leave to the engine and refreshes the review (booked leave appears on the engine's next read). Reject / cancel never touch the engine. Every approval is one governed cev_.

Pattern

Pay-run lifecycle & resource compositions

A pay run moves through four states: draft → previewed → approved → finalised. preview runs a non-persistent engine calculation (totals + per-employee entries are cached on LedraPay's control plane); approve is a control-plane transition that writes an evidence envelope — no engine command; finalise commits a real engine pay-run job. On DE that is a live Payroll Engine payrun job (asynchronous): once the commit lands, the committed per-employee results are read back through the same …/pay-runs/{id}/entries endpoint.

Joined reads are served by resource compositions — one partner call, merged server-side and fully evidenced (no N+1 from the browser). payrun_entries returns committed payroll results enriched per-record with each employee's statutory wage-type lines and name; employee_profile returns the directory enriched with each employee's current case values (e.g. salary). Components consume these through the ordinary …/pay-runs/{id}/entries and …/employees endpoints — the composition runs server-side.

DE proof — a finalised governed run. One employee: gross €3,500.00 → tax €415.50 → social €740.25 → net €2,344.25, across 63 statutory wage-type lines (Lohnsteuer, SolZ, KiSt, KV/RV/AV/PV …), each carrying real cev_* evidence from payrollengine.ch. The DE governed demo renders exactly this run.

Pattern

Theming — CSS custom properties

Every visual token is a --ledrapay-* variable that pierces the Shadow DOM and inherits from the host page — the right-hand payslip is the same component inside a themed div, restyled with nothing but CSS variables.

.my-brand {
  --ledrapay-bg: #141a17;          --ledrapay-surface: #1b2420;
  --ledrapay-surface-alt: #212b26; --ledrapay-text: #f0ede4;
  --ledrapay-text-muted: #a3ada6;  --ledrapay-border: rgba(255,255,255,.12);
  --ledrapay-primary: #3db892;     --ledrapay-primary-hover: #5ecaa6;
  --ledrapay-accent: #d9a441;      --ledrapay-radius: 12px;
  --ledrapay-font: 'YourFont', sans-serif;
}
TokenDefault value
--ledrapay-primary#0e8a6d
--ledrapay-primary-hover#0b6f58
--ledrapay-accent#d9a441
--ledrapay-teal#0e8a6d
--ledrapay-teal-bg#e6f2ee
--ledrapay-teal-bdrgba(14,138,109,0.22)
--ledrapay-bg#ffffff
--ledrapay-surface#faf9f5
--ledrapay-surface-alt#f4f1ea
--ledrapay-row-alt#fcfbf8
--ledrapay-seg-track#ece9e1
--ledrapay-hairlinergba(28,27,25,0.06)
--ledrapay-text#26251f
--ledrapay-text-muted#6f6d64
--ledrapay-text-light#9a988e
--ledrapay-text-faint#adaa9f
--ledrapay-borderrgba(28,27,25,0.09)
--ledrapay-error#b04a3a
--ledrapay-success#0b6f58
--ledrapay-warning#9a6a10
--ledrapay-info#2f6df0
--ledrapay-red#b04a3a
--ledrapay-red-bg#faf0ee
--ledrapay-green#0b6f58
--ledrapay-green-bg#e6f2ee
--ledrapay-blue#2f6df0
--ledrapay-blue-bg#eef3fd
--ledrapay-amber#9a6a10
--ledrapay-amber-bg#faf3e2
--ledrapay-gold#d9a441
--ledrapay-gold-bg#faf3e2
--ledrapay-gold-bdrgba(217,164,65,0.3)
--ledrapay-purple#6d5ae0
--ledrapay-purple-bg#f2eefb
--ledrapay-font-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif
--ledrapay-font-display'Source Serif 4',Georgia,'Times New Roman',serif
--ledrapay-monoui-monospace,'SF Mono',Menlo,Consolas,monospace
--ledrapay-radius12px
--ledrapay-radius-sm9px
--ledrapay-radius-lg16px
--ledrapay-shadow0 1px 3px rgba(28,27,25,0.06)
--ledrapay-shadow-md0 10px 30px -18px rgba(28,27,25,0.35)
Defaults inject once at :root, so any ancestor (a wrapper div, :root, the host element) that redefines a token themes every component — see the brand showcase → (one component, four client brands).

Pattern

Internationalisation — translate the UI, reshape the flow

Two independent axes. Language (setLedraPayStrings) controls UI text + number/date formatting. Jurisdiction (country) controls the currency and statutory fields — because an employee is paid in their jurisdiction's money whatever language you read it in. Flip both below and watch them move separately:

Language
Jurisdiction ↳ German + Germany = €; German + Australia = AU$
import { setLedraPayStrings, clearLedraPayStrings, de, fr } from '@ledrapay/components';
setLedraPayStrings('*', de);    // flip the WHOLE UI to German (or fr) — every component
clearLedraPayStrings();         // …back to English
// or scope to a jurisdiction / one instance / a few keys:
setLedraPayStrings('de', de);   el.strings = { 'onboarding.continue': 'Weiter' };

Reshape a multi-step flow without forking via steps-config — reorder/subset the built-in steps, relabel them, or add custom steps with their own fields (captured into the create payload as metadata):

<ledrapay-employee-onboarding steps-config='[
  {"id":"personal"},
  {"id":"compliance","label":"Right to work","fields":[
    {"key":"visa_status","label":"Visa status","type":"select","options":["Citizen","Visa 482"],"required":true}]},
  {"id":"employment"}]'>

Shared

Inputs every component accepts

Attribute / propertyTypePurpose
countryAU | DE | NZ | UK | USLocale, currency, jurisdiction-specific fields (auto-derived from the tenant's jurisdiction when connected)
.strings / strings{ key: text }UI-string overrides for this instance (JSON). Wins over the global setLedraPayStrings() catalogue; unset keys fall back to English. See Internationalisation
steps-config / .stepsConfig[ { id, label?, fields? } ]Onboarding only — override the wizard flow (reorder/subset/relabel built-ins, or add custom steps). JSON
api-urlstring (default /v1)LedraPay API base; same-origin or your proxy
tenant-idten_*Tenant scope for live data
embed-token / .embedTokenJWTLive credential (scoped + expiring). Use the property in production. Absent → sample mode
.tokenProvider() ⇒ Promise<string>Refresh callback invoked on 401
with-credentialsBooleanCookie-session mode: fetches send credentials:'include', no bearer — your same-origin proxy authenticates
Mode rule: no credential → sample data with a visible banner (data-sample attribute set). Credential present → live fetch on connect. Set credentials before appending the element.

Pattern

Which frameworks work?

All of them — the components are standard custom elements with Shadow-DOM isolation and plain DOM events, so anything that renders HTML can host them. Every component section below has tabbed source for vanilla, Lit (recommended — same library the components are built with; cleanest property/event bindings), React (19+ has first-class custom-element support; earlier versions use a ref for properties/events, shown), and Vue 3 (flag the tags via isCustomElement; .prop modifier for the token).

Also worksNotes
AngularAdd CUSTOM_ELEMENTS_SCHEMA; bind events with (ledrapay-save), properties with [embedToken]
Svelte / SolidJSCustom elements work natively; on:ledrapay-save / prop:embedToken
Rails / Django / Laravel / HTMXServer-rendered pages: emit the tag, hydrate the token via a small inline script — no build step needed
No-code embeds (Webflow etc.)Script tag + element markup; mock mode for design, token wiring at publish

Pattern

Security model — sessions, cookies, tokens

Three supported auth patterns, strongest first. The constant across all of them: partner lp_* keys never reach a browser, and components hold credentials in memory.

PatternHowUse when
1 · Embed token via propertyBackend mints POST /v1/tenants/{id}/embed/token with its session-authenticated user; page sets el.embedToken = token (property — never serialized into the DOM) + el.tokenProvider for silent refreshRecommended default. Token is tenant-pinned, component-scoped, 15-min TTL
2 · Session-backed refreshSame as 1, but the page never receives a long-lived value at all: tokenProvider hits your endpoint, which is protected by your httpOnly session cookie, and returns a fresh embed token each timeYou already have a session layer and want zero token bootstrapping in markup
3 · Cookie-session proxywith-credentials attribute: components fetch with credentials:'include' and no bearer; your same-origin proxy validates its session cookie and injects the LedraPay credential server-sideStrict no-tokens-in-JS policies; everything rides your existing httpOnly cookie
Do / don't: set tokens as properties in production (attributes are visible in the DOM and DevTools — fine for demos, not for prod) · never put embed tokens in localStorage (they're short-lived by design; memory is enough) · scope tokens with the components claim when minting · serve over a CSP that pins connect-src to your api-url · the token is tenant-pinned server-side, so even a leaked token cannot cross tenants and dies in 15 minutes.

Pattern

Mobile — Flutter, iOS, Android, React Native

Straight answer: the components are Web Components (DOM + Shadow DOM), so they cannot render inside Flutter's widget tree or SwiftUI directly. They reuse cleanly in mobile apps through two supported routes: the WebView bridge (full component UI, ~an afternoon to integrate) or the native path — your own widgets calling the same documented API the components call (the API-transparency tables above are your spec). Most teams ship the WebView first and go native screen-by-screen where it matters.

RouteEffortUse when
WebView bridgeHours — one embed page + a message channelFull payroll UI fast; web + mobile share one implementation; theming via CSS vars still works
Native via APIPer-screenBrand-critical screens, offline caching, platform UX (e.g. payslip in a native sheet)

1 · The embed page (you host this — web and mobile share it)

<script type="module" crossorigin="anonymous" integrity="sha384-C9MnCHscJp/a4a4nFVumGM98in+lDZ4/iftR/6rzLd17/wnoBtAQbMKbd/rcRO3N" src="https://cdn.ledrapay.com/v1.6.0/ledrapay-components.js"></script>
<script>
  // one bridge for every platform
  const bridge = (type, detail) => {
    const msg = JSON.stringify({ type, detail });
    window.LedraPay?.postMessage(msg);                          // Flutter JavaScriptChannel
    window.webkit?.messageHandlers?.ledrapay?.postMessage(msg); // iOS WKScriptMessageHandler
    window.LedraPayAndroid?.postMessage(msg);                   // Android JavascriptInterface
  };
  ['ledrapay-view-pay-run','ledrapay-complete','ledrapay-save','ledrapay-download']
    .forEach(ev => document.addEventListener(ev, e => bridge(ev, e.detail)));

  // the native side calls this after minting a token on YOUR backend
  window.ledrapayConnect = (tenantId, token) => {
    const el = document.createElement('ledrapay-pay-runs-list');
    el.setAttribute('country', 'AU');
    el.setAttribute('api-url', 'https://api.ledrapay.com/v1');
    el.setAttribute('tenant-id', tenantId);
    el.embedToken = token;                       // property — never in markup
    el.tokenProvider = () => new Promise(res => { window.__freshToken = res; bridge('token-expired', {}); });
    document.body.replaceChildren(el);
  };
  window.ledrapayRefresh = (token) => window.__freshToken?.(token);
</script>

2 · Flutter (webview_flutter)

final controller = WebViewController()
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..addJavaScriptChannel('LedraPay', onMessageReceived: (msg) async {
    final event = jsonDecode(msg.message);
    switch (event['type']) {
      case 'ledrapay-view-pay-run':
        Navigator.push(context, MaterialPageRoute(            // or stay in the web UI
          builder: (_) => PayRunScreen(id: event['detail']['id'])));
      case 'token-expired':
        final t = await backend.mintEmbedToken();             // your server, your session
        controller.runJavaScript("window.ledrapayRefresh('$t')");
      case 'ledrapay-complete':
        await backend.submitEmployee(event['detail']);        // writes go via YOUR backend
    }
  })
  ..setNavigationDelegate(NavigationDelegate(onPageFinished: (_) async {
    final t = await backend.mintEmbedToken();
    controller.runJavaScript("window.ledrapayConnect('$tenantId', '$t')");
  }))
  ..loadRequest(Uri.parse('https://yourapp.example/payroll-embed'));

// in build(): WebViewWidget(controller: controller)

3 · iOS (Swift · WKWebView)

let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "ledrapay")
let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = self
webView.load(URLRequest(url: URL(string: "https://yourapp.example/payroll-embed")!))

func webView(_ wv: WKWebView, didFinish nav: WKNavigation!) {
  Task {
    let token = try await backend.mintEmbedToken()          // your server, your session
    try await wv.evaluateJavaScript("window.ledrapayConnect('(tenantId)', '(token)')")
  }
}

func userContentController(_ uc: WKUserContentController, didReceive m: WKScriptMessage) {
  guard m.name == "ledrapay", let body = m.body as? String,
        let ev = try? JSONDecoder().decode(BridgeEvent.self, from: Data(body.utf8)) else { return }
  switch ev.type {
  case "ledrapay-view-pay-run":
    navigationController?.pushViewController(PayRunVC(id: ev.detail.id), animated: true)
  case "token-expired":
    Task { let t = try await backend.mintEmbedToken()
           try await webView.evaluateJavaScript("window.ledrapayRefresh('(t)')") }
  default: break
  }
}

4 · The native path — same API, your widgets

// Dart — everything a component does, your code can do (see "Calls in live mode")
final res = await http.get(
  Uri.parse('$base/v1/tenants/$tenant/pay-runs?limit=20'),
  headers: {'Authorization': 'Bearer $embedToken'});
final runs = jsonDecode(res.body)['data'];   // → your ListView

// Swift
var req = URLRequest(url: URL(string: "(base)/v1/tenants/(tenant)/pay-runs?limit=20")!)
req.setValue("Bearer (embedToken)", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: req)
React Native: same WebView pattern via react-native-webview (onMessage + injectJavaScript) — RN has no DOM, so the components can't render in the RN tree itself.

Mobile security notes: mint embed tokens on your backend behind the app's own auth (the app never holds a partner key); inject via ledrapayConnect after page load — never in the URL (WebView URLs leak into logs); the token-expired bridge is the mobile equivalent of tokenProvider; pin the embed page to your domain and restrict the WebView's navigation; tokens stay in memory — never in app storage.

Pattern

Do you need to know which API calls a component makes?

No — and yes. No, in that components fully encapsulate their data access: you give them a token, they render. Yes, in that there is nothing hidden to trust: every component calls only the documented public API — open the network tab and you'll see exactly the endpoints in each component's Calls in live mode table. That matters for debugging (a failing component maps to one documented endpoint), permissions (scope tokens knowing what will be called), and replaceability (anything a component does, your code can do with the same API).

Component

<ledrapay-employee-onboarding>

Multi-step wizard: personal → tax → bank → super → employment. Alias: lp-employee-onboarding.

No attributes beyond the shared inputs.

Emits
ledrapay-completeCustomEvent (bubbles, composed) — e.detail = { employee, employeeId, country }
Calls in live modeLedraPay API endpoint
api.createEmployee() POST /v1/tenants/{t}/employees
api.supportsEffectiveDating() GET /v1/tenants/{t}/packs

Live mode: live write wizard — Complete calls createEmployee (create_full_employee composition, cev_); creates a real employee on the tenant.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-employee-onboarding country="AU"></ledrapay-employee-onboarding>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-employee-onboarding');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-complete', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-employee-onboarding
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        @ledrapay-complete=${(e) => this.oncomplete(e.detail)}>
      </ledrapay-employee-onboarding>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_complete = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-complete', on_ledrapay_complete);
    return () => { el.removeEventListener('ledrapay-complete', on_ledrapay_complete); };
  }, [token]);
  return <ledrapay-employee-onboarding ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-employee-onboarding
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-complete="(e) => console.log(e.detail)" />
</template>
<ledrapay-employee-onboarding country="AU">RUNNING · SAMPLE

Component

<ledrapay-employees-list>

Employee directory: stats row + roster table; effective-dated reads via as-of; terminate/reactivate row actions. Alias: lp-employees-list.

Own attributeTypePurpose
as-ofStringEffective-dated read (YYYY-MM-DD) — renders the roster/profile in force at that date · default: (today)
Emits
ledrapay-view-employeeCustomEvent (bubbles, composed) — e.detail = { id }
ledrapay-employee-terminatedCustomEvent (bubbles, composed) — e.detail = { id, evidence_id }
ledrapay-employee-reactivatedCustomEvent (bubbles, composed) — e.detail = { id, evidence_id }
Calls in live modeLedraPay API endpoint
api.listEmployees() GET /v1/tenants/{t}/employees
api.getCapabilities() GET /v1/tenants/{t}/packs
api.endEmployment() POST /v1/tenants/{t}/employees/{id}/end-employment
api.reactivateEmployee() POST /v1/tenants/{t}/employees/{id}/reactivate
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-employees-list country="AU" as-of="…"></ledrapay-employees-list>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-employees-list');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('as-of', as_of);
container.appendChild(el);

el.addEventListener('ledrapay-view-employee', (e) => console.log(e.detail));
el.addEventListener('ledrapay-employee-terminated', (e) => console.log(e.detail));
el.addEventListener('ledrapay-employee-reactivated', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-employees-list
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        as-of=${this.asOf}
        @ledrapay-view-employee=${(e) => this.onviewEmployee(e.detail)}
        @ledrapay-employee-terminated=${(e) => this.onemployeeTerminated(e.detail)}
        @ledrapay-employee-reactivated=${(e) => this.onemployeeReactivated(e.detail)}>
      </ledrapay-employees-list>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, asOf }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_view_employee = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-view-employee', on_ledrapay_view_employee);
    const on_ledrapay_employee_terminated = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-employee-terminated', on_ledrapay_employee_terminated);
    const on_ledrapay_employee_reactivated = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-employee-reactivated', on_ledrapay_employee_reactivated);
    return () => { el.removeEventListener('ledrapay-view-employee', on_ledrapay_view_employee); el.removeEventListener('ledrapay-employee-terminated', on_ledrapay_employee_terminated); el.removeEventListener('ledrapay-employee-reactivated', on_ledrapay_employee_reactivated); };
  }, [token]);
  return <ledrapay-employees-list ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} as-of={asOf} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'asOf']);
</script>

<template>
  <ledrapay-employees-list
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :as-of="asOf"
    @ledrapay-view-employee="(e) => console.log(e.detail)"
    @ledrapay-employee-terminated="(e) => console.log(e.detail)"
    @ledrapay-employee-reactivated="(e) => console.log(e.detail)" />
</template>
<ledrapay-employees-list country="AU">RUNNING · SAMPLE

Component

<ledrapay-employee-detail>

Single employee profile: identity, employment, pay, tax, super, bank (PII masked). Alias: lp-employee-detail.

Own attributeTypePurpose
employee-idStringEmployee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees
as-ofStringEffective-dated read (YYYY-MM-DD) — renders the roster/profile in force at that date · default: (today)
Emits
ledrapay-employee-terminatedCustomEvent (bubbles, composed) — e.detail = { id, evidence_id }
ledrapay-employee-reactivatedCustomEvent (bubbles, composed) — e.detail = { id, evidence_id }
Calls in live modeLedraPay API endpoint
api.getEmployee() GET /v1/tenants/{t}/employees/{id}
api.getCapabilities() GET /v1/tenants/{t}/packs
api.terminateEmployee() DELETE /v1/tenants/{t}/employees/{id}
api.endEmployment() POST /v1/tenants/{t}/employees/{id}/end-employment
api.reactivateEmployee() POST /v1/tenants/{t}/employees/{id}/reactivate
api.getEmployeeReadiness() GET /v1/tenants/{tenant}/employees/{id}/readiness
Where does employee-id come from? Get it from ledrapay-employee-select events — or GET /v1/tenants/{t}/employees → data[].id (emp_*, deterministic, safe to store).
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-employee-detail country="AU" employee-id="…" as-of="…"></ledrapay-employee-detail>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-employee-detail');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('employee-id', employee_id);
el.setAttribute('as-of', as_of);
container.appendChild(el);

el.addEventListener('ledrapay-employee-terminated', (e) => console.log(e.detail));
el.addEventListener('ledrapay-employee-reactivated', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-employee-detail
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        employee-id=${this.employeeId}
        as-of=${this.asOf}
        @ledrapay-employee-terminated=${(e) => this.onemployeeTerminated(e.detail)}
        @ledrapay-employee-reactivated=${(e) => this.onemployeeReactivated(e.detail)}>
      </ledrapay-employee-detail>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, employeeId, asOf }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_employee_terminated = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-employee-terminated', on_ledrapay_employee_terminated);
    const on_ledrapay_employee_reactivated = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-employee-reactivated', on_ledrapay_employee_reactivated);
    return () => { el.removeEventListener('ledrapay-employee-terminated', on_ledrapay_employee_terminated); el.removeEventListener('ledrapay-employee-reactivated', on_ledrapay_employee_reactivated); };
  }, [token]);
  return <ledrapay-employee-detail ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} employee-id={employeeId} as-of={asOf} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'employeeId', 'asOf']);
</script>

<template>
  <ledrapay-employee-detail
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :employee-id="employeeId"
    :as-of="asOf"
    @ledrapay-employee-terminated="(e) => console.log(e.detail)"
    @ledrapay-employee-reactivated="(e) => console.log(e.detail)" />
</template>
<ledrapay-employee-detail country="AU">RUNNING · SAMPLE

Component

<ledrapay-tax-declaration>

Jurisdiction-aware tax form (TFN declaration in AU). Alias: lp-tax-declaration.

Own attributeTypePurpose
employee-idStringEmployee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees
Emits
ledrapay-saveCustomEvent (bubbles, composed) — e.detail = { employeeId, country }
ledrapay-cancelCustomEvent (bubbles, composed) — e.detail = (no payload)
Calls in live modeLedraPay API endpoint
api.getEmployeeTaxSettings() GET /v1/tenants/{t}/employees/{id}/tax-settings
api.updateEmployeeTaxSettings() PATCH /v1/tenants/{t}/employees/{id}/tax-settings
Where does employee-id come from? Get it from ledrapay-employee-select events — or GET /v1/tenants/{t}/employees → data[].id (emp_*, deterministic, safe to store).
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-tax-declaration country="AU" employee-id="…"></ledrapay-tax-declaration>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-tax-declaration');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('employee-id', employee_id);
container.appendChild(el);

el.addEventListener('ledrapay-save', (e) => console.log(e.detail));
el.addEventListener('ledrapay-cancel', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-tax-declaration
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        employee-id=${this.employeeId}
        @ledrapay-save=${(e) => this.onsave(e.detail)}
        @ledrapay-cancel=${(e) => this.oncancel(e.detail)}>
      </ledrapay-tax-declaration>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, employeeId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_save = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-save', on_ledrapay_save);
    const on_ledrapay_cancel = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-cancel', on_ledrapay_cancel);
    return () => { el.removeEventListener('ledrapay-save', on_ledrapay_save); el.removeEventListener('ledrapay-cancel', on_ledrapay_cancel); };
  }, [token]);
  return <ledrapay-tax-declaration ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} employee-id={employeeId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'employeeId']);
</script>

<template>
  <ledrapay-tax-declaration
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :employee-id="employeeId"
    @ledrapay-save="(e) => console.log(e.detail)"
    @ledrapay-cancel="(e) => console.log(e.detail)" />
</template>
<ledrapay-tax-declaration country="AU">RUNNING · SAMPLE

Component

<ledrapay-bank-details>

Account capture with country routing formats. Alias: lp-bank-details.

Own attributeTypePurpose
employee-idStringEmployee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees
Emits
ledrapay-saveCustomEvent (bubbles, composed) — e.detail = { employeeId, country }
ledrapay-cancelCustomEvent (bubbles, composed) — e.detail = (no payload)
Calls in live modeLedraPay API endpoint
api.updateEmployeeBankAccounts() PATCH /v1/tenants/{t}/employees/{id}/bank-accounts
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-bank-details country="AU" employee-id="…"></ledrapay-bank-details>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-bank-details');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('employee-id', employee_id);
container.appendChild(el);

el.addEventListener('ledrapay-save', (e) => console.log(e.detail));
el.addEventListener('ledrapay-cancel', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-bank-details
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        employee-id=${this.employeeId}
        @ledrapay-save=${(e) => this.onsave(e.detail)}
        @ledrapay-cancel=${(e) => this.oncancel(e.detail)}>
      </ledrapay-bank-details>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, employeeId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_save = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-save', on_ledrapay_save);
    const on_ledrapay_cancel = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-cancel', on_ledrapay_cancel);
    return () => { el.removeEventListener('ledrapay-save', on_ledrapay_save); el.removeEventListener('ledrapay-cancel', on_ledrapay_cancel); };
  }, [token]);
  return <ledrapay-bank-details ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} employee-id={employeeId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'employeeId']);
</script>

<template>
  <ledrapay-bank-details
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :employee-id="employeeId"
    @ledrapay-save="(e) => console.log(e.detail)"
    @ledrapay-cancel="(e) => console.log(e.detail)" />
</template>
<ledrapay-bank-details country="AU">RUNNING · SAMPLE

Component

<ledrapay-super-form>

Retirement setup: fund, member number, salary sacrifice. Alias: lp-super-form.

Own attributeTypePurpose
employee-idStringEmployee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees
Emits
ledrapay-saveCustomEvent (bubbles, composed) — e.detail = { employeeId, country }
ledrapay-cancelCustomEvent (bubbles, composed) — e.detail = (no payload)
Calls in live modeLedraPay API endpoint
api.updateEmployeePension() PATCH /v1/tenants/{t}/employees/{id}/pension
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-super-form country="AU" employee-id="…"></ledrapay-super-form>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-super-form');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('employee-id', employee_id);
container.appendChild(el);

el.addEventListener('ledrapay-save', (e) => console.log(e.detail));
el.addEventListener('ledrapay-cancel', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-super-form
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        employee-id=${this.employeeId}
        @ledrapay-save=${(e) => this.onsave(e.detail)}
        @ledrapay-cancel=${(e) => this.oncancel(e.detail)}>
      </ledrapay-super-form>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, employeeId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_save = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-save', on_ledrapay_save);
    const on_ledrapay_cancel = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-cancel', on_ledrapay_cancel);
    return () => { el.removeEventListener('ledrapay-save', on_ledrapay_save); el.removeEventListener('ledrapay-cancel', on_ledrapay_cancel); };
  }, [token]);
  return <ledrapay-super-form ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} employee-id={employeeId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'employeeId']);
</script>

<template>
  <ledrapay-super-form
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :employee-id="employeeId"
    @ledrapay-save="(e) => console.log(e.detail)"
    @ledrapay-cancel="(e) => console.log(e.detail)" />
</template>
<ledrapay-super-form country="AU">RUNNING · SAMPLE

Component

<ledrapay-payroll-readiness>

Per-employee readiness: the required fields blocking finalisation, tab by tab — before the run fails, not during. Alias: lp-payroll-readiness.

Own attributeTypePurpose
employee-idStringEmployee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees
Calls in live modeLedraPay API endpoint
api.getEmployeeReadiness() GET /v1/tenants/{t}/employees/{id}/readiness
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-payroll-readiness country="AU" employee-id="…"></ledrapay-payroll-readiness>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-payroll-readiness');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('employee-id', employee_id);
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-payroll-readiness
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        employee-id=${this.employeeId}>
      </ledrapay-payroll-readiness>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, employeeId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-payroll-readiness ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} employee-id={employeeId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'employeeId']);
</script>

<template>
  <ledrapay-payroll-readiness
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :employee-id="employeeId" />
</template>
<ledrapay-payroll-readiness country="AU">RUNNING · SAMPLE

Component

<ledrapay-pay-runs-list>

Run history with latest-run stat cards and per-run table. Alias: lp-pay-runs-list.

No attributes beyond the shared inputs.

Emits
ledrapay-view-pay-runCustomEvent (bubbles, composed) — e.detail = { id }
ledrapay-pay-run-createdCustomEvent (bubbles, composed) — e.detail = { payRun }
Calls in live modeLedraPay API endpoint
api.listPayRuns() GET /v1/tenants/{t}/pay-runs
api.createPayRun() POST /v1/tenants/{t}/pay-runs
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-pay-runs-list country="AU"></ledrapay-pay-runs-list>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-pay-runs-list');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-view-pay-run', (e) => console.log(e.detail));
el.addEventListener('ledrapay-pay-run-created', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-pay-runs-list
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        @ledrapay-view-pay-run=${(e) => this.onviewPayRun(e.detail)}
        @ledrapay-pay-run-created=${(e) => this.onpayRunCreated(e.detail)}>
      </ledrapay-pay-runs-list>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_view_pay_run = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-view-pay-run', on_ledrapay_view_pay_run);
    const on_ledrapay_pay_run_created = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-pay-run-created', on_ledrapay_pay_run_created);
    return () => { el.removeEventListener('ledrapay-view-pay-run', on_ledrapay_view_pay_run); el.removeEventListener('ledrapay-pay-run-created', on_ledrapay_pay_run_created); };
  }, [token]);
  return <ledrapay-pay-runs-list ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-pay-runs-list
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-view-pay-run="(e) => console.log(e.detail)"
    @ledrapay-pay-run-created="(e) => console.log(e.detail)" />
</template>
<ledrapay-pay-runs-list country="AU">RUNNING · SAMPLE

Component

<ledrapay-pay-run-detail>

Single run: entries, totals, lifecycle actions. Alias: lp-pay-run-detail.

Own attributeTypePurpose
pay-run-idStringPay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs
Emits
ledrapay-view-pay-runCustomEvent (bubbles, composed) — e.detail = { id }
ledrapay-employee-selectCustomEvent (bubbles, composed) — e.detail = { employee } — cancelable: preventDefault() suppresses the built-in payslip preview
Calls in live modeLedraPay API endpoint
api.getPayRun() GET /v1/tenants/{t}/pay-runs/{id}
api.getPayRunEntries() GET /v1/tenants/{t}/pay-runs/{id}/entries
api.getPayRunExceptions() GET /v1/tenants/{t}/pay-runs/{id}/exceptions
api.governedAdjustmentBulk() POST /v1/tenants/{t}/pay-runs/{id}/adjust/bulk
api.voidPayRun() POST /v1/tenants/{t}/pay-runs/{id}/void
api.clonePayRun() POST /v1/tenants/{t}/pay-runs/{id}/clone
api.listEmployees() GET /v1/tenants/{t}/employees
api.getCompany() GET /v1/tenants/{t}/company
api.addEntry() POST /v1/tenants/{t}/pay-runs/{id}/entries
api.removeEntry() DELETE /v1/tenants/{t}/pay-runs/{id}/entries/{eid}
api.updateEntry() PATCH /v1/tenants/{t}/pay-runs/{id}/entries/{eid}
api.previewPayRun() POST /v1/tenants/{t}/pay-runs/{id}/preview
api.approvePayRun() POST /v1/tenants/{t}/pay-runs/{id}/approve
api.finalisePayRun() POST /v1/tenants/{t}/pay-runs/{id}/finalise
api.submitSTP() POST /v1/tenants/{t}/pay-runs/{id}/submit
Where does pay-run-id come from? Get it from a list component's ledrapay-view-pay-run event (e.detail.id) — or call GET /v1/tenants/{t}/pay-runs and use data[].id. The dropdown does exactly that call.
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-pay-run-detail country="AU" pay-run-id="…"></ledrapay-pay-run-detail>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-pay-run-detail');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('pay-run-id', pay_run_id);
container.appendChild(el);

el.addEventListener('ledrapay-view-pay-run', (e) => console.log(e.detail));
el.addEventListener('ledrapay-employee-select', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-pay-run-detail
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        pay-run-id=${this.payRunId}
        @ledrapay-view-pay-run=${(e) => this.onviewPayRun(e.detail)}
        @ledrapay-employee-select=${(e) => this.onemployeeSelect(e.detail)}>
      </ledrapay-pay-run-detail>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, payRunId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_view_pay_run = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-view-pay-run', on_ledrapay_view_pay_run);
    const on_ledrapay_employee_select = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-employee-select', on_ledrapay_employee_select);
    return () => { el.removeEventListener('ledrapay-view-pay-run', on_ledrapay_view_pay_run); el.removeEventListener('ledrapay-employee-select', on_ledrapay_employee_select); };
  }, [token]);
  return <ledrapay-pay-run-detail ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} pay-run-id={payRunId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'payRunId']);
</script>

<template>
  <ledrapay-pay-run-detail
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :pay-run-id="payRunId"
    @ledrapay-view-pay-run="(e) => console.log(e.detail)"
    @ledrapay-employee-select="(e) => console.log(e.detail)" />
</template>
<ledrapay-pay-run-detail country="AU">RUNNING · SAMPLE

Component

<ledrapay-pay-run-entry>

Inline entry editor: pay items, hours × rates. Alias: lp-pay-run-entry.

Own attributeTypePurpose
pay-run-idStringPay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs
entry-idStringPay-run entry id — from GET …/pay-runs/{id}/entries (pick a run first)
Emits
ledrapay-saveCustomEvent (bubbles, composed) — e.detail = { entryId } (live) · { employee } (sample)
ledrapay-cancelCustomEvent (bubbles, composed) — e.detail = (no payload)
Calls in live modeLedraPay API endpoint
api.getPayRunEntries() GET /v1/tenants/{t}/pay-runs/{id}/entries
api.updateEntry() PATCH /v1/tenants/{t}/pay-runs/{id}/entries/{eid}
api.governedAdjustment() POST /v1/tenants/{t}/pay-runs/{id}/entries/{eid}/adjust
api.getPayItems() GET /v1/tenants/{t}/pay-items
Where does pay-run-id come from? Get it from a list component's ledrapay-view-pay-run event (e.detail.id) — or call GET /v1/tenants/{t}/pay-runs and use data[].id. The dropdown does exactly that call.
Where does entry-id come from? Call GET /v1/tenants/{t}/pay-runs/{id}/entries → data[].id (pick a run first).
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-pay-run-entry country="AU" pay-run-id="…" entry-id="…"></ledrapay-pay-run-entry>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-pay-run-entry');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('pay-run-id', pay_run_id);
el.setAttribute('entry-id', entry_id);
container.appendChild(el);

el.addEventListener('ledrapay-save', (e) => console.log(e.detail));
el.addEventListener('ledrapay-cancel', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-pay-run-entry
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        pay-run-id=${this.payRunId}
        entry-id=${this.entryId}
        @ledrapay-save=${(e) => this.onsave(e.detail)}
        @ledrapay-cancel=${(e) => this.oncancel(e.detail)}>
      </ledrapay-pay-run-entry>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, payRunId, entryId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_save = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-save', on_ledrapay_save);
    const on_ledrapay_cancel = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-cancel', on_ledrapay_cancel);
    return () => { el.removeEventListener('ledrapay-save', on_ledrapay_save); el.removeEventListener('ledrapay-cancel', on_ledrapay_cancel); };
  }, [token]);
  return <ledrapay-pay-run-entry ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} pay-run-id={payRunId} entry-id={entryId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'payRunId', 'entryId']);
</script>

<template>
  <ledrapay-pay-run-entry
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :pay-run-id="payRunId"
    :entry-id="entryId"
    @ledrapay-save="(e) => console.log(e.detail)"
    @ledrapay-cancel="(e) => console.log(e.detail)" />
</template>
<ledrapay-pay-run-entry country="AU">RUNNING · SAMPLE

Component

<ledrapay-payrun-exceptions>

Roster-to-Pay assurance: per-run exception report — each control is one way work and pay can mismatch. Alias: lp-payrun-exceptions.

Own attributeTypePurpose
pay-run-idStringPay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs
governance-urlStringLink target for evidence ids in the exception report
Emits
ledrapay-exceptions-verdictCustomEvent (bubbles, composed) — e.detail = { verdict, summary, payRunId }
Calls in live modeLedraPay API endpoint
api.getPayRunExceptions() GET /v1/tenants/{t}/pay-runs/{id}/exceptions
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-payrun-exceptions country="AU" pay-run-id="…" governance-url="…"></ledrapay-payrun-exceptions>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-payrun-exceptions');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('pay-run-id', pay_run_id);
el.setAttribute('governance-url', governance_url);
container.appendChild(el);

el.addEventListener('ledrapay-exceptions-verdict', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-payrun-exceptions
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        pay-run-id=${this.payRunId}
        governance-url=${this.governanceUrl}
        @ledrapay-exceptions-verdict=${(e) => this.onexceptionsVerdict(e.detail)}>
      </ledrapay-payrun-exceptions>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, payRunId, governanceUrl }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_exceptions_verdict = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-exceptions-verdict', on_ledrapay_exceptions_verdict);
    return () => { el.removeEventListener('ledrapay-exceptions-verdict', on_ledrapay_exceptions_verdict); };
  }, [token]);
  return <ledrapay-payrun-exceptions ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} pay-run-id={payRunId} governance-url={governanceUrl} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'payRunId', 'governanceUrl']);
</script>

<template>
  <ledrapay-payrun-exceptions
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :pay-run-id="payRunId"
    :governance-url="governanceUrl"
    @ledrapay-exceptions-verdict="(e) => console.log(e.detail)" />
</template>
<ledrapay-payrun-exceptions country="AU">RUNNING · SAMPLE

Component

<ledrapay-payroll-calendar>

Pay schedule builder. Alias: lp-payroll-calendar.

No attributes beyond the shared inputs.

Emits
ledrapay-saveCustomEvent (bubbles, composed) — e.detail = the schedule config object (frequency, anchor date, …)
Calls in live modeLedraPay API endpoint
api.listPayRuns() GET /v1/tenants/{t}/pay-runs
api.getCompany() GET /v1/tenants/{t}/company
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-payroll-calendar country="AU"></ledrapay-payroll-calendar>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-payroll-calendar');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-save', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-payroll-calendar
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        @ledrapay-save=${(e) => this.onsave(e.detail)}>
      </ledrapay-payroll-calendar>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_save = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-save', on_ledrapay_save);
    return () => { el.removeEventListener('ledrapay-save', on_ledrapay_save); };
  }, [token]);
  return <ledrapay-payroll-calendar ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-payroll-calendar
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-save="(e) => console.log(e.detail)" />
</template>
<ledrapay-payroll-calendar country="AU">RUNNING · SAMPLE

Component

<ledrapay-payslip-viewer>

Compliant payslip: earnings, deductions, super, leave balances, YTD. Alias: lp-payslip-viewer.

Own attributeTypePurpose
show-ytdBooleanShow the year-to-date column on the payslip · default: true
show-leaveBooleanShow leave balances on the payslip · default: true
show-governanceBooleanShow the governance/evidence strip · default: true
pay-run-idStringPay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs
Emits
ledrapay-printCustomEvent (bubbles, composed) — e.detail = (no payload)
ledrapay-downloadCustomEvent (bubbles, composed) — e.detail = (no payload)
Calls in live modeLedraPay API endpoint
api.listPayRuns() GET /v1/tenants/{t}/pay-runs
api.listPayslips() GET /v1/tenants/{t}/pay-runs/{id}/payslips
Where does pay-run-id come from? Get it from a list component's ledrapay-view-pay-run event (e.detail.id) — or call GET /v1/tenants/{t}/pay-runs and use data[].id. The dropdown does exactly that call.

Live mode: engine payslips exist for finalised runs; a previewed run may return none.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-payslip-viewer country="AU" show-ytd show-leave show-governance pay-run-id="…"></ledrapay-payslip-viewer>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-payslip-viewer');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('pay-run-id', pay_run_id);
container.appendChild(el);

el.addEventListener('ledrapay-print', (e) => console.log(e.detail));
el.addEventListener('ledrapay-download', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-payslip-viewer
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        pay-run-id=${this.payRunId}
        ?show-ytd=${true}
        ?show-leave=${true}
        ?show-governance=${true}
        @ledrapay-print=${(e) => this.onprint(e.detail)}
        @ledrapay-download=${(e) => this.ondownload(e.detail)}>
      </ledrapay-payslip-viewer>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, payRunId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_print = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-print', on_ledrapay_print);
    const on_ledrapay_download = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-download', on_ledrapay_download);
    return () => { el.removeEventListener('ledrapay-print', on_ledrapay_print); el.removeEventListener('ledrapay-download', on_ledrapay_download); };
  }, [token]);
  return <ledrapay-payslip-viewer ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} pay-run-id={payRunId} show-ytd="" show-leave="" show-governance="" />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'payRunId']);
</script>

<template>
  <ledrapay-payslip-viewer
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :pay-run-id="payRunId"
    @ledrapay-print="(e) => console.log(e.detail)"
    @ledrapay-download="(e) => console.log(e.detail)" />
</template>
<ledrapay-payslip-viewer country="AU">RUNNING · SAMPLE

Component

<ledrapay-pay-item-library>

Earnings/deduction catalogue with multipliers. Alias: lp-pay-item-library.

No attributes beyond the shared inputs.

Calls in live modeLedraPay API endpoint
api.getPayItemTypes() GET /v1/tenants/{t}/pay-item-types
api.getPayItems() GET /v1/tenants/{t}/pay-items
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-pay-item-library country="AU"></ledrapay-pay-item-library>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-pay-item-library');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-pay-item-library
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}>
      </ledrapay-pay-item-library>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-pay-item-library ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-pay-item-library
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token" />
</template>
<ledrapay-pay-item-library country="AU">RUNNING · SAMPLE

Component

<ledrapay-payments>

The last mile: the engine-generated ABA net-pay file per finalised run — surfaced and downloaded, never fabricated. Alias: lp-payments.

Own attributeTypePurpose
pay-run-idStringPay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs
Emits
ledrapay-downloadCustomEvent (bubbles, composed) — e.detail = { payRunId, kind: 'aba' }
ledrapay-super-batchCustomEvent (bubbles, composed) — e.detail = { payRunId, batch }
Calls in live modeLedraPay API endpoint
api.getPaymentFile() GET /v1/tenants/{t}/pay-runs/{id}/payment-file
api.listPayRuns() GET /v1/tenants/{t}/pay-runs
api.createSuperFromPayRun() POST /v1/tenants/{t}/pay-runs/{id}/super
api.listSuperBatches() GET /v1/tenants/{t}/superannuation
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-payments country="AU" pay-run-id="…"></ledrapay-payments>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-payments');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('pay-run-id', pay_run_id);
container.appendChild(el);

el.addEventListener('ledrapay-download', (e) => console.log(e.detail));
el.addEventListener('ledrapay-super-batch', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-payments
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        pay-run-id=${this.payRunId}
        @ledrapay-download=${(e) => this.ondownload(e.detail)}
        @ledrapay-super-batch=${(e) => this.onsuperBatch(e.detail)}>
      </ledrapay-payments>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, payRunId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_download = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-download', on_ledrapay_download);
    const on_ledrapay_super_batch = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-super-batch', on_ledrapay_super_batch);
    return () => { el.removeEventListener('ledrapay-download', on_ledrapay_download); el.removeEventListener('ledrapay-super-batch', on_ledrapay_super_batch); };
  }, [token]);
  return <ledrapay-payments ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} pay-run-id={payRunId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'payRunId']);
</script>

<template>
  <ledrapay-payments
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :pay-run-id="payRunId"
    @ledrapay-download="(e) => console.log(e.detail)"
    @ledrapay-super-batch="(e) => console.log(e.detail)" />
</template>
<ledrapay-payments country="AU">RUNNING · SAMPLE

Component

<ledrapay-leave-request>

Employee self-service: apply for leave, track requests. Creates pending requests only — no engine touch. Alias: lp-leave-request.

No attributes beyond the shared inputs.

Emits
ledrapay-leave-requestedCustomEvent (bubbles, composed) — e.detail = { employee_id, leave_type, start_date, end_date, quantity, unit, reason? }
Calls in live modeLedraPay API endpoint
api.getLeaveTypes() GET /v1/tenants/{t}/reference/leave-types
api.listEmployees() GET /v1/tenants/{t}/employees
api.listLeaveRequests() GET /v1/tenants/{t}/leave/requests
api.applyLeaveRequest() POST /v1/tenants/{t}/leave/requests
api.cancelLeaveRequest() POST /v1/tenants/{t}/leave/requests/{id}/cancel
api.getLeaveBalances() GET /v1/tenants/{tenant}/leave-balances
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-leave-request country="AU"></ledrapay-leave-request>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-leave-request');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-leave-requested', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-leave-request
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        @ledrapay-leave-requested=${(e) => this.onleaveRequested(e.detail)}>
      </ledrapay-leave-request>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_leave_requested = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-leave-requested', on_ledrapay_leave_requested);
    return () => { el.removeEventListener('ledrapay-leave-requested', on_ledrapay_leave_requested); };
  }, [token]);
  return <ledrapay-leave-request ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-leave-request
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-leave-requested="(e) => console.log(e.detail)" />
</template>
<ledrapay-leave-request country="AU">RUNNING · SAMPLE

Component

<ledrapay-leave-approvals>

Manager queue: approve (books to the engine, governed cev_) or reject pending requests. Alias: lp-leave-approvals.

No attributes beyond the shared inputs.

Emits
ledrapay-leave-approvedCustomEvent (bubbles, composed) — e.detail = { id }
ledrapay-leave-rejectedCustomEvent (bubbles, composed) — e.detail = { id }
Calls in live modeLedraPay API endpoint
api.listLeaveRequests() GET /v1/tenants/{t}/leave/requests?status=pending
api.approveLeaveRequest() POST /v1/tenants/{t}/leave/requests/{id}/approve
api.rejectLeaveRequest() POST /v1/tenants/{t}/leave/requests/{id}/reject
api.getLeaveBalances() GET /v1/tenants/{tenant}/leave-balances
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-leave-approvals country="AU"></ledrapay-leave-approvals>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-leave-approvals');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-leave-approved', (e) => console.log(e.detail));
el.addEventListener('ledrapay-leave-rejected', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-leave-approvals
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        @ledrapay-leave-approved=${(e) => this.onleaveApproved(e.detail)}
        @ledrapay-leave-rejected=${(e) => this.onleaveRejected(e.detail)}>
      </ledrapay-leave-approvals>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_leave_approved = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-leave-approved', on_ledrapay_leave_approved);
    const on_ledrapay_leave_rejected = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-leave-rejected', on_ledrapay_leave_rejected);
    return () => { el.removeEventListener('ledrapay-leave-approved', on_ledrapay_leave_approved); el.removeEventListener('ledrapay-leave-rejected', on_ledrapay_leave_rejected); };
  }, [token]);
  return <ledrapay-leave-approvals ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-leave-approvals
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-leave-approved="(e) => console.log(e.detail)"
    @ledrapay-leave-rejected="(e) => console.log(e.detail)" />
</template>
<ledrapay-leave-approvals country="AU">RUNNING · SAMPLE

Component

<ledrapay-leave-management>

Engine leave-type catalogue, accrual balances, entitlement config. Alias: lp-leave-management.

No attributes beyond the shared inputs.

Emits
ledrapay-saveCustomEvent (bubbles, composed) — e.detail = { country }
ledrapay-cancelCustomEvent (bubbles, composed) — e.detail = (no payload)
ledrapay-leave-bookedCustomEvent (bubbles, composed) — e.detail = the booked-leave record (engine response data)
ledrapay-leave-cancelledCustomEvent (bubbles, composed) — e.detail = { id }
ledrapay-leave-amendedCustomEvent (bubbles, composed) — e.detail = { id, quantity }
Calls in live modeLedraPay API endpoint
api.getLeaveTypes() GET /v1/tenants/{t}/reference/leave-types
api.getLeaveBalances() GET /v1/tenants/{t}/leave-balances
api.getLeaveTaken() GET /v1/tenants/{t}/leave
api.listEmployees() GET /v1/tenants/{t}/employees
api.getCapabilities() GET /v1/tenants/{t}/packs
api.bookLeave() POST /v1/tenants/{t}/leave/book
api.cancelLeave() POST /v1/tenants/{t}/leave/{id}/cancel
api.amendLeave() POST /v1/tenants/{t}/leave/{id}/amend
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-leave-management country="AU"></ledrapay-leave-management>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-leave-management');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-save', (e) => console.log(e.detail));
el.addEventListener('ledrapay-cancel', (e) => console.log(e.detail));
el.addEventListener('ledrapay-leave-booked', (e) => console.log(e.detail));
el.addEventListener('ledrapay-leave-cancelled', (e) => console.log(e.detail));
el.addEventListener('ledrapay-leave-amended', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-leave-management
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        @ledrapay-save=${(e) => this.onsave(e.detail)}
        @ledrapay-cancel=${(e) => this.oncancel(e.detail)}
        @ledrapay-leave-booked=${(e) => this.onleaveBooked(e.detail)}
        @ledrapay-leave-cancelled=${(e) => this.onleaveCancelled(e.detail)}
        @ledrapay-leave-amended=${(e) => this.onleaveAmended(e.detail)}>
      </ledrapay-leave-management>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_save = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-save', on_ledrapay_save);
    const on_ledrapay_cancel = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-cancel', on_ledrapay_cancel);
    const on_ledrapay_leave_booked = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-leave-booked', on_ledrapay_leave_booked);
    const on_ledrapay_leave_cancelled = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-leave-cancelled', on_ledrapay_leave_cancelled);
    const on_ledrapay_leave_amended = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-leave-amended', on_ledrapay_leave_amended);
    return () => { el.removeEventListener('ledrapay-save', on_ledrapay_save); el.removeEventListener('ledrapay-cancel', on_ledrapay_cancel); el.removeEventListener('ledrapay-leave-booked', on_ledrapay_leave_booked); el.removeEventListener('ledrapay-leave-cancelled', on_ledrapay_leave_cancelled); el.removeEventListener('ledrapay-leave-amended', on_ledrapay_leave_amended); };
  }, [token]);
  return <ledrapay-leave-management ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-leave-management
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-save="(e) => console.log(e.detail)"
    @ledrapay-cancel="(e) => console.log(e.detail)"
    @ledrapay-leave-booked="(e) => console.log(e.detail)"
    @ledrapay-leave-cancelled="(e) => console.log(e.detail)"
    @ledrapay-leave-amended="(e) => console.log(e.detail)" />
</template>
<ledrapay-leave-management country="AU">RUNNING · SAMPLE

Component

<ledrapay-org-setup>

Company, sites, departments. Alias: lp-org-setup.

No attributes beyond the shared inputs.

Emits
ledrapay-saveCustomEvent (bubbles, composed) — e.detail = (no payload)
ledrapay-cancelCustomEvent (bubbles, composed) — e.detail = (no payload)
Calls in live modeLedraPay API endpoint
api.getCompany() GET /v1/tenants/{t}/company
api.getCompanyBank() GET /v1/tenants/{t}/company/bank
api.setCompanyBank() PATCH /v1/tenants/{t}/company/bank
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-org-setup country="AU"></ledrapay-org-setup>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-org-setup');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-save', (e) => console.log(e.detail));
el.addEventListener('ledrapay-cancel', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-org-setup
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        @ledrapay-save=${(e) => this.onsave(e.detail)}
        @ledrapay-cancel=${(e) => this.oncancel(e.detail)}>
      </ledrapay-org-setup>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_save = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-save', on_ledrapay_save);
    const on_ledrapay_cancel = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-cancel', on_ledrapay_cancel);
    return () => { el.removeEventListener('ledrapay-save', on_ledrapay_save); el.removeEventListener('ledrapay-cancel', on_ledrapay_cancel); };
  }, [token]);
  return <ledrapay-org-setup ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-org-setup
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-save="(e) => console.log(e.detail)"
    @ledrapay-cancel="(e) => console.log(e.detail)" />
</template>
<ledrapay-org-setup country="AU">RUNNING · SAMPLE

Component

<ledrapay-cost-centres>

Cost allocation. Alias: lp-cost-centres.

No attributes beyond the shared inputs.

Calls in live modeLedraPay API endpoint
api.getCostCentres() GET /v1/tenants/{t}/cost-centres
api.createCostCentre() POST /v1/tenants/{t}/cost-centres
api.updateCostCentre() PATCH /v1/tenants/{t}/cost-centres/{code}
api.archiveCostCentre() DELETE /v1/tenants/{t}/cost-centres/{code}
api.allocateEmployeeCostCentre() PUT /v1/tenants/{t}/employees/{id}/cost-centre
api.clearEmployeeCostCentre() DELETE /v1/tenants/{t}/employees/{id}/cost-centre
api.getCostCentreMappings() GET /v1/tenants/{t}/cost-centre-mappings
api.createCostCentreMapping() POST /v1/tenants/{t}/cost-centre-mappings
api.deleteCostCentreMapping() DELETE /v1/tenants/{t}/cost-centre-mappings/{id}
api.listEmployees() GET /v1/tenants/{t}/employees
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-cost-centres country="AU"></ledrapay-cost-centres>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-cost-centres');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-cost-centres
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}>
      </ledrapay-cost-centres>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-cost-centres ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-cost-centres
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token" />
</template>
<ledrapay-cost-centres country="AU">RUNNING · SAMPLE

Component

<ledrapay-requirements>

Compliance/readiness checklist. Alias: lp-requirements.

No attributes beyond the shared inputs.

Calls in live modeLedraPay API endpoint
api.getCapabilities() GET /v1/tenants/{t}/packs
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-requirements country="AU"></ledrapay-requirements>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-requirements');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-requirements
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}>
      </ledrapay-requirements>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-requirements ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-requirements
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token" />
</template>
<ledrapay-requirements country="AU">RUNNING · SAMPLE

Component

<ledrapay-award-config>

Award rate tables and classifications. Alias: lp-award-config.

No attributes beyond the shared inputs.

Calls in live modeLedraPay API endpoint
api.getAwards() GET /v1/reference/awards
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-award-config country="AU"></ledrapay-award-config>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-award-config');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-award-config
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}>
      </ledrapay-award-config>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-award-config ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-award-config
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token" />
</template>
<ledrapay-award-config country="AU">RUNNING · SAMPLE

Component

<ledrapay-payroll-checklist>

Jurisdiction-keyed payroll-setup checklist; items deep-link to guide.ledrapay.com. Client-side only, no API. Alias: lp-payroll-checklist.

Own attributeTypePurpose
guide-baseStringBase URL for checklist deep-links (default guide.ledrapay.com) · default: https://guide.ledrapay.com
Emits
ledrapay-checkCustomEvent (bubbles, composed) — e.detail = { id, done }

Display/form component — no API calls of its own; emits its result for your code to submit.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-payroll-checklist country="AU" guide-base="…"></ledrapay-payroll-checklist>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-payroll-checklist');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('guide-base', guide_base);
container.appendChild(el);

el.addEventListener('ledrapay-check', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-payroll-checklist
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        guide-base=${this.guideBase}
        @ledrapay-check=${(e) => this.oncheck(e.detail)}>
      </ledrapay-payroll-checklist>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, guideBase }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_check = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-check', on_ledrapay_check);
    return () => { el.removeEventListener('ledrapay-check', on_ledrapay_check); };
  }, [token]);
  return <ledrapay-payroll-checklist ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} guide-base={guideBase} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'guideBase']);
</script>

<template>
  <ledrapay-payroll-checklist
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :guide-base="guideBase"
    @ledrapay-check="(e) => console.log(e.detail)" />
</template>
<ledrapay-payroll-checklist country="AU">RUNNING · SAMPLE

Component

<ledrapay-governance-trail>

Evidence ledger — receipts per action. Alias: lp-governance-trail.

No attributes beyond the shared inputs.

Calls in live modeLedraPay API endpoint
api.getAuditTrail() GET /v1/tenants/{t}/governance/audit-trail
api.verifyChain() POST /v1/tenants/{t}/governance/verify
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-governance-trail country="AU"></ledrapay-governance-trail>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-governance-trail');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-governance-trail
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}>
      </ledrapay-governance-trail>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-governance-trail ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-governance-trail
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token" />
</template>
<ledrapay-governance-trail country="AU">RUNNING · SAMPLE

Component

<ledrapay-stp-submission>

Lodgement status and authority responses. Alias: lp-stp-submission.

Own attributeTypePurpose
pay-run-idStringPay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs
Calls in live modeLedraPay API endpoint
api.listSubmissions() GET /v1/tenants/{t}/pay-runs/{id}/submissions
api.getSubmission() GET /v1/tenants/{t}/pay-runs/{id}/submissions/{submissionId}
api.submitSTP() POST /v1/tenants/{t}/pay-runs/{id}/submit
api.getCompany() GET /v1/tenants/{t}/company
Where does pay-run-id come from? Get it from a list component's ledrapay-view-pay-run event (e.detail.id) — or call GET /v1/tenants/{t}/pay-runs and use data[].id. The dropdown does exactly that call.

Live mode: STP lodgement is per pay run — read.stp_history; empty until a run is lodged.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-stp-submission country="AU" pay-run-id="…"></ledrapay-stp-submission>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-stp-submission');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('pay-run-id', pay_run_id);
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-stp-submission
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        pay-run-id=${this.payRunId}>
      </ledrapay-stp-submission>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, payRunId }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-stp-submission ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} pay-run-id={payRunId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'payRunId']);
</script>

<template>
  <ledrapay-stp-submission
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :pay-run-id="payRunId" />
</template>
<ledrapay-stp-submission country="AU">RUNNING · SAMPLE

Component

<ledrapay-super-overview>

FY superannuation obligations: reported vs paid vs outstanding vs overdue, per employee. Alias: lp-super-overview.

Own attributeTypePurpose
financial-yearNumberFinancial year to report (e.g. 2026)
Calls in live modeLedraPay API endpoint
api.getSuperannuationOverview() GET /v1/tenants/{t}/superannuation/overview?financialYear={fy}
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-super-overview country="AU" financial-year="…"></ledrapay-super-overview>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-super-overview');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('financial-year', financial_year);
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-super-overview
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        financial-year=${this.financialYear}>
      </ledrapay-super-overview>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, financialYear }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-super-overview ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} financial-year={financialYear} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'financialYear']);
</script>

<template>
  <ledrapay-super-overview
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :financial-year="financialYear" />
</template>
<ledrapay-super-overview country="AU">RUNNING · SAMPLE

Component

<ledrapay-dashboard>

Summary stats: workers, MTD gross, compliance. Alias: lp-dashboard.

No attributes beyond the shared inputs.

Calls in live modeLedraPay API endpoint
api.listEmployees() GET /v1/tenants/{t}/employees
api.listPayRuns() GET /v1/tenants/{t}/pay-runs
api.getAuditTrail() GET /v1/tenants/{t}/governance/audit-trail
api.getReportActivity() GET /v1/tenants/{t}/reports/activity
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-dashboard country="AU"></ledrapay-dashboard>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-dashboard');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-dashboard
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}>
      </ledrapay-dashboard>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-dashboard ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-dashboard
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token" />
</template>
<ledrapay-dashboard country="AU">RUNNING · SAMPLE

Component

<ledrapay-reports-dashboard>

Report generation and download. Alias: lp-reports-dashboard.

No attributes beyond the shared inputs.

Calls in live modeLedraPay API endpoint
api.getReportSummary() GET /v1/tenants/{t}/reports/summary
api.getReportActivity() GET /v1/tenants/{t}/reports/activity
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-reports-dashboard country="AU"></ledrapay-reports-dashboard>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-reports-dashboard');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-reports-dashboard
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}>
      </ledrapay-reports-dashboard>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-reports-dashboard ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-reports-dashboard
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token" />
</template>
<ledrapay-reports-dashboard country="AU">RUNNING · SAMPLE

Component

<ledrapay-kpi-tile>

Single stat tile: label, value, sub-line, optional trend. Presentational — compose rows from your own data. Alias: lp-kpi-tile.

Own attributeTypePurpose
labelString
valueString
subString
trendString
deltaString
loadingBooleandefault: false
accentStringdefault: ink

Display/form component — no API calls of its own; emits its result for your code to submit.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-kpi-tile country="AU" label="…" value="…" sub="…" trend="…" delta="…" loading accent="…"></ledrapay-kpi-tile>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-kpi-tile');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('label', label);
el.setAttribute('value', value);
el.setAttribute('sub', sub);
el.setAttribute('trend', trend);
el.setAttribute('delta', delta);
el.setAttribute('accent', accent);
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-kpi-tile
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        label=${this.label}
        value=${this.value}
        sub=${this.sub}
        trend=${this.trend}
        delta=${this.delta}
        accent=${this.accent}
        ?loading=${true}>
      </ledrapay-kpi-tile>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, label, value, sub, trend, delta, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-kpi-tile ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} label={label} value={value} sub={sub} trend={trend} delta={delta} accent={accent} loading="" />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'label', 'value', 'sub', 'trend', 'delta', 'accent']);
</script>

<template>
  <ledrapay-kpi-tile
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :label="label"
    :value="value"
    :sub="sub"
    :trend="trend"
    :delta="delta"
    :accent="accent" />
</template>
<ledrapay-kpi-tile country="AU">RUNNING · SAMPLE

Component

<ledrapay-chart>

Dependency-free chart primitive (bar · histogram · line · donut); host supplies data, tokens supply colour. Alias: lp-chart.

Own attributeTypePurpose
typeStringdefault: bar
dataArraydefault: []
headingString
heightNumberdefault: 200
value-formatStringdefault: number

Display/form component — no API calls of its own; emits its result for your code to submit.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-chart country="AU" type="…" data="…" heading="…" height="…" value-format="…"></ledrapay-chart>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-chart');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('type', type);
el.setAttribute('data', data);
el.setAttribute('heading', heading);
el.setAttribute('height', height);
el.setAttribute('value-format', value_format);
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-chart
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        type=${this.type}
        data=${this.data}
        heading=${this.heading}
        height=${this.height}
        value-format=${this.valueFormat}>
      </ledrapay-chart>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, type, data, heading, height, valueFormat }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-chart ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} type={type} data={data} heading={heading} height={height} value-format={valueFormat} />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'type', 'data', 'heading', 'height', 'valueFormat']);
</script>

<template>
  <ledrapay-chart
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :type="type"
    :data="data"
    :heading="heading"
    :height="height"
    :value-format="valueFormat" />
</template>
<ledrapay-chart country="AU">RUNNING · SAMPLE

Component

<ledrapay-portfolio-dashboard>

Bureau command centre: portfolio-wide KPIs + gross by client. Partner-scoped, first-party only. Alias: lp-portfolio-dashboard.

Own attributeTypePurpose
countryStringJurisdiction (AU | DE | NZ | UK | US) — locale, currency, statutory fields; auto-derived from the tenant when connected · default: AU
with-credentialsBooleanCookie-session mode — fetches send credentials:'include', no bearer; your same-origin proxy authenticates · default: false
Calls in live modeLedraPay API endpoint
api.getPortfolioSummary() GET /v1/partners/portfolio/summary
api.getPortfolioPipeline() GET /v1/partners/portfolio/pipeline

Live mode: partner-scoped, first-party only — sample mode here.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-portfolio-dashboard country="AU" country="…" with-credentials></ledrapay-portfolio-dashboard>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-portfolio-dashboard');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
el.setAttribute('country', country);
container.appendChild(el);
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-portfolio-dashboard
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        country=${this.country}
        ?with-credentials=${true}>
      </ledrapay-portfolio-dashboard>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token, country }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
  }, [token]);
  return <ledrapay-portfolio-dashboard ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} country={country} with-credentials="" />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token', 'country']);
</script>

<template>
  <ledrapay-portfolio-dashboard
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    :country="country" />
</template>
<ledrapay-portfolio-dashboard country="AU">RUNNING · SAMPLE

Component

<ledrapay-client-list>

Bureau client roster: per-client headcount, gross, in-flight runs, next pay date, health. Partner-scoped. Alias: lp-client-list.

Own attributeTypePurpose
with-credentialsBooleanCookie-session mode — fetches send credentials:'include', no bearer; your same-origin proxy authenticates · default: false
Emits
ledrapay-client-selectCustomEvent (bubbles, composed) — e.detail = { id, name }
Calls in live modeLedraPay API endpoint
api.getPortfolioClients() GET /v1/partners/portfolio/clients

Live mode: partner-scoped, first-party only — sample mode here.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-client-list country="AU" with-credentials></ledrapay-client-list>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-client-list');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-client-select', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-client-list
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        ?with-credentials=${true}
        @ledrapay-client-select=${(e) => this.onclientSelect(e.detail)}>
      </ledrapay-client-list>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_client_select = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-client-select', on_ledrapay_client_select);
    return () => { el.removeEventListener('ledrapay-client-select', on_ledrapay_client_select); };
  }, [token]);
  return <ledrapay-client-list ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} with-credentials="" />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-client-list
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-client-select="(e) => console.log(e.detail)" />
</template>
<ledrapay-client-list country="AU">RUNNING · SAMPLE

Component

<ledrapay-pipeline-board>

Portfolio kanban of pay runs by lifecycle column (draft · approved · blocked · finalised). Partner-scoped. Alias: lp-pipeline-board.

Own attributeTypePurpose
with-credentialsBooleanCookie-session mode — fetches send credentials:'include', no bearer; your same-origin proxy authenticates · default: false
Emits
ledrapay-run-selectCustomEvent (bubbles, composed) — e.detail = { pay_run_id, client_id }
Calls in live modeLedraPay API endpoint
api.getPortfolioPipeline() GET /v1/partners/portfolio/pipeline

Live mode: partner-scoped, first-party only — sample mode here.

Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-pipeline-board country="AU" with-credentials></ledrapay-pipeline-board>

// live mode — credentials BEFORE the element connects
const el = document.createElement('ledrapay-pipeline-board');
el.setAttribute('country', 'AU');
el.setAttribute('api-url', '/v1');
el.setAttribute('tenant-id', tenantId);
el.embedToken = token;            // property, not attribute: keeps it out of the DOM
container.appendChild(el);

el.addEventListener('ledrapay-run-select', (e) => console.log(e.detail));
import { LitElement, html } from 'lit';
import '@ledrapay/components';

class PayrollPage extends LitElement {
  render() {
    return html`
      <ledrapay-pipeline-board
        country="AU"
        api-url="/v1"
        tenant-id=${this.tenantId}
        .embedToken=${this.token}
        .tokenProvider=${() => this.refreshToken()}
        ?with-credentials=${true}
        @ledrapay-run-select=${(e) => this.onrunSelect(e.detail)}>
      </ledrapay-pipeline-board>`;
  }
}
// React 19+: custom elements get first-class props/events.
// Earlier React: set the token via ref (shown) so it stays a property.
import { useEffect, useRef } from 'react';
import '@ledrapay/components';

export function Payroll({ tenantId, token }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    el.embedToken = token;                       // property assignment
    el.tokenProvider = () => fetch('/api/embed-token').then(r => r.json()).then(d => d.token);
    const on_ledrapay_run_select = (e) => console.log(e.detail);
    el.addEventListener('ledrapay-run-select', on_ledrapay_run_select);
    return () => { el.removeEventListener('ledrapay-run-select', on_ledrapay_run_select); };
  }, [token]);
  return <ledrapay-pipeline-board ref={ref} country="AU" api-url="/v1" tenant-id={tenantId} with-credentials="" />;
}
<!-- vite.config: vue({ template: { compilerOptions: {
       isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>

<template>
  <ledrapay-pipeline-board
    country="AU" api-url="/v1"
    :tenant-id="tenantId"
    :embed-token.prop="token"
    @ledrapay-run-select="(e) => console.log(e.detail)" />
</template>
<ledrapay-pipeline-board country="AU">RUNNING · SAMPLE
API referenceopen full ↗
event console — interact with any component above; ledrapay-* events appear here