Ticketing — Date Filter Chips
A polished event-ticketing date filter that pairs one-tap window chips (today, this weekend, this week, this month) with a custom range calendar. Selecting a chip jumps the calendar to the matching month and updates an applied-range banner with a live show count, while tapping two days draws a custom start-to-end range. The matching event list re-sorts and re-filters instantly, showing perforated ticket posters, prices, and low-stock or sold-out badges.
MCP
Code
:root {
--brand: #7c3aed;
--brand-d: #6d28d9;
--ink: #0e0e16;
--ink-2: #3a3a4d;
--muted: #6c6c80;
--bg: #f5f4f9;
--surface: #ffffff;
--line: rgba(14, 14, 22, 0.1);
--ok: #16a34a;
--warn: #d97706;
--danger: #dc2626;
--accent: #ff3d81;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-sm: 0 2px 8px rgba(14, 14, 22, 0.08);
--sh-md: 0 14px 36px rgba(14, 14, 22, 0.16);
--sh-brand: 0 12px 28px rgba(124, 58, 237, 0.34);
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(900px 520px at 12% -8%, rgba(124, 58, 237, 0.14), transparent 60%),
radial-gradient(720px 440px at 108% 8%, rgba(255, 61, 129, 0.12), transparent 58%),
var(--bg);
min-height: 100vh;
padding: 32px 18px 64px;
}
.shell {
max-width: 760px;
margin: 0 auto;
display: grid;
gap: 22px;
}
/* ---------- Masthead ---------- */
.masthead__brand {
display: flex;
align-items: center;
gap: 14px;
}
.ticket-tag {
width: 42px;
height: 42px;
border-radius: 12px;
background: linear-gradient(135deg, var(--brand), var(--accent));
box-shadow: var(--sh-brand);
position: relative;
flex: none;
}
.ticket-tag::before,
.ticket-tag::after {
content: "";
position: absolute;
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--bg);
top: 50%;
transform: translateY(-50%);
}
.ticket-tag::before { left: -4px; }
.ticket-tag::after { right: -4px; }
.kicker {
margin: 0;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--brand);
}
.masthead h1 {
margin: 2px 0 0;
font-size: clamp(1.6rem, 5vw, 2.2rem);
font-weight: 800;
letter-spacing: -0.02em;
}
.masthead__sub {
margin: 12px 0 0;
color: var(--ink-2);
max-width: 48ch;
}
/* ---------- Filter card ---------- */
.filter {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-md);
padding: 22px;
display: grid;
gap: 18px;
}
.filter__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.filter__head h2 {
margin: 0;
font-size: 1.05rem;
font-weight: 700;
}
.link-btn {
border: none;
background: none;
color: var(--brand);
font: inherit;
font-weight: 600;
font-size: 0.88rem;
cursor: pointer;
padding: 6px 8px;
border-radius: var(--r-sm);
}
.link-btn:hover:not(:disabled) { background: rgba(124, 58, 237, 0.1); }
.link-btn:disabled { color: var(--muted); cursor: default; opacity: 0.6; }
/* ---------- Chips ---------- */
.chips {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.chip {
border: 1.5px solid var(--line);
background: var(--surface);
color: var(--ink-2);
font: inherit;
font-weight: 600;
font-size: 0.9rem;
padding: 9px 16px;
border-radius: 999px;
cursor: pointer;
transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}
.chip:hover {
border-color: var(--brand);
color: var(--brand);
transform: translateY(-1px);
}
.chip:active { transform: translateY(0); }
.chip[aria-pressed="true"] {
background: linear-gradient(135deg, var(--brand), var(--brand-d));
border-color: transparent;
color: #fff;
box-shadow: var(--sh-brand);
}
/* ---------- Calendar ---------- */
.calendar {
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
background: linear-gradient(180deg, rgba(124, 58, 237, 0.04), transparent 60%);
}
.calendar__toggle {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
padding: 13px 16px;
font-weight: 600;
user-select: none;
}
.calendar__toggle::-webkit-details-marker { display: none; }
.calendar__toggle-label {
display: inline-flex;
align-items: center;
gap: 9px;
color: var(--ink);
}
.calendar__chevron {
width: 10px;
height: 10px;
border-right: 2px solid var(--muted);
border-bottom: 2px solid var(--muted);
transform: rotate(45deg);
transition: transform 0.18s ease;
}
.calendar[open] .calendar__chevron { transform: rotate(-135deg); }
.calendar__body {
padding: 4px 16px 16px;
}
.cal-nav {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.cal-nav__btn {
width: 34px;
height: 34px;
border-radius: var(--r-sm);
border: 1px solid var(--line);
background: var(--surface);
color: var(--ink);
font-size: 1.2rem;
line-height: 1;
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease;
}
.cal-nav__btn:hover {
border-color: var(--brand);
background: rgba(124, 58, 237, 0.08);
}
.cal-nav__label {
font-weight: 700;
font-size: 0.95rem;
}
.cal-dow {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
margin-bottom: 4px;
}
.cal-dow span {
text-align: center;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.05em;
color: var(--muted);
text-transform: uppercase;
}
.cal-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
}
.cal-cell {
aspect-ratio: 1 / 1;
border: none;
background: none;
font: inherit;
font-weight: 500;
font-size: 0.88rem;
color: var(--ink);
border-radius: var(--r-sm);
cursor: pointer;
position: relative;
transition: background 0.1s ease, color 0.1s ease;
}
.cal-cell:hover:not(:disabled):not(.is-empty) {
background: rgba(124, 58, 237, 0.12);
}
.cal-cell.is-empty { cursor: default; }
.cal-cell:disabled {
color: var(--muted);
opacity: 0.4;
cursor: not-allowed;
}
.cal-cell.is-today::after {
content: "";
position: absolute;
bottom: 6px;
left: 50%;
transform: translateX(-50%);
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--accent);
}
.cal-cell.in-range {
background: rgba(124, 58, 237, 0.13);
border-radius: 0;
color: var(--brand-d);
}
.cal-cell.range-start,
.cal-cell.range-end {
background: var(--brand);
color: #fff;
font-weight: 700;
}
.cal-cell.range-start { border-radius: var(--r-sm) 0 0 var(--r-sm); }
.cal-cell.range-end { border-radius: 0 var(--r-sm) var(--r-sm) 0; }
.cal-cell.range-start.range-end { border-radius: var(--r-sm); }
.cal-cell.range-start.is-today::after,
.cal-cell.range-end.is-today::after { background: #fff; }
.cal-hint {
margin: 12px 0 0;
font-size: 0.8rem;
color: var(--muted);
}
/* ---------- Applied range ---------- */
.applied {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
padding: 12px 14px;
border-radius: var(--r-md);
background: rgba(124, 58, 237, 0.08);
border: 1px dashed rgba(124, 58, 237, 0.4);
}
.applied__label {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--brand);
}
.applied__value {
font-weight: 700;
color: var(--ink);
}
.applied__count {
margin-left: auto;
font-size: 0.82rem;
font-weight: 600;
color: var(--ink-2);
background: var(--surface);
padding: 3px 10px;
border-radius: 999px;
border: 1px solid var(--line);
}
/* ---------- Results ---------- */
.results__title {
margin: 0 0 6px;
font-size: 1.05rem;
font-weight: 700;
}
.event-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 12px;
}
.event {
display: flex;
gap: 14px;
align-items: stretch;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--sh-sm);
padding: 12px;
transition: transform 0.14s ease, box-shadow 0.14s ease;
}
.event:hover {
transform: translateY(-2px);
box-shadow: var(--sh-md);
}
.event__poster {
width: 84px;
flex: none;
border-radius: var(--r-sm);
display: grid;
place-content: center;
text-align: center;
color: #fff;
font-weight: 800;
font-size: 0.78rem;
letter-spacing: 0.04em;
padding: 8px;
position: relative;
overflow: hidden;
}
.event__poster span { position: relative; z-index: 1; line-height: 1.2; }
.event__date-badge {
position: absolute;
top: 6px;
left: 6px;
background: rgba(14, 14, 22, 0.65);
backdrop-filter: blur(4px);
border-radius: 7px;
padding: 3px 7px;
font-size: 0.62rem;
line-height: 1.1;
text-align: center;
z-index: 2;
}
.event__date-badge b { display: block; font-size: 0.95rem; }
.event__body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
gap: 4px;
}
.event__name {
margin: 0;
font-size: 1rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.event__meta {
margin: 0;
font-size: 0.84rem;
color: var(--muted);
display: flex;
flex-wrap: wrap;
gap: 4px 10px;
}
.event__price {
font-weight: 700;
color: var(--ink);
}
.badge {
display: inline-block;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
padding: 3px 8px;
border-radius: 999px;
align-self: flex-start;
}
.badge--low { background: rgba(217, 119, 6, 0.14); color: var(--warn); }
.badge--out { background: rgba(220, 38, 38, 0.14); color: var(--danger); }
.badge--hot { background: rgba(255, 61, 129, 0.14); color: var(--accent); }
.empty {
margin: 0;
padding: 28px 18px;
text-align: center;
color: var(--muted);
background: var(--surface);
border: 1px dashed var(--line);
border-radius: var(--r-md);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 22px);
background: var(--ink);
color: #fff;
padding: 11px 18px;
border-radius: 999px;
font-size: 0.88rem;
font-weight: 600;
box-shadow: var(--sh-md);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 40;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
@media (max-width: 520px) {
body { padding: 22px 12px 48px; }
.filter { padding: 16px; }
.chip { flex: 1 1 calc(50% - 5px); text-align: center; }
.event__poster { width: 66px; }
.applied__count { margin-left: 0; }
}(function () {
"use strict";
// --- Fixed "now" so the demo is deterministic ---
var NOW = new Date(2026, 5, 17); // 17 Jun 2026 (month is 0-based)
NOW.setHours(0, 0, 0, 0);
var DAY = 86400000;
var MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
var MONTHS_SHORT = ["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"];
// --- Fictional events (dates as [year, monthIndex, day]) ---
var POSTERS = [
"linear-gradient(135deg,#7c3aed,#ff3d81)",
"linear-gradient(135deg,#0ea5e9,#7c3aed)",
"linear-gradient(135deg,#f59e0b,#dc2626)",
"linear-gradient(135deg,#16a34a,#0ea5e9)",
"linear-gradient(135deg,#ff3d81,#f59e0b)",
"linear-gradient(135deg,#6d28d9,#0e0e16)"
];
var EVENTS = [
{ name: "Neon Bloom — Rooftop Set", venue: "The Halcyon Loft", city: "Brooklyn", price: 38, ymd: [2026,5,17], stock: "hot" },
{ name: "Velvet Static (DJ Aria)", venue: "Basement 9", city: "Brooklyn", price: 25, ymd: [2026,5,19], stock: "low" },
{ name: "Saturday Carnival of Sound", venue: "Pier 6 Open Air", city: "Brooklyn", price: 54, ymd: [2026,5,20], stock: "ok" },
{ name: "Lo-Fi Sunday Garden", venue: "Greenpoint Yard", city: "Brooklyn", price: 18, ymd: [2026,5,21], stock: "ok" },
{ name: "Midweek Jazz Lab", venue: "Blue Cellar", city: "Manhattan", price: 30, ymd: [2026,5,24], stock: "low" },
{ name: "Pulsewave Festival — Night 1", venue: "Eastern Arena", city: "Queens", price: 72, ymd: [2026,5,27], stock: "out" },
{ name: "Indie Echo Showcase", venue: "Marlow Hall", city: "Brooklyn", price: 28, ymd: [2026,5,30], stock: "ok" },
{ name: "Synth City Afterparty", venue: "Rift Warehouse", city: "Queens", price: 22, ymd: [2026,6,4], stock: "hot" },
{ name: "Acoustic Tides", venue: "Harbor Chapel", city: "Manhattan", price: 34, ymd: [2026,6,11], stock: "ok" },
{ name: "Bassline Bazaar", venue: "Underline", city: "Brooklyn", price: 26, ymd: [2026,6,18], stock: "low" }
];
EVENTS.forEach(function (e, i) {
e.date = new Date(e.ymd[0], e.ymd[1], e.ymd[2]);
e.date.setHours(0, 0, 0, 0);
e.poster = POSTERS[i % POSTERS.length];
});
// --- State ---
var state = { start: null, end: null, chip: null };
var viewMonth = new Date(NOW.getFullYear(), NOW.getMonth(), 1);
// --- DOM ---
var chipEls = Array.prototype.slice.call(document.querySelectorAll(".chip"));
var clearBtn = document.getElementById("clearBtn");
var calGrid = document.getElementById("calGrid");
var monthLabel = document.getElementById("monthLabel");
var calHint = document.getElementById("calHint");
var appliedEl = document.getElementById("applied");
var appliedValue = document.getElementById("appliedValue");
var appliedCount = document.getElementById("appliedCount");
var eventList = document.getElementById("eventList");
var emptyState = document.getElementById("emptyState");
var toastEl = document.getElementById("toast");
// --- Helpers ---
function startOfWeek(d) { // Monday-based
var c = new Date(d);
var day = (c.getDay() + 6) % 7;
c.setDate(c.getDate() - day);
c.setHours(0, 0, 0, 0);
return c;
}
function addDays(d, n) { var c = new Date(d); c.setDate(c.getDate() + n); return c; }
function sameDay(a, b) { return a && b && a.getTime() === b.getTime(); }
function fmt(d) { return d.getDate() + " " + MONTHS_SHORT[d.getMonth()][0] + MONTHS_SHORT[d.getMonth()].slice(1).toLowerCase(); }
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2200);
}
// --- Quick-range presets ---
function presetRange(key) {
var sow = startOfWeek(NOW);
switch (key) {
case "today":
return [NOW, NOW];
case "weekend": {
var sat = addDays(sow, 5), sun = addDays(sow, 6);
return [sat, sun];
}
case "week":
return [NOW, addDays(sow, 6)];
case "month": {
var first = new Date(NOW.getFullYear(), NOW.getMonth(), 1);
var last = new Date(NOW.getFullYear(), NOW.getMonth() + 1, 0);
return [NOW > first ? NOW : first, last];
}
}
return [null, null];
}
// --- Emit selection (console event for consumers) ---
function emit() {
var detail = {
start: state.start ? new Date(state.start) : null,
end: state.end ? new Date(state.end) : null,
preset: state.chip
};
document.dispatchEvent(new CustomEvent("daterange:change", { detail: detail }));
if (window.console && console.info) {
console.info("[date-filter] selection", detail);
}
}
// --- Chip handlers ---
chipEls.forEach(function (chip) {
chip.setAttribute("aria-pressed", "false");
chip.addEventListener("click", function () {
var key = chip.dataset.range;
if (state.chip === key) { clearAll(false); return; }
var r = presetRange(key);
state.start = r[0];
state.end = r[1];
state.chip = key;
// jump calendar to the start month
viewMonth = new Date(state.start.getFullYear(), state.start.getMonth(), 1);
render();
toast("Range: " + chip.textContent.trim());
emit();
});
});
// --- Calendar navigation ---
document.getElementById("prevMonth").addEventListener("click", function () {
viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() - 1, 1);
renderCalendar();
});
document.getElementById("nextMonth").addEventListener("click", function () {
viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 1);
renderCalendar();
});
// --- Calendar day click (custom range) ---
function onDayClick(date) {
state.chip = null; // custom selection clears preset
if (!state.start || (state.start && state.end)) {
state.start = date;
state.end = null;
calHint.textContent = "Now pick an end date.";
} else {
if (date < state.start) {
state.end = state.start;
state.start = date;
} else {
state.end = date;
}
calHint.textContent = "Tap a day to set the start, then tap again for the end.";
}
render();
emit();
}
// --- Clear ---
function clearAll(notify) {
state.start = null;
state.end = null;
state.chip = null;
calHint.textContent = "Tap a day to set the start, then tap again for the end.";
render();
if (notify !== false) toast("Dates cleared");
emit();
}
clearBtn.addEventListener("click", function () { clearAll(true); });
// --- Rendering ---
function renderCalendar() {
monthLabel.textContent = MONTHS[viewMonth.getMonth()] + " " + viewMonth.getFullYear();
calGrid.innerHTML = "";
var firstWeekday = (new Date(viewMonth.getFullYear(), viewMonth.getMonth(), 1).getDay() + 6) % 7;
var daysInMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 0).getDate();
for (var i = 0; i < firstWeekday; i++) {
var pad = document.createElement("span");
pad.className = "cal-cell is-empty";
pad.setAttribute("aria-hidden", "true");
calGrid.appendChild(pad);
}
for (var d = 1; d <= daysInMonth; d++) {
var cur = new Date(viewMonth.getFullYear(), viewMonth.getMonth(), d);
cur.setHours(0, 0, 0, 0);
var btn = document.createElement("button");
btn.type = "button";
btn.className = "cal-cell";
btn.textContent = d;
btn.setAttribute("role", "gridcell");
var label = d + " " + MONTHS[viewMonth.getMonth()] + " " + viewMonth.getFullYear();
btn.setAttribute("aria-label", label);
if (cur < NOW) {
btn.disabled = true;
}
if (sameDay(cur, NOW)) btn.classList.add("is-today");
if (state.start && state.end && cur > state.start && cur < state.end) {
btn.classList.add("in-range");
}
if (sameDay(cur, state.start)) { btn.classList.add("range-start"); btn.setAttribute("aria-selected", "true"); }
if (sameDay(cur, state.end)) { btn.classList.add("range-end"); btn.setAttribute("aria-selected", "true"); }
if (state.start && !state.end && sameDay(cur, state.start)) btn.classList.add("range-end");
(function (date) {
btn.addEventListener("click", function () { onDayClick(date); });
})(cur);
calGrid.appendChild(btn);
}
}
function renderApplied() {
var has = !!state.start;
clearBtn.disabled = !has;
if (!has) { appliedEl.hidden = true; return; }
appliedEl.hidden = false;
var label;
if (state.start && state.end && !sameDay(state.start, state.end)) {
label = fmt(state.start) + " – " + fmt(state.end);
} else {
label = fmt(state.start);
}
appliedValue.textContent = label;
var n = countMatches();
appliedCount.textContent = n + (n === 1 ? " show" : " shows");
}
function inRange(d) {
if (!state.start) return true;
var end = state.end || state.start;
return d >= state.start && d <= end;
}
function countMatches() {
return EVENTS.filter(function (e) { return inRange(e.date); }).length;
}
function badge(stock) {
if (stock === "out") return '<span class="badge badge--out">Sold out</span>';
if (stock === "low") return '<span class="badge badge--low">Low stock</span>';
if (stock === "hot") return '<span class="badge badge--hot">Selling fast</span>';
return "";
}
function renderEvents() {
var matches = EVENTS.filter(function (e) { return inRange(e.date); })
.sort(function (a, b) { return a.date - b.date; });
eventList.innerHTML = "";
emptyState.hidden = matches.length > 0;
matches.forEach(function (e) {
var dow = ["SUN","MON","TUE","WED","THU","FRI","SAT"][e.date.getDay()];
var li = document.createElement("li");
li.className = "event";
li.innerHTML =
'<div class="event__poster" style="background:' + e.poster + '">' +
'<div class="event__date-badge">' + dow + '<b>' + e.date.getDate() + '</b>' + MONTHS_SHORT[e.date.getMonth()] + '</div>' +
'<span>' + e.city.toUpperCase() + '</span>' +
'</div>' +
'<div class="event__body">' +
badge(e.stock) +
'<h3 class="event__name">' + e.name + '</h3>' +
'<p class="event__meta">' +
'<span>' + e.venue + '</span>' +
'<span class="event__price">$' + e.price + '</span>' +
'</p>' +
'</div>';
eventList.appendChild(li);
});
}
function syncChips() {
chipEls.forEach(function (chip) {
chip.setAttribute("aria-pressed", chip.dataset.range === state.chip ? "true" : "false");
});
}
function render() {
renderCalendar();
renderApplied();
renderEvents();
syncChips();
}
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>When are you going? — Date Filter</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="shell">
<header class="masthead">
<div class="masthead__brand">
<span class="ticket-tag" aria-hidden="true"></span>
<div>
<p class="kicker">Nightfall Live · Tickets</p>
<h1>When are you going?</h1>
</div>
</div>
<p class="masthead__sub">Pick a quick window or draw your own range. We'll surface the shows that fit.</p>
</header>
<section class="filter" aria-label="Date filter">
<div class="filter__head">
<h2>Filter by date</h2>
<button id="clearBtn" class="link-btn" type="button" disabled>Clear dates</button>
</div>
<div class="chips" role="group" aria-label="Quick date ranges">
<button class="chip" type="button" data-range="today">Today</button>
<button class="chip" type="button" data-range="weekend">This weekend</button>
<button class="chip" type="button" data-range="week">This week</button>
<button class="chip" type="button" data-range="month">This month</button>
</div>
<details class="calendar" id="calendarWrap">
<summary class="calendar__toggle">
<span class="calendar__toggle-label">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="4" width="18" height="17" rx="3" stroke="currentColor" stroke-width="2"/><path d="M3 9h18M8 2v4M16 2v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
Custom range
</span>
<span class="calendar__chevron" aria-hidden="true"></span>
</summary>
<div class="calendar__body">
<div class="cal-nav">
<button id="prevMonth" class="cal-nav__btn" type="button" aria-label="Previous month">‹</button>
<span id="monthLabel" class="cal-nav__label" aria-live="polite"></span>
<button id="nextMonth" class="cal-nav__btn" type="button" aria-label="Next month">›</button>
</div>
<div class="cal-dow" aria-hidden="true">
<span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span><span>Su</span>
</div>
<div id="calGrid" class="cal-grid" role="grid" aria-label="Choose a start and end date"></div>
<p class="cal-hint" id="calHint">Tap a day to set the start, then tap again for the end.</p>
</div>
</details>
<div class="applied" id="applied" hidden>
<span class="applied__label">Showing</span>
<span class="applied__value" id="appliedValue"></span>
<span class="applied__count" id="appliedCount"></span>
</div>
</section>
<section class="results" aria-label="Matching events">
<h2 class="results__title">Upcoming shows</h2>
<ul class="event-list" id="eventList"></ul>
<p class="empty" id="emptyState" hidden>No shows land in that window. Try a wider range.</p>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Date Filter Chips
A compact “when are you going?” control for an event-ticketing browse page. Four quick chips cover the windows people actually search for — today, this weekend, this week, and this month — and each one snaps the applied-range banner and the show list into place in a single tap. Tapping an active chip toggles it back off, so the same control both sets and clears a preset.
For anything the chips don’t cover, a collapsible mini calendar lets you draw a custom window: the first tap sets the start, the second sets the end, and out-of-order taps are reconciled automatically. Past days are disabled, today is dotted, and the selected span is highlighted as a continuous range with rounded caps. Choosing custom days quietly releases any active chip so the two modes never fight each other.
Every change updates a dashed applied-range banner with a human-readable label and a live count of matching shows, then re-filters and re-sorts the event list — each card carrying a perforated poster, venue, price, and a selling-fast, low-stock, or sold-out badge. The component emits a daterange:change event (and logs the selection) so a host page can react to the chosen window.
Illustrative UI only — fictional events, not a real ticketing service.