Signature Library · Indian
Tear-off Calendar
Today's page on a wall pad. Tear it off for the next day; Sundays print red.
Starts on the visitor's today. Sends a change event with the date.
States
Default
Hover
Active
Focus
Code
tear-calendar.html
<div class="sig-tear-calendar" role="group" aria-label="Daily calendar">
<span class="sig-tear-calendar__head">
<button class="sig-tear-calendar__today" type="button" data-today>Today</button>
<span class="sig-tear-calendar__year" data-year>2026</span>
</span>
<span class="sig-tear-calendar__pad">
<span class="sig-tear-calendar__under" data-under aria-hidden="true">
<span class="sig-tear-calendar__month" data-m>November</span>
<span class="sig-tear-calendar__num" data-d>5</span>
<span class="sig-tear-calendar__week" data-w>Thursday</span>
</span>
<button class="sig-tear-calendar__page" type="button" aria-label="Tear off this page" data-page>
<span class="sig-tear-calendar__month" data-m>November</span>
<span class="sig-tear-calendar__num" data-d>5</span>
<span class="sig-tear-calendar__week" data-w>Thursday</span>
</button>
</span>
</div>
tear-calendar.css
.sig-tear-calendar {
--sig-paper: #fbf7ec;
--sig-ink: #24242c;
--sig-sunday: #d3312f;
--sig-board: #b3242a;
--sig-speed: 460ms;
}
.sig-tear-calendar {
position: relative;
box-sizing: border-box;
display: grid;
width: 170px;
font-family: Georgia, 'Times New Roman', serif;
filter: drop-shadow(0 8px 10px rgba(0,0,0,.4));
}
.sig-tear-calendar__head {
display: flex;
justify-content: space-between;
align-items: center;
height: 28px;
padding: 0 10px;
color: #fff;
font-size: 12px;
background: linear-gradient(180deg, color-mix(in srgb, var(--sig-board) 80%, #fff), var(--sig-board));
border-radius: 6px 6px 0 0;
}
.sig-tear-calendar__today {
padding: 0;
color: inherit;
font-family: inherit;
font-size: 11px;
text-decoration: underline;
background: none;
border: 0;
cursor: pointer;
}
.sig-tear-calendar__year {
letter-spacing: 0.12em;
}
.sig-tear-calendar__pad {
position: relative;
display: block;
height: 142px;
box-shadow: 0 3px 0 #d8d2c2, 0 6px 0 #c2bcad;
}
.sig-tear-calendar__under {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 6px 4px;
color: var(--sig-ink);
font-family: Georgia, 'Times New Roman', serif;
background: var(--sig-paper);
border: 0;
}
.sig-tear-calendar__page {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 6px 4px;
color: var(--sig-ink);
font-family: Georgia, 'Times New Roman', serif;
background: var(--sig-paper);
border: 0;
cursor: pointer;
transform-origin: 90% 0;
}
.sig-tear-calendar__page:hover {
transform: rotate(-1.5deg);
}
.sig-tear-calendar__page:focus-visible {
outline: 2px solid var(--sig-board);
outline-offset: -3px;
}
.sig-tear-calendar__page[data-sun] {
--tint: var(--sig-sunday);
}
.sig-tear-calendar__under[data-sun] {
--tint: var(--sig-sunday);
}
.sig-tear-calendar__page[data-state=tear] {
animation: sig-tear var(--sig-speed) cubic-bezier(.3,.1,.5,1) forwards;
}
.sig-tear-calendar__month {
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--tint, var(--sig-ink));
}
.sig-tear-calendar__num {
font-family: 'Times New Roman', serif;
font-size: 78px;
font-weight: 700;
line-height: 1;
color: var(--tint, var(--sig-ink));
}
.sig-tear-calendar__week {
font-size: 13px;
font-style: italic;
color: var(--tint, var(--sig-ink));
}
@keyframes sig-tear {
0% { transform: rotate(0); opacity: 1; }
30% { transform: translateY(-6px) rotate(-4deg); opacity: 1; }
100% { transform: translate(46px, -170px) rotate(24deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.sig-tear-calendar__page {
animation: none !important;
transition: none !important;
}
}
tear-calendar.js
document.querySelectorAll(".sig-tear-calendar").forEach((el) => {
const page = el.querySelector("[data-page]");
const under = el.querySelector("[data-under]");
const year = el.querySelector("[data-year]");
const reset = el.querySelector("[data-today]");
const still = matchMedia("(prefers-reduced-motion: reduce)");
const plus = (at, by) => {
const day = new Date(at);
day.setDate(day.getDate() + by);
return day.getTime();
};
const paint = (face, at) => {
const day = new Date(at);
(face.querySelector("[data-m]")).textContent = day.toLocaleDateString("en-IN", { month: "long" });
(face.querySelector("[data-d]")).textContent = String(day.getDate());
(face.querySelector("[data-w]")).textContent = day.toLocaleDateString("en-IN", { weekday: "long" });
face.toggleAttribute("data-sun", day.getDay() === 0);
};
let shown = Date.now();
const show = () => {
paint(page, shown);
paint(under, plus(shown, 1));
year.textContent = String(new Date(shown).getFullYear());
el.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: new Date(shown) }));
};
const done = () => {
page.removeAttribute("data-state");
shown = plus(shown, 1);
show();
};
const tear = () => {
if (page.dataset.state) return;
if (still.matches) return done();
page.dataset.state = "tear";
};
const today = () => {
shown = Date.now();
show();
};
show();
page.addEventListener("click", tear);
page.addEventListener("animationend", done);
reset.addEventListener("click", today);
return () => {
page.removeEventListener("click", tear);
page.removeEventListener("animationend", done);
reset.removeEventListener("click", today);
};
});
tear-calendar.tsx
import { useEffect, useRef } from "react";
import type React from "react";
import "./tear-calendar.css"; // the CSS tab
function attach(el: HTMLElement) {
const page = el.querySelector("[data-page]") as HTMLElement;
const under = el.querySelector("[data-under]") as HTMLElement;
const year = el.querySelector("[data-year]") as HTMLElement;
const reset = el.querySelector("[data-today]") as HTMLElement;
const still = matchMedia("(prefers-reduced-motion: reduce)");
const plus = (at: number, by: number) => {
const day = new Date(at);
day.setDate(day.getDate() + by);
return day.getTime();
};
const paint = (face: HTMLElement, at: number) => {
const day = new Date(at);
(face.querySelector("[data-m]") as HTMLElement).textContent = day.toLocaleDateString("en-IN", { month: "long" });
(face.querySelector("[data-d]") as HTMLElement).textContent = String(day.getDate());
(face.querySelector("[data-w]") as HTMLElement).textContent = day.toLocaleDateString("en-IN", { weekday: "long" });
face.toggleAttribute("data-sun", day.getDay() === 0);
};
let shown = Date.now();
const show = () => {
paint(page, shown);
paint(under, plus(shown, 1));
year.textContent = String(new Date(shown).getFullYear());
el.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: new Date(shown) }));
};
const done = () => {
page.removeAttribute("data-state");
shown = plus(shown, 1);
show();
};
const tear = () => {
if (page.dataset.state) return;
if (still.matches) return done();
page.dataset.state = "tear";
};
const today = () => {
shown = Date.now();
show();
};
show();
page.addEventListener("click", tear);
page.addEventListener("animationend", done);
reset.addEventListener("click", today);
return () => {
page.removeEventListener("click", tear);
page.removeEventListener("animationend", done);
reset.removeEventListener("click", today);
};
}
export function SigTearCalendar({ children = "Today", ...props }: React.ComponentProps<"div">) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => attach(ref.current!), []);
return (
<div className="sig-tear-calendar" role="group" aria-label="Daily calendar" ref={ref} {...props}>
<span className="sig-tear-calendar__head">
<button className="sig-tear-calendar__today" type="button" data-today>{children}</button>
<span className="sig-tear-calendar__year" data-year>{"2026"}</span>
</span>
<span className="sig-tear-calendar__pad">
<span className="sig-tear-calendar__under" data-under aria-hidden="true">
<span className="sig-tear-calendar__month" data-m>{"November"}</span>
<span className="sig-tear-calendar__num" data-d>{"5"}</span>
<span className="sig-tear-calendar__week" data-w>{"Thursday"}</span>
</span>
<button className="sig-tear-calendar__page" type="button" aria-label="Tear off this page" data-page>
<span className="sig-tear-calendar__month" data-m>{"November"}</span>
<span className="sig-tear-calendar__num" data-d>{"5"}</span>
<span className="sig-tear-calendar__week" data-w>{"Thursday"}</span>
</button>
</span>
</div>
);
}
tear-calendar.html
<div class="sig-tear-calendar relative [box-sizing:border-box] grid w-[170px] [font-family:Georgia,_'Times_New_Roman',_serif] [filter:drop-shadow(0_8px_10px_rgba(0,0,0,.4))]" role="group" aria-label="Daily calendar">
<span class="[display:flex] [justify-content:space-between] items-center h-[28px] [padding:0_10px] text-[#fff] text-[12px] [background:linear-gradient(180deg,_color-mix(in_srgb,_#b3242a_80%,_#fff),_#b3242a)] rounded-[6px_6px_0_0]">
<button class="[padding:0] [color:inherit] [font-family:inherit] text-[11px] [text-decoration:underline] [background:none] [border:0] cursor-pointer" type="button" data-today>Today</button>
<span class="[letter-spacing:0.12em]" data-year>2026</span>
</span>
<span class="relative [display:block] h-[142px] [box-shadow:0_3px_0_#d8d2c2,_0_6px_0_#c2bcad]">
<span class="absolute [inset:0] [display:flex] [flex-direction:column] items-center justify-center [padding:6px_4px] text-[#24242c] [font-family:Georgia,_'Times_New_Roman',_serif] bg-[#fbf7ec] [border:0] [&[data-sun]]:[--tint:#d3312f]" data-under aria-hidden="true">
<span class="text-[12px] [letter-spacing:0.14em] [text-transform:uppercase] [color:var(--tint,_#24242c)]" data-m>November</span>
<span class="[font-family:'Times_New_Roman',_serif] text-[78px] font-[700] [line-height:1] [color:var(--tint,_#24242c)]" data-d>5</span>
<span class="text-[13px] [font-style:italic] [color:var(--tint,_#24242c)]" data-w>Thursday</span>
</span>
<button class="absolute [inset:0] [display:flex] [flex-direction:column] items-center justify-center [padding:6px_4px] text-[#24242c] [font-family:Georgia,_'Times_New_Roman',_serif] bg-[#fbf7ec] [border:0] cursor-pointer [transform-origin:90%_0] hover:[transform:rotate(-1.5deg)] focus-visible:[outline:2px_solid_#b3242a] focus-visible:[outline-offset:-3px] [&[data-sun]]:[--tint:#d3312f] [&[data-state=tear]]:[animation:sig-tear_460ms_cubic-bezier(.3,.1,.5,1)_forwards] motion-reduce:[animation:none!important]" type="button" aria-label="Tear off this page" data-page>
<span class="text-[12px] [letter-spacing:0.14em] [text-transform:uppercase] [color:var(--tint,_#24242c)]" data-m>November</span>
<span class="[font-family:'Times_New_Roman',_serif] text-[78px] font-[700] [line-height:1] [color:var(--tint,_#24242c)]" data-d>5</span>
<span class="text-[13px] [font-style:italic] [color:var(--tint,_#24242c)]" data-w>Thursday</span>
</button>
</span>
</div>
<!-- A class cannot hold keyframes: keep this in your CSS. -->
<style>
@keyframes sig-tear {
0% { transform: rotate(0); opacity: 1; }
30% { transform: translateY(-6px) rotate(-4deg); opacity: 1; }
100% { transform: translate(46px, -170px) rotate(24deg); opacity: 0; }
}
</style>
<!-- Needs the script from the JS tab. -->