Scheduling

EventCard

An event as a card: a date block, title, time, place, a line about it, an optional picture and badge, and a button.

#schedule#calendar#card#time#no-js

Demo

Preview
  • Date: Sat 14 Nov 2026
  • Time: 19:00–22:00
  • Place: LX Factory, Lisbon

Talks, demos and pizza. Bring a laptop.

People at laptops in a bright roomSold out

Workshop: building apps in a day

  • Date: Wed 20 Jan 2027
  • Time: 09:00–17:00
  • Place: Porto

Release party

Free
  • Date: Wed 2 Dec 2026
  • Time: 18:00
  • Place: Online

Pop in for the demo and stay for the questions.

Not going yet.

Office closed

  • Date: Fri 20 Nov 2026

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.qwe
main.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>}
Thread
76Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.