 Hello, EmberKonf. I'm James Steinbach, and I'm really delighted to be here with you today talking about responsive components. We've had the ability for years to make sites respond to whatever device they're on, and that's fantastic. That has allowed us to accommodate a wide variety of screen sizes and device types. We've got media queries that's been around since 2010, right? We can target pixel density. We can target device orientation. We can target user preferences like contrast levels, reduce motion, color scheme in play, and that's all awesome. That has allowed us to build sites, apps that work in a whole variety of places, but it's left us wanting something more. We've really preferred often that we could style a component based on its own size, not the browser, not the viewport, or the device, but the component. This ability would open up a whole host of amazing features like publishing embeddable components, native web components around the rise. This would play beautifully there, and allow us to publish components, to import components from other libraries, whether they are Ember or coming in from a native web component, and bring these things in and make them useful and accessible and properly styled for all their users. This really is an accessibility thing. This is not accessibility equals screen readers or semantics or RE attributes or anything like that. This is accessibility means making the components and the parts of the sites we build usable by everyone. Component-sized or targeted styling opens up a huge possibility for making completely independent components with good color contrast and font sizing, not relying on the viewport, which might or might not match the right targeted styles, but allow us to really target legibility and accessibility and usability. Universal access, no matter where in a page or site or app, the component gets used. We want to target the size of the element itself for our element queries, if you will. We've been able to do that, but it's been really bad in the past. Step one, figure out what size the element is going to be at a given breakpoint given your site's global breakpoints, then use the media queries that match each of those breakpoints to the ideal styling, whether like narrow or wide, tall or short versions of the component. We ended up with a ton of media queries. The code you see on the screen now is showing us how bad it used to be. All of this code is absolutely not here to tell you what to do, but to say it looks like a wall of text because it was a terrible wall of text, and that didn't even account for what happens if the widget you saw styled on the page before shows up in the main content or is in the sidebar. We looked at all of that stuff and said, yeah, yeah, we'll make this work. We can do this, but it wasn't ideal. It wasn't ideal by any stretch. I want to talk to you today about how to make universally accessible components, components that work in any size or shape, and respond to their own dimensions, not the viewport. Let's talk about some current techniques. In a talk like this, you might be like, well, James, are you going to tell me a bunch of things and then show me like the browser support screen and it's going to be like nonstop or red, down can I use, nothing ever works. It's polyfill city and nothing more. No, I've actually got several slides worth of content, four different techniques we can use to make self-responsive components today. Right now, after Ember Conf is over, fire up your editor and go. These will work. Three of them are CSS. One of them is JavaScript. So let's jump into the styles. First off, we can use Flexbox wrapping. Flexbox has been around since eight years ago and has been completely like rock solid stable for modern browsers for at least the last like six or so. If you allow a container, a flex parent, flex box to wrap its children and then give those children ideal with flex basis values, they can change between a stack and a row based on the parent's ability to fit all of the children flex basis values into a single row. And we can see that in action in this code pen. The component here in all three instances is identical. The markup does not change. It's simply the width of the div each component finds itself in. So there's nothing different at all of the HTML. No tricks, no markup changes, no classes. But we've got one and two column layouts, one column and the narrow two versions on the bottom and two columns in the top version where there's room for it. We've also got some cool layout tricks where the alignment of the buttons at the bottom appears to change. Awesome. We're locked and loaded here with layouts that respond to the ideal widths of pieces in the component. What if you want to respond to the width of the component itself and make a change between horizontal and vertical layouts in that case? Turns out you can. This technique was named by its author Hayden Pickering, the Holy Albatross, I do not recall why. But these are all live slides. Real markup, the link at the bottom of the slide deck, which you can see now. Hopefully, as well as I'll be putting links in Discord while this is playing, we'll get you there. And there's all hyperlinked information, so you can learn more on any of these slides. So you can check out Hayden's original work and see if he explains clearly why it's called that. But this uses a magic number of value, a breakpoint, if you will, for the parent, for the container, a container breakpoint. And then at that breakpoint, exactly, not the component, the children's ideal sizes. But the parent's breakpoint, if you wanna call that, will toggle between vertical and horizontal layouts at that breakpoint. And it looks like this in code. The heavy lifting here is being done by the calc function in the flex basis at the bottom. I'm not gonna explain the math behind it. You can read that in the linked articles from the slides. But if I say to say that function does a heavy lifting and at 720 pixels, for example, in this instance, the layout will toggle from a column to a row. And it looks like this in a browser. Yeah, I'm not changing the viewport size at all. There is nothing media query happening here. The HTML element, the viewport is staying the same. It's got the checkered background. You can see the white tick mark little outlines on the corners. Those are around the body element, which I'm animating with just CSS. So all I'm doing is changing the width of the body. Then inside of it is our layout component and all of its children. And you can see when we cross that 720 pixel threshold, we jump from stack to row. This doesn't necessarily help you with like, if you want the row to have variable widths on things. It's a lot trickier to try and go for that. This is really good for equal width columns, but we wanna change between stack and row at a given component breakpoint, I'll call it. And yeah, those are a lot of fun to work with. We also have a CSS grid based technique. In this technique, we're using display grid on the parent. And this is ideal for when you have like a gallery layout, just like items galore. And you want to adapt the number of items visible in a row based on how much space is available. And sure, you could use media queries to change like your grid template column count if you wanted to. But this repeat function for grid template columns does a lot of cool stuff for us. It's got two parameters, auto fit is the count. That means instead of like one or two or three, sticking as many columns of the width as you can safely fit into the parent. And then the second parameter is the width of a column, which is a min max function. It is nice and squishy and flexible, useful for our case here, which says it's gotta be at least 200 pixels, but at most one fraction. And that means, this is a fun one, the grid will have as many 200 pixel columns as it can fit, including the gap measurement. The grid will automatically take care of the gap measurement for you, no calc to remove 25 pixels or an M or something like that from the width of your columns. Grid is amazing at handling this for us. So grid will see how many 200 pixel columns it can fit. And the 1FR unit is a newer grid unit. And that means fractional space. Whatever space is available, before there's room to fit another 200 pixel column in there gets divided evenly between all of the grid columns that do exist. So you'll get a grid that never has extra empty space. All the columns are evenly distributed, but there will be only as many 200 pixel, in this case, columns as we can safely fit in the grid with no overflow. And it looks like this in a browser. Yeah, again, HTML element is not changing. We're not doing anything with the viewport, that's steady. We're only animating the size of the body with a stepped animation. And our gallery inside is adapting the number of columns it has to its width, based on an ideal child width. So that's a lot of fun. I wanna bring this one up, this is clamping, not because it's a component specific feature, but because all the articles about responsive components try to mention this. But in reality, every time they use CSS clamp and min and max, which are handy features and I would love for you to learn more about them, which is why I included the slide. They always use it with a viewport unit in a calc function, which means it's still based on the viewport like a media query, not based on the component size like we kind of want. And that means that the only things we've been able to change thus far about a component based on its own size is its layout, not font sizes, not border radius or colors or anything like that. Only based on its size, only thing that changes layout. So we're doing that. This would be really cool though, if we had like a container unit. I don't know, a container based unit would be really cool, like the elements own width, a percentage of that. Put a pin in that one in fact, because that's a really cool topic and I think I'd like to talk more about it in a few minutes. The fourth and final technique that is currently available works right now for responsively styled components that respond to their own self size, their own dimensions and not the viewport, is JavaScript and that is resize observer. There's a high likelihood some of you have already used this or used one of its really close cousins like intersection observer or mutation observer. Resize observer is a JavaScript API that watches performantly and elements size and then runs any JavaScript function you need to like modifying the DOM as the size changes. You can read more about it on MDM if you need to support IE, there is a polyfill. So let's see what it does though. Step one, create a new resize observer. We're gonna name it RO because abbreviations are fun. It's going to work. It's gonna take a function which operates on an array of entries. It's always an array because a single RO can dot observe several different elements. So even if you only observe one element, it's still gonna get put into an array, which is why we for loop our way through that. We normalize size because Firefox has a different opinion from Chromium and Webkit on whether or not the border box size should be an array of objects or just the object itself. Even though in theory, each element would only have one border box size object, but at any rate, then we have an action we're gonna either add or remove the class based on whether or not the width or inline size in this case, as we're describing it, horizontal writing mode assumed is at least 480 pixels. And then yeah, we're just gonna add and remove the class wide. And in a real browser, that behaves in a lovely way. As this card changes, you can see a toggle between red and white to yellow and red. And we're also changing the width every time we change. Again, no viewport stuff here, just resize observer paying attention to the size of the box as the body element again is animated through several steps of size. With this, we can change anything on the element. We can swap out some custom properties for CSS. We do all sorts of things. And there are a couple of Ember modifiers that bring in resize observer, which, you know, do a little research. There's some pretty good options out there. And this is super well supported. Oh man, for those of you who are colorblind and don't tell green and red from each other really well, green is 90%, 90 and a half percent global support. The only browsers with any significant unavailability for resize observer are some older safaris like three years and back older Opera Mini, which is just kind of a browser that doesn't want to update things. It feels like, and you see Android browser, which is really cool because let me talk about progressive enhancement for a minute. If you have a mobile device and your default styles for a component are the smaller version of its styles, and then you use a resize observer to let the browser know that it's wide or not and add that remove that class to toggle larger styles, then mobile devices like Safari on an iPhone that's four years old or Opera Mini or an old Android browser don't have any issue. You've progressively enhanced right around them and they never knew it, awesome sauce, let's go. So resize observers are a really helpful tool. Of course, with JavaScript anytime you take up memory observing things or listening to them, you wanna make sure you clean that up. If you're gonna observe a single element, great. If you're gonna stop paying attention to that element but keep the resize observer instance, watching other things, you can unobserve a given element. And if you just wanna tear down an entire resize observer instance, you can disconnect it. That was all stuff that works today. Go out and if you wanna quit watching the stream right now and just go implement things, awesome sauce. That's totally cool. And I don't say awesome sauce a lot but I said it twice now and I feel kind of self-conscious and goofy about it. So please bear with me, I feel silly. But I'm gonna put aside silliness and move into the future because what's coming is really fantastic. The future is container queries. And you might ask a question like, what about element queries? Isn't that a thing I remember? And that's the question I asked when container queries kind of landed because I remember element queries. I was at the third link on this slide but we saw the language start with selector queries back in 2011, yes, 11 years ago, which is a little interestingly, barely a year after Ethan Markott's responsive web design with media queries being a foundation. So less than a year after we really got good at media queries and they took off, we wanted selector queries and Andy wrote a little JavaScript library that does that client-side scripting, takes an HTML data attribute API and does some cool stuff. It worked for the time. A couple of years later, the language had shifted to element query because that seemed to be better word than selector. And Phil was still calling it element queries in 15 but the word container is sneaking in and that's actually really significant because we did end up shifting away from an element model of thinking about it to a container model of solving the problem. So the solution to container queries and before I proceed, I'm gonna pause and give Miriam Suzanne, like sitting here on my desk alone in my basement, big round of applause. Hopefully you all are doing that from home as well. I don't know if she'll hear us or not but Miriam has done so much work with the CSS working group, getting the spec ironed out, continuing to iterate on things and is lead on this particular project and is just on a fantastic job. Everything I've learned about container queries I've learned from Miriam. And there's kind of a problem in tech that some of you have observed where somebody from an underrepresented group has something amazing that they work on and contribute and publish. And then some tech bro says it differently like a month or two later and suddenly he ended up getting all that credit and I refuse to be that tech bro. That's not me. So Miriam, anything that happens after here, I'm super enthusiastically passing all the credit along to Miriam. And if it sounds weird after this moment in the talk, then please assume that I have confused something a little bit in my explanation and not done a great job and scold me but give her the credit. So let's talk about why this took 11 years since we first had that selector query library from Andy Hume, all the way to 2022 and we're still in the browser support increase stage of this. I just kind of spoiled the browser support slide. Yeah, it's not your favorite, but we'll get there and it'll be fine. Why would it take so long to get container queries going? Well, first we spent a lot of time thinking about selectors and elements and didn't shift the mental model until more recently and that was part of what allowed things to change. But here's a problem we're trying to avoid is loops. And in this particular pseudo syntax, colon query is not a thing. So please don't repeat this slide in your code base. It'll do nothing except get skipped by your browsers, every browser. Let's say you have an element and you tell it to be 400 pixels wide. Then you set up a query on that element to say if the element's at least 300 pixels wide then make it 200 pixels wide. And as soon as you do that the element is no longer matching the query. So it's 400 pixels wide but as soon as it becomes 400 pixels wide it matches the query and now it's 200 pixels wide and as soon as it is 200 pixels wide it no longer matches the query and it's 400 pixels wide but then it matches the query and we could do this all day, right? That's a loop and that's terrible. But having that foot gun by itself isn't the issue we're trying to solve because lots of code languages do have ways for users to make things too complicated or not work well. There's a philosophical issue. If we can picture browsers being philosophical and sorting out the chicken and the egg every element on the page has layout constrained by its content. Its sizing is based on the things inside it whether it's the amount of words the number of words in a paragraph or the font size on those words in a paragraph whether it's other elements images, buttons, forms, et cetera. The children of an element whether that's text content or other elements affect its size. So the browser can't lay out the element without knowing how all of its children all of its contents are styled. So it can't attempt a size-based change of style on the contents of an element. You can't have content-based sizing and size-based styling on the contents. So it's philosophically the browser doesn't have a way to pick which thing to do first. So we had to break the loop. And to do that, we have container queries. So step one, create a container. Here we're using the CSS property container type. This is what works now. The first time I gave this talk it was slightly different. I had to edit my code and my demos. So this is the work in progress specification. What this does is it creates a container and it disables content-based sizing. So now the children of this container no longer give it any size. This is kind of like when we used to float all of our layouts and then the container that held all our floated layout columns would collapse and the border we put on it or the background we gave it was gone and we got really grumpy at the browser and then realized, oh wait, that's kind of what float is meant to do. And then we had Clear Fix as a workaround. Yeah, that's what happens again. Only now it's intentional. So what you need to do is in this case our container type is inline size which in horizontal writing mode, left to right and right to left is width. We need to make sure that the element has width. That could be as easy as making sure it's display block which has a default width of 100% or grid or flex. You could give it an explicit size with a percentage or a pixel or an M or a RAM. You could put it inside of a flex box or a grid container that will either stretch it to match the flex container or fit it to a particular grid track. Just as long as the container has something telling it what size to be, what width to be in this case, you're okay. Because the children, the elements inside of it will not affect its width at all. Next we're gonna style the children and this is like, wow, is it really that short of a talk? Kind of, this part is, yeah. In a narrow container, give it a narrow CSS and then if its container is min width 480 pixels then we're gonna give it a wide version of the CSS and this is the real current syntax. This is the code. This is the thing to copy and paste into your demos and start playing with this. Now it's worth noting a widget could be inside of multiple nested containers. And if that's the case, then the nearest container will match the query, the matches a query will affect it. You might be thinking, wait a second, I'm going to have multiple containers and I'm going to nest them. And some of you may not be thinking that because this is all brand new and that's totally cool too. But some of you are thinking really fast and like jumping to the conclusion like, oh what if I have multiple containers and yes. Step three in these easy one, two, threes is usually profit but we're not gonna profit. We're gonna name our containers optionally. You can, after creating container type in page content and page content gallery, you can give them names. And then you can tell your widget to do different things whether it's in the main container. You can see the container name shows up right after at container or you can style it differently if the gallery container matches 480 pixels or more. So there we go. We can explicitly connect named containers to container queries. And this looks like so in a browser. This is the same code we saw earlier from the Flexbox demo. The same component markup is repeated three times but now still got Flexbox doing our layout. Don't change something that already works. Now I've got container queries changing the filter colors on the image and the text and the background colors and sizes and the button shape and corner radii. And all sorts of fun things. This is possible with no JavaScript just CSS container queries. So we get to the slide nobody wanted to see. This is our browser support and it's nothing now except Chrome with the flag. So polyfill look to the future. But as long as we're looking to the future I have more fun facts for you. Remember how I said we should put a pin in the idea of what if we had a CSS unit for the size of a container of an element. Ta-da! They called it query units or queue units but they mapped a component's height width or the minimum maximum value between the two. We can also, this is one free one. I'm gonna move through this really quickly. There's a new CSS syntax that lets a style apparent based on what it contains. The has selector here. And it works like this. We do one layout for the card if it doesn't have an image but if it does we give it a layout. But I would say this is awesome in CSS if you don't know the markup you're getting but if we're working with a templating system that has logic in it such as handlebars we can already kind of do this with class names and handlebars and it's probably and it's definitely a lot more stable because has is as bad at browser support as container queries today. Polyfills available for both of them. Highly recommended for you to check those out if this is something you want to start using. I had this idea for an Ember modifier you can take it or leave it. Just a little sample code. What if you pass the modifier an array of query objects each object had a name which is a class to be toggled and then condition min max size. We tell the resize observer create one tell it to observe the element disconnect it when we no longer need it and then we name it CQ and we can use an Ember modifier and pass a query array in and boom we can toggle class names real quickly from the component it'll behave like container queries but it's a resize observer under the hood. Just wanted to toss this out there as a potential it's not a polyfill but a workaround. So what can we do? We've already seen the browser support is on the way we can wait eagerly. Browser support changes lately like grid for example has been really, they've been really rapid and grid took like four months from the first major browser to the last major browser to get it all supported. So use a polyfill for now that sends signals to browser vendors of what's important they pay attention to those stats CSS working group is GitHub issues with comments so low barrier to entry if you want to join the discussion and then as browsers make tickets available in their feature trackers throw actual votes into those. More info to read if you're into reading hopefully you are. Thanks so much. This has been a really fun Ember talk. Again, really looking forward to hopefully meeting some of you in person at a future conference but for now if you want to continue the conversation I'd be totally on board with that. You can find me at JD Steinbach on Twitter and GitHub and .com and I'm totally happy to talk with you and Discord this week or any other venues in the future. Thanks so much.