Using Radix to Implement the Tabs Component
Implementation of headless components
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the The Approachable Guide to Accessible Components course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to The Approachable Guide to Accessible Components, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:14] In this lesson, we'll take the tabs component that we built in the previous module and create a headless version using Radix. As usual, you can find the starter code for the project in the lesson manuscript, and you can feel free to fork the project and follow along if you wish.
[00:15 - 00:28] Also, as usual, we've provided the CSS so that you can just focus solely on implementing the tabs component. And I want to take a minute here to explain why I'm having you implement a head less version of the tabs.
[00:29 - 00:46] I intentionally had us implement a manual implementation because I wanted you to learn how to read through the specifications, how to apply proper RE attributes, and how to wire up keyboard interactions. That's a really important skill to learn when you're implementing a custom component pattern.
[00:47 - 01:12] But I've also seen this emerging headless component pattern kind of gaining popularity maybe over the last couple years or so. And I think it's a kind of gives you best of both worlds because when there's not a native HTML equivalent, or you need to implement your own custom component, it can be kind of difficult because if there is a native HTML equivalent, a lot of times styling it is really tricky or finicky.
[01:13 - 01:44] And if there isn't and you have to implement your own custom component, that can be hard in itself because making sure you get functionality right is tricky or, and then making sure it's accessible and then styling it so this emerging headless component pattern I'm seeing, I think, kind of bridges that gap, it makes it so that you can style it and brand it the way you want, but it also gives you baked in accessibility by default. And so I really, I think learning the manual way can help you, like appreciate this headless component pattern.
[01:45 - 02:00] And so we're going to be using radix to implement it, but there are other component libraries out there like, let's see, headless y for using tail and CSS that's a good one. There's reach UI, which is kind of like the OG of the headless component libraries and then there's.
[02:01 - 02:08] Oh gosh, I think react area might be by the Adobe team. Yeah, so there's a couple of different options out there that you can use.
[02:09 - 02:28] Alright, so with explanation out of the way, let's go to code sandbox and open up tab.jsx and copy the code that is in the lesson manuscript and let's port it over here. All righty.
[02:29 - 02:49] And if you notice, the preview and code sandbox resembles the exact same UI that was displayed for initial tabs implementation. So I'm going to kind of do reverse of what I did with the manual implementation where I'm going to provide you all the code up front, and then we're going to retrace our steps and understand what each element does.
[02:50 - 02:59] And let's start off by learning the anatomy of a radix tabs component. And so actually let's go to the radix documentation first so radix is just a component library.
[03:00 - 03:13] They actually have a couple different things that they provide like icons, which is what we used when we wired up the error messaging for the text of the component. But primitives is probably their most popular package that they provide.
[03:14 - 03:31] And so if you go in to get started and you can see that they provide tons of components here. Radio group pop over menu bar. Obviously, we are going to be focusing on the tabs, but just wanted to give you a quick overview of what the documentation looks like and how you can kind of reference it later.
[03:32 - 03:38] Going forward. And so, yeah, so the radix tabs component, it kind of consists of four elements .
[03:39 - 03:50] And the first element is the tabs primitive root. Which contains all the tab component parts and acts as the container or the elements.
[03:51 - 04:03] Then we have the tabs primitive list. And this contains the triggers that are aligned along the edge of the active content. This is what the warrior specs would refer to as the tab list element.
[04:04 - 04:12] There we have the actual trigger. And this is the button that activates its associated content. So this is what's known as the tab element.
[04:13 - 04:30] And last but not least, we have the tabs primitive content and this just contains the content associated with each trigger or is also known as the tab panel element. Okay, so now we kind of have an idea of the structure of what a radix tabs component consists of.
[04:31 - 04:45] So if we go back to the warrior tabs pattern page. I want us to compare it to our new implementation and inspect how radix takes care of all the specifications for us by default.
[04:46 - 04:52] Okay, and so let's actually scroll up. And go to the keyboard interaction section.
[04:53 - 05:04] And so, yeah, so we're going to look at this here. So, if we remember, and let's actually open up this in a new window.
[05:05 - 05:16] Alright, so here's our component. So let's go ahead and test some keyboard interactions. And first we're going to do the tab. If I press tab, notice that it goes straight to the first tab in the list.
[05:17 - 05:24] And if I press tab again, it goes to our tab content. Which is exactly how it function in our last implementation.
[05:25 - 05:33] And if I press shift tab to go back takes us back to our first tab. Now, if I go and press, let's say I do the right arrow, notice that it goes to the second tab.
[05:34 - 05:39] I press the right arrow again, does the same thing. Right arrow one more time, it roves us back to the first tab.
[05:40 - 05:48] And now I'm going to do the left arrow again pressing left arrow now, roves us to the end. So, see, we have that roving tab index kind of implemented by default.
[05:49 - 05:52] Press left arrow takes us there. And then one more time to the front.
[05:53 - 06:06] And there's actually some bonus keyboard interactions we get that we didn't implement in the last, in the manual implementation because they were optional. But if I press the home key, or I'm sorry, I'm going to press the end key first .
[06:07 - 06:12] Notice that it takes us to the last tab list. And then if I press the home key, it takes us to the first tab in the list.
[06:13 - 06:23] So just by using radix, we kind of get to extra keyboard functionalities, you know, out of the box. All right.
[06:24 - 06:36] So now that we've looked at the keyboard interaction parts of the specs, let's go ahead and scroll down to the role states and properties part. And because I want us to properly vet that the accessibility properties are being applied correctly.
[06:37 - 06:55] And so I want us to review each of the bullet points outlined in this section and then compare it to the HTML that is generated by radix in the DOM. And so the bullet points won't be, won't go through each one in consecutive order because it kind of got to jump around a bit based on where the elements are in the DOM.
[06:56 - 07:02] But we'll cover each of these points. So let's go ahead and inspect.
[07:03 - 07:07] Let's see. Okay.
[07:08 - 07:15] And okay, let's look at this first element here. So the following code block that is generated, this is the tabs primitive list.
[07:16 - 07:19] This is what's generated from that. And it covers a following criteria from the specs.
[07:20 - 07:36] And so the first criteria is that the element that serves as a container for the tabs has role tab list. Okay. And so if we go here, and let me actually bump this up, making of accessibility, let's make it easy to see.
[07:37 - 07:42] So this element here has role tab list. Okay.
[07:43 - 07:54] And then if you go back to our specs, another thing is that the tab, let's see the tab list element. As a label provided by Aria label.
[07:55 - 08:07] Okay, so going back here, notice that we have our Aria label right here jazz musician quotes being properly wired up. And then last thing to look at is, let's see, going back to our specs here.
[08:08 - 08:17] The default value of our orientation for a tablet's element is horizontal. If you go back to our specs, we see, sorry, Tori, our Dom here, we'll see that.
[08:18 - 08:25] The area orientation is in fact set to horizontal. So there's three of our criteria already outlined here.
[08:26 - 08:39] So the next code block will take a look at is what is generated by the tabs primitive trigger. And just to go back to code sandbox really quick. So this element here is what generates the HTML that we're looking at right now, which is a button.
[08:40 - 08:53] And so the first one of the bullet points we want to look at from the spec is that each element that serves as a tab has role tab and is contained in the element with role tab list. Okay, so this one right here.
[08:54 - 09:04] So now, oops, if you go back here, each element that serves a tab has role tab. So notice that this is a button element, same element that we use in our manual implementation has a role of tab.
[09:05 - 09:11] And it's contained in the element with role tab list. This button is contained within the tab list. All right.
[09:12 - 09:23] So the next criteria we want to look at is that each element with role tab has a property area controls referring to its associated tab panel element. All right, let's see, where is that in here?
[09:24 - 09:27] Here we go. So that bullet point that we're looking at.
[09:28 - 09:35] So if you go back and look at this, notice that we have. Are you controls.
[09:36 - 09:41] That's a radix. Oh, dash colon r one content dash stand right.
[09:42 - 09:46] And if we make this a little bit bigger here. Scroll down, notice that.
[09:47 - 09:58] Point to the ID of radix r one content stand. So the are you controls of the trigger. The are you controls of the trigger here points to the content element here.
[09:59 - 10:10] So that's just like the spec says. And the last bullet point we'll look at for this particular element is that the active tab element has the state are selected set to true and all other tab elements have it set to false.
[10:11 - 10:19] Right. So looking at this bullet point right here. And if we notice we look at this, it has are you selected set to true.
[10:20 - 10:34] And these are all the other are you selected are set to false. Now, if I go and click on Charlie Parker here, notice that the DOM updates and now are you selected is set to true for the third tab. But all the other ones are set to false. So that functionality is working correctly.
[10:35 - 10:45] Okay, so the last element will inspect is the tabs primitive content. And if you look at it, it's right here in our tabs.jsx file.
[10:46 - 11:00] So we're going to look at what this generates in the DOM. So if we go down here . The first criteria that we're going to look at from the specs is each element that contains a content panel for a tab has role tab panel. Let's see.
[11:01 - 11:10] This one right here. And if you go back, we notice that this in fact does have the role to have panel.
[11:11 - 11:21] Yes, and these are the other corresponding tab panels. But they are noticed that there's a display none. So they're not, you know, visibly showing.
[11:22 - 11:41] Okay, and then the last bullet point for the tab spec that we'll take a look at is that each element with role tab panel has the property are labeled by referring to its associated tab element. Okay, so let's see. Each element that has associated tab panel. Okay, and that means we've gone through each of these bullet points now.
[11:42 - 11:54] And so if we go down here. So each one that has the role tab panel has property are labeled by and notice that the are labeled by is in fact on this.
[11:55 - 12:06] And that it refers to the associated tab element. Okay. So looking at the second one here, which refers to the second tab, it says rad ix dash colon r one dash trigger Ella.
[12:07 - 12:26] This area labeled by points. If we go to our trigger now or our button element, notice that it points to the ID with the same name. Same thing for the third tab. Notice radix dash r one trigger Charlie points to the ID of the corresponding button that would display that content.
[12:27 - 12:43] And so I wanted to show how to like properly vet a component library because even though these are, you know, there are a lot of component libraries out there. It's still, I think, important to vet the library to make sure that they're implementing the accessibility patterns correctly.
[12:44 - 12:56] And then it also draws a parallel to how we did it with the manual approach. And with that we've implemented our headless tabs component. Headless components have emerged as a modern pattern that is rapidly gaining traction in front of development.
[12:57 - 13:16] Their versatility, adaptability and accessibility advantages offered by this pattern make headless components an excellent choice when constructing accessible components. I really thought it was important for us to draw the parallels between the manual approach and the headless approach, because it shows that there are different ways you can implement a component pattern.
[13:17 - 13:28] And I also wanted to provide you with an additional tool in your tool belt so that you can make the best choice for you when implementing accessible components. [sighs]