 Welcome to another episode of GUI Challenges, where I build interfaces my way, and then I challenge you to do it your way, because with our creative minds combined, we're going to find multiple ways to solve these interfaces and expand the diversity of our skills. And in today's GUI Challenge, we're building the notorious, the hated, the classically everywhere in all sorts of websites, carousel. This was by far the most complex GUI Challenge component so far. And to this list of features, CSS features that it uses, we've got scroll snap, scroll padding, over scroll behavior, inert, tab index, display contents, scroll end polyfill, Aria attributes like Aria disabled, Aria selected, Poz and set and set size and Aria roll description, Aria live and roll. We also used grid and surprisingly no flex box, intersection and mutation observers, document.active element, cascade layers, prefers motion, prefers color scheme, forced colors, wear selector. We used nesting, we have right to left and left to right support with logical properties and keyboard JavaScript functionality. We used will change, we have custom properties, we're using focus visible, there's masks, transitions and animations, JS classes with private methods, state machine offerings, and a whole bunch more. And check out all the different demos I was able to build with it. So this is the default demo where we have buttons, pagination, it's scrollable, kind of like as you'd expect, there's little animations as items come in. Here's one with thumbnails. I've overlaid the scroll snap tools so that you can see that this item snapping the center and as we scroll over another item snapping the center. Super cool. And then we have options. So this one has no pagination, it's just a little simple boolean that you pass into the HTML. Here's one with no arrows, right? No arrows. This one is no scroll bar. Nice. It's kind of handy sometimes with carousels. Here's one where you go one at a time, so no matter how hard I throw, I'll just like try to throw this, I'm swiping with like all my might and it totally doesn't matter. And then let me go one at a time. This one is called scroll hint. So there's a custom style applied so that you can peek into the items that are up and coming. That's kind of nice. And it goes really well with scroll start. So you can set scroll start into the middle and just tell it an index of an element to start out and it'll start there. And then you add the styles for the scroll hint. And now you have, look at this. This looks really nice, right? Very professional looking. And it gives a good hint to the users about where they can scroll. Here's an example of full width because you got to have a full width one. You'll notice no animations. So that one's just sliding content coming in and out. Here is an e-commerce example where we have like a product image and all the different thumbnails. And notice what happens down here when there's too many thumbnails to fit inside of the line that's being allocated by the carousel. We've given it a scroll synchronized effect with the item that's in view and the items down below. Very cool stuff. Here's another kind of example of a carousel. This one's a paged collection. So we have many items inside of one page. Whereas this one above, we had one item in a page. And we can see that we can easily put a whole bunch of products in here and have a nice little product carousel. And we have testimonials because of course you're going to be on some WordPress site that's going to have some testimonials in a carousel. So there's that example there. And then this last one here is kind of like Xbox or like Windows 8 or whatever you want to call it. I've customized the pagination styles down here and I've added a custom staggered animation to these items as they come in. All different layers of this carousel project here. So the first one was creating a component that had all these various features that handled accessibility that handled right to left that handled all these preferences. And then on top of that was layering. How do I write some custom styles or make it easy to make custom styles so that we can do things like change the pagination. Just whatever it is you want maybe put the arrows somewhere else. And so that's where that wear selector comes into play and some of the custom properties. So that is a overview of the sort of features and the different demo examples that I have here. But let's talk about what I don't have. And what I don't have is I don't have an example of cyclical scrolling. Now that's pretty popular with carousels where you'll get to the end and instead of it saying no next item here it will allow you to hit next and go back to the beginning. I could build that into mine. It just I didn't have enough time. I also didn't think it was like that critical and carousel experiences. But maybe it is to you. Let me know in the comments down below. Another feature I didn't do was autoplay. And autoplay is one of the contentious items about a carousel wear especially like for screen reader users. If you are even trying to do your best and an item slides in and it announces itself and says like mission impossible to or something like that you're just like wait a sec that's super distracting for where I am. So you can layer in some autoplay on top of this component if you like. But I haven't done that work as just one of the other features that I didn't build into this one. Another feature that I don't have is the ability for you to sort of change some of these properties dynamically later. So on load my JavaScript goes and looks for all the components that are using the GUI carousel class and it kind of hydrates them and upgrades them. It adds the pagination dynamically and it adds the arrow controls dynamically. There's even a really cool feature in that that I can talk about soon. But if you go add an additional item into here, my script doesn't run and pick up that a new element has come in or that an element has been removed or that any of its attributes have been changed. So that would be a nice addition to here to be to make it sort of like dynamic to client side interaction. So if you wanted to pop items in, pop items out or change some of the properties dynamically at runtime, this doesn't really handle that. Some of it it does because it's CSS handled, but some of it it doesn't because it's JavaScript handled. And the last thing I didn't do is I didn't make it a web component. This totally could be a web component with slots because the markup that I ended up using for this whole carousel component is pretty lightweight. And it would play itself really well into slots, especially since things like the pagination and the arrows get added for you. And all I really need you as a developer to add and put into this component are items and the items like we saw can be a whole page of items. Right. It can be a whole page of items. So this is a grid layout. This is responsive to sort of like various viewports. But that's on you to bring those items. And so they make a really well sort of concept for slots where you're just like, here's a gooey carousel component. And then here's all the slots. And then I do the work to wrap that into something. So anyway, that would be a really nice next version to add in terms of DX and getting this thing hydrated well. And anyway, so those are the things it doesn't have. But I don't know. The list of things it has is quite massive. And there's even some really fun things I liked. Like if we look at this grid here, look at how the carousel gutters are labeled and our carousel scroller is labeled. So this is our carousel scroller row. As you can see why it's our scroller road has a scroll bar. And then down here is our carousel pagination. So even in like inspecting the grid layouts, I've added some nice little tidbits in there to help you understand which items are supposed to go where you can even place them by those names, if you like, all sorts of cool stuff. And I can't wait to continue breaking it down. In fact, let's check out the state machine that I made for this. So this was here, me trying to explain everything that I built. So we'll hit the simulate button down here and go through the functionality of a carousel in the state machine. So first, you're going to import the script. That script is going to run a setup. It's going to stash a bunch of selectors. It's going to create the pagination, create controls. It's going to watch for intersections. It's going to watch for mutations. It's going to add some aria and it's going to determine what element is current. Once it has done that, we go to the idle state. And once we're idle, we're waiting for users to scroll, to click pagination, to click arrows on their keyboard or to click the arrows that are on the GUI. And so here, let's just test a quick path. We'll go to scroll, scroll, end will fire. And then we synchronize and we synchronize. We set the current item based on what's snapped. We set the pagination so that the dot matches the current item. We scroll pagination in, in case that dot that was scrolled to was out of view. We toggle inert on all the items that aren't the current item. We simulate the snapped pseudo class by applying a class to this item. And that's what gives the hook for custom animations, you know, things kind of scaling in or ahead that staggered fade-in effect. And then we toggle the controls, their disabled state. So the controls are the left and right arrows that you see in the GUI. And once that's done, we're back to idle. So kind of cool. We can also, you know, click pagination. Then we have some guards up here for right to left because previous and next kind of change in terms of what's next when you hit right to left on your arrow keys. And all of that gets handled in the script. And so when you click one of those items or you click the pagination, it essentially sets the new item. And then we scroll that item into view, we hit scroll, end, and we go back through our synchronization phase. So that's the state machine that's kind of handling all the different ways that a user can input, scroll is going to cover mouse and touch. Click is going to cover mouse and sort of touch. And then we have all the different arrow key examples here as well. And that is the state machine. And from here, let's hit up that debugging corner. And I'll see you over there in just a second. The debugging corner. I always think it sounds like I'm saying bikini bottom there. You know, like, oh, welcome to bikini bottom. That's my really bad accent. But hopefully you found it mildly entertaining. Anyway, this is the debugging corner. And then today I have a whole bunch of different examples in here. So this first one is on iPad. It's just set to the current like thumbnails one. You can see it's swiping really nice. We can tap through. Safari has a really snappy scroll snap. Like look at how snappy that is, especially watch it over here on desktop. I mean, if they were going for a snappy scroll snap, they nailed it. So this one up here has shows the scroll start. So we've got the scroll position in the middle. It's just a really nice presentation. And that's Safari stable. We have Safari on iOS. Everything looking really great on here. We can scroll through all the different examples. No arrows, no scroll bar. One at a time. Just really try to swipe and look at it. Yeah, it's really good. It's nice and controlled there in our scroll hint. Here we are in Android, seeing the same example. All sorts of good demos over here. Dark theme, light theme. Then what we have here is we have forced colors active. And then I want to do this because it's just so much fun. So you can change forced colors with light and forced colors with dark. And it's really nice. And the way that I achieved these handling this is really interesting. And I'll go into it in just a second. But essentially by setting a anywhere where I had a box shadow, I also put a one pixel transparent border. And then forced colors is really good about picking those things up and putting borders around them for you. Plus, look at the captions. Isn't that cool? We'll go back to dark. I thought that was really neat. And then look, my disabled button turns green. That was the only thing I was like, that's a little funky. Green usually means go, but here it is in green. At least it signals nice to the mouse and to a screen reader. What this button's functionality is. But, you know, anyway, I thought this was cool to have forced colors in here. In fact, like look at some of these demos down here. Kind of fun. The hubs, we have our pagination down here and looking to fade in. This is kind of cool. I thought forced colors turned out kind of awesome. It looks like super retro. A lot of it's out of my hands, but I played into it nice. And I thought that turned out really cool. Move back to the prefers color scheme light and see what it's doing here. Look, it's like we have blue link text. I like that the link text is a little darker blue, because sometimes I think the accessibility of the default blue color is a little low. But anyway, that's forced colors. We'll go back to forced dark just because it looks so much more 80s. It's awesome. And then over here, we have Firefox rocking the same scenario. This one's in light theme. And it's got good behavior of all these different options that you want to as well. So that is an overview of some of the preferences that we handle. But let's cover some of the ways that you can interact with it. I mean, you've seen me. So we have touch up here panning. I've noticed that a lot of carousels don't feature this where they don't. It's not a scrollable area. They've done too many 3D transforms. And so they're managing everything with a 3D transform, which makes it kind of, you have to read a lot more JavaScript to then track a touch to map it back to 3D anyway. So I left out all alone and that that way this is very touch tangible and it's touched sort of to the built in touch that you have on every device, which is nice. And you can see how that's nice up here on Safari where we get that snappier experience because we're using just the built in scroll behavior there. So we have touch, we have mouse. We've obviously seen the mouse going on here and let's test out some keyboard usage. So I'm going to hit tab. And so this is where accessibility starts to come in really important into the document structure and the way that the elements are formed. So we noticed that the first item that I tabbed into was the next button. And that's because the previous button is disabled. That takes it out of the tab focus order. But another important thing to notice is that these buttons are technically above the scroll area. And that's why there's a first to get focus. So they come first on the document. So they're going to receive focus first. And it's important that when you tap into a scroller or in a scroller in a carousel that the carousel announces itself as a carousel, which we can see in our aria labels. Well, here, let's just go in here. Let's go to, uh, we'll pull this out, uh, close out of that. We'll go to the elements panel and we'll hit this accessibility button and pull this down. Yeah, let's look at the accessibility tree. This is super cool stuff. All right. So if we, here's our generic carousel item, this has got a label on it. I've called it featured items carousel. You could call this whatever you want. This is like hot new movies in 2022. This could be horror films, you know, from the 1980s or something like that. Um, when you twirl it open, we can see our buttons. We have our button here, which is currently disabled. So a screen reader is able to see that. The next button is clearly items. Let's go to next item. It's focusable. It's currently focused, which is cool to know. We have our navigation down here at the bottom, which we'll talk about in a second, but this is our group. This is our item scroller. So again, notice that there's a lot of information to the screen reader that tells them what this content is. And the order here is nice so that when we tabbed in, we hit our next button first. If we tab again, let's just do that. We go into the content. So this is the current item that's featured. And this is currently a link, um, but it's also a figure with a big caption and we could scroll that open if we wanted to and see all of this awesome information. So notice also, this says generic elements, um, one of three. So this is an item inside of the group. We've used, um, aria pause and set and aria set size to give these information to the screen readers. So the set size tells it the total number of items and the pause and set says which position you are in the set of items. We're in one of three. So screen reader user gets to hear that without even having reached the pagination yet. The figure has an image and a big caption. So these things get read out to screen readers. They know that they can access the alt text. They can read the link text and they can access it. And, um, everything is there for them. And if they tab again, they tab down into the pagination. Now the pagination dots operate exactly like tabs do, where if I hit left and right on my arrow keys, they go through all of the items. Now there's a cool critical thing to note here. So I'm going to twirl open the navigation and notice the difference between the scroll group area and the navigation and the navigation has all buttons exposed at all times. And notice in our scroller, we're only seeing, um, one of three. We're not seeing all of them. And it looks like it actually didn't pick up that I changed scroll position. Um, no, I think we're still on, it says still one of three. We should be on two of three. Um, let's see, generic, uh, well, I'll just kind of ignore that DevTools is a little bit behind us right now. Cause look, it's still focusing the item here on the left. But, uh, the point was that we only have one element in the scroller that's screen reader accessible at any time, but in navigation, we have all three buttons. This is the difference between using inert and using tab index. So tab index is used up down here on the tabs so that when you tab in, it will focus you to the one that you want. So here's, we're going to go to the middle. But the other ones are still visible to a screen reader. They can use their virtual cursor to move over to those elements, and those elements will announce themselves and tell you what's behind them. Look, they have a title blue ocean with a large wave. So as a screen reader user, even if you hover with your mouse, you can see the title that's on the dot. Here you go. Item number three, African Sahara with a draft. So I'll click there as a screen reader user. You can go down to this tab area and preview and listen to what each of those items are without actually even navigating to them yet. And then you can navigate to them, hit shift tab to move your focus back up into the item that's there and follow the link to the destination. So that's the sort of like overview of how I made this component accessible. I'm very fortunate and got to do a review with internal accessibility teams to make sure that this was accessible. And I loved the result. The result of that was, wow, this is a very accessible carousel. We still can debate whether or not it's as usable as it could be, but it is accessible. And I think that's a critical distinction to think about between like, is something accessible and is it usable? Because there's all sorts of usability preferences that users will bring to the table, right? Expectations that they want from something. But then there's also, is it accessible? Was a screen reader able to get same information as a sighted user? And is it usable enough that they can move through there in a decently natural way? And so those were the sort of competing ideals that came into this carousel that I think also are what make a carousel so complex and so controversial with people's that you can make one that's very usable, but it's not accessible. You can make one very accessible, but it's not very usable. And I really tried to find the harmony between these things today, and it was pretty tough. Okay. What else should we talk about in here? Let's, we can talk about right to left. We've got our forced colors. We've got our accessibility tree. We've talked about mostly things. I don't know if I really fully explained the difference between tab index and inert, but inert is pulling the item and all of its children out of the screen reader flow and tab index is keeping them in there. And that's an important distinction because we want users to be able to explore all the items, but we don't want them to be able to explore. Well, all of the pagination, but not all the items. And that's how we're kind of controlling tab focus also is inert is helping items not be in the tab flow if they shouldn't be. So I mean, that's just a little bit of the work that's gone into accessibility here. What's kind of cool too about accessibility is the author experience. So let's go look at some HTML of what it's like to author one of these versus what the HTML is when it's all in the page. All right, I'm in my trusty IDE. Let's expand this HTML example here for the basics of this GUI carousel. So we have a div class of GUI carousels. This is your sort of base hook that into the whole component and I've given it an ARIA label. And this is where you can give it a meaningful carousel title. So screen reader users, if they can't see the little header that you've put, or maybe this just helps the association between like what is inside of this group of content. And we're calling this one meaningful carousel title, but you, you know, in what my examples, I said featured items. So this is your opportunity to give that. Now the JavaScript will add a whole bunch of additional ARIA attributes onto this element for you and we'll get into that later. But you as an author of just sort of making one of these don't have to worry about it. We also have this next layer called class GUI carousel scroller. So the scroller element is what contains all of your snap pages, which then you get to put all of your items in here. And that's just, that's the structure. That's all you need to think about. You have a GUI carousel, a GUI carousel scroller and then a GUI carousel snap child. And those are the only things that are controlled by my component. The rest of it is all up to you. So you put your items inside of here and it will swipe between these pages full with all the sort of goodies that come with this carousel and what's cool is all of these items will get additional ARIA attributes put on them too, right? We had set size that was an and pause and set. These are different attributes that need to get put on the HTML element. So instead of asking you the author to put those on there, I've written script that goes through and it looks at how many children you have and it puts those labels on there for you. So just kind of cool stuff where I'm trying to make it easy. Another thing where I'm trying to make it easy is this is where the controls go. You don't have to worry about them, though. They're automatically injected for you. Same with the pagination, which goes down here. Pagination would be here and you don't have to worry about it. And I thought that was kind of nice that those things will conditionally get applied and they're based on the amount of items that you have, right? So now you don't have to duplicate a loop where you might have had a loop that's like putting items in these carousel snap pages in here, but now you don't have to additionally wrote another loop just to put the pagination dots in. And I will go and look at all the items that you put in the scroller and make pagination dots for them. Kind of cool. I thought the other kind of cool thing about that layout, at least that these are conditionally applied. So if you think about this page loads and there's no arrows and there's no pagination dots, surely there's going to be some layout shift because I'm going to be putting those in after the pages load. But no, we see it as grid. We were looking at that grid earlier where I had pre allocated space for the pagination and for the arrows. I'm able with CSS grid to make sure that that space is being held waiting for the buttons to appear. So when the buttons do get put into the DOM, there's already a grid row for it and there's no layout shift as that item comes into view. So I thought that was cool too is that grid helped me build something that could hydrate healthy without layout shift because I'm thinking about these things ahead of time. We can see that over here in the GUI carousel class. So if we look at this grid, we have displayed grid, we have grid template columns. So we have a gutter. We have a flexible middle row and then we have another gutter that's using our custom property and those named areas. So that's creating a column named carousel gutter, a free column, and then a call a column named carousel gutter again. So those are three columns we saw earlier. And here's our grid template rows. So we have our carousel scroller at one FR and our carousel pagination using this custom property called carousel pagination size, which is being set to var size eight right here, which is like to rem or something like that. And that's what I mean about that pre allocated size that grid row is made before the pagination and it's not intrinsically sized. So it's not being a row sized based on the content, which is what the scroller is, this one is being sized based on a fixed size. And that's how I can pre allocate the space. So cool stuff. And I think before the end of this GUI challenge, even though there's so much more to share, I'm going to go look at the output HTML and go compare it against what we learned here. So this is what we input in terms of HTML. And let's go look at what we get in the DOM and just to show how much upgrading and how much like how much JavaScript really needed participate in this custom component and order for it to be accessible. Okay, so we're going to look at the default example here. Let me scroll up. That's the thumbnails example. And the GUI carousel. Okay, so we're looking at just a section. Here's the header for the carousel. And here's the carousel itself, which we have the grid plate. So here's our gutters again. Here's our pagination pre allocated looks good. And now let's look at the HTML. Pull this open so we can just see some more of this. Oh, we'll go here. All right, so we have carousel pagination dot. So these are all the options that you can pass in to the kind of hydrator, the component is going to look at these attributes to see what it is and how you want to configure this carousel. I guess we didn't talk about that in the sort of base HTML, but most of these are set to auto, but here we can go over these ideas really quick here. So carousel pagination dots, you can also choose gallery. So that decides whether or not it's going to make little button dots or if it's going to make image dots for you, we have carousel controls. That's whether or not you want to show the arrows or not carousel scroll bar. So whether or not you want to see the scroll bar, carousel snapstop auto means let users free scroll anywhere they want, you can pass always and it will stop always on each item. And then we have our label. This is something you authored, but here's something that's being added from JavaScript, we have tab index negative one and are your role description equals carousel tab index negative one is in is essential. Because you want to have keyboard events. So here if I tap in here and I hit tab and I hit left arrow on my keyboard, notice that the page didn't scroll. Like when you have a scroll area and you're using a keyboard inside of it, those events will find the nearest kind of scroll container to emit the events on and it usually bubbles up all the way. But if you set tab index to negative one, you're essentially saying I want the events for these keyboard events to be trapped inside of this kind of container and that should be the target. So it's hard to explain but imagine you're pushing arrow keys inside of here. And the event dot target is body. And what I wanted to know is which carousel this was pressed inside of and I had to pass tab index negative one to make this an interactive element to sort of trap this. So tab index negative one means right. I want this to be like a focusable element but it's only programmatically focusable. And so this is only matters to the the scroller of the body like this document scroller and doesn't matter to a screen reader user. So it's kind of tricky but it was really essential to getting my keyboard events right. Then we have our role description carousel which is something that we can reference later. So here's our controls. So this got added entirely right. We didn't author this controls get added. They also used display contents which is interesting. So we almost are simulating subgrid because we have a container for our buttons. But our buttons are being placed as if they have no container and they're finding their own columns. So they're claiming a column here. Look grid column one. So they're busting out of their parent sharing the grid of gooey carousel and claiming that row or that column and then centering themselves inside of it is kind of a cool trick. But then let's look at these buttons. So we have a tight button. It has a title. Here's some classes. We have our controls. We have our label. So these are things being added so that screen reader users are getting everything that they need about the relationships between these buttons and the controls and the scroller itself. Okay. Now we have the gooey carousel scroller. This is called a roll group. We have a label items scroller and we have our alive polite. So these are all things being added from JavaScript to help again the screen reader experience. We're giving the scroller. It's a clear label and we're telling it to politely announce the items that are being snapped into view in here. And this is kind of a nice way for screen reader users to kind of get a because right there's different ways to announce things. There's interruptive announcements and these ones are polite. So these will be waiting for other things to complete before they go announcing themselves. Here's a snap item itself. So again the snap children you don't manage. And here's our Aria label one of three are your role description and inert is being put on here. We have our snap in view here. This is an end again are you able to three are you able to three and then down here we have our navigation where we're using our pause and set. So this is because this is the tab element and this helps users know which item they're at in the tab set. So we're as up above we're just sort of informing them about which position this item is in the view. Down here we're saying which item you are in this tab set. And so this again gets completely generated for you GUI carousel pagination. We have a type of button a role of tab. We have a title on here. We have an Aria label Aria set size Aria position set Aria controls and Aria selected and tab index negative one. So this is how we're managing which item is currently the first tab focus item is tab index zero. We have Aria selected being the way that we're articulating this item is selected and that is kind of it. So the JavaScript is going in and really doing a lot of hydration for you doing a lot of this kind of additional metadata work to tell a screen reader user what the intent of this documents is and what this interaction pattern is and that concludes this GUI challenge. I'm going to follow this up with a really robust blog post. I might even do a part two because the amount of JavaScript and CSS that it took to make this work is really fun to dig into. I'll see you all in the next GUI challenge. Check out the source code on GitHub and be on the lookout for a follow up. Take care y'all see on the next GUI challenge.