HTML Forms, Accessibility, Semantics, and more (with Vue.js)


So, I want to go over this topic about some form inputs of the checkbox radio because I saw this comment come up in the Vue.js Developers Facebook group the other day and I just wanted to address it because I think it was an interesting subject that some people probably get a little bit hung up on, and I wanted to go it’s a good opportunity to go over some of the web fundamentals and also a little bit into how you might address these things in Vue.

So, to get started the question was about taking a group of checkboxes and making them behave like a radio.

So if we have a label element, and we can close that. I’m just working here in codesandbox so it’s a little bit easy to follow along. And we can say close that and see that a little bit better, and then we have an input of type equals checkbox and along with that we have a label that is “Select me”.

Right! So, this is just standard HTML. If we click that we can check it on and off and the question was about getting this label to work with multiple or getting this these checkboxes to work where if I select one it will deselect the other. So if I select this one and now I go and select this one it should deselect this one like that.

So what I wanted to the point that I want to get across is assuming that visuals don’t matter then this is actually the behavior the default browser behavior that you would expect from a radio input, not a checkbox.

So to implement that, now you could have some JavaScript that listens to a checked event and then goes to all the other checkboxes maybe in the same div, of or in the same form fiel,d or fieldset, or whatever and deselect all the other ones, but what you’ll find is that while you can technically achieve that, you end up recreating some of the default browser behavior that’s available for something already inherently which is a radio.

On top of that, you’re going to be changing some of the semantics of what your form actually says. So this input type checkbox actually tells the browser that this input behaves a certain way, and when you change the default behavior it you’re losing some of the semantics in what your form should actually be doing.

On top of that, there’s some accessibility concerns regarding users that rely on both just keyboards or keyboards with a combination of screen readers or things like that.

What I mean by that is, if I go and I use my keyboard to navigate around this form every checkbox that I that is on the page regardless whether I have JavaScript or not. Or I guess you could technically change this behavior with JavaScript, but what I’m the point I’m getting to is that every checkbox is reachable by tabbing to it. The same is not the case for a group of radios.

So if we go here, and I’ll just put a little line break in there so we have some separation, maybe two. Okay.

So if you have a group of radios and we give them a value equals radio-one, and this one’s going to be value equals radio-two then what we still want to do is we say type radio name equals radios, name equals radios.

So if we do this then what we can see is as I tab through this form I can get to all of the checkboxes. Right? Back, forward, back, forward.

And if I go to the radio and then I tap again, well I get out of the form and I’m going to also. Well, let’s do this, let’s do this correct.

Right, this is a form, and that’s form, and at the bottom of the form we’re gonna have a button that says “submit”.

Okay, and let’s put another br and br, ao I have a little bit of space.

Cool. So again, if I’m in this this form and I tab to the first element it’s going to take me to the first checkbox, and if I tap again I go to the next checkbox because those are two technically unrelated checkboxes.

They could still be part of a fieldset so they could still be within a related field, but I can still tab to each one individually. I can activate it just with the just by pressing the spacebar. Oops. Cool. Just by pressing the spacebar and that’ll turn it on or off, and that’s the the default expected browser behavior.

If I go to the radio, I can select one and if I hit tab again you’ll notice that I don’t go to the next radio. The reason for that is because they are both part of the same, they both have the same name for the input.

So if I want to select different input I can tab to the Select one, I can hit left or right, or up or down, and that will switch my selection. And that is the default expected browser behavior for people that are using keyboards, like myself for example.

If I’m going through a form it’s much faster for me to just use the keyboard to navigate around, and I can select that, select that, and I can, you know, select which one of the form radios I want and then hit the submit button.

So I just wanted to take this opportunity to explain a little bit about how the browser expects these native these native form elements to work, and why it’s important to work within what the browser expects and make sure that your HTML is semantic.

Regardless whether you’re using something like Vue for your front-end rendering it’s so important to to follow the the best practices described in the web standards, yeah, as part of the web standards so that we keep our forms accessible. We keeps our we keep our websites and applications semantic, and our browsers know. There’s just a lot of benefit that comes from that on top of that.

If I were to try to implement some custom JavaScript to treat checkboxes like a radio then I have a whole lot of extra work to make sure that it actually behaves like a radio, in that if I tab to it and I hit tab again, if this was a radio I would expect it to go down to, let’s say radio-one right? And this is a radio-two.

