Introduction to the Modal Project
Hey, welcome to another code-along video. This one's going to be covering building a modal with Tailwind CSS and Alpine.js. I'm very excited about this video because I really love using Alpine.js. It's actually perfect for this kind of stuff. It's very simple to build little components and they're very easy to copy and paste into other projects.
I want something that looks like this here. This is a modal that I built for my pricing table Tailwind CSS template demo page. These demo pages actually get a decent amount of traffic, so I figured I'd make a model that pops out after three seconds. And I could click "No, thanks" or away on it and it'll disappear, and it would be a good way to promote the videos where I explain how to build this stuff. So I want something that looks like this, except on here. This is the starter file that I've come up with. And as you can see, the starting content for the modal itself is at the bottom here because it hasn't been taken out of the page's flow yet, and I'll show you how to do that so it could be positioned against the viewport rather than page elements.
This is the file, very simple. It just has the lorem ipsum in it. It includes Tailwind, Alpine and Carla. I say this every time, but I'll say it again: It's not good to run applications in production with Tailwind like this. Tailwind is actually quite big. You'll probably want to use something like PurgeCSS to optimize it, but for the purpose of this tutorial, it doesn't really matter. So let's get started.
Building the Full-Screen Overlay
We have the modal here. What we need to first notice is that on the final version, when the modal opens, the background goes gray. It's not gray, it's actually white and it's partially transparent. So how I do this is by taking the modal here, if the whole background has to go white, that means that the container of the modal needs to be full width and full height, right? So top, bottom, left, and right is covered.
So the way that we do this is by pulling this div out of the flow of the page with fixed and aligning it against the viewport. So using something like inset-0 sets top, bottom, left, and right to zero. If I add bg-white, it'll be very, very obvious what's going on here. So this is before and this is after. So the content is still there, it's just covered by this white background div that's aligned top, bottom, left, right zero because it's fixed. Believe it or not, if the content behind this was long enough and I had the scrollbar, I could scroll but the screen would still remain white.
What else do we need to do here? Well, we need to align this content in the center, both vertically and horizontally. Flex is pretty good for this. It's just... I don't know if it's the best way to go about it. It's kind of a weird use case because there's only one column in that case but using flex, you just need to type in flex, use the flex class, items-center to center vertically and justify-center to center horizontally. That'll look like so, which is good. That's what we need. And obviously we also need to make the background partially transparent, so bg-opacity-75, and that is perfect.
Styling the Modal Content Box
The container div is actually done. We can actually move on to the modal.
I'm going to fly through this a little bit faster than usual simply because I want to get to the Alpine part. I feel like that's the most interesting part of this tutorial, and my other videos cover a lot of these classes anyways. So if you watch a couple other code-alongs, you'll get a really good idea of how a lot of Tailwind works.
There's two divs in here. I want them to act like rows instead of columns. How I do this is by making this into a flex container and then flex-column. And then I added a background of white, a shadow of 2xl, rounded-large, added a border with two, made that border gray-400, and added padding all around of six. And that makes it look like so. So padding all around of six, background is white, border width two of gray 400. You can see the shadow is here, and the corners are rounded. Very simple stuff. If you're really interested in learning the... seeing how more complex stuff is built, I really recommend you watch the video here of me building the pricing table. That'll give you a much better idea.
Continuing on, we're probably ready to start styling these. What I did here was I actually used another flex container and I added justify-between and that basically puts them at opposite ends within the same row. They're still their own columns, but they're just spaced out to opposite ends. I also added a margin bottom of 4 here to space it out from the content here. This I made font-bold and text-3xl, I believe. No, 2xl. I remember it being smaller than that. That looks cool.
I have an SVG here that I'll put into the button. Instead of just writing "close," I'll add an actual icon. And that looks great. So far, so good. None of this obviously works yet, but I'll show you how to make it work in just a second. So the button doesn't need to be styled because it has that icon. Just a couple last little things to style would be this. So I'm going to uncomment this YouTube video. I was worried that it would get distracting before, but now that we're almost done the design, I'll uncomment it. I need to add a margin bottom of six here to space this text from the video. And this text is actually perfect width to fit within this modal, but I would prefer it to be text-centered. Oops, wrong one. text-center. If the content is longer or shorter, it'll be in the middle, it'll be nicer. It'll just be a nicer looking modal that way.
Ensuring Mobile Responsiveness
All right, so the design is finished, and it looks great, and it is mobile responsive. So if I show you this, it is mobile responsive. Like as you can see, you can scroll behind it. The only thing I will change is adding a little bit of padding on both sides here, just so the scrollbar doesn't actually go over it. The way I do this is by going into the containing div, adding a px-4. That spaces it, but it also will add that padding now on the desktop version, so I'll remove it. Because Tailwind is mobile-first, it'll look at this, say, "Okay, this is cool, but only if we're below this width. If we're above this minimum width, we'll add a zero, or we'll just make it padding left-right with zero." Again, my other tutorials or code-alongs will go over this in greater depth.
Managing State with Alpine.js
We are now ready for Alpine.js, and this is very, very exciting because Alpine.js is incredibly, incredibly powerful. We need to basically keep track... I'm going to move this down because we're going to need more space. We need Alpine.js to basically create a component that tracks whether the modal is open or not. How we do this is by first defining the actual data we need to keep track of. So x-data equals, oops, double quotes, braces, open, and by default right now I'll make it true.
Okay, it doesn't change anything because as of right now, it's just tracking whether it's open or not. How we actually keep it open or closed based on this data is x-show, and we'll set it to open. What this basically means is if open is true, it'll show this whole thing with the grayed-out background and everything. And right now it is, right? So it looks the same. But what if I set this to false? It doesn't show up because it's closed. So the basic functionality is there. We know how to keep track of this stuff.
But now how do we actually manipulate that data, allow the user to manipulate that without even knowing that this is a component they're manipulating, right? Well, the way you do this is by using something very similar to Vue, it's @click. Let's fix the button first and then worry about clicking outside the modal to close it. @click equal to, and then double quotes, open is equal to false when this button is clicked. open will be set to false, which means it'll be closed because that closing button will only be used to close it. And as you can see, it works exactly as intended.
Implementing Click-Away to Close
Now we need to figure out how we're going to close it by clicking outside here. We basically want the Alpine.js to say, okay, if we register a click on this div here, it all needs to close. If it's clicked within here, it's fine, leave it.
And so we can set an @click event—kind of, I guess it's a listener—on this modal here to say if a click is made away from this element, close it. Let's do that. So this is the actual modal element. This is the container that grays out everything behind it. If we do @click.away equal to the same thing in double quotes, open is equal to false, and save that and refresh the page, if we click here now, it closes. So if I click here, nothing happens. If I click this X, it'll close. If I click anywhere here, even play the video, I can even play the video, but if I... it'll all work. If I click anywhere out here, it closes. Exactly what we're looking for.
Triggering the Modal on a Timer
So carrying on, we obviously don't want this to be open as soon as we refresh the page. Actually, most modals will probably function that way, but in my case, I wanted it to fade in after a couple seconds for... I wanted the user to first see the demo, look at the, in this case, pricing table, and then be kind of hit with this very subtle modal promoting this video.
Well, how do we do this? The best way that I found, it's a little bit confusing to explain it because I wanna... let me explain. I use x-init, which basically runs a function that you pass into here when the component is initialized. Originally, I was... I wrote a function and I passed it in, and the function was written in a within a script tag below the component. I want to copy and paste this modal into other projects, I wanted to be as modular as possible. And so what I ended up doing was just writing like an arrow function within here, right? That used something called setTimeout, which I'll explain in a second.
Okay, setTimeout takes another arrow function and an amount of milliseconds within this. So if this amount of milliseconds passes, it's going to run whatever's in within this function. And in our case, it's going to run open is equal to true. Let me break this down a little bit by just kind of indenting it how our typical function would be indented. So x-init takes a method or a function. Within the function I'm passing in, it's just setTimeout. setTimeout will do whatever is within the passed-in function after this amount of milliseconds passes. So in our case, it'll open the modal after 3000, what I would assume is milliseconds. Let's see if it works. Yep, default false. Let's see if it works. Three, two, one, open. This is exactly what we wanted.
Adding Smooth Fade Transitions & Outro
The last thing I will note is that we're missing one little subtle thing that you'll see in this example. When after three seconds the modal does pop up, you'll notice it fades in really nicely. And this is kind of like the, kind of like the cherry on top type thing. Originally I wanted to do this with CSS, which is kind of... it just wouldn't work. And looking back, it was kind of foolish to try it because CSS doesn't really know when the changes are happening to the component. I'm sure there's a way you could you could rig it up somehow, like I said, an event and then add CSS classes. I'm not sure. I'm more of a back-end dev myself. I like the the easier, simpler way to do stuff. And to my surprise, Alpine.js actually includes transition functionality out of the box. And specifically in this case, I use it on x-show. Every time x-show is triggered to either close or open this, we can add a transition. We can set the duration, and we can set it to 500 milliseconds. Every time it opens, it's going to be with a transition of 500 milliseconds. Every time it closes, it's going to be with the same transition. And this transition is now set on the main container div, which means that the background that is a little bit faded out is also now going to slowly pop in.
So let's refresh the page. Three, two, one, and you can see that it fades in. Even when I click away now, it fades out. Let's see one more time. Yep, that's exactly what we're looking for. So this is the final product. I'm going to push this to GitHub and make sure that the link is in the description below.
If you are interested in these kinds of videos, I have a lot of fun making them. I can cover even other topics, even related to working in the field itself. I work as a full-stack developer currently. If you're interested in anything, please let me let me know in the comments. And and please do subscribe so we can grow this channel. Thank you for watching and I'll see you next time.