RSS FeedTwitterMastodonBlueskyShare IconHeart IconGithub IconArrow IconClock IconGUI Challenges IconHome IconNote IconBlog IconCSS IconJS IconHTML IconShows IconGit IconSpeaking IconTools Icon
Screen of the demo
A series of images of an avatar doing a bunch of skateboard tricks.

Cyclical keyboard UX with a radio group and CSS trig functions

4 min read

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">

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 */
    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().


Try it on Codepen #

I used CSS nesting and trig functions, so you'll need Canary with web experiements turns on, for now.

Mentions #

Join the conversation on

  • Alex Ewetumo
  • Luis A. Linan
  • bigandy
  • Roni Laukkarinen
  • André Pinto
  • Полина Емельнянова

@argyleink woah, that's a novel way to implement a loading animation!

🤍 Gray 🖤🤍 Gray 🖤

Crawl the CSS Webring?

previous sitenext site
a random site