Signature Library · Indian
Banana Leaf Menu
A meal laid on a leaf: tap a dish to serve it and the bill adds up.
Sends a change event with the total. Write each dish with its price at the end.
States
Default
Hover
Active
Focus
Code
banana-leaf.html
<div class="sig-banana-leaf">
<span class="sig-banana-leaf__rib" aria-hidden="true"></span>
<div class="sig-banana-leaf__plate" role="group" aria-label="Dishes">
<button class="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="40" aria-label="Rice, ₹40">
<span class="sig-banana-leaf__food" aria-hidden="true"></span>
<span class="sig-banana-leaf__name">Rice</span>
</button>
<button class="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="20" aria-label="Sambar, ₹20">
<span class="sig-banana-leaf__food" aria-hidden="true"></span>
<span class="sig-banana-leaf__name">Sambar</span>
</button>
<button class="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="15" aria-label="Poriyal, ₹15">
<span class="sig-banana-leaf__food" aria-hidden="true"></span>
<span class="sig-banana-leaf__name">Poriyal</span>
</button>
<button class="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="5" aria-label="Appalam, ₹5">
<span class="sig-banana-leaf__food" aria-hidden="true"></span>
<span class="sig-banana-leaf__name">Appalam</span>
</button>
<button class="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="25" aria-label="Payasam, ₹25">
<span class="sig-banana-leaf__food" aria-hidden="true"></span>
<span class="sig-banana-leaf__name">Payasam</span>
</button>
</div>
<span class="sig-banana-leaf__sum" aria-live="polite">
Bill ₹
<span class="sig-banana-leaf__total" data-total>0</span>
</span>
</div>
banana-leaf.css
.sig-banana-leaf {
--sig-leaf: #2f8a3b;
--sig-w: 320px;
--sig-speed: 160ms;
}
.sig-banana-leaf {
position: relative;
box-sizing: border-box;
display: grid;
gap: 6px;
width: var(--sig-w);
max-width: 100%;
padding: 16px 22px 12px;
font-family: system-ui, sans-serif;
color: #f4f7e4;
background: repeating-linear-gradient(100deg, transparent 0 14px, rgba(0,0,0,.09) 14px 15px), linear-gradient(170deg, color-mix(in srgb, var(--sig-leaf) 82%, #fff), var(--sig-leaf) 55%, color-mix(in srgb, var(--sig-leaf) 74%, #000));
border-radius: 62% 38% 38% 62% / 50% 12% 12% 50%;
}
.sig-banana-leaf__rib {
position: absolute;
left: 8px;
right: 0;
top: calc(50% - 1px);
height: 2px;
background: color-mix(in srgb, var(--sig-leaf) 55%, #fff);
opacity: 0.55;
}
.sig-banana-leaf__plate {
position: relative;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 4px 8px;
}
.sig-banana-leaf__dish {
display: grid;
justify-items: center;
gap: 2px;
padding: 2px;
color: inherit;
font: inherit;
font-size: 11px;
background: none;
border: 0;
border-radius: 8px;
cursor: pointer;
}
.sig-banana-leaf__dish:focus-visible {
outline: 2px solid #fff;
}
.sig-banana-leaf__dish:hover {
--grow: 1.08;
}
.sig-banana-leaf__dish[aria-pressed=true] {
--grow: 1.08;
--serve: 1;
}
.sig-banana-leaf__dish:nth-child(5n+2) {
--dish: #d9822b;
}
.sig-banana-leaf__dish:nth-child(5n+3) {
--dish: #e4c33a;
}
.sig-banana-leaf__dish:nth-child(5n+4) {
--dish: #e8c98f;
}
.sig-banana-leaf__dish:nth-child(5n) {
--dish: #c98a4e;
}
.sig-banana-leaf__food {
width: 36px;
height: 30px;
background: var(--dish, #fff6dd);
border-radius: 52% 48% 46% 54% / 60% 58% 42% 40%;
box-shadow: inset -3px -3px 4px rgba(0,0,0,.2);
opacity: calc(0.45 + 0.55 * var(--serve, 0));
transform: scale(var(--grow, 1));
transition: transform var(--sig-speed) ease, opacity var(--sig-speed) ease;
}
.sig-banana-leaf__name {
white-space: nowrap;
}
.sig-banana-leaf__sum {
position: relative;
text-align: center;
font-size: 13px;
font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
.sig-banana-leaf__food {
animation: none !important;
transition: none !important;
}
}
banana-leaf.js
document.querySelectorAll(".sig-banana-leaf").forEach((el) => {
const dishes = Array.from(el.querySelectorAll("[data-price]"));
const total = el.querySelector("[data-total]");
const pick = (e) => {
const hit = (e.target).closest("[data-price]");
if (!hit) return;
hit.setAttribute("aria-pressed", String(hit.getAttribute("aria-pressed") !== "true"));
const sum = dishes.filter((d) => d.getAttribute("aria-pressed") === "true").reduce((n, d) => n + Number(d.dataset.price), 0);
total.textContent = String(sum);
el.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: sum }));
};
el.addEventListener("click", pick);
return () => el.removeEventListener("click", pick);
});
banana-leaf.tsx
import { useEffect, useRef } from "react";
import type React from "react";
import "./banana-leaf.css"; // the CSS tab
function attach(el: HTMLElement) {
const dishes = Array.from(el.querySelectorAll("[data-price]")) as HTMLElement[];
const total = el.querySelector("[data-total]") as HTMLElement;
const pick = (e: MouseEvent) => {
const hit = (e.target as HTMLElement).closest("[data-price]");
if (!hit) return;
hit.setAttribute("aria-pressed", String(hit.getAttribute("aria-pressed") !== "true"));
const sum = dishes.filter((d) => d.getAttribute("aria-pressed") === "true").reduce((n, d) => n + Number(d.dataset.price), 0);
total.textContent = String(sum);
el.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: sum }));
};
el.addEventListener("click", pick);
return () => el.removeEventListener("click", pick);
}
export function SigBananaLeaf(props: React.ComponentProps<"div">) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => attach(ref.current!), []);
return (
<div className="sig-banana-leaf" ref={ref} {...props}>
<span className="sig-banana-leaf__rib" aria-hidden="true" />
<div className="sig-banana-leaf__plate" role="group" aria-label="Dishes">
<button className="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="40" aria-label="Rice, ₹40">
<span className="sig-banana-leaf__food" aria-hidden="true" />
<span className="sig-banana-leaf__name">{"Rice"}</span>
</button>
<button className="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="20" aria-label="Sambar, ₹20">
<span className="sig-banana-leaf__food" aria-hidden="true" />
<span className="sig-banana-leaf__name">{"Sambar"}</span>
</button>
<button className="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="15" aria-label="Poriyal, ₹15">
<span className="sig-banana-leaf__food" aria-hidden="true" />
<span className="sig-banana-leaf__name">{"Poriyal"}</span>
</button>
<button className="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="5" aria-label="Appalam, ₹5">
<span className="sig-banana-leaf__food" aria-hidden="true" />
<span className="sig-banana-leaf__name">{"Appalam"}</span>
</button>
<button className="sig-banana-leaf__dish" type="button" aria-pressed="false" data-price="25" aria-label="Payasam, ₹25">
<span className="sig-banana-leaf__food" aria-hidden="true" />
<span className="sig-banana-leaf__name">{"Payasam"}</span>
</button>
</div>
<span className="sig-banana-leaf__sum" aria-live="polite">
{"Bill ₹"}
<span className="sig-banana-leaf__total" data-total>{"0"}</span>
</span>
</div>
);
}
banana-leaf.html
<div class="sig-banana-leaf relative [box-sizing:border-box] grid gap-[6px] w-[320px] [max-width:100%] [padding:16px_22px_12px] [font-family:system-ui,_sans-serif] text-[#f4f7e4] [background:repeating-linear-gradient(100deg,_transparent_0_14px,_rgba(0,0,0,.09)_14px_15px),_linear-gradient(170deg,_color-mix(in_srgb,_#2f8a3b_82%,_#fff),_#2f8a3b_55%,_color-mix(in_srgb,_#2f8a3b_74%,_#000))] rounded-[62%_38%_38%_62%_/_50%_12%_12%_50%]">
<span class="absolute [left:8px] [right:0] [top:calc(50%_-_1px)] h-[2px] [background:color-mix(in_srgb,_#2f8a3b_55%,_#fff)] [opacity:0.55]" aria-hidden="true"></span>
<div class="relative [display:flex] [flex-wrap:wrap] justify-center gap-[4px_8px]" role="group" aria-label="Dishes">
<button class="grid [justify-items:center] gap-[2px] [padding:2px] [color:inherit] [font:inherit] text-[11px] [background:none] [border:0] rounded-[8px] cursor-pointer focus-visible:[outline:2px_solid_#fff] hover:[--grow:1.08] [&[aria-pressed=true]]:[--grow:1.08] [&[aria-pressed=true]]:[--serve:1] [&:nth-child(5n+2)]:[--dish:#d9822b] [&:nth-child(5n+3)]:[--dish:#e4c33a] [&:nth-child(5n+4)]:[--dish:#e8c98f] [&:nth-child(5n)]:[--dish:#c98a4e]" type="button" aria-pressed="false" data-price="40" aria-label="Rice, ₹40">
<span class="w-[36px] h-[30px] [background:var(--dish,_#fff6dd)] rounded-[52%_48%_46%_54%_/_60%_58%_42%_40%] [box-shadow:inset_-3px_-3px_4px_rgba(0,0,0,.2)] [opacity:calc(0.45_+_0.55_*_var(--serve,_0))] [transform:scale(var(--grow,_1))] [transition:transform_160ms_ease,_opacity_160ms_ease] motion-reduce:[transition:none!important]" aria-hidden="true"></span>
<span class="whitespace-nowrap">Rice</span>
</button>
<button class="grid [justify-items:center] gap-[2px] [padding:2px] [color:inherit] [font:inherit] text-[11px] [background:none] [border:0] rounded-[8px] cursor-pointer focus-visible:[outline:2px_solid_#fff] hover:[--grow:1.08] [&[aria-pressed=true]]:[--grow:1.08] [&[aria-pressed=true]]:[--serve:1] [&:nth-child(5n+2)]:[--dish:#d9822b] [&:nth-child(5n+3)]:[--dish:#e4c33a] [&:nth-child(5n+4)]:[--dish:#e8c98f] [&:nth-child(5n)]:[--dish:#c98a4e]" type="button" aria-pressed="false" data-price="20" aria-label="Sambar, ₹20">
<span class="w-[36px] h-[30px] [background:var(--dish,_#fff6dd)] rounded-[52%_48%_46%_54%_/_60%_58%_42%_40%] [box-shadow:inset_-3px_-3px_4px_rgba(0,0,0,.2)] [opacity:calc(0.45_+_0.55_*_var(--serve,_0))] [transform:scale(var(--grow,_1))] [transition:transform_160ms_ease,_opacity_160ms_ease] motion-reduce:[transition:none!important]" aria-hidden="true"></span>
<span class="whitespace-nowrap">Sambar</span>
</button>
<button class="grid [justify-items:center] gap-[2px] [padding:2px] [color:inherit] [font:inherit] text-[11px] [background:none] [border:0] rounded-[8px] cursor-pointer focus-visible:[outline:2px_solid_#fff] hover:[--grow:1.08] [&[aria-pressed=true]]:[--grow:1.08] [&[aria-pressed=true]]:[--serve:1] [&:nth-child(5n+2)]:[--dish:#d9822b] [&:nth-child(5n+3)]:[--dish:#e4c33a] [&:nth-child(5n+4)]:[--dish:#e8c98f] [&:nth-child(5n)]:[--dish:#c98a4e]" type="button" aria-pressed="false" data-price="15" aria-label="Poriyal, ₹15">
<span class="w-[36px] h-[30px] [background:var(--dish,_#fff6dd)] rounded-[52%_48%_46%_54%_/_60%_58%_42%_40%] [box-shadow:inset_-3px_-3px_4px_rgba(0,0,0,.2)] [opacity:calc(0.45_+_0.55_*_var(--serve,_0))] [transform:scale(var(--grow,_1))] [transition:transform_160ms_ease,_opacity_160ms_ease] motion-reduce:[transition:none!important]" aria-hidden="true"></span>
<span class="whitespace-nowrap">Poriyal</span>
</button>
<button class="grid [justify-items:center] gap-[2px] [padding:2px] [color:inherit] [font:inherit] text-[11px] [background:none] [border:0] rounded-[8px] cursor-pointer focus-visible:[outline:2px_solid_#fff] hover:[--grow:1.08] [&[aria-pressed=true]]:[--grow:1.08] [&[aria-pressed=true]]:[--serve:1] [&:nth-child(5n+2)]:[--dish:#d9822b] [&:nth-child(5n+3)]:[--dish:#e4c33a] [&:nth-child(5n+4)]:[--dish:#e8c98f] [&:nth-child(5n)]:[--dish:#c98a4e]" type="button" aria-pressed="false" data-price="5" aria-label="Appalam, ₹5">
<span class="w-[36px] h-[30px] [background:var(--dish,_#fff6dd)] rounded-[52%_48%_46%_54%_/_60%_58%_42%_40%] [box-shadow:inset_-3px_-3px_4px_rgba(0,0,0,.2)] [opacity:calc(0.45_+_0.55_*_var(--serve,_0))] [transform:scale(var(--grow,_1))] [transition:transform_160ms_ease,_opacity_160ms_ease] motion-reduce:[transition:none!important]" aria-hidden="true"></span>
<span class="whitespace-nowrap">Appalam</span>
</button>
<button class="grid [justify-items:center] gap-[2px] [padding:2px] [color:inherit] [font:inherit] text-[11px] [background:none] [border:0] rounded-[8px] cursor-pointer focus-visible:[outline:2px_solid_#fff] hover:[--grow:1.08] [&[aria-pressed=true]]:[--grow:1.08] [&[aria-pressed=true]]:[--serve:1] [&:nth-child(5n+2)]:[--dish:#d9822b] [&:nth-child(5n+3)]:[--dish:#e4c33a] [&:nth-child(5n+4)]:[--dish:#e8c98f] [&:nth-child(5n)]:[--dish:#c98a4e]" type="button" aria-pressed="false" data-price="25" aria-label="Payasam, ₹25">
<span class="w-[36px] h-[30px] [background:var(--dish,_#fff6dd)] rounded-[52%_48%_46%_54%_/_60%_58%_42%_40%] [box-shadow:inset_-3px_-3px_4px_rgba(0,0,0,.2)] [opacity:calc(0.45_+_0.55_*_var(--serve,_0))] [transform:scale(var(--grow,_1))] [transition:transform_160ms_ease,_opacity_160ms_ease] motion-reduce:[transition:none!important]" aria-hidden="true"></span>
<span class="whitespace-nowrap">Payasam</span>
</button>
</div>
<span class="relative [text-align:center] text-[13px] font-[700]" aria-live="polite">
Bill ₹
<span data-total>0</span>
</span>
</div>
<!-- Needs the script from the JS tab. -->