SDK · web components · framework-agnostic
Payroll UI you drop into any app.
Thirty-one production-ready components — onboarding wizards, pay-run dashboards, payslip viewers, governance trails — as standard web components. One script tag, Shadow-DOM isolated, works in React, Vue, Rails, or plain HTML — and in mobile apps via the WebView bridge (Flutter · iOS · Android). The components below are not screenshots: this page loads the real SDK bundle and they're running right now, in sample mode.
Quick start
One script tag
<!-- 1. Load the SDK --> <script type="module" src="https://cdn.ledrapay.com/v1/ledrapay-components.js"></script> <!-- 2. Use a component — no token: renders sample data for development --> <ledrapay-payslip-viewer country="AU" show-ytd></ledrapay-payslip-viewer>
Or via npm: npm i @ledrapay/components, then import '@ledrapay/components'. Components register themselves; use the tags anywhere.
Running on this page
The SDK, in action
Everything in the frames below is the actual SDK bundle executing in your browser — interact with it. No token is set, so each component shows its sample-data banner; with an embed-token attribute these exact components render live payroll from the Ledra Pay API.
Live mode
Same components, real payroll: the embed token
Partner API keys (lp_*) must never reach a browser. Instead, your backend mints a short-lived, tenant-scoped embed token and hands it to the page:
# Your server (only place your partner key lives) curl -X POST $BASE/v1/tenants/$TENANT/embed/token \ -H "Authorization: Bearer lp_live_..." \ -d '{"components": ["payslip-viewer", "pay-runs-list"]}' # → { "token": "eyJ…", "expires_in": 900 }
// Your page — create the component once the token is in hand // (components fetch on connect, so credentials go on before mounting) const el = document.createElement('ledrapay-pay-runs-list'); el.setAttribute('api-url', '/v1'); el.setAttribute('tenant-id', tenantId); el.setAttribute('embed-token', token); el.tokenProvider = () => fetch('/my-backend/refresh-embed-token').then(r => r.json()).then(d => d.token); container.appendChild(el);
tokenProvider and components refresh themselves on 401.This is a real capture of the pay-runs component in live mode — rendering an actual previewed AU pay run (18 employees, engine-computed totals) next to a sample-mode component on the same page:
Try it live · sandbox
Paste a sandbox key, watch real payroll render
This panel talks to a live Ledra Pay sandbox through a same-origin bridge. Paste your lp_sandbox_* key — the page lists your tenants, mints a short-lived embed token, and mounts the component against real engine-computed AU data. Sandbox keys only — never paste a live key into any web page, including this one.
Under the hood: GET /partners/tenants → POST /tenants/{id}/embed/token → component mounts with the scoped token. Your key stays in this browser tab and is used only for those two calls — the mounted component itself authenticates with the expiring embed token, exactly as your production pages would.
Component linking — what the View click above is doing. Components communicate through bubbling, composed DOM events; you wire them like any DOM:
// the list announces intent — it never navigates for you container.addEventListener('ledrapay-view-pay-run', (e) => { // e.detail = { id: 'pr_…' } const detail = document.createElement('ledrapay-pay-run-detail'); detail.setAttribute('pay-run-id', e.detail.id); detail.setAttribute('tenant-id', tenantId); // same credentials detail.setAttribute('embed-token', token); container.appendChild(detail); // or route to your own page });
Full input/output specs for all 31 components — attributes, events, theming variables, linking patterns — live in the component playground →
Universal schema, endpoints, and how each engine (myaccountant · Payroll Engine) maps onto it — with the data-flow learnings — in the schema & data-flow spec →
Make it yours, no fork: re-skin every component with --ledrapay-* CSS variables, translate the UI with a string catalogue (setLedraPayStrings() / per-instance .strings), and reshape multi-step flows with steps-config. See the brand showcase → (one component, four client brands) and the i18n + steps demo →.
Client methods · effective dating & capabilities
Effective dating, capabilities, and the DE extras
The client surfaces effective-dating as plain options and exposes the engine capability matrix so your UI can gate features instead of eating a 501. Entries and totals also carry the additive DE/PE extras (social, wage_types, total_social) — the components render them automatically when present.
// reads — point-in-time with as_of (default: today) await api.getEmployee(id, { as_of: '2026-08-01' }); // value in force on that date await api.listEmployees(1, { as_of: '2026-10-01' }); // writes — effective_from dates the change await api.updateEmployee(id, { employment: { annual_salary: 90000 } }, { effective_from: '2026-09-01' }); await api.createEmployee(data, { effective_from: '2026-09-01' }); // capability gate — branch instead of failing if (await api.supportsEffectiveDating()) showAsOfPicker(); const caps = await api.getCapabilities(); // { effective_dating: 'native' | 'unsupported', … }
as_of/effective_from returns 501 CAPABILITY_NOT_SUPPORTED — today/omitted always works. The ledrapay-employee-onboarding wizard already uses supportsEffectiveDating() to show its Effective From field only where it is honoured.Try it live · effective dating
Watch a future-dated raise resolve by date — live
This runs the full round-trip against a live Payroll-Engine sandbox: create an employee at €60,000, post a raise to €90,000 effective 1 Sep 2026, then read the same record as_of three dates. No key needed — it talks to a dedicated demo backend. (This is the PE-native path; the AU engine reports effective_dating: unsupported, which the matrix and the 501 gate make explicit.)
(no run yet)
Under the hood, each line is a real evidenced call: POST …/employees → PATCH …/employees/{id} with effective_from → three GET …/employees/{id}?as_of=…. Every write returns an evidence_id (cev_*).
Try it live · Germany
The same components, rendering a real German payslip — computed by payrollengine.ch
This mounts the actual SDK components against a finalised German pay run that was onboarded and computed through the governed rail: Ledra Pay → the Smartta evidenced executor → the real payrollengine.ch engine, each step leaving a cev_ evidence envelope. The partner lifecycle is preview → approve → finalise; finalise committed a real Payroll Engine payrun job, and its committed per-employee entries are read back through the payrun_entries resource composition. You'll see the German surface — Lohnsteuer, the social-contributions total (KV/RV/AV/PV), and the per-employee wage-type breakdown — the same components used for AU, just country="DE". The number is the engine's, not Ledra Pay's: net €2,344.25 from €3,500.00 gross.
Under the hood: connect → mint an embed token → mount <ledrapay-pay-run-detail country="DE"> + <ledrapay-payslip-viewer country="DE"> against the finalised governed pay run. Finalise committed a real payrollengine.ch payrun job through Smartta's evidenced executor; its committed per-employee entries — the 63-line statutory wage-type breakdown and the employee name — are read back through the payrun_entries resource composition and served from the evidenced control plane.
Catalog
All 31 components
Every component also registers a lp-* alias (lp-payslip-viewer ≡ ledrapay-payslip-viewer). Common attributes everywhere: country (AU·DE·NZ·UK·US), api-url, tenant-id, embed-token.
| Tag (ledrapay-…) | What it does | Notable |
|---|---|---|
| payslip-viewer | Compliant payslip with earnings, deductions, super, leave balances | show-ytd · show-governance · print/download events |
| employee-onboarding | Multi-step wizard: personal → tax → bank → super → employment | emits ledrapay-complete with the assembled employee |
| pay-runs-list | Pay run history with status, totals, latest-run stat cards | emits ledrapay-view-pay-run |
| employees-list | Employee directory — searchable roster of workers | emits ledrapay-view-employee |
| employee-detail | Single employee profile: employment, pay, tax, super, bank (PII masked) | employee-id attribute |
| pay-run-detail | Single run: entries, per-employee lines, action states | pay-run-id attribute |
| pay-run-entry | Inline entry editor: pay items, hours × rates, gross/tax/net | draft-state editing |
| dashboard | Summary stats: workers, MTD gross, compliance state | — |
| tax-declaration | Country-aware tax form (TFN declaration in AU, etc.) | jurisdiction fields switch on country |
| super-form | Retirement setup: fund selection, member number, salary sacrifice | AU: USI lookup pattern |
| bank-details | Account capture with country routing formats | masked display |
| leave-management | Balances, accrual rates, request workflow | — |
| pay-item-library | Earnings/deduction catalogue with rate multipliers | — |
| stp-submission | Lodgement status and authority responses | AU STP Phase 2 shapes |
| award-config | Award rate tables and classifications | AU Modern Awards |
| payroll-calendar | Pay schedule builder with upcoming periods | — |
| org-setup | Company, sites, department hierarchy | — |
| cost-centres | Cost allocation management | — |
| reports-dashboard | Report generation and download | — |
| governance-trail | Evidence ledger view — receipts per action | pairs with /governance endpoints |
| requirements | Compliance/readiness checklist per employee | pairs with pack validation |
Events
Listening to components
document.querySelector('ledrapay-employee-onboarding') .addEventListener('ledrapay-complete', (e) => { // e.detail = the assembled employee — submit via YOUR backend with governance_reason });
| Event | Emitted by |
|---|---|
| ledrapay-complete | onboarding wizard finish (employee payload in detail) |
| ledrapay-save / ledrapay-cancel | forms (tax, super, bank) |
| ledrapay-view-pay-run / ledrapay-employee-select | lists (navigation intents) |
| ledrapay-print / ledrapay-download | payslip viewer actions |
governance_reason, idempotency, and the evidence chain apply. The SDK never gives a browser an ungoverned write path.Next
Where to go from here
Walk the full lifecycle in the AU end-to-end guide, browse the API reference, or request sandbox keys to point these components at real data.