Data

IdCard

An ID badge with a lanyard slot: photo or initials, name, role, organisation, number, issued and expiry dates, an Expired stamp, and a barcode slot.

#document#card#user#print#no-js

Demo

Preview
Analytical EnginesStaff

Ada Lovelace

Analytical engineerEnginesAE-0042
Issued
Expires
Analytical EnginesVisitor

Grace Hopper

VisitorV-2026-118
Issued
Expires
Contractor

Alan Turing

Research fellowBP-1912
Expires

Get it

It needs theme.qwe, as every component does, and avatar.qwe; 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/avatar.qwe$ curl -O https://quillolang.org/components/ui/id_card.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/id_card.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/id_card.qwe
view IdCardDemo() =    <div.demo-row style="align-items: flex-start; gap: 24px">        <IdCard id="id-card-demo-ada" name="Ada Lovelace" role="Analytical engineer" department="Engines" org="Analytical Engines" kind="Staff"                number="AE-0042" issued="2026-01-10" expires="2027-01-10"/>        <IdCard id="id-card-demo-visitor" name="Grace Hopper" role="Visitor" org="Analytical Engines" kind="Visitor" tone="warning"                src="https://i.pravatar.cc/192?img=47" number="V-2026-118" issued="2026-10-28" expires="2026-10-28" expired={true}>            <svg width="160" height="30" viewBox="0 0 100 20" preserveAspectRatio="none" role="img" aria-label="Barcode" shape-rendering="crispEdges">                <path fill="currentColor" d="M0 0h2v20H0zM3 0h1v20H3zM6 0h3v20H6zM11 0h1v20h-1zM14 0h2v20h-2zM18 0h1v20h-1zM21 0h3v20h-3zM26 0h1v20h-1zM29 0h2v20h-2zM33 0h1v20h-1zM36 0h3v20h-3zM41 0h2v20h-2zM45 0h1v20h-1zM48 0h3v20h-3zM53 0h1v20h-1zM56 0h2v20h-2zM60 0h1v20h-1zM63 0h3v20h-3zM68 0h2v20h-2zM72 0h1v20h-1zM75 0h3v20h-3zM80 0h1v20h-1zM83 0h2v20h-2zM87 0h1v20h-1zM90 0h3v20h-3zM95 0h1v20h-1zM98 0h2v20h-2z"/>            </svg>        </IdCard>        <IdCard id="id-card-demo-wide" layout="landscape" name="Alan Turing" role="Research fellow" org="Bletchley Park" kind="Contractor" tone="neutral"                number="BP-1912" expires="2027-06-23" logo="https://dummyimage.com/80x20/ffffff/17161f.png&text=BP"/>    </div>

Source

