Finance

LoanApplicationSteps

Where a loan application stands, from submitted to disbursed: the steps done with their dates, the current one, and what is needed next.

#finance#loans#steps#status#no-js

Demo

Preview

Personal loan, $24,000

Reference APP-2026-04817

  1. Submitted, CompletedOct 20, 2026
  2. Documents, Current stepSince Oct 21, 2026
  3. Credit check, Not startedExpected Oct 30, 2026
  4. Approval, Not startedExpected Nov 4, 2026
  5. Disbursement, Not startedExpected Nov 6, 2026

What we need next (2 to go)

We need two more documents before we can run the credit check.

  • Photo ID, received
  • Last two payslips, needed PDF or a photo, each page readable
  • Proof of address, needed A utility bill from the last three months
Upload documents

Car loan, €15,000

Reference APP-2026-03321

  1. Submitted, Completed2 Oct 2026
  2. Documents, Completed3 Oct 2026
  3. Credit check, Completed7 Oct 2026
  4. Approval, DeclinedDeclined 9 Oct 2026
  5. Disbursement, CancelledWill not happen

Why it was declined

Your monthly debts would be 52% of your income with this loan. A smaller amount or a longer term may be approved.

Try another amount

Loan of $8,500

Reference APP-2026-02210

  1. Submitted, CompletedSep 14, 2026
  2. Documents, CompletedSep 15, 2026
  3. Credit check, CompletedSep 16, 2026
  4. Approval, CompletedSep 18, 2026
  5. Disbursement, CompletedSep 19, 2026

All done

$8,500 was paid into your account ending 3390 on Sep 19.

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/loan_application_steps.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/loan_application_steps.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/loan_application_steps.qwe
view LoanApplicationStepsDemo() {    <div.demo-stack>        <LoanApplicationSteps id="loan-application-steps-demo-docs" reference="APP-2026-04817" product="Personal loan" amount="$24,000"                              current="documents" dates={{ submitted: "2026-10-20", documents: "2026-10-21" }}                              expected={{ credit_check: "2026-10-30", approval: "2026-11-04", disbursement: "2026-11-06" }}                              note="We need two more documents before we can run the credit check."                              needed={[{ label: "Photo ID", done: true },                                       { label: "Last two payslips", detail: "PDF or a photo, each page readable" },                                       { label: "Proof of address", detail: "A utility bill from the last three months" }]}                              action_href="#upload" action_label="Upload documents"/>        <div.demo-row style="align-items: stretch">            <div style="flex: 1 1 320px; max-width: 420px">                <LoanApplicationSteps id="loan-application-steps-demo-declined" reference="APP-2026-03321" product="Car loan" amount="€15,000"                                      current="approval" outcome="declined" locale="en-GB"                                      dates={{ submitted: "2026-10-02", documents: "2026-10-03", credit_check: "2026-10-07", approval: "2026-10-09" }}                                      note="Your monthly debts would be 52% of your income with this loan. A smaller amount or a longer term may be approved."                                      action_href="#calculator" action_label="Try another amount"/>            </div>            <div style="flex: 1 1 320px; max-width: 420px">                <LoanApplicationSteps id="loan-application-steps-demo-done" reference="APP-2026-02210" amount="$8,500" outcome="complete"                                      dates={{ submitted: "2026-09-14", documents: "2026-09-15", credit_check: "2026-09-16", approval: "2026-09-18", disbursement: "2026-09-19" }}                                      note="$8,500 was paid into your account ending 3390 on Sep 19."/>            </div>        </div>    </div>}

Source

