EventCard
An event as a card: a date block, title, time, place, a line about it, an optional picture and badge, and a button.
Demo
Workshop: building apps in a day
Release party
FreePop in for the demo and stay for the questions.
Not going yet.
Office closed
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/event_card.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/event_card.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/event_card.qwe
component EventCardDemoLive() { state going = false <div.demo-stack> <EventCard id="event-card-demo-live" day="2026-12-02" start="18:00" title="Release party" place="Online" badge="Free" tone="success" text="Pop in for the demo and stay for the questions." action_label={going ? "You're in" : "I'm in"} on_action={() => { going = !going }}/> <p.demo-note>{going ? "Going." : "Not going yet."}</p> </div>}view EventCardDemo() = <div.demo-stack> <div.demo-row style="align-items: stretch"> <div style="flex: 1 1 320px"> <EventCard id="event-card-demo-first" day="2026-11-14" start="19:00" end="22:00" title="Quillo meetup" href="#event_card" place="LX Factory, Lisbon" text="Talks, demos and pizza. Bring a laptop." action_label="Register" action_href="#event_card"/> </div> <div style="flex: 1 1 320px"> <EventCard id="event-card-demo-image" day="2027-01-20" start="09:00" end="17:00" title="Workshop: building apps in a day" place="Porto" image="https://picsum.photos/seed/workshop/800/300" image_alt="People at laptops in a bright room" badge="Sold out" tone="danger" action_label="Sold out" disabled={true}/> </div> </div> <div.demo-narrow> <EventCardDemoLive/> </div> <div.demo-narrow id="event-card-demo-stack"> <EventCard day="2026-11-20" title="Office closed" tone="neutral" horizontal={false} action={false}/> </div> </div>Source
ui/event_card.qwe
// EventCard: an event to go to: a block with its date, the title, when and where, a// line about it, and a button (Register, Get tickets). A picture can go across the top,// and a badge in its corner (Sold out, Free, Online).//// <EventCard day="2026-11-14" start="19:00" end="22:00" title="Quillo meetup" place="Lisbon, LX Factory"// text="Talks, demos and pizza." action_label="Register" action_href="/events/7/register"/>// <EventCard day="2026-12-02" title="Release party" place="Online" badge="Free"// on_action={() => { going = true }} action_label={going ? "Going" : "I'm in"}/>//// day: the date ("YYYY-MM-DD"), shown as a block (month, day, weekday) and said in full.// start, end: "HH:MM". title; href: a link on the title. place; text: a line or two.// image: a picture across the top (image_alt says what it shows). badge: a word on the// corner; tone: its colour and the date's, "accent" (default), "success", "warning",// "danger", "info" or "neutral". action_label: the button's text ("Details");// action_href makes it a link; action: false drops it. disabled: the button is off// (sold out). horizontal: the date block beside the text (default) or above it.// on_action(): inside one of your components, called when the button is pressed.import ui/theme.qwe use ui_clslet event_card_css = css`.ui-event-card { display: grid; min-width: 0; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); font: 14px/1.5 var(--ui-font); color: var(--ui-fg); overflow: hidden; --t: var(--ui-accent); --ts: var(--ui-accent-soft); }.ui-event-card-success { --t: var(--ui-success); --ts: var(--ui-success-soft); }.ui-event-card-warning { --t: var(--ui-warning); --ts: var(--ui-warning-soft); }.ui-event-card-danger { --t: var(--ui-danger); --ts: var(--ui-danger-soft); }.ui-event-card-info { --t: var(--ui-info); --ts: var(--ui-info-soft); }.ui-event-card-neutral { --t: var(--ui-muted); --ts: var(--ui-subtle); }.ui-event-card-media { position: relative; }.ui-event-card-media img { display: block; width: 100%; height: 160px; object-fit: cover; }.ui-event-card-badge { position: absolute; top: 10px; right: 10px; padding: 3px 9px; border-radius: 999px; background: var(--ui-surface); color: var(--t); font-size: 12px; font-weight: 600; box-shadow: var(--ui-shadow); }.ui-event-card-body { display: flex; gap: 16px; padding: 16px; }.ui-event-card-stack .ui-event-card-body { flex-direction: column; gap: 12px; }.ui-event-card-date { display: grid; justify-items: center; align-content: center; flex: none; width: 60px; padding: 8px 4px; border-radius: var(--ui-radius-sm); background: var(--ts); color: var(--t); line-height: 1.1; }.ui-event-card-stack .ui-event-card-date { width: fit-content; min-width: 60px; }.ui-event-card-date span { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }.ui-event-card-date b { font-size: 26px; font-weight: 700; color: var(--ui-fg); }.ui-event-card-main { display: grid; gap: 4px; flex: 1; min-width: 0; align-content: start; }.ui-event-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }.ui-event-card-title { margin: 0; font-size: 16px; font-weight: 650; line-height: 1.3; }.ui-event-card-title a { color: inherit; text-decoration: none; }.ui-event-card-title a:hover { text-decoration: underline; }.ui-event-card-title a:focus-visible { outline: none; border-radius: 3px; box-shadow: var(--ui-focus); }.ui-event-card-top .ui-event-card-badge { position: static; flex: none; padding: 2px 8px; background: var(--ts); box-shadow: none; }.ui-event-card-meta { display: flex; flex-wrap: wrap; gap: 2px 14px; margin: 0; padding: 0; list-style: none; color: var(--ui-muted); font-size: 13px; }.ui-event-card-meta li { display: inline-flex; align-items: center; gap: 5px; }.ui-event-card-meta svg { width: 14px; height: 14px; }.ui-event-card-text { margin: 4px 0 0; color: var(--ui-muted); }.ui-event-card-action { display: flex; align-items: center; gap: 10px; margin-top: 10px; }.ui-event-card-btn { display: inline-flex; align-items: center; justify-content: center; height: 36px; padding: 0 16px; border: 1px solid transparent; border-radius: var(--ui-radius-sm); background: var(--t); color: var(--ui-accent-fg); font: 500 14px/1 var(--ui-font); text-decoration: none; cursor: pointer; transition: background .15s;}.ui-event-card-btn:hover { background: color-mix(in srgb, var(--t) 88%, var(--ui-fg)); }.ui-event-card-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-event-card-btn:disabled, .ui-event-card-btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; pointer-events: none; }@media (prefers-reduced-motion: reduce) { .ui-event-card-btn { transition: none; } }`view EventCard(day: str, title: str, start: str? = null, end: str? = null, place: str? = null, text: str? = null, href: str? = null, image: str? = null, image_alt: str = "", badge: str? = null, tone: str = "accent", action: bool = true, action_label: str = "Details", action_href: str? = null, disabled: bool = false, horizontal: bool = true, id: str? = null, on_action = null, class: str? = null) { style global event_card_css let when = start != null ? (end != null ? `${start}–${end}` : start) : null let said_date = date.format(day, "dddd D MMMM YYYY") <article class={ui_cls(["ui-event-card", tone != "accent" && `ui-event-card-${tone}`, !horizontal && "ui-event-card-stack", class])} id={id} aria-label={`${title}, ${said_date}${when != null ? `, ${when}` : ""}${place != null ? `, ${place}` : ""}`}> {image != null && <div.ui-event-card-media> <img src={image} alt={image_alt} loading="lazy"/> {badge != null && <span.ui-event-card-badge>{badge}</span>} </div>} <div.ui-event-card-body> <time.ui-event-card-date datetime={day} aria-hidden="true"> <span>{date.format(day, "MMM")}</span> <b>{date.parts(day).day}</b> <span>{date.format(day, "ddd")}</span> </time> <div.ui-event-card-main> <div.ui-event-card-top> <h3.ui-event-card-title>{href != null ? <a href={href}>{title}</a> : title}</h3> {badge != null && image == null && <span.ui-event-card-badge>{badge}</span>} </div> <ul.ui-event-card-meta> <li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg><span.ui-sr>Date:</span> {date.format(day, "ddd D MMM YYYY")}</li> {when != null && <li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg><span.ui-sr>Time:</span> {when}</li>} {place != null && <li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s7-6.5 7-12a7 7 0 0 0-14 0c0 5.5 7 12 7 12z"/><circle cx="12" cy="10" r="2.5"/></svg><span.ui-sr>Place:</span> {place}</li>} </ul> {text != null && <p.ui-event-card-text>{text}</p>} {action && <div.ui-event-card-action> {action_href != null ? <a.ui-event-card-btn href={action_href} aria-disabled={disabled ? "true" : null}>{action_label}</a> : <button.ui-event-card-btn type="button" disabled={disabled} @click={() => { on_action && on_action() }}>{action_label}</button>} </div>} </div> </div> </article>}Related
AgendaListEvents grouped by day with the date at the left, each with its time, title and place; today and tomorrow named.6 views · 6 downloadsDayViewOne day as a column of hours with its events drawn at their times, side by side when they overlap, and all-day events above.7 views · 7 downloadsWeekViewA week of hour columns with events drawn at their times, side by side when they overlap, and all-day events across the top.5 views · 6 downloadsAvailabilityGridDays across and hours down: click or drag across the cells to mark when you are available; the slots go in a hidden input.7 views · 8 downloadsBookingSummaryWhat was booked: the place and picture, dates in and out with the nights counted, guests, price lines, total and status.6 views · 7 downloadsOpeningHoursA week's opening hours with today marked and whether it is open now, closing soon, or when it opens next.6 views · 6 downloads
Thread
Thread 0 posts
On EventCard: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.