YearCalendar
A year as twelve small months, with dates and runs of dates marked in colours, today ringed, and a key.
Demo
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | 31 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- Public holiday
- Leave
- Travel
- Deadline
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
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/year_calendar.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/year_calendar.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/year_calendar.qwe
view YearCalendarDemo() = <div.demo-stack> <YearCalendar id="year-calendar-demo" year={2026} today="2026-10-28" href="#year_calendar-{day}" marks={[{ day: "2026-01-01", tone: "danger", label: "New Year's Day" }, { day: "2026-04-03", tone: "danger", label: "Good Friday" }, { day: "2026-04-25", tone: "danger", label: "Freedom Day" }, { day: "2026-12-25", tone: "danger", label: "Christmas" }, { day: "2026-08-03", to: "2026-08-14", tone: "success", label: "Summer leave" }, { day: "2026-03-16", to: "2026-03-18", tone: "info", label: "Conference" }, { day: "2026-11-20", tone: "warning", label: "Release" }]} legend={[{ tone: "danger", label: "Public holiday" }, { tone: "success", label: "Leave" }, { tone: "info", label: "Travel" }, { tone: "warning", label: "Deadline" }]}/> <div id="year-calendar-demo-sunday" style="max-width: 240px"> <YearCalendar year={2027} week_start={7} label="2027, weeks from Sunday"/> </div> </div>Source
ui/year_calendar.qwe
// YearCalendar: a whole year as twelve small months, with dates marked in colours// (holidays, bookings, days off) and today ringed; a key under it says what each colour// means. Each month is a table, and a marked day is named with what it is.//// <YearCalendar year={2026} today="2026-10-28" marks={[// { day: "2026-12-25", tone: "danger", label: "Christmas" },// { day: "2026-08-03", to: "2026-08-14", tone: "success", label: "Holiday" },// ]} legend={[{ tone: "danger", label: "Public holiday" }, { tone: "success", label: "Leave" }]}/>//// year: the year shown. marks: a list of { day ("YYYY-MM-DD"), to? (the last day of a run// of days), tone? ("accent" default, "success", "warning", "danger", "info"), label? }.// today: the day ringed ("YYYY-MM-DD"). week_start: 1 (Monday, default) or 7 (Sunday).// legend: a list of { tone, label } shown under the months. href: a link for each day,// with {day} replaced by its date ("/day/{day}"), to open it. label: what screen readers// call it (the year).import ui/theme.qwe use ui_clslet year_calendar_css = css`.ui-year-calendar { display: grid; gap: 14px; min-width: 0; font: 12px/1.2 var(--ui-font); color: var(--ui-fg); }.ui-year-calendar-months { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 18px 22px; }.ui-year-calendar-month { width: 100%; border-collapse: collapse; table-layout: fixed; }.ui-year-calendar-month caption { padding: 0 0 6px 4px; font-size: 13px; font-weight: 600; text-align: left; }.ui-year-calendar-month th { padding: 2px 0; color: var(--ui-muted); font-size: 10px; font-weight: 600; text-align: center; }.ui-year-calendar-month td { padding: 1px; text-align: center; }.ui-year-calendar-day { display: grid; place-items: center; width: 100%; aspect-ratio: 1; max-width: 26px; margin: auto; border-radius: 50%; color: inherit; font-variant-numeric: tabular-nums; text-decoration: none; --t: var(--ui-accent); }a.ui-year-calendar-day:hover { background: var(--ui-subtle); }a.ui-year-calendar-day:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-year-calendar-weekend { color: var(--ui-muted); }.ui-year-calendar-marked { background: color-mix(in srgb, var(--t) 22%, transparent); color: var(--ui-fg); font-weight: 600; }a.ui-year-calendar-marked:hover { background: color-mix(in srgb, var(--t) 36%, transparent); }.ui-year-calendar-today { box-shadow: inset 0 0 0 2px var(--ui-accent); font-weight: 700; }.ui-year-calendar-success { --t: var(--ui-success); }.ui-year-calendar-warning { --t: var(--ui-warning); }.ui-year-calendar-danger { --t: var(--ui-danger); }.ui-year-calendar-info { --t: var(--ui-info); }.ui-year-calendar-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0; padding: 0; list-style: none; color: var(--ui-muted); font-size: 12px; }.ui-year-calendar-legend li { display: inline-flex; align-items: center; gap: 6px; }.ui-year-calendar-legend i { width: 12px; height: 12px; border-radius: 50%; background: color-mix(in srgb, var(--t) 40%, transparent); --t: var(--ui-accent); }`let year_calendar_months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]let year_calendar_weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]// The marks as a map of each date they cover to the mark: runs are spread over their days.fn year_calendar_by_day(marks: list) -> map = marks | reduce({}, (acc, m) => { let n = m.to != null ? date.days_between(m.day, m.to) : 0 range(0, n + 1) | reduce(acc, (a, i) => set(a, date.add_days(m.day, i), m)) })view YearCalendar(year: int, marks: list = [], today: str? = null, week_start: int = 1, legend: list = [], href: str? = null, label: str? = null, id: str? = null, class: str? = null) { style global year_calendar_css let by_day = year_calendar_by_day(marks) let heads = range(0, 7) | map((i) => year_calendar_weekdays[(i + week_start - 1) % 7]) <section class={ui_cls(["ui-year-calendar", class])} id={id} aria-label={label ?? str(year)}> <div.ui-year-calendar-months> {for m in range(1, 13) => { let first = date.of(year, m, 1) let lead = (date.weekday(first) - week_start + 7) % 7 let n = date.days_in_month(year, m) let cells = (range(0, lead) | map((_i) => null)) + range(1, n + 1) let weeks = range(0, ceil(len(cells) / 7)) | map((r) => slice(cells, r * 7, r * 7 + 7)) <table.ui-year-calendar-month key={m}> <caption>{year_calendar_months[m - 1]}</caption> <thead><tr>{for h in heads => <th key={h} scope="col" abbr={h}>{slice(h, 0, 1)}</th>}</tr></thead> <tbody> {for [r, wk] in enumerate(weeks) => <tr key={r}> {for [c, d] in enumerate(wk) => { let iso = d == null ? null : date.of(year, m, d) let mark = iso == null ? null : get(by_day, iso) let wd = iso == null ? 0 : date.weekday(iso) let cls = ui_cls(["ui-year-calendar-day", wd >= 6 && "ui-year-calendar-weekend", mark != null && "ui-year-calendar-marked", mark != null && mark.tone != null && mark.tone != "accent" && `ui-year-calendar-${mark.tone}`, iso == today && "ui-year-calendar-today"]) let said = iso == null ? null : `${d} ${year_calendar_months[m - 1]}${iso == today ? ", today" : ""}${mark != null && mark.label != null ? `, ${mark.label}` : ""}` <td key={c}> {iso != null && (href != null ? <a class={cls} href={replace(href, "{day}", iso)} data-day={iso} aria-label={said} title={mark?.label}>{d}</a> : <span class={cls} data-day={iso} aria-label={said} title={mark?.label} role={mark != null || iso == today ? "img" : null}>{d}</span>)} </td> }} </tr>} </tbody> </table> }} </div> {len(legend) > 0 && <ul.ui-year-calendar-legend aria-label="Key"> {for l in legend => <li key={l.label}><i class={l.tone != null && l.tone != "accent" ? `ui-year-calendar-${l.tone}` : null} aria-hidden="true"></i>{l.label}</li>} </ul>} </section>}Related
DayViewOne 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 downloadsGanttChartTasks as bars over dates, filled by progress, in groups with spanning bars, with milestones and a today line.7 views · 7 downloadsAgendaListEvents grouped by day with the date at the left, each with its time, title and place; today and tomorrow named.6 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 · 7 downloadsEventCardAn event as a card: a date block, title, time, place, a line about it, an optional picture and badge, and a button.7 views · 6 downloads
Thread
Thread 0 posts
On YearCalendar: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.