CSS trig functions can do some neat layout stuff, like circles! In this bite size blog post I quickly share how I turned the cyclical roving tab index feature of a radio group, into a circle so it can cycle seamlessly.
I'm just inside a radio group usin arrow keys.
The gist #
Bramus wrote a great post on the new CSS trigonometry functions, and it got me thinking. Those thoughts led to this demo, where I wanted the infinite cycling of focus of a radio group, to tail call itself via a circle layout.
<fieldset style="--sibling-count: 8">
<input style="--sibling-index: 1" type="radio" name="cyclical-group" checked>
<input style="--sibling-index: 2" type="radio" name="cyclical-group">
<input style="--sibling-index: 3" type="radio" name="cyclical-group">
<input style="--sibling-index: 4" type="radio" name="cyclical-group">
<input style="--sibling-index: 5" type="radio" name="cyclical-group">
<input style="--sibling-index: 6" type="radio" name="cyclical-group">
<input style="--sibling-index: 7" type="radio" name="cyclical-group">
<input style="--sibling-index: 8" type="radio" name="cyclical-group">
</fieldset>
I also used a custom property version of this spec proposal I have open with the CSSWG for sibling-count()
and sibling-index()
, where an element could know how many siblings it has and which index it currently is. By "used a version of this proposal" I mean, I hand wrote the values 😅
fieldset {
/* divide circle by total children */
--_offset: calc(360deg / var(--sibling-count));
/* size also used for circle translateX and Y */
--_circle-size: 25vmin;
inline-size: var(--_circle-size);
block-size: var(--_circle-size);
/* 1x1 centered cell */
--_cell-size: 10vmin;
display: grid;
place-content: center;
grid: var(--_cell-size) / var(--_cell-size);
/* stack them together in 1 cell */
> * {
grid-area: 1/1;
}
}
Get used to that nesting syntax!! woot woot!
With a grid layout setup with all the radios aligned in the middle, and the radius known of the circle, we can do the math.
input {
/* take child index * circle fraction offset */
--_angle: calc(var(--sibling-index) * var(--_offset));
/* cos() translateX, sin() translateY */
translate:
calc(cos(var(--_angle)) * var(--_circle-size))
calc(sin(var(--_angle)) * var(--_circle-size))
;
}
And that's that, individual transforms making x
and y
easy to set. Each radio takes it's current index and multiplies it by the ratio in offset, then uses that angle against the circle radius with cos()
and sin()
.
Rad.
Try it on Codepen #
I used CSS nesting and trig functions, so you'll need Canary with web experiements turns on, for now.