Playground · every component is running
SDK component playground
All 23 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.
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 Ledra Pay, 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 Ledra Pay'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-primary: #7FD1AE; --ledrapay-bg: #16181D;
--ledrapay-text: #F5F2E8; --ledrapay-border: #2E4A3F;
--ledrapay-font: 'YourFont', sans-serif; --ledrapay-radius: 8px;
}
: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) | Ledra Pay 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 Ledra Pay 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" src="https://cdn.ledrapay.com/v1/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('Ledra Pay', 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-award-config>
Award rate tables and classifications. Alias: lp-award-config.
No attributes beyond the shared inputs.
| Calls in live mode | Ledra Pay API endpoint |
|---|---|
| api.getAwards() | GET /v1/reference/AU/awards |
no traffic captured yet — connect a key and use the component (or hit run ▸) | |
Live mode here: GET /v1/reference/awards — endpoint pending.
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-bank-details>
Account capture with country routing formats. Alias: lp-bank-details.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-cancel | CustomEvent (bubbles, composed) — payload in e.detail |
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-bank-details country="AU"></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
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()}
@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 }) {
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} />;
}<!-- vite.config: vue({ template: { compilerOptions: {
isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>
<template>
<ledrapay-bank-details
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.
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-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-dashboard>
Summary stats: workers, MTD gross, compliance. Alias: lp-dashboard.
No attributes beyond the shared inputs.
| Calls in live mode | Ledra Pay 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 ▸) | |
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-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) — payload in e.detail |
| Calls in live mode | Ledra Pay 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 ▸) | |
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>
Alias: lp-employees-list.
| Own attribute | Type | |
|---|---|---|
| as-of | String | default: (today) |
| Emits | |
|---|---|
| ledrapay-view-employee | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-employee-terminated | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-employee-reactivated | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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 | |
|---|---|---|
| employee-id | String | |
| as-of | String | default: (today) |
| Emits | |
|---|---|
| ledrapay-employee-terminated | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-employee-reactivated | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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}/connectors |
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 ▸) | |
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-governance-trail>
Evidence ledger — receipts per action. Alias: lp-governance-trail.
No attributes beyond the shared inputs.
| Calls in live mode | Ledra Pay 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-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) — payload in e.detail |
| ledrapay-cancel | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-leave-booked | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-leave-cancelled | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-leave-amended | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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-leave-request>
Alias: lp-leave-request.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-leave-requested | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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 ▸) | |
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>
Alias: lp-leave-approvals.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-leave-approved | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-leave-rejected | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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 ▸) | |
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-org-setup>
Company, sites, departments. Alias: lp-org-setup.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-cancel | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay API endpoint |
|---|---|
| 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-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-pay-item-library>
Earnings/deduction catalogue with multipliers. Alias: lp-pay-item-library.
No attributes beyond the shared inputs.
| Calls in live mode | Ledra Pay 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-pay-run-detail>
Single run: entries, totals, lifecycle actions. Alias: lp-pay-run-detail.
| Own attribute | Type | |
|---|---|---|
| pay-run-id | String |
| Emits | |
|---|---|
| ledrapay-view-pay-run | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-employee-select | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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.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.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}/lodge |
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 | |
|---|---|---|
| pay-run-id | String | |
| entry-id | String |
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-cancel | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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 ▸) | |
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-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) — payload in e.detail |
| Calls in live mode | Ledra Pay API endpoint |
|---|---|
| api.listPayRuns() | GET /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));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-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);
return () => { el.removeEventListener('ledrapay-view-pay-run', on_ledrapay_view_pay_run); };
}, [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)" />
</template>
Component
<ledrapay-payroll-calendar>
Pay schedule builder. Alias: lp-payroll-calendar.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — payload in e.detail |
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-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 | |
|---|---|---|
| show-ytd | Boolean | default: true |
| show-leave | Boolean | default: true |
| show-governance | Boolean | default: true |
| pay-run-id | String |
| Emits | |
|---|---|
| ledrapay-print | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-download | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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 ▸) | |
| api.downloadPayslipPdf() | GET /v1/tenants/{t}/payslips/{id}/pdf |
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-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-reports-dashboard>
Report generation and download. Alias: lp-reports-dashboard.
No attributes beyond the shared inputs.
| Calls in live mode | Ledra Pay API endpoint |
|---|---|
| api.getReportSummary() | GET /v1/tenants/{t}/reports/summary |
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-requirements>
Compliance/readiness checklist. Alias: lp-requirements.
No attributes beyond the shared inputs.
| Calls in live mode | Ledra Pay 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-stp-submission>
Lodgement status and authority responses. Alias: lp-stp-submission.
| Own attribute | Type | |
|---|---|---|
| pay-run-id | String |
| Calls in live mode | Ledra Pay API endpoint |
|---|---|
| api.getSTPSubmissions() | GET /v1/tenants/{t}/pay-runs/{id}/stp |
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 ▸) | |
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-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-form>
Retirement setup: fund, member number, salary sacrifice. Alias: lp-super-form.
No attributes beyond the shared inputs.
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-cancel | CustomEvent (bubbles, composed) — payload in e.detail |
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-super-form country="AU"></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
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()}
@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 }) {
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} />;
}<!-- vite.config: vue({ template: { compilerOptions: {
isCustomElement: (t) => t.startsWith('ledrapay-') } } }) -->
<script setup>
import '@ledrapay/components';
defineProps(['tenantId', 'token']);
</script>
<template>
<ledrapay-super-form
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-tax-declaration>
Jurisdiction-aware tax form (TFN declaration in AU). Alias: lp-tax-declaration.
| Own attribute | Type | |
|---|---|---|
| employee-id | String |
| Emits | |
|---|---|
| ledrapay-save | CustomEvent (bubbles, composed) — payload in e.detail |
| ledrapay-cancel | CustomEvent (bubbles, composed) — payload in e.detail |
| Calls in live mode | Ledra Pay 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 ▸) | |
Live mode here: GET/PATCH …/employees/{id}/tax-settings — endpoint pending.
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 · primitive
<ledrapay-kpi-tile>
A single dashboard stat tile — label, value, sub-line and an optional trend. Presentational (no API calls); compose rows of them from your own data. Alias: lp-kpi-tile.
| Attribute | Values |
|---|---|
| label · value · sub | strings shown in the tile |
| trend | up · down · flat (colours the delta) |
| delta | e.g. +2.4% |
| accent | ink · green · blue · amber (left bar) |
| loading | boolean — skeleton |
Component · primitive
<ledrapay-chart>
A dependency-free, themeable chart — bar, histogram, line or donut. Data is passed in via the data property/attribute; with none it shows a sample series. Every chart has a table fallback for accessibility. Alias: lp-chart.
| Attribute | Values |
|---|---|
| type | bar · histogram · line · donut |
| data | [{ "label": "…", "value": 0 }] (property or JSON attribute) |
| value-format | money · number · percent |
| heading · height | title · px |