ui/id_card.qwe
// IdCard: an ID badge: a photo, the name, role and organisation, the badge number,// when it was issued and expires, and room for a barcode or QR (children), with a// slot for a lanyard at the top. Portrait or landscape.////   <IdCard name="Ada Lovelace" role="Analytical engineer" org="Analytical Engines" number="AE-0042"//           src="/img/ada.jpg" issued="2026-01-10" expires="2027-01-10"/>//   <IdCard layout="landscape" {...staff}><Barcode text={staff.number}/></IdCard>//// name, role, org: who and where; department: a line under the role. src: the// photo (initials on a colour without one). number: the badge's number, in a// monospace face. issued, expires: ISO dates, shown as "Jan 10, 2026"; expired is// marked when `expired` is true (there is no clock here; pass it). kind: a label// along the top band ("Staff", "Visitor", "Contractor"). tone: the band's colour,// "accent" (default), "success", "warning", "danger", "info" or "neutral". logo: the// organisation's logo on the band. children: a barcode or QR along the bottom.// layout: "portrait" (default) or "landscape". id: its id. Needs avatar.qwe.import ui/avatar.qwe use Avatarimport ui/theme.qwe use ui_clslet id_card_css = css`.ui-id-card { --t: var(--ui-accent); --tf: var(--ui-accent-fg); position: relative; display: flex; flex-direction: column; width: 240px; max-width: 100%;  min-width: 0; border-radius: 14px; background: var(--ui-surface); color: var(--ui-fg); font: 13px/1.4 var(--ui-font); box-shadow: var(--ui-shadow);  border: 1px solid var(--ui-border); overflow: hidden; }.ui-id-card-success { --t: var(--ui-success); --tf: #fff; }.ui-id-card-warning { --t: var(--ui-warning); --tf: #fff; }.ui-id-card-danger { --t: var(--ui-danger); --tf: #fff; }.ui-id-card-info { --t: var(--ui-info); --tf: #fff; }.ui-id-card-neutral { --t: var(--ui-fg); --tf: var(--ui-bg); }.ui-id-card-band { display: grid; justify-items: center; gap: 6px; padding: 10px 14px 12px; background: var(--t); color: var(--tf); }.ui-id-card-hole { width: 36px; height: 7px; border-radius: 4px; background: var(--ui-bg); box-shadow: inset 0 1px 2px rgba(0, 0, 0, .3); }.ui-id-card-band-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; }.ui-id-card-org { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }.ui-id-card-logo { max-height: 20px; max-width: 80px; object-fit: contain; }.ui-id-card-kind { padding: 1px 8px; border-radius: 999px; background: rgba(255, 255, 255, .22); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }.ui-id-card-body { display: grid; justify-items: center; gap: 4px; padding: 18px 16px 14px; text-align: center; }.ui-id-card-photo { width: 96px; height: 96px; margin-bottom: 8px; border-radius: 12px; overflow: hidden; box-shadow: 0 0 0 3px var(--ui-surface), 0 0 0 4px var(--ui-border); }.ui-id-card-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }.ui-id-card-photo > .ui-id-card-initials { --s: 96px; }.ui-id-card-name { margin: 0; font-size: 17px; font-weight: 700; line-height: 1.2; }.ui-id-card-role { font-weight: 500; }.ui-id-card-dept { color: var(--ui-muted); }.ui-id-card-number { margin-top: 6px; font: 600 13px/1.3 var(--ui-mono); letter-spacing: .1em; }.ui-id-card-dates { display: flex; justify-content: center; gap: 8px 18px; margin: 10px 0 0; }.ui-id-card-dates div { display: grid; gap: 1px; }.ui-id-card-dates dt { color: var(--ui-muted); font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }.ui-id-card-dates dd { margin: 0; font-size: 12px; font-variant-numeric: tabular-nums; }.ui-id-card-expired .ui-id-card-dates dd:last-of-type, .ui-id-card-expired .ui-id-card-stamp { color: var(--ui-danger); }.ui-id-card-stamp { position: absolute; top: 46%; left: 50%; padding: 2px 10px; border: 3px solid currentColor; border-radius: 4px; color: var(--ui-danger);  font-size: 18px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; transform: translate(-50%, -50%) rotate(-18deg); opacity: .8; pointer-events: none; }.ui-id-card-code { display: grid; justify-items: center; padding: 0 14px 14px; }.ui-id-card-code:empty { display: none; }.ui-id-card-landscape { width: 380px; }.ui-id-card-landscape .ui-id-card-body { grid-template-columns: auto 1fr; justify-items: start; column-gap: 16px; text-align: left; }.ui-id-card-landscape .ui-id-card-photo { grid-row: 1 / span 6; margin-bottom: 0; }.ui-id-card-landscape .ui-id-card-dates { justify-content: flex-start; }@media print { .ui-id-card { box-shadow: none; } .ui-id-card-band { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }`let id_card_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]// An ISO date as "Jan 10, 2026"; other text as it is.fn id_card_date(d) -> str {    let p = split(slice(str(d), 0, 10), "-")    len(p) == 3 && int(p[1]) != null && int(p[2]) != null ? `${id_card_months[int(p[1]) - 1]} ${int(p[2])}, ${p[0]}` : str(d)}view IdCard(name: str, role: str? = null, org: str? = null, department: str? = null, src: str? = null, number: str? = null,            issued = null, expires = null, expired: bool = false, kind: str? = null, tone: str = "accent", logo: str? = null,            layout: str = "portrait", id: str? = null, class: str? = null, children) {    style global id_card_css    <article class={ui_cls(["ui-id-card", tone != "accent" && `ui-id-card-${tone}`, layout == "landscape" && "ui-id-card-landscape",                            expired && "ui-id-card-expired", class])} id={id} aria-label={`ID card: ${name}${expired ? ", expired" : ""}`}>        <div.ui-id-card-band>            <span.ui-id-card-hole aria-hidden="true"></span>            {(org != null || kind != null || logo != null) && <div.ui-id-card-band-row>                {logo != null ? <img.ui-id-card-logo src={logo} alt={org ?? ""}/> : <span.ui-id-card-org>{org}</span>}                {kind != null && <span.ui-id-card-kind>{kind}</span>}            </div>}        </div>        <div.ui-id-card-body>            <div.ui-id-card-photo>{src != null ? <img src={src} alt=""/> : <Avatar name={name} shape="square" class="ui-id-card-initials"/>}</div>            <h3.ui-id-card-name>{name}</h3>            {role != null && <span.ui-id-card-role>{role}</span>}            {department != null && <span.ui-id-card-dept>{department}</span>}            {number != null && <span.ui-id-card-number>{number}</span>}            {(issued != null || expires != null) && <dl.ui-id-card-dates>                {issued != null && <div><dt>Issued</dt><dd><time datetime={str(issued)}>{id_card_date(issued)}</time></dd></div>}                {expires != null && <div><dt>Expires</dt><dd><time datetime={str(expires)}>{id_card_date(expires)}</time></dd></div>}            </dl>}        </div>        <div.ui-id-card-code>{children}</div>        {expired && <span.ui-id-card-stamp aria-hidden="true">Expired</span>}    </article>}
Thread
56Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.