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>
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.
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.
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);
});
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.
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.
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;
}
| Token | Default value | |
|---|---|---|
| --ledrapay-primary | #0e8a6d | |
| --ledrapay-primary-hover | #0b6f58 | |
| --ledrapay-accent | #d9a441 | |
| --ledrapay-teal | #0e8a6d | |
| --ledrapay-teal-bg | #e6f2ee | |
| --ledrapay-teal-bd | rgba(14,138,109,0.22) | |
| --ledrapay-bg | #ffffff | |
| --ledrapay-surface | #faf9f5 | |
| --ledrapay-surface-alt | #f4f1ea | |
| --ledrapay-row-alt | #fcfbf8 | |
| --ledrapay-seg-track | #ece9e1 | |
| --ledrapay-hairline | rgba(28,27,25,0.06) | |
| --ledrapay-text | #26251f | |
| --ledrapay-text-muted | #6f6d64 | |
| --ledrapay-text-light | #9a988e | |
| --ledrapay-text-faint | #adaa9f | |
| --ledrapay-border | rgba(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-bd | rgba(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-mono | ui-monospace,'SF Mono',Menlo,Consolas,monospace | |
| --ledrapay-radius | 12px | |
| --ledrapay-radius-sm | 9px | |
| --ledrapay-radius-lg | 16px | |
| --ledrapay-shadow | 0 1px 3px rgba(28,27,25,0.06) | |
| --ledrapay-shadow-md | 0 10px 30px -18px rgba(28,27,25,0.35) |
: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:
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 / property | Type | Purpose |
|---|---|---|
| country | AU | DE | NZ | UK | US | Locale, 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-url | string (default /v1) | LedraPay API base; same-origin or your proxy |
| tenant-id | ten_* | Tenant scope for live data |
| embed-token / .embedToken | JWT | Live credential (scoped + expiring). Use the property in production. Absent → sample mode |
| .tokenProvider | () ⇒ Promise<string> | Refresh callback invoked on 401 |
| with-credentials | Boolean | Cookie-session mode: fetches send credentials:'include', no bearer — your same-origin proxy authenticates |
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 works | Notes |
|---|---|
| Angular | Add CUSTOM_ELEMENTS_SCHEMA; bind events with (ledrapay-save), properties with [embedToken] |
| Svelte / SolidJS | Custom elements work natively; on:ledrapay-save / prop:embedToken |
| Rails / Django / Laravel / HTMX | Server-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.
| Pattern | How | Use when |
|---|---|---|
| 1 · Embed token via property | Backend 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 refresh | Recommended default. Token is tenant-pinned, component-scoped, 15-min TTL |
| 2 · Session-backed refresh | Same 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 time | You already have a session layer and want zero token bootstrapping in markup |
| 3 · Cookie-session proxy | with-credentials attribute: components fetch with credentials:'include' and no bearer; your same-origin proxy validates its session cookie and injects the LedraPay credential server-side | Strict no-tokens-in-JS policies; everything rides your existing httpOnly cookie |
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.
| Route | Effort | Use when |
|---|---|---|
| WebView bridge | Hours — one embed page + a message channel | Full payroll UI fast; web + mobile share one implementation; theming via CSS vars still works |
| Native via API | Per-screen | Brand-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-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-complete | CustomEvent (bubbles, composed) — e.detail = { employee, employeeId, country } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.createEmployee() | POST /v1/tenants/{t}/employees |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.supportsEffectiveDating() | GET /v1/tenants/{t}/packs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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 attribute | Type | Purpose |
|---|---|---|
| as-of | String | Effective-dated read (YYYY-MM-DD) — renders the roster/profile in force at that date · default: (today) |
| Emits | |
|---|---|
| ledrapay-view-employee | CustomEvent (bubbles, composed) — e.detail = { id } |
| ledrapay-employee-terminated | CustomEvent (bubbles, composed) — e.detail = { id, evidence_id } |
| ledrapay-employee-reactivated | CustomEvent (bubbles, composed) — e.detail = { id, evidence_id } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.listEmployees() | GET /v1/tenants/{t}/employees |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCapabilities() | GET /v1/tenants/{t}/packs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.endEmployment() | POST /v1/tenants/{t}/employees/{id}/end-employment |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.reactivateEmployee() | POST /v1/tenants/{t}/employees/{id}/reactivate |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-employee-detail>
Single employee profile: identity, employment, pay, tax, super, bank (PII masked). Alias: lp-employee-detail.
| Own attribute | Type | Purpose |
|---|---|---|
| employee-id | String | Employee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees |
| as-of | String | Effective-dated read (YYYY-MM-DD) — renders the roster/profile in force at that date · default: (today) |
| Emits | |
|---|---|
| ledrapay-employee-terminated | CustomEvent (bubbles, composed) — e.detail = { id, evidence_id } |
| ledrapay-employee-reactivated | CustomEvent (bubbles, composed) — e.detail = { id, evidence_id } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getEmployee() | GET /v1/tenants/{t}/employees/{id} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCapabilities() | GET /v1/tenants/{t}/packs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.terminateEmployee() | DELETE /v1/tenants/{t}/employees/{id} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.endEmployment() | POST /v1/tenants/{t}/employees/{id}/end-employment |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.reactivateEmployee() | POST /v1/tenants/{t}/employees/{id}/reactivate |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getEmployeeReadiness() | GET /v1/tenants/{tenant}/employees/{id}/readiness |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-tax-declaration>
Jurisdiction-aware tax form (TFN declaration in AU). Alias: lp-tax-declaration.
| Own attribute | Type | Purpose |
|---|---|---|
| employee-id | String | Employee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees |
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — e.detail = { employeeId, country } |
| ledrapay-cancel | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getEmployeeTaxSettings() | GET /v1/tenants/{t}/employees/{id}/tax-settings |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.updateEmployeeTaxSettings() | PATCH /v1/tenants/{t}/employees/{id}/tax-settings |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-bank-details>
Account capture with country routing formats. Alias: lp-bank-details.
| Own attribute | Type | Purpose |
|---|---|---|
| employee-id | String | Employee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees |
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — e.detail = { employeeId, country } |
| ledrapay-cancel | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.updateEmployeeBankAccounts() | PATCH /v1/tenants/{t}/employees/{id}/bank-accounts |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-super-form>
Retirement setup: fund, member number, salary sacrifice. Alias: lp-super-form.
| Own attribute | Type | Purpose |
|---|---|---|
| employee-id | String | Employee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees |
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — e.detail = { employeeId, country } |
| ledrapay-cancel | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.updateEmployeePension() | PATCH /v1/tenants/{t}/employees/{id}/pension |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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 attribute | Type | Purpose |
|---|---|---|
| employee-id | String | Employee id (emp_*) — from a ledrapay-view-employee / ledrapay-employee-select event or GET …/employees |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getEmployeeReadiness() | GET /v1/tenants/{t}/employees/{id}/readiness |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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-run | CustomEvent (bubbles, composed) — e.detail = { id } |
| ledrapay-pay-run-created | CustomEvent (bubbles, composed) — e.detail = { payRun } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.listPayRuns() | GET /v1/tenants/{t}/pay-runs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.createPayRun() | POST /v1/tenants/{t}/pay-runs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-pay-run-detail>
Single run: entries, totals, lifecycle actions. Alias: lp-pay-run-detail.
| Own attribute | Type | Purpose |
|---|---|---|
| pay-run-id | String | Pay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs |
| Emits | |
|---|---|
| ledrapay-view-pay-run | CustomEvent (bubbles, composed) — e.detail = { id } |
| ledrapay-employee-select | CustomEvent (bubbles, composed) — e.detail = { employee } — cancelable: preventDefault() suppresses the built-in payslip preview |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPayRun() | GET /v1/tenants/{t}/pay-runs/{id} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getPayRunEntries() | GET /v1/tenants/{t}/pay-runs/{id}/entries |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getPayRunExceptions() | GET /v1/tenants/{t}/pay-runs/{id}/exceptions |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.governedAdjustmentBulk() | POST /v1/tenants/{t}/pay-runs/{id}/adjust/bulk |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.voidPayRun() | POST /v1/tenants/{t}/pay-runs/{id}/void |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.clonePayRun() | POST /v1/tenants/{t}/pay-runs/{id}/clone |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listEmployees() | GET /v1/tenants/{t}/employees |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCompany() | GET /v1/tenants/{t}/company |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.addEntry() | POST /v1/tenants/{t}/pay-runs/{id}/entries |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.removeEntry() | DELETE /v1/tenants/{t}/pay-runs/{id}/entries/{eid} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.updateEntry() | PATCH /v1/tenants/{t}/pay-runs/{id}/entries/{eid} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.previewPayRun() | POST /v1/tenants/{t}/pay-runs/{id}/preview |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.approvePayRun() | POST /v1/tenants/{t}/pay-runs/{id}/approve |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.finalisePayRun() | POST /v1/tenants/{t}/pay-runs/{id}/finalise |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.submitSTP() | POST /v1/tenants/{t}/pay-runs/{id}/submit |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-pay-run-entry>
Inline entry editor: pay items, hours × rates. Alias: lp-pay-run-entry.
| Own attribute | Type | Purpose |
|---|---|---|
| pay-run-id | String | Pay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs |
| entry-id | String | Pay-run entry id — from GET …/pay-runs/{id}/entries (pick a run first) |
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — e.detail = { entryId } (live) · { employee } (sample) |
| ledrapay-cancel | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPayRunEntries() | GET /v1/tenants/{t}/pay-runs/{id}/entries |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.updateEntry() | PATCH /v1/tenants/{t}/pay-runs/{id}/entries/{eid} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.governedAdjustment() | POST /v1/tenants/{t}/pay-runs/{id}/entries/{eid}/adjust |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getPayItems() | GET /v1/tenants/{t}/pay-items |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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.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>
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 attribute | Type | Purpose |
|---|---|---|
| pay-run-id | String | Pay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs |
| governance-url | String | Link target for evidence ids in the exception report |
| Emits | |
|---|---|
| ledrapay-exceptions-verdict | CustomEvent (bubbles, composed) — e.detail = { verdict, summary, payRunId } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPayRunExceptions() | GET /v1/tenants/{t}/pay-runs/{id}/exceptions |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-payroll-calendar>
Pay schedule builder. Alias: lp-payroll-calendar.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — e.detail = the schedule config object (frequency, anchor date, …) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.listPayRuns() | GET /v1/tenants/{t}/pay-runs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCompany() | GET /v1/tenants/{t}/company |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-payslip-viewer>
Compliant payslip: earnings, deductions, super, leave balances, YTD. Alias: lp-payslip-viewer.
| Own attribute | Type | Purpose |
|---|---|---|
| show-ytd | Boolean | Show the year-to-date column on the payslip · default: true |
| show-leave | Boolean | Show leave balances on the payslip · default: true |
| show-governance | Boolean | Show the governance/evidence strip · default: true |
| pay-run-id | String | Pay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs |
| Emits | |
|---|---|
| ledrapay-print | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| ledrapay-download | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.listPayRuns() | GET /v1/tenants/{t}/pay-runs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listPayslips() | GET /v1/tenants/{t}/pay-runs/{id}/payslips |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-pay-item-library>
Earnings/deduction catalogue with multipliers. Alias: lp-pay-item-library.
No attributes beyond the shared inputs.
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPayItemTypes() | GET /v1/tenants/{t}/pay-item-types |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getPayItems() | GET /v1/tenants/{t}/pay-items |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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 attribute | Type | Purpose |
|---|---|---|
| pay-run-id | String | Pay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs |
| Emits | |
|---|---|
| ledrapay-download | CustomEvent (bubbles, composed) — e.detail = { payRunId, kind: 'aba' } |
| ledrapay-super-batch | CustomEvent (bubbles, composed) — e.detail = { payRunId, batch } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPaymentFile() | GET /v1/tenants/{t}/pay-runs/{id}/payment-file |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listPayRuns() | GET /v1/tenants/{t}/pay-runs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.createSuperFromPayRun() | POST /v1/tenants/{t}/pay-runs/{id}/super |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listSuperBatches() | GET /v1/tenants/{t}/superannuation |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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-requested | CustomEvent (bubbles, composed) — e.detail = { employee_id, leave_type, start_date, end_date, quantity, unit, reason? } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getLeaveTypes() | GET /v1/tenants/{t}/reference/leave-types |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listEmployees() | GET /v1/tenants/{t}/employees |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listLeaveRequests() | GET /v1/tenants/{t}/leave/requests |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.applyLeaveRequest() | POST /v1/tenants/{t}/leave/requests |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.cancelLeaveRequest() | POST /v1/tenants/{t}/leave/requests/{id}/cancel |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getLeaveBalances() | GET /v1/tenants/{tenant}/leave-balances |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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-approved | CustomEvent (bubbles, composed) — e.detail = { id } |
| ledrapay-leave-rejected | CustomEvent (bubbles, composed) — e.detail = { id } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.listLeaveRequests() | GET /v1/tenants/{t}/leave/requests?status=pending |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.approveLeaveRequest() | POST /v1/tenants/{t}/leave/requests/{id}/approve |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.rejectLeaveRequest() | POST /v1/tenants/{t}/leave/requests/{id}/reject |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getLeaveBalances() | GET /v1/tenants/{tenant}/leave-balances |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-leave-management>
Engine leave-type catalogue, accrual balances, entitlement config. Alias: lp-leave-management.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — e.detail = { country } |
| ledrapay-cancel | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| ledrapay-leave-booked | CustomEvent (bubbles, composed) — e.detail = the booked-leave record (engine response data) |
| ledrapay-leave-cancelled | CustomEvent (bubbles, composed) — e.detail = { id } |
| ledrapay-leave-amended | CustomEvent (bubbles, composed) — e.detail = { id, quantity } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getLeaveTypes() | GET /v1/tenants/{t}/reference/leave-types |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getLeaveBalances() | GET /v1/tenants/{t}/leave-balances |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getLeaveTaken() | GET /v1/tenants/{t}/leave |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listEmployees() | GET /v1/tenants/{t}/employees |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCapabilities() | GET /v1/tenants/{t}/packs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.bookLeave() | POST /v1/tenants/{t}/leave/book |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.cancelLeave() | POST /v1/tenants/{t}/leave/{id}/cancel |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.amendLeave() | POST /v1/tenants/{t}/leave/{id}/amend |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-org-setup>
Company, sites, departments. Alias: lp-org-setup.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| ledrapay-cancel | CustomEvent (bubbles, composed) — e.detail = (no payload) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getCompany() | GET /v1/tenants/{t}/company |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCompanyBank() | GET /v1/tenants/{t}/company/bank |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.setCompanyBank() | PATCH /v1/tenants/{t}/company/bank |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-cost-centres>
Cost allocation. Alias: lp-cost-centres.
No attributes beyond the shared inputs.
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getCostCentres() | GET /v1/tenants/{t}/cost-centres |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.createCostCentre() | POST /v1/tenants/{t}/cost-centres |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.updateCostCentre() | PATCH /v1/tenants/{t}/cost-centres/{code} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.archiveCostCentre() | DELETE /v1/tenants/{t}/cost-centres/{code} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.allocateEmployeeCostCentre() | PUT /v1/tenants/{t}/employees/{id}/cost-centre |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.clearEmployeeCostCentre() | DELETE /v1/tenants/{t}/employees/{id}/cost-centre |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCostCentreMappings() | GET /v1/tenants/{t}/cost-centre-mappings |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.createCostCentreMapping() | POST /v1/tenants/{t}/cost-centre-mappings |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.deleteCostCentreMapping() | DELETE /v1/tenants/{t}/cost-centre-mappings/{id} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listEmployees() | GET /v1/tenants/{t}/employees |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-requirements>
Compliance/readiness checklist. Alias: lp-requirements.
No attributes beyond the shared inputs.
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getCapabilities() | GET /v1/tenants/{t}/packs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-award-config>
Award rate tables and classifications. Alias: lp-award-config.
No attributes beyond the shared inputs.
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getAwards() | GET /v1/reference/awards |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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 attribute | Type | Purpose |
|---|---|---|
| guide-base | String | Base URL for checklist deep-links (default guide.ledrapay.com) · default: https://guide.ledrapay.com |
| Emits | |
|---|---|
| ledrapay-check | CustomEvent (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>
Component
<ledrapay-governance-trail>
Evidence ledger — receipts per action. Alias: lp-governance-trail.
No attributes beyond the shared inputs.
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getAuditTrail() | GET /v1/tenants/{t}/governance/audit-trail |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.verifyChain() | POST /v1/tenants/{t}/governance/verify |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-stp-submission>
Lodgement status and authority responses. Alias: lp-stp-submission.
| Own attribute | Type | Purpose |
|---|---|---|
| pay-run-id | String | Pay-run id (run_*) — from a ledrapay-view-pay-run event or GET …/pay-runs |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.listSubmissions() | GET /v1/tenants/{t}/pay-runs/{id}/submissions |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getSubmission() | GET /v1/tenants/{t}/pay-runs/{id}/submissions/{submissionId} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.submitSTP() | POST /v1/tenants/{t}/pay-runs/{id}/submit |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getCompany() | GET /v1/tenants/{t}/company |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-super-overview>
FY superannuation obligations: reported vs paid vs outstanding vs overdue, per employee. Alias: lp-super-overview.
| Own attribute | Type | Purpose |
|---|---|---|
| financial-year | Number | Financial year to report (e.g. 2026) |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getSuperannuationOverview() | GET /v1/tenants/{t}/superannuation/overview?financialYear={fy} |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-dashboard>
Summary stats: workers, MTD gross, compliance. Alias: lp-dashboard.
No attributes beyond the shared inputs.
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.listEmployees() | GET /v1/tenants/{t}/employees |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.listPayRuns() | GET /v1/tenants/{t}/pay-runs |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getAuditTrail() | GET /v1/tenants/{t}/governance/audit-trail |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getReportActivity() | GET /v1/tenants/{t}/reports/activity |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-reports-dashboard>
Report generation and download. Alias: lp-reports-dashboard.
No attributes beyond the shared inputs.
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getReportSummary() | GET /v1/tenants/{t}/reports/summary |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getReportActivity() | GET /v1/tenants/{t}/reports/activity |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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 attribute | Type | Purpose |
|---|---|---|
| label | String | |
| value | String | |
| sub | String | |
| trend | String | |
| delta | String | |
| loading | Boolean | default: false |
| accent | String | default: 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>
Component
<ledrapay-chart>
Dependency-free chart primitive (bar · histogram · line · donut); host supplies data, tokens supply colour. Alias: lp-chart.
| Own attribute | Type | Purpose |
|---|---|---|
| type | String | default: bar |
| data | Array | default: [] |
| heading | String | |
| height | Number | default: 200 |
| value-format | String | default: 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>
Component
<ledrapay-portfolio-dashboard>
Bureau command centre: portfolio-wide KPIs + gross by client. Partner-scoped, first-party only. Alias: lp-portfolio-dashboard.
| Own attribute | Type | Purpose |
|---|---|---|
| country | String | Jurisdiction (AU | DE | NZ | UK | US) — locale, currency, statutory fields; auto-derived from the tenant when connected · default: AU |
| with-credentials | Boolean | Cookie-session mode — fetches send credentials:'include', no bearer; your same-origin proxy authenticates · default: false |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPortfolioSummary() | GET /v1/partners/portfolio/summary |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
| api.getPortfolioPipeline() | GET /v1/partners/portfolio/pipeline |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
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 attribute | Type | Purpose |
|---|---|---|
| with-credentials | Boolean | Cookie-session mode — fetches send credentials:'include', no bearer; your same-origin proxy authenticates · default: false |
| Emits | |
|---|---|
| ledrapay-client-select | CustomEvent (bubbles, composed) — e.detail = { id, name } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPortfolioClients() | GET /v1/partners/portfolio/clients |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>
Component
<ledrapay-pipeline-board>
Portfolio kanban of pay runs by lifecycle column (draft · approved · blocked · finalised). Partner-scoped. Alias: lp-pipeline-board.
| Own attribute | Type | Purpose |
|---|---|---|
| with-credentials | Boolean | Cookie-session mode — fetches send credentials:'include', no bearer; your same-origin proxy authenticates · default: false |
| Emits | |
|---|---|
| ledrapay-run-select | CustomEvent (bubbles, composed) — e.detail = { pay_run_id, client_id } |
| Calls in live mode | LedraPay API endpoint |
|---|---|
| api.getPortfolioPipeline() | GET /v1/partners/portfolio/pipeline |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
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>