So if I were to be on, say this is check-one and this is check-two and they all set some values checkbox-one value equals checkbox-two. Or I guess they don’t actually need a value because they’ll be they can default either true or false or 1 or 0, but they do need a name.

Yeah and then so, yeah getting back to to where we were, we can say. Let me do one step further and just say that these are related fields so I’m gonna do a fieldset.

Right? Close that off. There we go.

Okay, so that is a much more semantic way to place these. These radios don’t necessarily need to be in a fieldset because they have they share the same name.

And so let’s say I wanted to implement this field set to behave like radio I could technically have some JavaScript that says when I click this check-two, look find this fieldset go through all the other checkboxes in and uncheck all of the ones that are not the one that I just checked and that would work just fine.

However to implement this in a way that would work in the same way as a radio I would also have to have some sort of keyboard event listener that if I’m on I’m currently on one and I hit tab again I should actually I would expect to go to the input radio-one, but in reality I go to check-two. If you can see that, if I tab I still go to check-two, when if this was a radio I would go to radio-one.

This is also a good opportunity for me to say to share why radio names are so important. Is if we take this off and that re-rnders, one thing we’re gonna see about a keyboard navigation is if I tab and I’m on the first check and then I tab again I’m on the second check and I tab again you see how it went to the first radio?

So that actually is not the expected behavior for a group of radio buttons or radio inputs that are related when one is selected. In fact, if one is selected I should go check-one, check-two, radio-two, and then if I go backwards through the list from submit and I go backwards I should go to the selected option, and then check-two, check-one.

So here I do go to radio-two. That’s only because it is selected and in fact you see what happens here also another thing is as I select different options, I cannot actually deselect what radio-one, radio-two is.

So the reason for that is, once again, because they don’t actually have a name. When it has a name it knows which value to currently select. It also knows which value to navigate to with the keyboard.

So that was just a quick little explainer on some of the more some of the little nuances with form markup, accessibility, semantics, when to use one or the other, things like that.

So something else I’m going to get to get into that comes up a little quite often is you might see people trying to style these checkboxes or radio little UI elements. And well, the truth is that you you cannot style those.

These are going to be different across every browser but every browser is going to implement their own unique styles for checkboxes and radios.

So you can’t style them, so what are the options? Well, you could use something like a div or some other sort of component or HTML tag and then put some custom styles on it, and that works fine but once again you run into the issue that, yes if you’re oh if you if you take a div and put an event handler on it or event listener for the click event, yes you can technically give us some styles that will make it look like it’s checked or unchecked or whatever, but once again you run into the promblem of all of a sudden it it’s not accessible by keyboard navigation. Right?

And that’s one of the that’s something that I noticed a lot because I actually use my keyboard a lot when I’m filling out forms. Is that these custom these these custom UI components for checkboxes and radios are not actually I can’t actually get to them without the mouse.

So we’re gonna look into how you can implement some more accessible patterns around these things.

The first thing I’m going to show is this, well, hiding these. Right, so, if we go down to our Styles and we have some default styles and we’ll get to that in a minute. But let’s say I took every input with the type equals radio, and every input with a type equals checkbox, and I just said display is none.

Okay, so this is good. I’ve hidden my UI right and what I can do now is I can I can take those and I can say well if we have a label and we have some input and we have some text well we can take the label. The label is outside of the input so what are we going to do? Well, one thing we can do is we can say we can put a little span and a closing span and I’m gonna copy that, paste it there, and I’m actually gonna paste it there as well. And this is going to be radio-one, radio-two. This is going to be check-one, check-two in that space there and make that a two.

Right so now I’ve got my spans and what I can do is I can say for each of these we’re going to hide the the little UI for it, and we’re going to say let’s see see if I can – probably can’t do that we’re gonna stick with regular CSS. We’re gonna say these guys, bring them down here. I’m gonna put it on it’s own line. And we’re going to say this guy and then with a span, and before, no, the span itself yeah.

We’ll do before the span and the same for the checkbox we’re gonna say content is just an empty thing. I’m gonna give it a border of 1 px solid. I’m going to give it a width of .5em maybe a height of .5em, and what we’ve got there? We also need a display property of inline-block. Cool!

So now I’ve got my little custom UI elements that I can say that I can I can start to play around with. So what are the issues that we have here? Is well, first off some of you that might know accessibility better probably screaming at me for putting this display:none.

