Signature Library · Indian
Temple Bell
A brass bell as a toggle: dull when off, swings and glows when switched on.
Sends a toggle event with true or false.
States
Default
Hover
Active
Focus
Code
temple-bell.html
<button class="sig-temple-bell" type="button" aria-pressed="false" data-done="Notifying">
<span class="sig-temple-bell__chain" aria-hidden="true"></span>
<span class="sig-temple-bell__halo" aria-hidden="true"></span>
<svg class="sig-temple-bell__bell" viewBox="0 0 100 112" aria-hidden="true">
<circle class="sig-temple-bell__clapper" cx="50" cy="100" r="9"></circle>
<path class="sig-temple-bell__metal" d="M50 6c5 0 8 3 8 8v3c16 5 24 20 26 42 1 13 6 21 12 27v6H4v-6c6-6 11-14 12-27 2-22 10-37 26-42v-3c0-5 3-8 8-8Z"></path>
<path class="sig-temple-bell__shine" d="M33 36c-7 9-8 22-9 34h6c1-12 3-24 9-32Z"></path>
<path class="sig-temple-bell__band" d="M11 78h78v5H11Z"></path>
</svg>
<span class="sig-temple-bell__word" data-label>Notify me</span>
</button>
temple-bell.css
.sig-temple-bell {
--sig-brass: #e0ab3d;
--sig-chain: #8a6a2f;
--sig-text: #f1f1f6;
--sig-speed: 900ms;
}
.sig-temple-bell {
position: relative;
box-sizing: border-box;
display: grid;
justify-items: center;
gap: 8px;
padding: 0;
color: var(--sig-text);
font-family: system-ui, sans-serif;
font-size: 14px;
font-weight: 600;
background: none;
border: 0;
cursor: pointer;
--tone: saturate(.3) brightness(.68);
}
.sig-temple-bell:hover {
--swing: sig-swing var(--sig-speed) ease-in-out;
}
.sig-temple-bell:focus-visible {
outline: 2px solid var(--sig-brass);
outline-offset: 6px;
border-radius: 8px;
}
.sig-temple-bell[aria-pressed=true] {
--tone: drop-shadow(0 0 14px color-mix(in srgb, var(--sig-brass) 70%, transparent));
}
.sig-temple-bell[data-state=ring] {
--swing: sig-swing var(--sig-speed) ease-in-out;
--spread: sig-halo var(--sig-speed) ease-out;
}
.sig-temple-bell__chain {
width: 5px;
height: 30px;
background: repeating-linear-gradient(180deg, var(--sig-chain) 0 5px, transparent 5px 7px);
}
.sig-temple-bell__halo {
position: absolute;
top: 34px;
left: calc(50% - 50px);
width: 100px;
height: 100px;
box-sizing: border-box;
border: 2px solid var(--sig-brass);
border-radius: 50%;
opacity: 0;
animation: var(--spread, none);
}
.sig-temple-bell__bell {
display: block;
width: 96px;
height: 104px;
overflow: visible;
filter: var(--tone);
transform-origin: 50% 0;
animation: var(--swing, none);
}
.sig-temple-bell__metal {
fill: var(--sig-brass);
}
.sig-temple-bell__shine {
fill: #fff;
opacity: 0.5;
}
.sig-temple-bell__band {
fill: #000;
opacity: 0.25;
}
.sig-temple-bell__clapper {
fill: color-mix(in srgb, var(--sig-brass) 60%, #000);
transform-origin: 50px 20px;
animation: var(--swing, none);
animation-direction: reverse;
}
@keyframes sig-swing {
0%, 100% { transform: rotate(0); }
20% { transform: rotate(13deg); }
45% { transform: rotate(-10deg); }
70% { transform: rotate(6deg); }
88% { transform: rotate(-3deg); }
}
@keyframes sig-halo {
0% { transform: scale(.6); opacity: .8; }
100% { transform: scale(1.5); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.sig-temple-bell__halo,
.sig-temple-bell__bell,
.sig-temple-bell__clapper {
animation: none !important;
transition: none !important;
}
}
temple-bell.js
document.querySelectorAll(".sig-temple-bell").forEach((el) => {
const word = el.querySelector("[data-label]");
const idle = word.textContent;
const ring = () => {
const on = el.getAttribute("aria-pressed") !== "true";
el.setAttribute("aria-pressed", String(on));
word.textContent = on ? el.dataset.done ?? "" : idle;
el.removeAttribute("data-state");
void el.offsetWidth;
el.dataset.state = "ring";
el.dispatchEvent(new CustomEvent("toggle", { bubbles: true, detail: on }));
};
const rest = () => el.removeAttribute("data-state");
el.addEventListener("click", ring);
el.addEventListener("animationend", rest);
return () => {
el.removeEventListener("click", ring);
el.removeEventListener("animationend", rest);
};
});
temple-bell.tsx
import { useEffect, useRef } from "react";
import type React from "react";
import "./temple-bell.css"; // the CSS tab
function attach(el: HTMLElement) {
const word = el.querySelector("[data-label]") as HTMLElement;
const idle = word.textContent;
const ring = () => {
const on = el.getAttribute("aria-pressed") !== "true";
el.setAttribute("aria-pressed", String(on));
word.textContent = on ? el.dataset.done ?? "" : idle;
el.removeAttribute("data-state");
void el.offsetWidth;
el.dataset.state = "ring";
el.dispatchEvent(new CustomEvent("toggle", { bubbles: true, detail: on }));
};
const rest = () => el.removeAttribute("data-state");
el.addEventListener("click", ring);
el.addEventListener("animationend", rest);
return () => {
el.removeEventListener("click", ring);
el.removeEventListener("animationend", rest);
};
}
export function SigTempleBell({ children = "Notify me", ...props }: React.ComponentProps<"button">) {
const ref = useRef<HTMLButtonElement>(null);
useEffect(() => attach(ref.current!), []);
return (
<button className="sig-temple-bell" type="button" aria-pressed="false" data-done="Notifying" ref={ref} {...props}>
<span className="sig-temple-bell__chain" aria-hidden="true" />
<span className="sig-temple-bell__halo" aria-hidden="true" />
<svg className="sig-temple-bell__bell" viewBox="0 0 100 112" aria-hidden="true">
<circle className="sig-temple-bell__clapper" cx="50" cy="100" r="9" />
<path className="sig-temple-bell__metal" d="M50 6c5 0 8 3 8 8v3c16 5 24 20 26 42 1 13 6 21 12 27v6H4v-6c6-6 11-14 12-27 2-22 10-37 26-42v-3c0-5 3-8 8-8Z" />
<path className="sig-temple-bell__shine" d="M33 36c-7 9-8 22-9 34h6c1-12 3-24 9-32Z" />
<path className="sig-temple-bell__band" d="M11 78h78v5H11Z" />
</svg>
<span className="sig-temple-bell__word" data-label>{children}</span>
</button>
);
}
temple-bell.html
<button class="sig-temple-bell relative [box-sizing:border-box] grid [justify-items:center] gap-[8px] [padding:0] text-[#f1f1f6] [font-family:system-ui,_sans-serif] text-[14px] font-[600] [background:none] [border:0] cursor-pointer [--tone:saturate(.3)_brightness(.68)] hover:[--swing:sig-swing_900ms_ease-in-out] focus-visible:[outline:2px_solid_#e0ab3d] focus-visible:[outline-offset:6px] focus-visible:rounded-[8px] [&[aria-pressed=true]]:[--tone:drop-shadow(0_0_14px_color-mix(in_srgb,_#e0ab3d_70%,_transparent))] [&[data-state=ring]]:[--swing:sig-swing_900ms_ease-in-out] [&[data-state=ring]]:[--spread:sig-halo_900ms_ease-out]" type="button" aria-pressed="false" data-done="Notifying">
<span class="w-[5px] h-[30px] [background:repeating-linear-gradient(180deg,_#8a6a2f_0_5px,_transparent_5px_7px)]" aria-hidden="true"></span>
<span class="absolute [top:34px] [left:calc(50%_-_50px)] w-[100px] h-[100px] [box-sizing:border-box] [border:2px_solid_#e0ab3d] rounded-[50%] [opacity:0] [animation:var(--spread,_none)] motion-reduce:[animation:none!important]" aria-hidden="true"></span>
<svg class="[display:block] w-[96px] h-[104px] [overflow:visible] [filter:var(--tone)] [transform-origin:50%_0] [animation:var(--swing,_none)] motion-reduce:[animation:none!important]" viewBox="0 0 100 112" aria-hidden="true">
<circle class="[fill:color-mix(in_srgb,_#e0ab3d_60%,_#000)] [transform-origin:50px_20px] [animation:var(--swing,_none)] [animation-direction:reverse] motion-reduce:[animation:none!important]" cx="50" cy="100" r="9"></circle>
<path class="[fill:#e0ab3d]" d="M50 6c5 0 8 3 8 8v3c16 5 24 20 26 42 1 13 6 21 12 27v6H4v-6c6-6 11-14 12-27 2-22 10-37 26-42v-3c0-5 3-8 8-8Z"></path>
<path class="[fill:#fff] [opacity:0.5]" d="M33 36c-7 9-8 22-9 34h6c1-12 3-24 9-32Z"></path>
<path class="[fill:#000] [opacity:0.25]" d="M11 78h78v5H11Z"></path>
</svg>
<span data-label>Notify me</span>
</button>
<!-- A class cannot hold keyframes: keep this in your CSS. -->
<style>
@keyframes sig-swing {
0%, 100% { transform: rotate(0); }
20% { transform: rotate(13deg); }
45% { transform: rotate(-10deg); }
70% { transform: rotate(6deg); }
88% { transform: rotate(-3deg); }
}
@keyframes sig-halo {
0% { transform: scale(.6); opacity: .8; }
100% { transform: scale(1.5); opacity: 0; }
}
</style>
<!-- Needs the script from the JS tab. -->