Vue + Django: Combining Django Templates and Vue Single File Components without compromise
Published November 22, 2023
This video features Mike Hoolehan at DjangoCon US 2025 in Chicago, Illinois, USA.
This talk was presented at: https://2025.djangocon.us/talks/unleash-your-django-frontend-integrate-web-components-into-django-templates-with-vue/
LINKS:
Follow Mike Hoolehan 👇
Follow DjangoCon US 👇
https://fosstodon.org/@djangocon
https://x.com/djangocon
Follow DEFNA 👇
https://www.defna.org/
Video production by the presenter and DjangoCon US 2025 volunteers.
Django templates can work alongside Vue without becoming a single-page application or requiring a full REST API. By compiling Vue single-file components into native web components, the application can use custom tags and HTML attributes directly in Django templates while retaining Django-rendered content, CSS, and web fonts; Vue handles richer behavior such as animation, fetch requests, reactive updates, teleporting content, and shared state. The demonstrated dice-rolling app used partial HTML or targeted JSON responses from Django, with Vue progressively adding quick rolls, animations, a modal display, and shared color settings; production deployment can treat Vue’s build output as ordinary Django static files.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Before I start, I just want to say how great it is to see so many different people with different backgrounds and different viewpoints here today. Django Khan is truly very welcoming and inclusive. I mean they're actually letting me present on an alternative to HTMX, so talk about open to new opinions. Now most Django projects will need to add some interactivity to their front end, something that goes beyond Django static templates. For many devs who, like me, refuse to give up Django templates, then that means uh a choice is often to add HTML-first frameworks such as HTMX, Unpoly, or Alpine. And that is often exactly the right choice. Because all these tools are very, very good.
However, I think sometimes this decision is based in part on some misconceptions about what it means to incorporate a full-weight JavaScript. uh build-based JavaScript framework into Django. So for example, if I use a build-based JavaScript framework, I can't use Django templates anymore. Or I'll have to build a full-fledged REST API. Or HTML-first frameworks can do everything that a build-based JavaScript framework can do. A second base a second code base in JavaScript will slow me down. Or JavaScript is poorly designed and quirky. Well, I believe that all of these statements I believe that most of these statements are false.
But my goal is to let you see for yourself. I'm going to start with a Django-only application. I'm going to introduce View. And I'm going to incrementally add some interactivity. All in the next 40 minutes. Now, my goal is not to convince you that this is the end-all-be-all, one-size-fits-all approach to JavaScript and Django. Rather, my goal is to show you that this approach might look quite a bit different than what you expected. It might be simpler, cleaner, and easier. So the demo app we're going to be working on today deals with dice rolling for tabletop role-playing games That's a hobby of mine.
I'm gonna explain a little bit about that topic. Just in case you don't end up using Vue, you might get something else out of this talk. So in t tabletop role-playing games, players uh take the role of characters in universes spanning many different genres. For most of these games, you need only your imagination, pencil and paper, or the electronic equivalent, and a set of dice. Now, RPGs use dice to introduce randomness and drama. So you can't just say that you're disabling the self-uh the self-destruct protocols to save yourself, the ship, and the cat You have to roll the dice to see if you succeed or not. Now besides the familiar
cube-shaped uh six-sided die, We often use additional shape die in role-playing games, such as the tetrahedron and the icosahedron. That's there in the foreground there. Now, saying words like icosahedron can really slow a game down. So we usually refer to the dice by the number of faces that they have. So we have D4, D6, D8, D10, D12, and D20. Now, to easily communicate which dice we should roll in a given situation, gamers have developed dice notation. So for example, if you had to arm wrestle an orc, you might need to roll two eight-sided die. Dice notation, that's two d8.
If you need to roll three six-sided die, three that's three d6. And if you needed to roll a single twenty-sided die and two four-sided die at the same time, so we can combine different dice in the same roll. That would be written as 1d20 plus 2d4. We can drop the 1 off the D20, so it's just D20 plus 2D4. Okay, now what our app does that we're developing here is let a user define their own dice roll presets for use in their own games. And then roll them from the web. So without physical dice, they could just do it directly from the web. Now the app we have is very simple. We have an overview here, just a single model. With the dice that's called dice preset.
It's got a name, description, and icon field, and also the dice notation. So this dice notation defines what the actual dice the role are. We have two views: a list view, which shows the user all their presets, and a detail view, which is for one dice preset, and it also serves to give the dice roll result for a single dice roll. For the dice roll logic, I'm using a Python package called D20. Just give it dice notation and it'll give us back a result. And uh for the dice icons, I'm using a web font called dice font. This has glyphs for all the different dice that we would use in our role-playing games. And for general UI toolkit, Pico CSS, a CSS
only framework, no JavaScript at all. So this application currently in Django has no JavaScript at all. I'll give you a view of this app. Uh here, this is running my Django run server. And we see we looping through showing the user all their presets. We have cards for each. This is courtesy of Pico CSS. We show the title, the description, the icon. We have a placeholder for this quick roll. uh which is non-functional. We have a little settings dialog up here, which a single setting, which is just the color, also non-functional right now. If we click on one of our cards, we'll get the detail view that I said, which gives us one dice roll result.
Here's the total for the whole roll and the individual dice with our dice font icon. If we roll again, this just serves to reload this page. So each time we reload this page, this detail view, we get a new dice roll All right, that's our app as it stands right now. You can see it does everything functionally that we need to do we need it to do. It's just quite static. So let's introduce view, make this more interactive. So we can bootstrap a new view project using this command here from the command line. NPM is the node package manager. You can think of it a little bit like pip for Python. So npm create view latest will bootstrap a new simple view project for us. When we run this, it'll ask us a few questions. what we would like to name our project, what directory we would like to put it in, some optional uh additional features we can include the project
If we answer all those questions though, it's going to tell us, it's going to create the files for us for our base project, and it will instruct us how to run it. Enter into the directory we just created. NPM install will cause all our will install of all of our dependencies, very similar to pip install. NPM run dev will run our view development server so we can view our application in our browser. Now I've already done all this to save some time and so I could present it to you in my IDE. So I'm going to switch over to my IDE web storm here, and we can see all the files that it would have created for us or did create for us. We have three files here, kind of meta package uh meta information. This package is uh project information dependencies, readme of course, and vtconfig
is the basic build configuration. I'm going to skip all this because I have limited time. Instead, we're going to focus on the application part that we've been given. That's this index. html we can start with. This would serve as the root HTML page if we were running in a single page application. Of course, we will not be running in a single page application. We're running through Vue , sorry, through Django, in a multi-page application. So we're not going to actually use this in our integration with Django, but it does come in handy a little bit during development, which we'll see in a moment. Basically, two things going on here. We have this div with the app ID, which is a container, and uh we have this script tag being loaded here. Uh which is loading this main.
js. This is our main JavaScript entry point for our application. So let's take a look at that. Again, not much going on here. We are importing this app. view component. We're using this view function create app to sort of turn it into an applet. And then we're mounting that app here into our app div, which is the one we just saw in our index. html So let's run this with the npn run dev. I can do that with the button here. It launches the Vue Dev server, and we can go to this URL to view it. So I'll do that in my browser. Second tab here, reload, and we get this message here. You did it. This just serves to demonstrate that we have successfully created a view
bare bones demo project, and it's running in our dev server Uh where does this message come from? You did it in this link to the docs. Well, let's go back to our main JavaScript and look at this app. view. Now you can see this HTML here corresponds to the stuff we are seeing on the screen. Now this file app. view or any. SFC. SFCs are the building blocks of a view application. They encapsulate the presentation, the logic, and the styling of a widget, or perhaps even a behavior. I will uh explain the anatomy of a view single file component in the course of
changing this into something a little more useful for our project. So I'm going to rename this to Dice icon. Alright, that refactored it. Uh updated my main with a new name. And we're going to get rid of this old content here. And we'll start here then with the template. The template you can think of is very similar to a Django template. It's mainly HTML, but we have a limited ability to interpolate variables and some simple JavaScript code, much like we could do with template tags or whatever in a Django template. For now, I'm just gonna hard code a single icon using that web font. And the way that web font works is we can just
give a class that kind of encodes the dice that we want. So this would be a D20 with the value 8. If we go look at our view dev server now, we see that we have an icon, uh, but it's quite small. I'll zoom in a little bit. You can kind of see that it's there. Well, being small leads us to the second part here of our view single file component, the style. We can use this style to style our template content. So we use that just regular old CSS here. So I'm going to change this to a larger font size. If we go back and look, we see now our icon is much bigger. And note also I don't need to reload the page here.
The Vue Dev server has this hot module replacement, which means that it automatically updates our JavaScript. And shows us the results without having to reload our page. Okay, of course, we need to show more than a 20-sided die with value 8. So that brings us to the final part, which is this script And this is where we have the JavaScript logic, our variables, our functions, our lifecycle hooks, anything with our JavaScript code. Here we're going to use uh two properties. Properties are how we parameterize a component, like how we would parameterize a function. So anytime we would use this component in our in our application, we would need to provide the values for any of these properties. So we're going to have two properties in this case. And that would be the size of the die
and the value of the die. So for example, a d20 with a value of 4. Now that we have props, we can use these in our templates. What we want is to interpolate those size and values into the string. So we can do that with Vue by letting Vue know this is now a dynamic property. We do that by just simply putting a colon in front of it. Now this is no longer a string just to be taken literally. It is now dynamic code to be evaluated. So we can change that into a JavaScript string, and now we can interpolate our size and our value. Okay, we've set up a useful component, but we have a bit of a problem. Like I said, if we use this component in our application, we need to provide values to these props.
But if we go back to our main, you can see we're using this dice icon now, but at no point are we providing any property values. So how can we do that? How can we give these values to properties values Well the answer here, surprisingly enough, is native web components. Now web components are a W3C standard which have near universal browser support. They allow developers to build uh reusable UI components authored in straight vanilla JavaScript. And we can tie custom tag names to these u uh c custom uh web components. Um Now, web components have been around for a while, but they haven't really caught on, I think in great part because they're rather unwieldy to author.
But Vue single file components are very easy to author. And Vue has a cool feature where it can cross-compile VueSingleFile components into native web components. Once we have native web components, we can use our components in our Django templates simply by custom tag name. And we could provide any needed property values as simple HTML attributes. So I'll demonstrate. So we're going to modify this main to not do any of this stuff, but instead use the custom elements registry on our browser. This is built into your browser to define a new element. And we'll give it the tag dice icon. And we'll use view to cross-compile
our dice icon element. Now there's one option we need to give, and that's shadow root. False. Now the reason for this is that by default custom elements are attached to your page using a shadow DOM. Shadow DOM will isolate All the elements uh HTML, CSS, JavaScript, completely from the rest of the DOM on your page. This unfortunately was disastrous if we were going to use these to integrate into our Django application because it also means that your components would not inherit any of your project's styles, web fonts, anything like that. So this was basically a showstopper for this approach until about, well, until less than a year ago
when Vue finally gave the option to disable this shadow route. So now we can place our custom elements on our page without and have them inherit the project styles of our page. Okay, so with this code we have now a custom element that has a custom tag named Dice Icon. So at this point we can use it by name instead. So I'm going to go to my index. html and wipe out that old container. And now use it by name. And we provide our property values as simple HTML attributes. So 20-sided die with value three. I'm gonna put a few more dice on there as well.
And give it some different values. Okay. If we go to our dev server now, we see how we have all of our dice shown with their proper values. Now, of course, this is still running in our Vue Dev server, which by the way could be cool if you're working on a larger team. You can have your JavaScript side of your team just work on your view components, your JavaScript components without ever having to load up your Django application. They can build in isolation and hand over to your Django team. But then how would the Django team get it into the application? Well, it's pretty simple. This same entry point that we have in our index. html, we just need to take that and stick it somewhere in our Django template tree.
So I'm going to stick it on the base here. It's a dice icon, so I think we would use it throughout our application. So if we put it in our base, it'll be available everywhere. So somewhere at the bottom of my base. Here. I'll just copy and paste that same script tag. The only thing is we need to update the URL to point to the host for our Vue Dev server, which is running on localhost 5173. Now our entry point is in our Django application. Now in our Django application we could use this component by name. So I'm going to go to this roll dice result here. where we show our results for our dice. I'm gonna remove this old uh web font call and now use this
ice icon component Right. So here with the size, we're going to pass the field value Django model field size and the Django model value field value here. And we'll get rid of the old one. Let's look at our Django app. Yeah, I'll do roll here. Okay, well we still have our icons. Nothing looks really that different How can we see if we're actually using a component now? We're gonna modify our component. I'm gonna just use the style here to make it red. Go back to Django.
Yep, it's red. So we're using a web component now. I'll go ahead and remove that again. Go back, it's back. Notice we didn't have to reload the page because we're using our Vue Dev server, we get that hot module replacement that automatically updates our JavaScript. Doesn't matter now that we're going through Django, we get all the benefits of the Vue Dev Server. So for example, I can bring up the dev console here with the Vue DevTools, and we can inspect our components here using VueDevTools. We can even do some neat tricks like changing their property values on the fly. So we have a dice with the value 3. We can edit here down here to make it a 5. And it's automatically updated there. Very helpful in development. Okay, so now we have our first component in Django.
Of course, this component is very simple. All it really did was replace some HTML. So let's give it an actual reason for existing. I'm going to try to animate this dice roll. So to do that, I'm going to use a third-party package. I'm going to drop the terminal here and install a dependency to our pack to our project. NPM install. The package I want is called Motion V. This is a animation for a view package. So this will go download the dependency, install another package, very similar to using pip install for dependencies. Once that's installed, I can import it into my component.
And now I can use it in my component. The way motion works is you just use a motion tag and then you wrap the thing you want to animate in that tag. And you give it some of the animation parameters. I'm gonna have it start up a little bit upward so it looks like it drops. Have it animate downward. Maybe some rotation. Rotate. And Well, I'll have it springy Okay. So now we go back to our page, see if we have a animation.
Yeah, we do. So motion is a really cool library. It does a whole lot more than this keyframing, choreographed animations. But this simple example is just to illustrate it's very easy to bring in third-party packages once we're in the build-based system. and have have access to npm repository. And often when we install code like this and use it from uh npm, it's much simpler to use than had we used a CDN. We can just use straightforward code in view or JavaScript. The way that it makes sense. Okay, so at this point we have the basic loop for doing a Vue plus Django integration. In Vue, we author a single file component. In an entry point, then we make sure to cross-compile that into a native web component.
We make sure that entry point is imported somewhere in our Django template tree, whether it's the base template or a leaf page. And then we use that component by its custom tag name, providing any property values as simple HTML attributes. Before we move on to the next component, let me talk a little bit about production configuration. So you noticed in this template, this base template, I hard-coded to the view development server Well, just like Django run server, this view to the view dev server is not intended for production use. Instead, when we do a view production build, it creates just static JavaScript files. So the easiest way to handle this in the Django production is to have Vue output its directory, its uh built files into the Vue,
sorry, into the Django static directory. And just treat them as you would any other static files that you have. So at that point, it's no different than you know your own project JS. Of course, in your template, you would need to not use the local host directory, you would use a static URL using the Django static tag. But of course the view development server is still very useful. So what I usually do is set up a Django setting or a context processor, something like that. That will switch URLs based off of the Django debug setting. So if I'm running a Django debug on my local machine, it will use the dev server URL here. If alternatively I'm in production mode with debug false, it'll switch to always using the static URL. Not too complicated. Okay, so let's now move on to our next component.
We want to implement this quick roll. What we want to happen is when we hit this button that we see the dice roll result. And we want to be able to do it for all these buttons. So we don't want to have a new page load. We just want to inject it in into each of these cards. So Ajax, right? And of course HTMX is would be very suitable for uh this would be very suitable for HTMX as well. Um so to start, I'm gonna just re-implement this whole card in um in view. So that HTML for that card is here in my Django templates. Just going to copy and paste all that. Now switching to view , going to create a new component. We'll call it dice preset card.
And I'll copy, I'll paste that template there. Alright, we'll move the script. Script uh in this case we're gonna just need a single prop here. And that prop. Will be the role URL. This is the Ajax URL that we're going to need to hit to get our dynamic response, the dice result back. Now you might ask, well, what about the uh the title here and the icon. We could put those as properties, but in fact this these are already being perfectly well rendered in our Django template. We just need to pull this inside of our uh view component So Vue does have a mechanism for this called slot. With a slot, we can well we can do just that. We can accept HTML given to us and then slot it in somewhere into our template
So we'll modify this a little bit. So all this code here, we'll just leave this as a slot to keep be given to us. So I'll name that as a slot. And we'll empty it out. So it will be given to us. Okay, we have a new component, so we need to register it and cross-compile it. So I'll just copy and paste that code and we'll call this dice preset card. And the component that we want is dice preset card. Now that it's registered, we can use it in our Django template. So back to our Django template, we'll replace all this with dice preset card. And remember we need to give it the property here, which is our role URL, our Ajax
URL. It's the same as our detail UR URL here, so I'm just going to copy and paste this. Alright, now this is the content. This div here is the content that we wanted to slot in. So we just denote that with a little slot attribute here. So Vue will catch that, recognize that that's the content that should be slotted in. And notice that it has Django template tags inside. It's no problem. It'll all be rendered correctly and then passed to Vue Get rid of this button, this is part of our component now, because it will be doing our Ajax call. Okay, let's look again at our Django page? Well, it looks the same. Uh how do we know we have a component? Well, I'm gonna try removing this to
do from our component here. Uh huh, not the whole thing. Yeah, and it's gone there. So again, no need to reload. Okay, but our button still doesn't work, so let's implement that. So again, what we wanted to happen this is this be an Ajax call. And what we want to show. is this uh content here, just the result and the dice icons, not the whole page. So just like HTMX, if we were gonna do like a form submission that should re redisplay the form, we only want to get the HTML for the form back, not the entire page. So same case here. We want just this partial HTML response, partial response. So I've set this up in my Django view
already, just to save some time, but I'm going to show that to you. So this is my Django view for that detailed view. This is the get part here, and you can see what we're doing is rolling the dice, storing these into some variables. This is the D20 roll. And then we're setting up our context for our templates. And then here at the bottom, if it's an Ajax request, and we can figure that out by taking a look at the headers, then we're just going to show this partial HTML result. This uh template here is the same one that we just modified here that just has the results. So if Ajax, partial template, just that little result box. Otherwise, the entire view that you had just seen So our view is okay, we just need to update our component.
So let's do so. The first thing we'll do is create a variable that will hold our uh HTML from our server. Now I'm using a ref here. A ref is how we create a reactive variable in view. Reactive variable means that when its value changes, then any other template any other um variables. uh any other computed code and yes any other templates that depend on it will automatically be updated uh based on the new value of this variable. That's exactly what we want here. We're going to show this role HTML in our template. So we want whenever we get a new response from the server that our template is updated with that new data, with that new HTML. All right, next we need a function that'll actually do that uh roll
or actually do the fet uh get request to do the role. So we'll create a function here. I'll call it roll. And it will do yes, it will do that, which is to use the native browser fetch API. This just does a simple get request, it's built into your browser. And it will use it will go get that uh URL that we have as a from set from our prop. Store that into a variable, and then our role HTML will just be the text. Part of that response, a text part that we know to be HTML. We need to show this in our template. So we can do this with a div here, and we'll set the
Uh we'll use the VHTML attribute. VHT VHTML attribute will set the inner HTML of this div to whatever the right-hand side is on this expression. So we just want to pass it our server response. And finally we need to kick this off whenever we click the button. So down here we can add an event handler, a click handler. And when that happens, we want to execute our role function up ahead. So when we click roll, we send off Ajax, we store that into our role HTML variable, because that's reactive, our HTML will be updated. Let's take a look.
There we go. And one cool thing to note is that the response that we get back from our server actually contains web components itself. So remember, these dice icons are web components. So we don't have to do anything different to get these to render correctly. They're already registered in our browser. So it doesn't matter if the browser encounters these in our Django template, our view template. Or even dynamic responses from the server, whenever that dice icon tag is seen, it will be put on our page, rendered correctly, and all the behaviors will work as we appro as we expect as well All right, now of course our view components are not limited to consuming pre-rendered HTML. We can, of course, consume JSON data, as you probably expected.
But even if we want to use JSON data, we don't need to build a full-fledged REST API. We can just return targeted JSON responses. So let's say in this case we wanted to show some sort of celebration animation when the user rolled the max possible result. Well, in order for that to happen, our component needs to know whether there was the max possible result. So let's modify our response here to return JSON and also clue our component into whether we had a max result. So I'm going to change this from HTTP response to JSON response. And we're going to return two pieces of information. First, we'll continue to return that HTML. Whoops. But we'll also return whether we had the max roll or not.
And I have a function already in this project that will do that. So it just takes the dice results and then returns true or false, whether that was max or not. Okay, if we go back to our component and quick roll now, we see that we're rendering JSON data. So we're getting our JSON, but of course we don't really want to render it. We want to consume it uh properly. So we'll go back to our component now and update our response or our role function to consume that JSON. So we got two pieces of information out of that. One was HTML and one was ismax. So we're gonna get that out of our response. json now. And our role HTML value then would just be that HTML part. Whoops.
And we need a new variable for that isMax part, which is a Boolean. And we're gonna set it to false by default. And now we can set that here. Yeah. Now I said we wanted to show an animation when that occurred, so that means motion again. Import that and we'll put in some simple little animation with some exclamation marks. What can we do? Uh, animate we'll make it fade out.
Scale up and make it take a little time, two and a half seconds or so Now we only want this celebration to occur when we had the max, so we can use the VIF directive to do that. So VIF will conditionally show this element or not, depending on some Boolean expression that we give it. In this case, our Boolean expression is just the ismax that we got from our server. Okay, let's check it out. I'm gonna roll the D4 here. I got a 25% chance of rolling high. There we go. Okay, so already our app is becoming much more interactive, and because it's more interactive, it's much more usable for our users.
One more thing I want to do though, I want to focus the user's attention on this dice roll a little bit better. I have in my base template in Django, which I'll show you here. This dialog element with the ID modal. This is courtesy of Pico CSS. It's a full screen modal dialogue, has a backdrop, and then it will occupy the full screen. It has CSS styling on it such that if there's nothing in this element, then it's hidden from view. But if there is any content in it, it will be shown So if we want our dice rolls to go full screen and be prominent, we just need to put them into this element here. If this were HTMX, for example, we would maybe do that HTMX Ajax call and have it target into this modal. But with Vue, we'll use a different mechanism called teleport.
Teleport allows us to take parts of our view template and move them anywhere we want in our page. And we can even do this conditionally. It's as simple as wrapping our content that we want moved in a teleport tag. So I'm gonna teleport it to that modal. And we're gonna put both the celebration and the result over there. So if we go back to our screen uh app now, we see that yeah, it's now full screen because it's been teleported to that dialogue. Now unfortunately Pico CSS is CSS only, so it doesn't give us any mechanism to clear out this
or to close this dialog. So if we want to close, we've got to empty it out again. We could do this a variety of ways. I'm just going to set up a simple timer here. With a ref here. At the end of our Ajax, what we'll do is start off that timer. Yeah, and we'll make it two-second timer here. And at the end of that timer, we're gonna set that modal timer value back to null. So what happens is uh we start out with a modal timer null. At the end of our Ajax, we set it to some non-null value. And once that timeout is completed, it goes back to set it to null. So we can use this now to conditionally teleport.
We'll use the disabled tag on that teleport, and we'll have it disabled when the modal timer is null. So let's look how that looks. What did I do? I misspelled disabled. All right. So it goes modal. Two seconds later, it's unteleported or the teleporter is disabled. That causes our modal dialogue to be empty again, so it's hidden And our content goes back to where it was originally placed because the teleport no longer occurs. All right, so teleport allows us to move content across our page. And you can think of how this would be useful for stuff like
status overlays or or um snack bars, things like that. But what if we need to share information across our page, so state information between our components? Well, we can do that by introducing external state. To illustrate this, we're going to implement this settings dialog here. What we want to happen is when we change these colors up here, that our dice icons will change color as well, any that are on the page and any future rolls that we make. We can introduce external state as I said. So this will live outside of our components, but it will be such that whenever our component uh whenever our external state is updated, our components will react to that and re-render them or react in whatever way uh
is appropriate for them. We'll introduce state. And as I said, it's going to live external to our components, so that means we'll instantiate it in our main JS, not in any specific component. And we can do that up here. And I'm going to use this create pinia function. Penia is the leading state management uh package for view. So this creates a state. The other thing we need to do is let Vue know which of our components should be able to use that state. So we can do this with this configure app function, and we just say that this particular app uses the state. state that we just created.
Okay. This just sets up the mechanism for state management. We also need to say what pieces of state we actually want to persist or to uh to to uh share. We can do that by creating a new store. A store is just an organizational unit of uh of uh estate variables. So we'll call this a store. This sets up the boilerplate to create a new store, give it a name. But the important part is here we need to provide the specific variables that we want to have as part of our external state. In this case our dice color. We'll set it to empty string by default, meaning the default color, whatever gray that was. And then we also need to export it here.
This just gives us a mechanism to potentially have private variables or private functions in our state. Only those variables here and then return will be shareable. Okay, so now we have state and a store with a single uh piece of information that we're including in that state. All right, let's go back to our Django and turn this into a view component. So That HTML is here in Dice Settings. I'm just going to copy and paste this select box. We'll create a new component again. We'll call it Dice ColorChoice.
And paste that template. Now we need to tie this template or this select box to our reactive state. To do that, first we'll grab a handle to our state, our store specifically, and now we can use this V model. uh directive to tie them together. So V model and we want this store dice color. So this sets up a two-way binding whenever a user adjusts this widget mechanically on the page It will set the reactive variable to that whatever they chose. Vice versa, if we were to use code to update that dice color variable, then the widget would update to show whatever we just chowed and changed to. Uh new component here, we gotta register it.
So this was dice color choice. And our component was dice color choice. Now that we've registered, we can use it in Django, so we no longer need this. We can just do dice color choice. And the final thing is we want our icon to use this color now. So we go back to our icon. And grab a handle to our store. And now we can style our icon here. Whoops. So I
style our icon here with that color. So from our store we'd use that dice color. All right, let's see if we got it. I'll get some rolls on the page here And there we go. So we have shared state. When we update our widget here, the other components immediately get that reactive reactive state. And our new roles will have that as well. Okay, I'm gonna I'm gonna leave the demo here then almost out of time, but I think we accomplished quite a bit in a very short amount of time. I hope that I've demonstrated that rather than making your Django project more complicated, view and
web components might make your pro your development a little simpler. By just giving you the tools that you need to and the environment that you need to interact uh to develop interactivity. This was a whirlwind introduction. I'm sorry for that, but I have here linked a GitHub repository that's ready to run. You can download that and run it. It's also completely annotated and documented with all the techniques that I've mentioned today, plus many, many more. So feel free to check that out. Write some PRs or some issues or some discussions. Also, feel free to please come talk to me during the conference. I'd be happy to talk to you about this Thanks so much for your time.
Yes. Vue single-file components can be cross-compiled into native web components, then used in Django templates by their custom tag names with values passed as ordinary HTML attributes.
Discussed at 14:18Disable Vue’s default shadow DOM by compiling the component with `shadowRoot: false`. This lets the component remain a web component while inheriting styles and fonts from the surrounding Django page.
Discussed at 15:04Load the Vue entry-point script from the Django template tree, typically the base template, and use the component’s custom tag in Django templates. The Vue development server can still provide hot module replacement and Vue DevTools while Django serves the pages.
Discussed at 22:07Have Django return a targeted partial HTML response for an Ajax request, then use Vue’s native `fetch` API to retrieve it and render it with a reactive variable and `v-html`. The response can even contain other registered web components, which the browser will render normally.
Discussed at 23:44Yes. Django can return a focused JSON response containing only the data the component needs, such as rendered HTML plus an `isMax` flag, and Vue can consume those fields to update the UI or trigger an animation.
Discussed at 31:28Use Vue’s `Teleport` component to move content to a target element such as a modal in the Django base template. It can be enabled conditionally, and a timer or other reactive state can later disable it so the modal becomes empty and hidden.
Discussed at 35:23Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026