ui/loan_application_steps.qwe
// LoanApplicationSteps: where a loan application stands. Its steps (submitted,// documents, credit check, approval, disbursement) in a row, or a column when// narrow: those done with a tick and the day they were done, the current one// marked (aria-current="step") with what is happening, those ahead with the day// they are expected. Under them, what is needed next as a checklist, and a button// to do it.////   <LoanApplicationSteps reference="APP-2026-04817" amount="$24,000" product="Personal loan" current="documents"//                         dates={{ submitted: "2026-10-20" }} expected={{ approval: "2026-11-04" }}//                         note="We need two more documents before the credit check."//                         needed={[{ label: "Photo ID", done: true }, { label: "Last two payslips" }]}//                         action_href="/applications/04817/upload" action_label="Upload documents"/>//   <LoanApplicationSteps reference="APP-2026-03321" current="approval" outcome="declined" note="Your debts are too high for this amount."/>//// current: the key of the step it is at. steps: the steps, a list of { key, label,// detail? } (detail is a line under a step); by default submitted, documents,// credit_check, approval and disbursement. dates: when each step was done, a map// of key to ISO date; expected: when steps ahead should be, the same way.// outcome: "declined" (the current step failed, in red, and the rest will not// happen), "complete" (every step done, the last included), or null while it goes// on. note: a line about the current step. needed: what is needed next, a list of// { label, done?, detail? } (or plain text). action_href / action_label: a button// under it ("Continue"). reference, product, amount: a line at the top (amount as// text, formatted). locale: how dates are written (the program's; en-US).// label: what screen readers call the steps ("Application progress"). id, class.import ui/theme.qwe use ui_clslet loan_application_steps_css = css`.ui-loan-steps { container-type: inline-size; display: grid; gap: 18px; min-width: 0; padding: 18px 20px; border: 1px solid var(--ui-border);  border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.45 var(--ui-font);  --ui-loan-steps-on: var(--ui-accent); }.ui-loan-steps-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 16px; }.ui-loan-steps-title { margin: 0; font-size: 16px; font-weight: 650; }.ui-loan-steps-ref { margin: 0; color: var(--ui-muted); font-size: 13px; font-variant-numeric: tabular-nums; }.ui-loan-steps-list { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }.ui-loan-steps-step { position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 0 12px; padding-bottom: 18px; }.ui-loan-steps-step:last-child { padding-bottom: 0; }.ui-loan-steps-step::before { content: ""; position: absolute; top: 30px; bottom: 2px; left: 13px; width: 2px; border-radius: 1px; background: var(--ui-border); }.ui-loan-steps-step:last-child::before { display: none; }.ui-loan-steps-done::before { background: var(--ui-loan-steps-on); }.ui-loan-steps-dot { display: grid; place-items: center; width: 28px; height: 28px; border: 2px solid var(--ui-border); border-radius: 50%;  background: var(--ui-surface); color: var(--ui-muted); font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }.ui-loan-steps-dot svg { width: 14px; height: 14px; }.ui-loan-steps-done .ui-loan-steps-dot { border-color: var(--ui-loan-steps-on); background: var(--ui-loan-steps-on); color: var(--ui-accent-fg); }.ui-loan-steps-current .ui-loan-steps-dot { border-color: var(--ui-loan-steps-on); color: var(--ui-loan-steps-on);  box-shadow: 0 0 0 4px color-mix(in srgb, var(--ui-loan-steps-on) 18%, transparent); }.ui-loan-steps-failed .ui-loan-steps-dot { border-color: var(--ui-danger); background: var(--ui-danger); color: #fff; box-shadow: none; }.ui-loan-steps-text { display: grid; gap: 1px; padding-top: 3px; min-width: 0; }.ui-loan-steps-label { font-weight: 600; }.ui-loan-steps-todo .ui-loan-steps-label, .ui-loan-steps-skipped .ui-loan-steps-label { color: var(--ui-muted); font-weight: 500; }.ui-loan-steps-skipped .ui-loan-steps-label { text-decoration: line-through; }.ui-loan-steps-when { color: var(--ui-muted); font-size: 12px; font-variant-numeric: tabular-nums; }.ui-loan-steps-current .ui-loan-steps-when { color: var(--ui-loan-steps-on); font-weight: 600; }.ui-loan-steps-failed .ui-loan-steps-when { color: var(--ui-danger); }.ui-loan-steps-detail { color: var(--ui-muted); font-size: 13px; }@container (min-width: 640px) {  .ui-loan-steps-list { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }  .ui-loan-steps-step { grid-template-columns: 1fr; justify-items: center; gap: 8px; padding: 0 6px; text-align: center; }  .ui-loan-steps-step::before { top: 13px; bottom: auto; left: calc(50% + 20px); right: calc(-50% + 20px); width: auto; height: 2px; }  .ui-loan-steps-text { padding-top: 0; }}.ui-loan-steps-next { display: grid; gap: 10px; padding: 14px 16px; border-radius: var(--ui-radius-sm); background: var(--ui-subtle); }.ui-loan-steps-failed-note { background: var(--ui-danger-soft); }.ui-loan-steps-next h4 { margin: 0; font-size: 13px; font-weight: 650; }.ui-loan-steps-note { margin: 0; }.ui-loan-steps-needs { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }.ui-loan-steps-needs li { display: grid; grid-template-columns: 18px 1fr; gap: 8px; align-items: start; }.ui-loan-steps-check { display: grid; place-items: center; width: 18px; height: 18px; margin-top: 1px; border: 1.5px solid var(--ui-strong); border-radius: 4px; }.ui-loan-steps-check svg { width: 11px; height: 11px; }.ui-loan-steps-got .ui-loan-steps-check { border-color: var(--ui-success); background: var(--ui-success); color: #fff; }.ui-loan-steps-got .ui-loan-steps-need { color: var(--ui-muted); text-decoration: line-through; }.ui-loan-steps-need small { display: block; color: var(--ui-muted); font-size: 12px; text-decoration: none; }.ui-loan-steps-action { justify-self: start; display: inline-flex; align-items: center; height: 34px; padding: 0 14px; border-radius: var(--ui-radius-sm);  background: var(--ui-accent); color: var(--ui-accent-fg); font-weight: 600; text-decoration: none; }.ui-loan-steps-action:hover { filter: brightness(1.08); }.ui-loan-steps-action:focus-visible { outline: none; box-shadow: var(--ui-focus); }`let loan_application_steps_default = [    { key: "submitted", label: "Submitted" },    { key: "documents", label: "Documents" },    { key: "credit_check", label: "Credit check" },    { key: "approval", label: "Approval" },    { key: "disbursement", label: "Disbursement" },]fn loan_application_steps_tick() = <svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7.5l2.6 2.6L11 4.5"/></svg>view LoanApplicationSteps(current: str? = null, steps: list = loan_application_steps_default, dates: map = {}, expected: map = {},                          outcome: str? = null, note: str? = null, needed: list = [], action_href: str? = null,                          action_label: str = "Continue", reference: str? = null, product: str? = null, amount: str? = null,                          locale: str? = null, label: str = "Application progress", id: str? = null, class: str? = null) {    style global loan_application_steps_css    let at = outcome == "complete" ? len(steps) : (index_of(for s in steps => s.key, current) ?? 0)    let at_i = at < 0 ? 0 : at    let declined = outcome == "declined"    let day = (d) => date.format(str(d), { locale: locale ?? "en-US", style: "medium" })    let items = for n in needed => type_of(n) == "str" ? { label: n } : n    let left = len(filter(items, (n) => n.done != true))    let title = product != null ? (amount != null ? `${product}, ${amount}` : product) : (amount != null ? `Loan of ${amount}` : null)    <section class={ui_cls(["ui-loan-steps", class])} id={id} aria-label={title ?? label}>        {(title != null || reference != null) && <div.ui-loan-steps-head>            {title != null && <h3.ui-loan-steps-title>{title}</h3>}            {reference != null && <p.ui-loan-steps-ref>Reference {reference}</p>}        </div>}        <ol.ui-loan-steps-list aria-label={label}>            {for [i, s] in enumerate(steps) {                let phase = i < at_i ? "done" : i == at_i && outcome != "complete" ? (declined ? "failed" : "current") : (declined ? "skipped" : "todo")                let done_on = get(dates, s.key, null)                let due_on = get(expected, s.key, null)                let when = phase == "done" ? (done_on != null ? day(done_on) : "Done")                    : phase == "failed" ? (done_on != null ? `Declined ${day(done_on)}` : "Declined")                    : phase == "current" ? (done_on != null ? `Since ${day(done_on)}` : "In progress")                    : phase == "skipped" ? "Will not happen"                    : (due_on != null ? `Expected ${day(due_on)}` : "Not started")                let said = match phase {                    "done" => "Completed"                    "current" => "Current step"                    "failed" => "Declined"                    "skipped" => "Cancelled"                    _ => "Not started"                }                <li class={`ui-loan-steps-step ui-loan-steps-${phase}`} key={s.key} data-key={s.key} data-state={phase}                    aria-current={phase == "current" || phase == "failed" ? "step" : null}>                    <span.ui-loan-steps-dot aria-hidden="true">{phase == "done" ? loan_application_steps_tick() : (phase == "failed" ? "✕" : str(i + 1))}</span>                    <span.ui-loan-steps-text>                        <span.ui-loan-steps-label>{s.label}<span.ui-sr>, {said}</span></span>                        <span.ui-loan-steps-when>{when}</span>                        {s.detail != null && <span.ui-loan-steps-detail>{s.detail}</span>}                    </span>                </li>            }}        </ol>        {(note != null || len(items) > 0 || action_href != null) && <div class={ui_cls(["ui-loan-steps-next", declined && "ui-loan-steps-failed-note"])}>            <h4>{declined ? "Why it was declined" : outcome == "complete" ? "All done" : len(items) > 0 ? (left > 0 ? `What we need next (${left} to go)` : "We have everything we need") : "What happens next"}</h4>            {note != null && <p.ui-loan-steps-note>{note}</p>}            {len(items) > 0 && <ul.ui-loan-steps-needs>                {for n in items => <li class={n.done == true ? "ui-loan-steps-got" : null} key={n.label}>                    <span.ui-loan-steps-check aria-hidden="true">{n.done == true && loan_application_steps_tick()}</span>                    <span.ui-loan-steps-need>{n.label}<span.ui-sr>{n.done == true ? ", received" : ", needed"}</span>{" "}                        {n.detail != null && <small>{n.detail}</small>}                    </span>                </li>}            </ul>}            {action_href != null && <a.ui-loan-steps-action href={action_href}>{action_label}</a>}        </div>}    </section>}
Thread
710Useful? Like it, or add to its thread below.

Thread 0 posts

On LoanApplicationSteps: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.