05Event invite with RSVP
Three buttons for yes, maybe and no stand beside the date card, and in the loop the yes button cuts to its pressed state as a check icon pops up.
Best forWebinar and event invitations, booking confirmations
aria-pressedcubic-beziertransform: scale

<main class="stage">
<div class="mk is-demo" id="mk">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" class="mk__table">
<tr><td align="center" class="mk__wrap">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" class="mk__card" style="max-width:400px">
<tr><td class="mk__brand">◆ Acme</td></tr>
<tr><td class="mk__evrow">
<div class="mk__datecard" aria-hidden="true">
<span class="mk__month">SEP</span>
<span class="mk__day">24</span>
<span class="mk__weekday">THU</span>
</div>
<div class="mk__evinfo">
<span class="mk__evtitle">Fall meetup, the second one</span>
<span class="mk__meta">Online webinar · 7:30 PM</span>
<span class="mk__meta">2 talks + 30-min Q&A</span>
</div>
</td></tr>
<tr><td class="mk__title">Will you join?</td></tr>
<tr><td align="center" class="mk__rsvp">
<button type="button" class="mk__opt mk__yes" aria-pressed="false">
<span class="mk__check" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" class="mk__ico"><path d="M232,56V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56Z" opacity="0.2"/><path d="M205.66,85.66l-96,96a8,8,0,0,1-11.32,0l-40-40a8,8,0,0,1,11.32-11.32L104,164.69l90.34-90.35a8,8,0,0,1,11.32,11.32Z"/></svg></span>
Going
</button>
<button type="button" class="mk__opt" aria-pressed="false">Maybe</button>
<button type="button" class="mk__opt" aria-pressed="false">No</button>
</td></tr>
<tr><td align="center" class="mk__body">Tap one and the confirmation link arrives by reply.</td></tr>
<tr><td align="center" class="mk__footer">Acme Inc · Seoul · <a href="#" class="mk__unsub">Unsubscribe</a></td></tr>
</table>
</td></tr>
</table>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-base;
$easing: $ease-pop;
$color: $stage-orange;
@include stage($stage-ink);
.stage { position: relative; }
.mk { width: min(100%, 400px); }
.mk__table { border-collapse: collapse; }
.mk__wrap { padding: 0; }
.mk__card {
border-collapse: collapse; background: $stage-paper; border-radius: $r-card;
box-shadow: $shadow-raised; overflow: hidden;
}
.mk__card td { padding: $sp-1 $sp-5; font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif; -webkit-text-size-adjust: 100%; line-height: 1; }
.mk__brand { font-weight: 800; font-size: 11px; line-height: 1; color: rgba(23,20,26,.62); letter-spacing: .02em; padding-top: $sp-2 !important; }
.mk__evrow { display: flex; align-items: center; gap: $sp-3; padding-top: $sp-1 !important; }
.mk__datecard {
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0;
width: clamp(52px, 16vw, 64px); height: clamp(52px, 16vw, 64px);
background: $stage-orange; border-radius: $r-control; color: $subject-ink;
}
.mk__month { font-weight: 700; font-size: 9px; line-height: 1.2; }
.mk__day { font-weight: 800; font-size: clamp(18px, 6vw, 22px); line-height: 1; }
.mk__weekday { font-weight: 600; font-size: 8px; line-height: 1.2; }
.mk__evinfo { display: flex; flex-direction: column; gap: $sp-1; min-width: 0; }
.mk__evtitle { font-weight: 800; font-size: clamp(12px, 3.8vw, 14px); line-height: 1.25; color: $subject-ink; }
.mk__meta { font-weight: 500; font-size: 9px; line-height: 1.3; color: rgba(23,20,26,.62); }
.mk__title { font-weight: 800; font-size: clamp(11px, 3.4vw, 13px); line-height: 1.3; color: $subject-ink; padding-top: $sp-2 !important; }
.mk__rsvp { display: flex; justify-content: center; gap: $sp-2; padding-top: 0 !important; padding-bottom: $sp-1 !important; }
.mk__opt {
display: inline-flex; align-items: center; justify-content: center; gap: $sp-1;
font-family: inherit; font-weight: 700; font-size: 11px; line-height: 1.6;
padding: $sp-1 $sp-4; border-radius: $r-pill; cursor: pointer;
color: rgba(23,20,26,.65); background: rgba(23,20,26,.05); border: 1px solid rgba(23,20,26,.16);
}
.mk__opt:hover { background: rgba(23,20,26,.09); }
.mk__check { display: none; line-height: 0; }
.mk__ico { width: 13px; height: 13px; }
.mk__opt.is-on {
color: $subject-cream; background: $subject-ink; border-color: $subject-ink;
}
.mk__opt.is-on .mk__check { display: inline-flex; }
.mk__opt:focus-visible { outline: 2px solid $stage-orange; outline-offset: 2px; }
.mk__body { font-weight: 500; font-size: clamp(9px, 2.8vw, 10px); line-height: 1.4; color: rgba(23,20,26,.62); padding-top: 0 !important; }
.mk__footer { font-weight: 500; font-size: 8px; line-height: 1.4; color: rgba(23,20,26,.62); padding-top: $sp-1 !important; padding-bottom: $sp-2 !important; }
.mk__footer a { color: inherit; }
// ── 미리보기 루프: 참석 버튼이 눌린 상태로 한 프레임에 컷 전환되고 체크가 튀어 오른다 (2s 주기, 복귀 85%)
// 실제 클릭은 부드러운 전환(is-on 클래스) — 컷은 격자 미리보기 루프에만 쓴다
.mk.is-demo .mk__yes { animation-name: evYes; animation-duration: 2s; animation-timing-function: steps(1, end); animation-iteration-count: infinite; }
.mk.is-demo .mk__yes .mk__check { display: inline-flex; }
.mk.is-demo .mk__yes .mk__check { animation-name: evCheck; animation-duration: 2s; animation-timing-function: cubic-bezier(.2, 1.4, .4, 1); animation-iteration-count: infinite; }
.mk.is-demo .mk__datecard { animation-name: evTilt; animation-duration: 2s; animation-timing-function: $ease-pop; animation-iteration-count: infinite; }
@keyframes evYes {
0%, 34% { color: rgba(23,20,26,.65); background: rgba(23,20,26,.05); border-color: rgba(23,20,26,.16); }
35%, 84% { color: $subject-cream; background: $subject-ink; border-color: $subject-ink; }
85%, 100% { color: rgba(23,20,26,.65); background: rgba(23,20,26,.05); border-color: rgba(23,20,26,.16); }
}
@keyframes evCheck {
0%, 34% { transform: scale(0); }
44% { transform: scale(1.25); }
52%, 84% { transform: scale(1); }
85%, 100% { transform: scale(0); }
}
@keyframes evTilt {
0%, 10% { transform: rotate(0deg) scale(1); }
16% { transform: rotate(-3deg) scale(1.05); }
24%, 100% { transform: rotate(0deg) scale(1); }
}
// ── 320×200 폰 실측 대비 압축 (BD9)
@media (max-width: 340px), (max-height: 220px) {
.mk__card td { padding-left: $sp-3; padding-right: $sp-3; padding-top: 0; padding-bottom: 0; }
.mk__brand { padding-top: 0 !important; font-size: 9px !important; }
.mk__datecard { width: 44px; height: 44px; }
.mk__day { font-size: 15px !important; }
.mk__evtitle { font-size: 10px !important; }
.mk__meta { font-size: 8px !important; }
.mk__evrow { padding-top: $sp-1 !important; }
.mk__title { padding-top: $sp-1 !important; font-size: 10px !important; }
.mk__opt { padding: 0 $sp-3 !important; font-size: 10px !important; }
.mk__body { font-size: 8px !important; }
.mk__footer { font-size: 7px !important; padding-top: 0 !important; padding-bottom: $sp-1 !important; }
}
@media (prefers-reduced-motion: reduce) {
.mk.is-demo .mk__yes,
.mk.is-demo .mk__yes .mk__check,
.mk.is-demo .mk__datecard { animation: none !important; }
.mk.is-demo .mk__yes .mk__check { transform: scale(1); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.stage {
position: relative;
}
.mk {
width: min(100%, 400px);
}
.mk__table {
border-collapse: collapse;
}
.mk__wrap {
padding: 0;
}
.mk__card {
border-collapse: collapse;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
overflow: hidden;
}
.mk__card td {
padding: 4px 20px;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
-webkit-text-size-adjust: 100%;
line-height: 1;
}
.mk__brand {
font-weight: 800;
font-size: 11px;
line-height: 1;
color: rgba(23, 20, 26, 0.62);
letter-spacing: 0.02em;
padding-top: 8px !important;
}
.mk__evrow {
display: flex;
align-items: center;
gap: 12px;
padding-top: 4px !important;
}
.mk__datecard {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0;
width: clamp(52px, 16vw, 64px);
height: clamp(52px, 16vw, 64px);
background: #ff4d1f;
border-radius: 12px;
color: #17141a;
}
.mk__month {
font-weight: 700;
font-size: 9px;
line-height: 1.2;
}
.mk__day {
font-weight: 800;
font-size: clamp(18px, 6vw, 22px);
line-height: 1;
}
.mk__weekday {
font-weight: 600;
font-size: 8px;
line-height: 1.2;
}
.mk__evinfo {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.mk__evtitle {
font-weight: 800;
font-size: clamp(12px, 3.8vw, 14px);
line-height: 1.25;
color: #17141a;
}
.mk__meta {
font-weight: 500;
font-size: 9px;
line-height: 1.3;
color: rgba(23, 20, 26, 0.62);
}
.mk__title {
font-weight: 800;
font-size: clamp(11px, 3.4vw, 13px);
line-height: 1.3;
color: #17141a;
padding-top: 8px !important;
}
.mk__rsvp {
display: flex;
justify-content: center;
gap: 8px;
padding-top: 0 !important;
padding-bottom: 4px !important;
}
.mk__opt {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
font-family: inherit;
font-weight: 700;
font-size: 11px;
line-height: 1.6;
padding: 4px 16px;
border-radius: 999px;
cursor: pointer;
color: rgba(23, 20, 26, 0.65);
background: rgba(23, 20, 26, 0.05);
border: 1px solid rgba(23, 20, 26, 0.16);
}
.mk__opt:hover {
background: rgba(23, 20, 26, 0.09);
}
.mk__check {
display: none;
line-height: 0;
}
.mk__ico {
width: 13px;
height: 13px;
}
.mk__opt.is-on {
color: #fff7e6;
background: #17141a;
border-color: #17141a;
}
.mk__opt.is-on .mk__check {
display: inline-flex;
}
.mk__opt:focus-visible {
outline: 2px solid #ff4d1f;
outline-offset: 2px;
}
.mk__body {
font-weight: 500;
font-size: clamp(9px, 2.8vw, 10px);
line-height: 1.4;
color: rgba(23, 20, 26, 0.62);
padding-top: 0 !important;
}
.mk__footer {
font-weight: 500;
font-size: 8px;
line-height: 1.4;
color: rgba(23, 20, 26, 0.62);
padding-top: 4px !important;
padding-bottom: 8px !important;
}
.mk__footer a {
color: inherit;
}
.mk.is-demo .mk__yes {
animation-name: evYes;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
.mk.is-demo .mk__yes .mk__check {
display: inline-flex;
}
.mk.is-demo .mk__yes .mk__check {
animation-name: evCheck;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
}
.mk.is-demo .mk__datecard {
animation-name: evTilt;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
}
@keyframes evYes {
0%, 34% {
color: rgba(23, 20, 26, 0.65);
background: rgba(23, 20, 26, 0.05);
border-color: rgba(23, 20, 26, 0.16);
}
35%, 84% {
color: #fff7e6;
background: #17141a;
border-color: #17141a;
}
85%, 100% {
color: rgba(23, 20, 26, 0.65);
background: rgba(23, 20, 26, 0.05);
border-color: rgba(23, 20, 26, 0.16);
}
}
@keyframes evCheck {
0%, 34% {
transform: scale(0);
}
44% {
transform: scale(1.25);
}
52%, 84% {
transform: scale(1);
}
85%, 100% {
transform: scale(0);
}
}
@keyframes evTilt {
0%, 10% {
transform: rotate(0deg) scale(1);
}
16% {
transform: rotate(-3deg) scale(1.05);
}
24%, 100% {
transform: rotate(0deg) scale(1);
}
}
@media (max-width: 340px), (max-height: 220px) {
.mk__card td {
padding-left: 12px;
padding-right: 12px;
padding-top: 0;
padding-bottom: 0;
}
.mk__brand {
padding-top: 0 !important;
font-size: 9px !important;
}
.mk__datecard {
width: 44px;
height: 44px;
}
.mk__day {
font-size: 15px !important;
}
.mk__evtitle {
font-size: 10px !important;
}
.mk__meta {
font-size: 8px !important;
}
.mk__evrow {
padding-top: 4px !important;
}
.mk__title {
padding-top: 4px !important;
font-size: 10px !important;
}
.mk__opt {
padding: 0 12px !important;
font-size: 10px !important;
}
.mk__body {
font-size: 8px !important;
}
.mk__footer {
font-size: 7px !important;
padding-top: 0 !important;
padding-bottom: 4px !important;
}
}
@media (prefers-reduced-motion: reduce) {
.mk.is-demo .mk__yes,
.mk.is-demo .mk__yes .mk__check,
.mk.is-demo .mk__datecard {
animation: none !important;
}
.mk.is-demo .mk__yes .mk__check {
transform: scale(1);
}
}var mk = document.getElementById('mk');
var opts = document.querySelectorAll('.mk__opt');
for (var i = 0; i < opts.length; i++) {
opts[i].addEventListener('click', function () {
mk.classList.remove('is-demo');
var on = this.classList.contains('is-on');
for (var j = 0; j < opts.length; j++) {
opts[j].classList.remove('is-on');
opts[j].setAttribute('aria-pressed', 'false');
}
if (!on) {
this.classList.add('is-on');
this.setAttribute('aria-pressed', 'true');
}
});
}import { useState } from "react";
import styles from "./EventInvite.module.scss";
export type EventInviteProps = { duration?: number; easing?: string; color?: string };
export default function EventInvite({ duration = 300, easing = "cubic-bezier(.2,1.4,.4,1)", color = "#ff4d1f" }: EventInviteProps) {
const [demo, setDemo] = useState(true);
const [pick, setPick] = useState(-1);
return (
<main className={styles.stage}>
<div className={styles.mk}>
<div className={styles.card}>
<div className={styles.brand}>◆ Acme</div>
<div className={styles.evrow}>
<div className={styles.datecard} style={{ background: color }} aria-hidden="true">
<span className={styles.month}>9월</span>
<span className={styles.day}>24</span>
<span className={styles.weekday}>목요일</span>
</div>
<div className={styles.evinfo}>
<span className={styles.evtitle}>가을 정기 모임, 두 번째</span>
<span className={styles.meta}>온라인 웨비나 · 19:30</span>
<span className={styles.meta}>발표 2편 + 질문 30분</span>
</div>
</div>
<div className={styles.title}>참석하시겠어요?</div>
<div className={styles.rsvp}>
{["참석", "미정", "불참"].map((label, i) => (
<button
key={label}
type="button"
className={`${styles.opt} ${i === 0 ? styles.yes : ""} ${pick === i ? styles.isOn : ""}`}
style={demo && i === 0 ? { animationName: "evYes", animationDuration: "2s", animationTimingFunction: "steps(1, end)", animationIterationCount: "infinite", transitionDuration: `${duration}ms` } : { transitionDuration: `${duration}ms` }}
aria-pressed={pick === i}
onClick={() => { setDemo(false); setPick((p) => (p === i ? -1 : i)); }}
>
{i === 0 ? (
<span className={styles.check} style={demo ? { animationName: "evCheck", animationDuration: "2s", animationTimingFunction: easing, animationIterationCount: "infinite" } : undefined} aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" className={styles.ico}><path d="M232,56V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56Z" opacity="0.2"/><path d="M205.66,85.66l-96,96a8,8,0,0,1-11.32,0l-40-40a8,8,0,0,1,11.32-11.32L104,164.69l90.34-90.35a8,8,0,0,1,11.32,11.32Z"/></svg>
</span>
) : null}
{label}
</button>
))}
</div>
<div className={styles.body}>누르면 답장으로 확정 링크를 보내드려요.</div>
<div className={styles.footer}>Acme Inc · 서울 · <a href="#" className={styles.unsub}>수신거부</a></div>
</div>
</div>
</main>
);
}05 Event invite with RSVP — Measured area 2.231% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · Marketing Email Design: 9 Templates, Outlook-Safe
The post this demo lives in
Marketing Email Design: 9 Templates, Outlook-SafeMarketing email design shapes the emails you send to sell — newsletters, coupons, countdowns, reminders. These nine are See all demos in this post

