v133 · css
Fan Menu
Two demos driven purely by sibling-index() and sibling-count(): a radial fan menu where each item's angular position is calc((sibling-index() - 1) * arc / sibling-count()), and a cascading list where width and animation delay are fractions of the sibling count. Add or remove items — every calculation reflows automatically with zero JavaScript.
Feature detection: checking…
180°
100px
6 items
Radial fan — position from sibling-index() / sibling-count()
angle = (i−1) × 180° / 6
hub
Cascading reveal list — width & delay from sibling-index() / sibling-count()
- 1
- 2
- 3
- 4
- 5
- 6
Key CSS — everything derives from sibling-index() / sibling-count()
/* Fan menu: angular position */
.fan-item {
--angle: calc(
(sibling-index() - 1) * 1deg * var(--arc) / sibling-count()
);
transform: rotate(var(--angle)) translateY(-var(--radius));
}
.fan-item-inner {
/* Counter-rotate to keep label upright */
transform: rotate(calc(-1 * var(--angle)));
/* Hue gradient across siblings */
border-left-color: oklch(0.55 0.18 calc(sibling-index() * 240deg / sibling-count()));
}
/* Cascading list: width and animation delay */
.delay-item {
width: calc(50% + 50% * sibling-index() / sibling-count());
animation-delay: calc(sibling-index() * 80ms);
}
.fan-item {
--angle: calc(
(sibling-index() - 1) * 1deg * var(--arc) / sibling-count()
);
transform: rotate(var(--angle)) translateY(-var(--radius));
}
.fan-item-inner {
/* Counter-rotate to keep label upright */
transform: rotate(calc(-1 * var(--angle)));
/* Hue gradient across siblings */
border-left-color: oklch(0.55 0.18 calc(sibling-index() * 240deg / sibling-count()));
}
/* Cascading list: width and animation delay */
.delay-item {
width: calc(50% + 50% * sibling-index() / sibling-count());
animation-delay: calc(sibling-index() * 80ms);
}
/*
Fan positions are computed entirely in CSS.
The only JS is adding/removing <li> elements and
updating the --arc CSS variable from a slider.
sibling-count() automatically updates when items are added/removed.
*/
.fan-menu {
--arc: 180; /* degrees — updated by JS slider */
--radius: 100px;
}
.fan-item {
--angle: calc(
(sibling-index() - 1) * 1deg * var(--arc) / sibling-count()
);
transform: rotate(var(--angle)) translateY(calc(-1 * var(--radius)));
transform-origin: 16px 16px; /* hub center */
}
.fan-item-inner {
/* Counter-rotate so the label reads straight regardless of angle */
transform: rotate(calc(-1 * var(--angle)));
}