So one thing that we’ve done here is by hiding these inputs we’ve effectively killed the keyboard navigation right?

Because although it’s hidden and it’s still part of the form, although it’s hidden if I try to tab to it, I can’t actually reach it because display:none prevents it from or removes it from the flow of keyboard navigation.

So one thing we have is if you go to or just Google it, you can find this visually-hidden class. It’s a nice CSS helper tool that you can put on any of any component that you want to be still accessible within the DOM, within the flow of the page but not actually visible.

So we’re gonna undo that change bring back our inputs and we’re going to give the ones that we want to hide a class of visually-hidden as you can see as I make these changes over there on the right, they’ll start to kind of pop off one at a time.

And now what we can do is as I tab to each of the inputs you can’t see them but there you go, you see how the the submit just lit up? So if I tab back I’m on one of the two radios, which ever one is selected. And if I tab back now I’m on check-two presumably. If I tab back one more time I’m on check-one. Tab forward, check-two. Tab forward, one of the radios. Tab forward, I should be on the submit, and there it is.

So if I had a screen reader going on you would probably hear, you know, this is a radio input, one of them is selected blah blah blah. It has the label of check-two or the label of check or radio-two, radio-one and all that.

So so with this pattern we get to keep our accessibility in mind and our keyboard navigation for any users that prefer keyboard navigation, like myself when filling out forms, but we also don’t have to rely on the default UI.

So let’s continue is like now if I want to check this checkbox one well that’s great, it’s accessible, and it’s checking on and off as I click, but it’s not actually showing me that it’s clicked, right? So let’s fix that.

First of all, let me also fix these radios. If I go here I know that the expected UI from radio is to be round so we can say border-radius is 50%. That should make those round. Cool. Now I’ve got some chest custom checkboxes and custom radios. I’m gonna make these a little bit bigger. Let’s go point seven, point seven.

All right! Some fatty ones. Perfect. And maybe there’s like a margin-right of like point two em’s.

That’s looking good so now what we can do is first I need to check whether it is selected or not. And what I can do is I can go ahead and copy this line really quick, and I can say, well if an input with the type of radio is checked, or an input with type checkbox is checked, and then the very next element is a span and we use the before pseudo selector, I can say background-color of red. Why not?

And you see how these just turned on really quick so that makes it really easy for me to now be able to turn these on and off.

I can hit the check box just as expected, and I can hit the radio and it works just as expected. I can also use my keyboard navigation. Turn those on and off, and I can use my keyboard on the radio, and then I could submit. And that is great!

So now we’re getting kind of the best of the best of every world for semantics, and accessibility, and keyboard navigation, and custom UI, and the design looks good. I mean sort of, good enough. I’ll leave it up to you to design them exactly how you want.

But there’s one thing missing and that is if you see when I’m hovering on this submit button that is that is telling me that this is the currently, current item in focus. And if I tap back I know that I’m on the radios and if I had had a screen reader it wouldn’t be a problem because it would tell me that I’m on the radios, but for a user like myself, I need some help.

Like, I’m relying on on my vision to tell me, you know where I’m at in this form and currently I don’t know. If I hit space okay I know that I was on check-two, and if I hit you know back-tab or shift-tab, I imagine him on check-one.

Okay, cool, so that’s great for, you know, me kind of knowing where I am, but we can do better. And how we’re going to do that is, once again let’s kind of copy this same pattern that we’re doing. This becomes a lot easier if you’re using sass, but okay.

So what we’re going to do is we’re gonna say we can’t we can’t put a focus. Well we could put a focus on the span, right? So we could say tab-index of zero.

That’s gonna mean that as I tab through, let’s let’s experiment with this really quick. As I tab through so let’s say, let’s erase all that. Now let’s say we have a span with a focus state and we’ll target the before. We’re going to give this an outline of one one pixel solid black. Actually will go one pixel dotted black. Yeah?

So now if I tab through we can see that my my input, well I guess I don’t even need this because what’s gonna happen is as I tab through I can see that now my span is selected, but what’s going on is you know I can I can hit the spacebar, but it doesn’t do anything, and if I hit tab my focus ring goes away and if I hit the spacebar and now I’m selecting.

So what’s happening here is I’m focusing on the visually hidden input. Well let’s let’s let’s go back so right now I’m focusing on the visually hidden input. I hit tab again and because I’m using a tab index of zero this now is going to accept focus from the keyboard.

