CollateralList
What secures a loan: each item's valuation and documents, and the loan-to-value ratio as a meter against the lender's limit.
Demo
Loan to value
68.8%limit 80%- Owed
- $182,450.00
- Collateral value
- $265,000.00
- Room under the limit
- $29,550.00
- Property
14 Alder Row, Fairhaven
Three-bedroom house, freehold
$265,000.00Valued Aug 12, 2026 by Coastline Surveyors- Title deed verified
- Valuation report verified
- Buildings insurance pending
Loan to value
78.7%limit 80%- Owed
- $410,000.00
- Collateral value
- $521,000.00
- Room under the limit
- $6,800.00
- Property
Unit 7, Millbrook Trading Estate
$380,000.00Valued Jun 30, 2026 by Halden & Pryce- Lease verified
- Valuation report verified
- Vehicles
Two delivery vans
Registrations KX24 FHD, KX24 FHE
$64,000.00Valued Sep 2, 2026- Registration documents verified
- Fleet insurance missing
- Deposit
Cash deposit, 12-month term
Account •••• 2207
$77,000.00ValuationNo documents yet.
Loan to value
91.5%limit 85%- Owed
- 21.500,00 €
- Collateral value
- 23.500,00 €
- Over the limit by
- 1.525,00 €
- Vehicle
2024 Kestrel Tourer 1.6
23.500,00 €Valued 01.10.2026- Fahrzeugbrief verified
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/collateral_list.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/collateral_list.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/collateral_list.qwe
view CollateralListDemo() { <div.demo-stack> <CollateralList id="collateral-list-demo-home" loan={182450} limit={80} items={[ { name: "14 Alder Row, Fairhaven", kind: "Property", detail: "Three-bedroom house, freehold", value: 265000, valued_on: "2026-08-12", valuer: "Coastline Surveyors", documents: [{ name: "Title deed", status: "verified", href: "#deed" }, { name: "Valuation report", status: "verified", href: "#valuation" }, { name: "Buildings insurance", status: "pending" }] }, ]}/> <CollateralList id="collateral-list-demo-business" loan={410000} label="Business loan collateral" items={[ { name: "Unit 7, Millbrook Trading Estate", kind: "Property", value: 380000, valued_on: "2026-06-30", valuer: "Halden & Pryce", documents: [{ name: "Lease", status: "verified" }, { name: "Valuation report", status: "verified", href: "#unit7" }] }, { name: "Two delivery vans", kind: "Vehicles", detail: "Registrations KX24 FHD, KX24 FHE", value: 64000, valued_on: "2026-09-02", documents: [{ name: "Registration documents", status: "verified" }, { name: "Fleet insurance", status: "missing" }] }, { name: "Cash deposit, 12-month term", kind: "Deposit", detail: "Account •••• 2207", value: 77000 }, ]}/> <div.demo-narrow style="max-width: 460px"> <CollateralList id="collateral-list-demo-car" loan={21500} limit={85} currency="EUR" locale="de-DE" items={[ { name: "2024 Kestrel Tourer 1.6", kind: "Vehicle", value: 23500, valued_on: "2026-10-01", documents: [{ name: "Fahrzeugbrief", status: "verified" }] }, ]}/> </div> </div>}Source
ui/collateral_list.qwe
// CollateralList: what secures a loan. Each item with its valuation (who valued// it and when) and its documents, each verified, pending or missing; above them,// the loan-to-value ratio (what is owed against what the items are worth) as a// meter with the lender's limit marked on it, green under the limit, amber close// to it and red over it, with how much more could be borrowed or how far over it is.//// <CollateralList loan={182450} limit={80} items={[// { name: "14 Alder Row, Fairhaven", kind: "Property", value: 265000, valued_on: "2026-08-12", valuer: "Coastline Surveyors",// documents: [{ name: "Title deed", status: "verified", href: "/docs/41" }, { name: "Insurance", status: "pending" }] }]}/>//// loan: what is owed (a number or a decimal). limit: the highest loan-to-value the// lender allows, in percent (80). near: how many points under the limit counts as// close (5). items: a list of { name, kind?, value, valued_on?, valuer?, detail?,// documents? }: value its valuation; valued_on an ISO date; detail a line under the// name (a registration, a policy number); documents a list of { name, status?,// href? }, status "verified", "pending" or "missing" (and a document with an href// is a link). currency: ISO code ("USD"); locale: how amounts and dates are// written (the program's; en-US). label: what screen readers call it ("Collateral").// id, class.import ui/theme.qwe use ui_clslet collateral_list_css = css`.ui-collateral { display: grid; gap: 14px; min-width: 0; color: var(--ui-fg); font: 14px/1.45 var(--ui-font); --ui-collateral-tone: var(--ui-success); }.ui-collateral-near { --ui-collateral-tone: var(--ui-warning); }.ui-collateral-over { --ui-collateral-tone: var(--ui-danger); }.ui-collateral-ltv { display: grid; gap: 10px; padding: 16px 18px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); }.ui-collateral-ltv-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 16px; }.ui-collateral-ltv-head h3 { margin: 0; font-size: 14px; font-weight: 600; }.ui-collateral-ratio { font-size: 26px; font-weight: 700; letter-spacing: -.01em; color: var(--ui-collateral-tone); font-variant-numeric: tabular-nums; }.ui-collateral-ratio small { margin-left: 6px; color: var(--ui-muted); font-size: 13px; font-weight: 500; letter-spacing: 0; }.ui-collateral-meter { position: relative; height: 12px; margin: 4px 0 18px; border-radius: 999px; background: var(--ui-subtle); box-shadow: inset 0 0 0 1px var(--ui-border); }.ui-collateral-fill { position: absolute; inset: 0 auto 0 0; border-radius: inherit; background: var(--ui-collateral-tone); }.ui-collateral-limit { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; background: var(--ui-fg); border-radius: 1px; }.ui-collateral-limit span { position: absolute; top: calc(100% + 3px); left: 50%; transform: translateX(-50%); color: var(--ui-muted); font-size: 11px; white-space: nowrap; font-variant-numeric: tabular-nums; }.ui-collateral-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px 16px; margin: 0; }.ui-collateral-facts dt { color: var(--ui-muted); font-size: 12px; }.ui-collateral-facts dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }.ui-collateral-facts .ui-collateral-room dd { color: var(--ui-collateral-tone); }.ui-collateral-items { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }.ui-collateral-item { display: grid; gap: 10px; padding: 14px 16px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); }.ui-collateral-item-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 6px 16px; }.ui-collateral-kind { display: inline-block; margin-bottom: 2px; color: var(--ui-muted); font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }.ui-collateral-name { margin: 0; font-size: 15px; font-weight: 600; }.ui-collateral-detail { margin: 0; color: var(--ui-muted); font-size: 13px; }.ui-collateral-value { text-align: right; }.ui-collateral-value strong { display: block; font-size: 17px; font-variant-numeric: tabular-nums; }.ui-collateral-value small { color: var(--ui-muted); font-size: 12px; }.ui-collateral-share { height: 4px; border-radius: 999px; background: var(--ui-subtle); overflow: hidden; }.ui-collateral-share span { display: block; height: 100%; background: var(--ui-accent); }.ui-collateral-docs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }.ui-collateral-doc { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px 3px 8px; border: 1px solid var(--ui-border); border-radius: 999px; font-size: 12px; background: var(--ui-bg); }.ui-collateral-doc::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--ui-strong); }.ui-collateral-verified::before { background: var(--ui-success); }.ui-collateral-pending::before { background: var(--ui-warning); }.ui-collateral-missing { border-style: dashed; border-color: var(--ui-danger); color: var(--ui-danger); }.ui-collateral-missing::before { background: var(--ui-danger); }.ui-collateral-doc a { color: inherit; font-weight: 500; text-decoration: none; }.ui-collateral-doc a:hover { text-decoration: underline; }.ui-collateral-doc a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-collateral-doc-state { color: var(--ui-muted); }.ui-collateral-none { margin: 0; color: var(--ui-muted); font-size: 12px; }`fn collateral_list_money(x: dec, currency: str, locale: str?) -> str { let m = money.of(x, currency, "half_up") if is_err(m) { return str(x) } locale != null ? money.format(m, locale) : money.format(m)}view CollateralList(items: list, loan, limit = 80, near = 5, currency: str = "USD", locale: str? = null, label: str = "Collateral", id: str? = null, class: str? = null) { style global collateral_list_css let owed = dec(loan) let cap = dec(limit) let worth = dec(sum(for it in items => dec(it.value))) let ltv = worth > 0 ? round(owed * 100 / worth, 1) : 0m let tone = worth <= 0 || ltv > cap ? "over" : (ltv >= cap - dec(near) ? "near" : "ok") let most = round(worth * cap / 100, 2, "down") let room = most - owed let scale = cap * 1.25m > ltv ? cap * 1.25m : ltv let top = scale > 100 ? scale : 100m let fmt = (x: dec) => collateral_list_money(x, currency, locale) let day = (d: str) => date.format(d, { locale: locale ?? "en-US", style: "medium" }) <section class={ui_cls(["ui-collateral", tone != "ok" && `ui-collateral-${tone}`, class])} id={id} aria-label={label} data-tone={tone}> <div.ui-collateral-ltv> <div.ui-collateral-ltv-head> <h3 id={id != null ? `${id}-ltv` : null}>Loan to value</h3> <span.ui-collateral-ratio data-key="ltv">{ltv}%<small>limit {cap}%</small></span> </div> <div.ui-collateral-meter role="meter" aria-label="Loan to value" aria-valuemin="0" aria-valuemax={top} aria-valuenow={ltv} aria-valuetext={`${ltv}%, ${tone == "over" ? "over" : "within"} the ${cap}% limit`}> <span.ui-collateral-fill style={`width: ${round((ltv > top ? top : ltv) * 100 / top, 2)}%`}></span> <span.ui-collateral-limit style={`left: ${round(cap * 100 / top, 2)}%`} aria-hidden="true"><span>Limit {cap}%</span></span> </div> <dl.ui-collateral-facts> <div><dt>Owed</dt><dd data-key="owed">{fmt(owed)}</dd></div> <div><dt>Collateral value</dt><dd data-key="worth">{fmt(worth)}</dd></div> <div.ui-collateral-room> <dt>{room >= 0 ? "Room under the limit" : "Over the limit by"}</dt> <dd data-key="room">{fmt(room >= 0 ? room : -room)}</dd> </div> </dl> </div> <ul.ui-collateral-items aria-label={label}> {for [i, it] in enumerate(items) { let value = dec(it.value) let share = worth > 0 ? round(value * 100 / worth, 1) : 0m let docs = it.documents ?? [] <li.ui-collateral-item key={i} data-n={i}> <div.ui-collateral-item-head> <div> {it.kind != null && <span.ui-collateral-kind>{it.kind}</span>} <h4.ui-collateral-name>{it.name}</h4> {it.detail != null && <p.ui-collateral-detail>{it.detail}</p>} </div> <div.ui-collateral-value> <strong data-key="value">{fmt(value)}</strong> <small>{it.valued_on != null ? `Valued ${day(it.valued_on)}` : "Valuation"}{it.valuer != null ? ` by ${it.valuer}` : ""}</small> </div> </div> <div.ui-collateral-share aria-hidden="true" title={`${share}% of the collateral`}><span style={`width: ${share}%`}></span></div> {len(docs) == 0 ? <p.ui-collateral-none>No documents yet.</p> : <ul.ui-collateral-docs aria-label={`Documents for ${it.name}`}> {for d in docs { let st = d.status ?? "pending" <li class={`ui-collateral-doc ui-collateral-${st}`} key={d.name} data-status={st}> {d.href != null ? <a href={d.href}>{d.name}</a> : <span>{d.name}</span>}{" "} <span.ui-collateral-doc-state>{match st { "verified" => "verified" "missing" => "missing" _ => "pending" }}</span> </li> }} </ul>} </li> }} </ul> </section>}Related
AmortizationTableA loan's payment schedule worked out from its amount, rate and term: each month's principal, interest and balance, grouped by year with the year's totals.7 views · 7 downloadsLoanOffersLoan offers side by side: each lender's APR, term, monthly payment, fees and total cost, with the cheapest in total marked.8 views · 7 downloadsPaymentHistoryTwenty-four months of payments as a grid of years and months, on time, 30, 60 or 90 days late, or nothing due, as credit reports show them, with a legend.7 views · 7 downloadsTransactionListTransactions grouped by day with category, money in and out, the running balance, pending ones marked, and a search that filters as you type.7 views · 6 downloadsBillListUpcoming bills soonest first, overdue and due-soon ones marked, autopay on or off, with the total to pay and a Pay button each.7 views · 5 downloadsCreditScoreA credit score on a gauge of its bands from poor to excellent, the band it falls in, the change since the last report and the factors that move it.7 views · 8 downloads
Thread
Thread 0 posts
On CollateralList: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.