How to make responsive Likert scales in CSS (like Qualtrics)

At work I’ve been updating many forms to be responsive and work on mobile. This is not as exciting as it sounds - but like any boring but necessary task, there is fun to be had searching for an optimal solution. Psychology is just full of forms and surveys which contain Likert scales, so I came up with the below solution based on radio input elements, for future projects, and it works very nicely. I design with following criteria in mind:

Bonus features:

I looked at both flexbox and grid solutions, I used this great guide to grid to experiment a lot as well as this article on container queries which led me to a near solution with flex: the so called Flexbox Holy Albatross. This was close to the final solution bar one issue: in the vertical arrangement, if the content of one box was bigger than the others, then it wouldn't adjust the sizes of all the boxes, leaving one bigger than the others. In the end I could see no way of getting this behaviour without specifying the number of columns somewhere in the css.

The other downside to this approach is that it relies on media queries, meaning that the scale won't behave as well in situations where it has variable widths. E.g. The media query I have specified works well in my full page setup but I had to add an extra media query for it's display on my front page.

The Flexbox Holy Albatross would eliminate this downside, but I would have to then fix row heights, which in my case is less achievable as the contents of the likert's are unknown, but the width of the container is.

Final setup:



It uses grid under the hood, and a single media query, which switches the orientation when the viewport width goes below 680px. It uses outline as well as background shade to indicate selection, which is important for colour vision deficiencies. I choose this way rather than say indicating selection with dark background and light colour because in binary choices it might not be clear which was selected. The inputs are hidden with opacity, which means that you can still use keyboard shortcuts to navigate the form, and switch them with arrow keys. It recreates the standard blue focus outline too (black when selected but not focusd, important for keyboard users.

It is basically everything you could ever want in a Likert scale.

Source code

HTML

<div class="likert">
    <label><input name="l" type="radio" value="1"/><span>I don’t like it at all</span></label>
    <label><input name="l" type="radio" value="2"/><span>I don’t like it</span></label>
    <label><input name="l" type="radio" value="3"/><span>I am indifferent</span></label>
    <label><input name="l" type="radio" value="4"/><span>I like it</span></label>
    <label><input name="l" type="radio" value="5"/><span>I like it a lot</span></label>
</div>
    

CSS

Not all likert scales have 5 elements, you can add a variable on the likert element's style attribute e.g. style="--likert-rows:6", to change the elements. I was unable to keep the likert buttons the same size using either grid or flex, whilst still not specifying the number of rows somewhere. It would be great in the future to do this with attr() - but I couldn't get it to work.

I was also tempted to put all the colours as variables in the main likert class, to make them easy to tweak and override as a batch - but it didn't feel necessary in the end, I kept it simple.

    .likert {
        --likert-rows: 5;
        display: inline-grid;
        max-width: 900px;
        grid-auto-rows: 1fr;
        gap: 1em;
        grid-template-columns: repeat(var(--likert-rows), minmax(0, 1fr));
    }

    @media only screen and (max-width: 680px) {
        .likert {
            grid-template-columns: minmax(0, 400px);
            justify-content: center;
        }
    }

    .likert input {
        max-width: 250px;
        position: fixed;
        opacity: 0;
        pointer-events: none;
    }


    .likert span {
        border-radius: 5px;
        display: flex;
        justify-content: center;
        align-items: center;
        text-align: center;
        box-sizing: border-box;
        width: 100%;
        height: 100%;
        padding: 20px;
        background: #dcdcdc;
        transition: background .2s ease-in-out;
    }

    .likert input:checked + span {
        outline: black auto 1px;
        background: transparent;
    }

    .likert input:focus + span {
        outline: -webkit-focus-ring-color auto 1px;
    }

    .likert span:hover {
        background: #f1f1f1;
        outline: lightgrey auto 0.5px;
    }