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.

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

Pattern

Component linking — list → detail

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

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

Pattern

Leave flow — request → approve → booked

The request→approval lifecycle lives in 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.

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

Pattern

Pay-run lifecycle & resource compositions

A pay run moves through four states: draft → previewed → approved → finalised. preview runs a non-persistent engine calculation (totals + per-employee entries are cached on 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.

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

Pattern

Theming — CSS custom properties

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

.my-brand {
  --ledrapay-primary: #7FD1AE;   --ledrapay-bg: #16181D;
  --ledrapay-text: #F5F2E8;      --ledrapay-border: #2E4A3F;
  --ledrapay-font: 'YourFont', sans-serif;  --ledrapay-radius: 8px;
}
All variables: --ledrapay-accent · --ledrapay-amber · --ledrapay-amber-bg · --ledrapay-bg · --ledrapay-blue · --ledrapay-blue-bg · --ledrapay-border · --ledrapay-error · --ledrapay-font · --ledrapay-gold · --ledrapay-gold-bd · --ledrapay-gold-bg · --ledrapay-green · --ledrapay-green-bg · --ledrapay-info · --ledrapay-mono · --ledrapay-primary · --ledrapay-primary-hover · --ledrapay-radius · --ledrapay-radius-lg · --ledrapay-radius-sm · --ledrapay-red · --ledrapay-red-bg · --ledrapay-shadow · --ledrapay-shadow-md · --ledrapay-success · --ledrapay-surface · --ledrapay-surface-alt · --ledrapay-teal · --ledrapay-teal-bd · --ledrapay-teal-bg · --ledrapay-text · --ledrapay-text-light · --ledrapay-text-muted · --ledrapay-warning
Defaults inject once at :root, so any ancestor (a wrapper div, :root, the host element) that redefines a token themes every component — see the brand showcase → (one component, four client brands).

Pattern

Internationalisation — translate the UI, reshape the flow

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

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

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

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

Shared

Inputs every component accepts

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

Pattern

Which frameworks work?

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

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

Pattern

Security model — sessions, cookies, tokens

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

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

Pattern

Mobile — Flutter, iOS, Android, React Native

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

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

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

<script type="module" 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: same WebView pattern via react-native-webview (onMessage + injectJavaScript) — RN has no DOM, so the components can't render in the RN tree itself.

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

Pattern

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

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

Component

<ledrapay-award-config>

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

No attributes beyond the shared inputs.

Calls in live modeLedra Pay API endpoint
api.getAwards() GET /v1/reference/AU/awards

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>
<ledrapay-award-config country="AU">RUNNING · SAMPLE

Component

<ledrapay-bank-details>

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

No attributes beyond the shared inputs.

Emits
ledrapay-saveCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-cancelCustomEvent (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>
<ledrapay-bank-details country="AU">RUNNING · SAMPLE

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>
<ledrapay-cost-centres country="AU">RUNNING · SAMPLE

Component

<ledrapay-dashboard>

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

No attributes beyond the shared inputs.

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

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

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

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

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

Component

<ledrapay-employee-onboarding>

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

No attributes beyond the shared inputs.

Emits
ledrapay-completeCustomEvent (bubbles, composed) — payload in e.detail
Calls in live modeLedra Pay API endpoint
api.createEmployee() POST /v1/tenants/{t}/employees
api.supportsEffectiveDating() GET /v1/tenants/{t}/packs
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-employee-onboarding country="AU"></ledrapay-employee-onboarding>

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

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

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

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

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

Component

<ledrapay-employees-list>

Alias: lp-employees-list.

Own attributeType
as-ofStringdefault: (today)
Emits
ledrapay-view-employeeCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-employee-terminatedCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-employee-reactivatedCustomEvent (bubbles, composed) — payload in e.detail
Calls in live modeLedra Pay API endpoint
api.listEmployees() GET /v1/tenants/{t}/employees
api.getCapabilities() GET /v1/tenants/{t}/packs
api.endEmployment() POST /v1/tenants/{t}/employees/{id}/end-employment
api.reactivateEmployee() POST /v1/tenants/{t}/employees/{id}/reactivate
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-employees-list country="AU" as-of="…"></ledrapay-employees-list>

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

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

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

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

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

Component

<ledrapay-employee-detail>

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

Own attributeType
employee-idString
as-ofStringdefault: (today)
Emits
ledrapay-employee-terminatedCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-employee-reactivatedCustomEvent (bubbles, composed) — payload in e.detail
Calls in live modeLedra Pay API endpoint
api.getEmployee() GET /v1/tenants/{t}/employees/{id}
api.getCapabilities() GET /v1/tenants/{t}/connectors
api.terminateEmployee() DELETE /v1/tenants/{t}/employees/{id}
api.endEmployment() POST /v1/tenants/{t}/employees/{id}/end-employment
api.reactivateEmployee() POST /v1/tenants/{t}/employees/{id}/reactivate
Where does employee-id come from? Get it from ledrapay-employee-select events — or GET /v1/tenants/{t}/employeesdata[].id (emp_*, deterministic, safe to store).
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-employee-detail country="AU" employee-id="…" as-of="…"></ledrapay-employee-detail>

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

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

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

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

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

Component

<ledrapay-governance-trail>

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

No attributes beyond the shared inputs.

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

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

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

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

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

Component

<ledrapay-leave-management>

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

No attributes beyond the shared inputs.

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

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

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

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

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

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

Component

<ledrapay-leave-request>

Alias: lp-leave-request.

No attributes beyond the shared inputs.

Emits
ledrapay-leave-requestedCustomEvent (bubbles, composed) — payload in e.detail
Calls in live modeLedra Pay API endpoint
api.getLeaveTypes() GET /v1/tenants/{t}/reference/leave-types
api.listEmployees() GET /v1/tenants/{t}/employees
api.listLeaveRequests() GET /v1/tenants/{t}/leave/requests
api.applyLeaveRequest() POST /v1/tenants/{t}/leave/requests
api.cancelLeaveRequest() POST /v1/tenants/{t}/leave/requests/{id}/cancel
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-leave-request country="AU"></ledrapay-leave-request>

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

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

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

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

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

Component

<ledrapay-leave-approvals>

Alias: lp-leave-approvals.

No attributes beyond the shared inputs.

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

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

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

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

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

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

Component

<ledrapay-org-setup>

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

No attributes beyond the shared inputs.

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

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

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

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

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

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

Component

<ledrapay-pay-item-library>

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

No attributes beyond the shared inputs.

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

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

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

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

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

Component

<ledrapay-pay-run-detail>

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

Own attributeType
pay-run-idString
Emits
ledrapay-view-pay-runCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-employee-selectCustomEvent (bubbles, composed) — payload in e.detail
Calls in live modeLedra Pay API endpoint
api.getPayRun() GET /v1/tenants/{t}/pay-runs/{id}
api.getPayRunEntries() GET /v1/tenants/{t}/pay-runs/{id}/entries
api.voidPayRun() POST /v1/tenants/{t}/pay-runs/{id}/void
api.clonePayRun() POST /v1/tenants/{t}/pay-runs/{id}/clone
api.listEmployees() GET /v1/tenants/{t}/employees
api.addEntry() POST /v1/tenants/{t}/pay-runs/{id}/entries
api.removeEntry() DELETE /v1/tenants/{t}/pay-runs/{id}/entries/{eid}
api.updateEntry() PATCH /v1/tenants/{t}/pay-runs/{id}/entries/{eid}
api.previewPayRun() POST /v1/tenants/{t}/pay-runs/{id}/preview
api.approvePayRun() POST /v1/tenants/{t}/pay-runs/{id}/approve
api.finalisePayRun() POST /v1/tenants/{t}/pay-runs/{id}/finalise
api.submitSTP() POST /v1/tenants/{t}/pay-runs/{id}/lodge
Where does pay-run-id come from? Get it from a list component's ledrapay-view-pay-run event (e.detail.id) — or call GET /v1/tenants/{t}/pay-runs and use data[].id. The dropdown does exactly that call.
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-pay-run-detail country="AU" pay-run-id="…"></ledrapay-pay-run-detail>

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

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

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

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

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

Component

<ledrapay-pay-run-entry>

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

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

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

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

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

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

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

Component

<ledrapay-pay-runs-list>

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

No attributes beyond the shared inputs.

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

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

el.addEventListener('ledrapay-view-pay-run', (e) => console.log(e.detail));
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>
<ledrapay-pay-runs-list country="AU">RUNNING · SAMPLE

Component

<ledrapay-payroll-calendar>

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

No attributes beyond the shared inputs.

Emits
ledrapay-saveCustomEvent (bubbles, composed) — 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>
<ledrapay-payroll-calendar country="AU">RUNNING · SAMPLE

Component

<ledrapay-payslip-viewer>

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

Own attributeType
show-ytdBooleandefault: true
show-leaveBooleandefault: true
show-governanceBooleandefault: true
pay-run-idString
Emits
ledrapay-printCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-downloadCustomEvent (bubbles, composed) — payload in e.detail
Calls in live modeLedra Pay API endpoint
api.listPayRuns() GET /v1/tenants/{t}/pay-runs
api.listPayslips() GET /v1/tenants/{t}/pay-runs/{id}/payslips
api.downloadPayslipPdf() GET /v1/tenants/{t}/payslips/{id}/pdf
Where does pay-run-id come from? Get it from a list component's ledrapay-view-pay-run event (e.detail.id) — or call GET /v1/tenants/{t}/pay-runs and use data[].id. The dropdown does exactly that call.
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-payslip-viewer country="AU" show-ytd show-leave show-governance pay-run-id="…"></ledrapay-payslip-viewer>

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

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

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

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

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

Component

<ledrapay-reports-dashboard>

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

No attributes beyond the shared inputs.

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

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

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

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

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

Component

<ledrapay-requirements>

Compliance/readiness checklist. Alias: lp-requirements.

No attributes beyond the shared inputs.

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

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

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

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

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

Component

<ledrapay-stp-submission>

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

Own attributeType
pay-run-idString
Calls in live modeLedra Pay API endpoint
api.getSTPSubmissions() GET /v1/tenants/{t}/pay-runs/{id}/stp
api.getSubmission() GET /v1/tenants/{t}/pay-runs/{id}/submissions/{submissionId}
api.submitSTP() POST /v1/tenants/{t}/pay-runs/{id}/submit
Where does pay-run-id come from? Get it from a list component's ledrapay-view-pay-run event (e.detail.id) — or call GET /v1/tenants/{t}/pay-runs and use data[].id. The dropdown does exactly that call.
Source — vanilla · Lit · React · Vue
<!-- sample mode -->
<ledrapay-stp-submission country="AU" pay-run-id="…"></ledrapay-stp-submission>

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

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

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

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

Component

<ledrapay-super-form>

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

No attributes beyond the shared inputs.

Emits
ledrapay-saveCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-cancelCustomEvent (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>
<ledrapay-super-form country="AU">RUNNING · SAMPLE

Component

<ledrapay-tax-declaration>

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

Own attributeType
employee-idString
Emits
ledrapay-saveCustomEvent (bubbles, composed) — payload in e.detail
ledrapay-cancelCustomEvent (bubbles, composed) — payload in e.detail
Calls in live modeLedra Pay API endpoint
api.getEmployeeTaxSettings() GET /v1/tenants/{t}/employees/{id}/tax-settings
api.updateEmployeeTaxSettings() PATCH /v1/tenants/{t}/employees/{id}/tax-settings

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>
<ledrapay-tax-declaration country="AU">RUNNING · SAMPLE

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.

AttributeValues
label · value · substrings shown in the tile
trendup · down · flat (colours the delta)
deltae.g. +2.4%
accentink · green · blue · amber (left bar)
loadingboolean — 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.

AttributeValues
typebar · histogram · line · donut
data[{ "label": "…", "value": 0 }] (property or JSON attribute)
value-formatmoney · number · percent
heading · heighttitle · px
API referenceopen full ↗
event console — interact with any component above; ledrapay-* events appear here