This is a span and now it’s going to be focused but the span doesn’t have a default way of handling you know it’s not going to say well if you hit the spacebar on me I’m going to select the checkbox or the radio, right? So if we hit spacebar, enter doesn’t do anything.

So we can use some JavaScript and we can listen to keydown events on the span, find the input right before it, or the label above it and find the input inside of that, and you know select that, and that’s slightly better than what we had before, or what we were thinking about doing before with the checkboxes trying to behave like radios.

This is still more semantic and and still relatively easy to do. We are navigating but another problem here you can see is now I need to do I need to tab twice to get, you know, to each element so so that is not the solution that I’m really interested in.

So we’re going to undo that and instead what we can do is yeah what I was doing before is just kind of like going along with the same pattern. We’re still going to use the focus state. So we can get rid of this guy.

We’re gonna say when this radio is focused, or when this checkbox is focused, go to the the sibling span, right? The sibling being they share they both share the same parent and we’re going to go from the input, when it receives focus we’re going to go to the very next sibling, which is a span, and we’re going to get the before pseudo selector and we’re going to give it an outline of one pixel dotted black.

And this can be anything. There’s actually ways to get the default browser outline if you’re interested in that, or if you prefer having your brand Styles you know like maybe your brand style is blue or green or whatever and you want like a three pixel solid, I don’t know, what’s a random color? Dark cyan. Sure, why not?

That’s the case so now we can say if I tab through, you can see how now I can totally see where I’m at and sure that submit button is not it doesn’t match the styles but that’s easy enough to fix, but now what we can do is, yeah tab through and we get the same behavior that the browser was expecting, and we have our our custom UI components, which in this case, you know, probably don’t look great.

But this is just the foundational work that you can go off and build just about anything you want and you still get keyboard navigation. I’m not using a mouse for any of this, and you can submit you know you can keep your forms looking beautiful, telling the browser exactly what semantically, they represent, telling screen readers exactly semantically what they represent, and allowing keyboard users or users that use the keyboard for navigation to get around and not be hindered by, you know, markup that is is not what it’s intended.

So that is my spiel on checkboxes versus radios, and how to implement custom ones, and how to keep everything semantic and accessible. I hope that’s helpful to you all.

One last thing that I want to end with is answering kind of the question of being able to set your your form values or do things in Vue.

So the way that I would probably do that is have a data function that returns an object and it’s going to be checkbox-one is going to be false by default. Checkbox-two, it’s going to be false by default. And then radio’s is going to be string that’s r-one. All right. And we’ve got a problem here, here just wants a comma.

And then we can see that, let’s see, how about just before the form and just under here we’re going to actually see our binded data.

I’ll say this I think I’m actually going to wrap in an object and stay inputs is an object with all these data properties. Put that over so that we can say throw a little pre tah, and then we can just throw out into the DOM our inputs.

All right, so now we can see that our checkbox-1 is false checkbox-2 is false radios is set to r1 which is going to be radio-1. And now I need to actually hook those up to my form components.

So on the checkbox-one I could say v-model equals checkbox-one, and I can say the same for checkbox-two, and then I can say the same for this. And this is going to be radios. And then I can say the same for that.

So these two have to match because they are the same value I believe and because we are passing in value through v-model, we no longer. I think you do you need the value there, so radios should be that.

All right let me look this up really quick and come back.

I’ve figured out what I was doing wrong. Everything was almost right except that I didn’t bind to the actual inputs object. There we go, inputs dot radios and inputs dot radios, and now that I’ve changed that you can see that because this is r1 it’s selected and if I check checkbox-one.

Actually you know what let me do this without the mouse.

If we go through keyboard navigate, check checkbox oops. I hit enter instead of space. We check the checkbox-one with the spacebar we can see that those values are changing and then if we go to the radio well you can see that because it’s bound through v-model changing those will change a little value down here.

So that is all I have. I hope that this was helpful for those of you that are either learning or getting started with Vue, or interested in semantic markup or accessibility, or any of you that are contributing to the web platform I hope this was helpful. Please let me know, and if you have any questions feel free to reach out, and that’s it for tonight.

Thank you so much for reading. If you liked this article, please share it. It's one of the best ways to support me. You can also sign up for my newsletter or follow me on Twitter if you want to know when new articles are published.

Originally published on