import ui/theme.qwe use theme_cssuse ui/assembly_notice.qwerouteGET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>
Example
The code of the preview above.
demos/assembly_notice.qwe
viewAssemblyNoticeDemo() = <AssemblyNotice id="assembly-notice-demo" coop="Bayanihan Multi-Purpose Cooperative" title="38th Annual General Assembly" date="2026-03-14" time="9:00 AM to 1:00 PM · registration from 7:30 AM" venue="Municipal Gymnasium" address="Rizal Avenue, Poblacion, San Isidro" online="https://meet.example.org/bmpc-aga-2026" quorum="25% of the 4,812 regular members in good standing (1,203 members)" agenda={["Call to order and invocation","Determination of quorum","Approval of the minutes of the 37th General Assembly", { title: "Reports of the Board, the Treasurer and the committees", detail: "The audited financial statements for 2025" }, { title: "Allocation of net surplus", detail: "Dividend on share capital and patronage refund" }, { title: "Election of directors", detail: "Three seats on the Board, two on the Audit Committee" },"Other matters","Adjournment", ]} text="Only regular members in good standing may vote. Bring your member ID; members joining online vote through the link sent by text message." by={{ name: "Ma. Cristina Ocampo", role: "Board Secretary" }} issued="2026-02-12" action="#assembly-rsvp" rsvp_by="2026-03-06" hidden={{ assembly: "aga-2026" }} member={{ number: "M-004217", name: "Lourdes Villanueva" }}/>
Source
ui/assembly_notice.qwe
// AssemblyNotice: the notice of a co-op's or credit union's general assembly:// when and where (a hall, an online link, or both), the quorum it needs, the// agenda as a numbered list, who gives notice, and a form to say whether the member// will come. The form is a plain <form method="post">: member_number, name,// attendance ("in_person", "online" or "absent") and question, with any `hidden`// fields.//// <AssemblyNotice coop="Bayanihan Multi-Purpose Cooperative" title="38th Annual General Assembly"// date="2026-03-14" time="9:00 AM to 1:00 PM" venue="Municipal Gymnasium, Poblacion"// online="https://meet.example.org/bmpc-aga" quorum="25% of the 4,812 regular members in good standing"// agenda={["Call to order", "Determination of quorum", { title: "Election of directors", detail: "Three seats" }]}// action="/assembly/2026/rsvp" rsvp_by="2026-03-06" hidden={{ assembly: "aga-2026" }}/>//// title: what meets ("Annual General Assembly"). coop: the co-op's name above it.// kind: a word above the title ("Notice of meeting"). date: an ISO date, shown in// full ("Saturday, March 14, 2026"); time: as text ("9:00 AM", "9:00 AM to// 1:00 PM"). venue: where, as text; address: a line under it. online: a link to// join online (online_label, "Join online"); with it the form offers attending// online. quorum: the quorum, as text. agenda: the items in order, each text or// { title, detail? }. text: a paragraph under the agenda (who may vote, what to// bring). by: who gives the notice, { name, role } ("Board Secretary"); issued:// the notice's date. action: where the RSVP posts ("/rsvp"); rsvp_by: the date to// answer by; hidden: other fields to send, as { name: value }; rsvp: false leaves// the form out. member: a member to fill in, { number, name }. id: a prefix for its// ids, unique on the page ("assembly-notice").import ui/theme.qwe use ui_clslet assembly_notice_css = css`.ui-assembly-notice { display: grid; gap: 20px; min-width: 0; max-width: 720px; padding: 26px 28px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.5 var(--ui-font); }.ui-assembly-notice-head { display: grid; gap: 4px; text-align: center; padding-bottom: 16px; border-bottom: 1px solid var(--ui-border); }.ui-assembly-notice-coop { margin: 0; color: var(--ui-muted); font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }.ui-assembly-notice-kind { margin: 0; color: var(--ui-accent); font-size: 13px; font-weight: 600; }.ui-assembly-notice .ui-assembly-notice-title { display: block; margin: 0; font-size: 22px; font-weight: 700; line-height: 1.2; letter-spacing: -.01em; }.ui-assembly-notice-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; margin: 0; }.ui-assembly-notice-facts div { display: grid; gap: 2px; align-content: start; min-width: 0; padding: 12px 14px; border-radius: var(--ui-radius-sm); background: var(--ui-subtle); }.ui-assembly-notice-facts dt { color: var(--ui-muted); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }.ui-assembly-notice-facts dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }.ui-assembly-notice-facts .ui-assembly-notice-sub { display: block; color: var(--ui-muted); font-weight: 400; font-size: 13px; }.ui-assembly-notice-facts a { color: var(--ui-accent); font-weight: 600; }.ui-assembly-notice-facts a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-assembly-notice h4 { margin: 0 0 8px; font-size: 13px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; color: var(--ui-muted); }.ui-assembly-notice-agenda { margin: 0; padding-left: 26px; display: grid; gap: 6px; }.ui-assembly-notice-agenda li { padding-left: 4px; }.ui-assembly-notice-agenda li::marker { color: var(--ui-muted); font-weight: 600; font-variant-numeric: tabular-nums; }.ui-assembly-notice-detail { display: block; color: var(--ui-muted); font-size: 13px; }.ui-assembly-notice-text { margin: 0; color: var(--ui-muted); }.ui-assembly-notice-by { margin: 0; display: grid; gap: 0; }.ui-assembly-notice-by b { font-weight: 600; }.ui-assembly-notice-by span { color: var(--ui-muted); font-size: 13px; }.ui-assembly-notice-rsvp { display: grid; gap: 14px; padding: 18px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); }.ui-assembly-notice-rsvp h4 { margin: 0; }.ui-assembly-notice-due { margin: -8px 0 0; color: var(--ui-muted); font-size: 13px; }.ui-assembly-notice-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 12px; }.ui-assembly-notice-field { display: grid; gap: 5px; min-width: 0; }.ui-assembly-notice-field label, .ui-assembly-notice-choice legend { font-size: 13px; font-weight: 500; }.ui-assembly-notice-req { margin-left: 2px; color: var(--ui-danger); }.ui-assembly-notice-input { box-sizing: border-box; width: 100%; height: 38px; margin: 0; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.5 var(--ui-font); }textarea.ui-assembly-notice-input { height: auto; padding: 8px 12px; resize: vertical; }.ui-assembly-notice-input:hover { border-color: var(--ui-strong); }.ui-assembly-notice-input:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-assembly-notice-choice { display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 0; padding: 0; border: 0; min-width: 0; }.ui-assembly-notice-choice legend { width: 100%; margin-bottom: 6px; padding: 0; }.ui-assembly-notice-choice label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }.ui-assembly-notice-choice input { margin: 0; accent-color: var(--ui-accent); }.ui-assembly-notice-send { justify-self: start; height: 40px; padding: 0 20px; border: 0; border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg); font: 600 14px var(--ui-font); cursor: pointer; }.ui-assembly-notice-send:hover { filter: brightness(1.08); }.ui-assembly-notice-send:focus-visible { outline: none; box-shadow: var(--ui-focus); }@media (max-width: 560px) { .ui-assembly-notice { padding: 20px 16px; } .ui-assembly-notice-row { grid-template-columns: 1fr; } }@media print { .ui-assembly-notice { border: 0; padding: 0; background: #fff; color: #000; } .ui-assembly-notice-rsvp { display: none; } }`// An ISO date in full ("Saturday, March 14, 2026"), or long ("March 14, 2026"); other text as it is.fnassembly_notice_date(d, style: str = "full") -> str = date.valid(str(d), "date") ? date.format(str(d), { locale: "en-US", style }) : str(d)viewAssemblyNotice(title: str = "Annual General Assembly", coop: str? = null, kind: str? = "Notice of meeting", date = null, time: str? = null, venue: str? = null, address: str? = null, online: str? = null, online_label: str = "Join online", quorum: str? = null, agenda: list = [], text: str? = null, by: map? = null, issued = null, action: str = "/rsvp", rsvp_by = null, hidden: map = {}, rsvp: bool = true, member: map? = null, id: str = "assembly-notice", class: str? = null) { style global assembly_notice_css <article class={ui_cls(["ui-assembly-notice", class])} aria-labelledby={`${id}-title`}> <header.ui-assembly-notice-head> {coop != null && <p.ui-assembly-notice-coop>{coop}</p>} {kind != null && <p.ui-assembly-notice-kind>{kind}</p>} <h3.ui-assembly-notice-title id={`${id}-title`}>{title}</h3> </header> <dl.ui-assembly-notice-facts> {date != null && <div.ui-assembly-notice-when> <dt>When</dt> <dd><time datetime={str(date)}>{assembly_notice_date(date)}</time>{time != null && <span.ui-assembly-notice-sub>{time}</span>}</dd> </div>} {(venue != null || online != null) && <div.ui-assembly-notice-where> <dt>Where</dt> <dd> {venue != null && venue} {address != null && <span.ui-assembly-notice-sub>{address}</span>} {online != null && <span class={venue != null ? "ui-assembly-notice-sub" : null}> {venue != null && "or "}<a href={online} target="_blank" rel="noopener">{online_label}<span.ui-sr> (opens in a new tab)</span></a> </span>} </dd> </div>} {quorum != null && <div.ui-assembly-notice-quorum><dt>Quorum</dt><dd>{quorum}</dd></div>} </dl> {len(agenda) > 0 && <section aria-labelledby={`${id}-agenda`}> <h4 id={`${id}-agenda`}>Agenda</h4> <ol.ui-assembly-notice-agenda> {for [i, a] inenumerate(agenda) => <li key={i}> {type_of(a) == "map" ? [a.title, a.detail != null && <span.ui-assembly-notice-detail>{a.detail}</span>] : a} </li>} </ol> </section>} {text != null && <p.ui-assembly-notice-text>{text}</p>} {by != null && <p.ui-assembly-notice-by> <b>{by.name}</b> {by.role != null && <span>{by.role}</span>} {issued != null && <span><time datetime={str(issued)}>{assembly_notice_date(issued, "long")}</time></span>} </p>} {rsvp && <form.ui-assembly-notice-rsvp method="post" action={action} aria-labelledby={`${id}-rsvp`}> {for k inkeys(hidden) => <inputtype="hidden" name={k} value={str(hidden[k])}/>} <h4 id={`${id}-rsvp`}>Will you attend?</h4> {rsvp_by != null && <p.ui-assembly-notice-due>Please answer by <time datetime={str(rsvp_by)}>{assembly_notice_date(rsvp_by, "long")}</time>.</p>} <div.ui-assembly-notice-row> <div.ui-assembly-notice-field> <labelfor={`${id}-number`}>Member number<span.ui-assembly-notice-req aria-hidden="true">*</span></label> <input.ui-assembly-notice-input id={`${id}-number`} name="member_number" required={true} autocomplete="off" value={member != null ? member.number : null}/> </div> <div.ui-assembly-notice-field> <labelfor={`${id}-name`}>Name<span.ui-assembly-notice-req aria-hidden="true">*</span></label> <input.ui-assembly-notice-input id={`${id}-name`} name="name" required={true} autocomplete="name" value={member != null ? member.name : null}/> </div> </div> <fieldset.ui-assembly-notice-choice> <legend>Attendance<span.ui-assembly-notice-req aria-hidden="true">*</span></legend> <label><inputtype="radio" name="attendance" value="in_person" required={true}/> In person</label> {online != null && <label><inputtype="radio" name="attendance" value="online"/> Online</label>} <label><inputtype="radio" name="attendance" value="absent"/> I cannot attend</label> </fieldset> <div.ui-assembly-notice-field> <labelfor={`${id}-question`}>A question for the board (optional)</label> <textarea.ui-assembly-notice-input id={`${id}-question`} name="question" rows="2"></textarea> </div> <button.ui-assembly-notice-send type="submit">Send my answer</button> </form>} </article>}