The code of the preview above.
demos/email_preview.qwe
Copy view EmailPreviewDemo () = <div .demo-stack> <EmailPreview id="email-preview-demo-q3" subject="Q3 report, with the numbers" date="2026-10-27T14:30:00Z" labels={["Work" , "Finance" ]} starred={true } unread={true } me="ada@example.com" from={{ name: "Grace Hopper" , email: "grace@example.com" , src: "https://i.pravatar.cc/80?img=47" }} to={["ada@example.com" , { name: "Alan Turing" , email: "alan@example.com" }]} cc={[{ name: "Finance" , email: "finance@example.com" }]} body="Hi Ada,\n\nThe Q3 report is attached, with the spreadsheet behind it.\nPage 4 has the one question I still have.\n\nThanks,\nGrace" attachments={[{ name: "Q3 report.pdf" , size: 2483112 , href: "#email_preview" }, { name: "Q3 numbers.xlsx" , size: 84211 , href: "#email_preview" }, { name: "chart.png" , size: 312000 }]}/> <EmailPreview id="email-preview-demo-html" subject="Welcome to Quillo" from={{ email: "hello@quillolang.org" }} date="2026-10-01" > <p >Thanks for signing up. <a href="#email_preview" >Start with the tour</a >, or read <strong >the guide</strong >.</p > </EmailPreview > </div > ui/email_preview.qwe
Copy import ui/avatar.qwe use Avatar import ui/theme.qwe use ui_clslet email_preview_css = css` .ui-email-preview { display: grid; gap: 16px; min-width: 0; padding: 20px 22px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.55 var(--ui-font); } .ui-email-preview-top { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; } .ui-email-preview-subject { margin: 0; font-size: 20px; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; } .ui-email-preview-unread .ui-email-preview-subject { font-weight: 750; } .ui-email-preview-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ui-accent); } .ui-email-preview-label { padding: 1px 8px; border-radius: 4px; background: var(--ui-accent-soft); color: var(--ui-accent); font-size: 12px; font-weight: 500; } .ui-email-preview-star { width: 18px; height: 18px; margin-left: auto; color: var(--ui-warning); } .ui-email-preview-head { display: grid; grid-template-columns: auto 1fr auto; gap: 2px 12px; align-items: center; } .ui-email-preview-head .ui-avatar { grid-row: span 2; } .ui-email-preview-from { min-width: 0; overflow-wrap: anywhere; } .ui-email-preview-from b { font-weight: 650; } .ui-email-preview-addr { color: var(--ui-muted); font-size: 13px; } .ui-email-preview-to { grid-column: 2 / -1; color: var(--ui-muted); font-size: 13px; overflow-wrap: anywhere; } .ui-email-preview-date { color: var(--ui-muted); font-size: 13px; white-space: nowrap; font-variant-numeric: tabular-nums; } .ui-email-preview-body { min-width: 0; overflow-wrap: anywhere; } .ui-email-preview-body p { margin: 0 0 12px; white-space: pre-line; } .ui-email-preview-body > :last-child { margin-bottom: 0; } .ui-email-preview-files { display: grid; gap: 8px; padding-top: 14px; border-top: 1px solid var(--ui-border); } .ui-email-preview-files h4 { margin: 0; color: var(--ui-muted); font-size: 12px; font-weight: 600; } .ui-email-preview-files ul { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; } .ui-email-preview-file { display: flex; align-items: center; gap: 10px; max-width: 260px; padding: 8px 12px 8px 8px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); color: var(--ui-fg); text-decoration: none; } a.ui-email-preview-file:hover { border-color: var(--ui-accent); background: var(--ui-accent-soft); } a.ui-email-preview-file:focus-visible { outline: none; box-shadow: var(--ui-focus); } .ui-email-preview-ext { --k: var(--ui-muted); flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 6px; background: color-mix(in srgb, var(--k) 16%, transparent); color: var(--k); font: 700 9px/1 var(--ui-font); } .ui-email-preview-k-pdf { --k: var(--ui-danger); } .ui-email-preview-k-image { --k: var(--ui-success); } .ui-email-preview-k-doc { --k: var(--ui-info); } .ui-email-preview-k-sheet { --k: var(--ui-success); } .ui-email-preview-k-archive { --k: var(--ui-warning); } .ui-email-preview-fname { display: grid; min-width: 0; font-size: 13px; } .ui-email-preview-fname span:first-child { overflow: hidden; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; } .ui-email-preview-fname span + span { color: var(--ui-muted); font-size: 12px; } @media (max-width: 520px) { .ui-email-preview { padding: 16px; } .ui-email-preview-head { grid-template-columns: auto 1fr; } .ui-email-preview-date { grid-column: 2; } } ` let email_preview_days = ["Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" , "Sun" ]let email_preview_months = ["Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" ]fn email_preview_date (d) -> str { let s = str (d) let day = slice (s, 0 , 10 ) if !date.valid (day) { return s } let p = date.parts (day) let base = `${email_preview_days[p.weekday - 1]}, ${email_preview_months[p.month - 1]} ${p.day}, ${p.year}` len (s) >= 16 ? `${base}, ${slice(s, 11, 16)}` : base } fn email_preview_size (n: num ) -> str { if n < 1024 { return `${n} B` } let unit = n < 1024 * 1024 ? 1024 : (n < 1024 * 1024 * 1024 ? 1024 * 1024 : 1024 * 1024 * 1024 ) let tenths = round (n * 10 / unit) `${floor(tenths / 10)}${tenths % 10 == 0 ? "" : ` .${tenths % 10 }`} ${unit == 1024 ? "KB" : (unit == 1024 * 1024 ? "MB" : "GB")}` } fn email_preview_ext (name: str ) -> str { let parts = split (name, "." ) len (parts) > 1 ? lower (last (parts)) : "" } fn email_preview_kind (name: str ) -> str = match email_preview_ext (name) { "pdf" => "pdf" "png" | "jpg" | "jpeg" | "gif" | "webp" | "svg" => "image" "doc" | "docx" | "txt" | "md" | "odt" => "doc" "xls" | "xlsx" | "csv" | "ods" => "sheet" "zip" | "gz" | "tar" | "7z" => "archive" _ => "file" } fn email_preview_people (people: list , me) -> str = join (people | map ((p) => { let addr = type_of (p) == "map" ? p.email : str (p) me != null && addr == me ? "me" : (type_of (p) == "map" ? (p.name ?? p.email) : str (p)) }), ", " ) view EmailPreview (subject: str , from: map , to: list = [], cc: list = [], me: str ? = null , date = null , body: str ? = null , attachments: list = [], labels: list = [], starred: bool = false , unread: bool = false , id: str ? = null , class: str ? = null , children) { style global email_preview_css <article class={ui_cls (["ui-email-preview" , unread && "ui-email-preview-unread" , class])} id={id} aria-label={`Email: ${subject}` }> <div .ui-email-preview-top> {unread && <span .ui-email-preview-dot role="img" aria-label="Unread" ></span >} <h2 .ui-email-preview-subject>{subject}</h2 > {for l in labels => <span .ui-email-preview-label key={l}>{l}</span >} {starred && <svg .ui-email-preview-star viewBox="0 0 24 24" fill="currentColor" role="img" aria-label="Starred" > <path d="M12 2.5l2.9 6.1 6.6.8-4.9 4.6 1.3 6.6L12 17.3l-5.9 3.3 1.3-6.6L2.5 9.4l6.6-.8Z" /> </svg >} </div > <div .ui-email-preview-head> <Avatar name={from.name ?? from.email} src={from.src}/> <div .ui-email-preview-from> <b >{from.name ?? from.email}</b > {from.name != null && from.email != null && <span .ui-email-preview-addr> <{from.email}></span >} </div > {date != null && <time .ui-email-preview-date datetime={str (date)}>{email_preview_date (date)}</time >} {(len (to) > 0 || len (cc) > 0 ) && <div .ui-email-preview-to> {len (to) > 0 && `to ${email_preview_people(to, me)}` }{len (to) > 0 && len (cc) > 0 && "; " }{len (cc) > 0 && `cc ${email_preview_people(cc, me)}` } </div >} </div > <div .ui-email-preview-body> {body != null && for [i, para] in enumerate (re.split (trim (body), "\\n\\s*\\n" )) => <p key={i}>{para}</p >} {children} </div > {len (attachments) > 0 && <div .ui-email-preview-files> <h4 >{len (attachments)} {len (attachments) == 1 ? "attachment" : "attachments" }</h4 > <ul > {for f in attachments => { let inner = [ <span class={`ui-email-preview-ext ui-email-preview-k-${email_preview_kind(f.name)}` } aria-hidden="true" >{upper (slice (email_preview_ext (f.name), 0 , 4 ))}</span >, <span .ui-email-preview-fname><span >{f.name}</span >{f.size != null && <span >{email_preview_size (f.size)}</span >}</span >, ] <li key={f.name}>{f.href != null ? <a .ui-email-preview-file href={f.href} download={f.name} aria-label={`Download ${f.name}` }>{inner}</a > : <span .ui-email-preview-file>{inner}</span >}</li > }} </ul > </div >} </article > }