Skip to content

Layouts & Theming in Vuetify 3


Do you want to avoid tinkering with CSS to get your website looking just right?

Branding is everything in today's digital landscape. With Vuetify 3's customizable theming options, you can create web projects that perfectly reflect your unique branding, and visual identity. Additionally, you can reduce the duplicated code by creating layouts representing your application's different page structures.

In a previous article introducing Vuetify 3 with Vue 3, we created an application that allowed us to add, display, and delete jokes. image If you'd like to follow along with this article, please clone the GitHub repository.

git clone
# or `` for ssh

# then cd into the project directory
cd layouts-and-theming-in-vueityf

In this article, we'll go over how you can create layouts in Vuetify 3 to allow you to reuse the same layout for pages that share the same structure. We will also go over how you can customize the look and feel of your application to match your brand's visual identity through the powerful theming tools that Vuetify 3 offers us.

Layouts in Vuetify 3

What are layouts, and how are they useful?

In web development, layouts are akin to blueprints for the structure of your web pages. They provide a consistent and reusable frame for the different pages of an application. A well-designed layout can improve the user experience by providing familiarity and predictability as the user navigates through other parts of your application.

Layouts are particularly helpful in reducing code duplication. Instead of defining the same structure for each page—headers, footers, and navigation menus, you define them once in a layout, and then apply that layout to any page that uses the same structure. This speeds up development, and makes your code easier to maintain.

Using Layouts in Vuetify 3

Creating a layout in Vuetify 3 is as simple as creating a new Vue component. This component will include the common elements shared across multiple pages like headers, footers, and navigation bars. Here is an example of a basic layout:

      <v-header> ... </v-header>
      <v-navigation-drawer> ... </v-navigation-drawer>
    <v-footer> ... </v-footer>

This layout has a header, a navigation drawer, and a footer. The <router-view/> component is where the content of the specific pages will be injected.

Building a layout for our application

Let's create a layout for our application. We'll have a top navigation bar and a footer. Inside the layouts directory, create a new file named Default.vue and add the following code:

    <v-app-bar app color="primary" dark>
      <v-toolbar-title>Joke Machine</v-toolbar-title>


    <v-footer color="primary" app>
      <span>&copy; 2023 Joke Machine</span>

Additionally, in order for us to use our layout, we will need to modify our router/index.ts file to use the Default.vue component:

const routes = [
    component: () => import('@/layouts/Default.vue'),

This will make sure that our respective pages use the Default.vue layout that we created. The contents of the pages will appear in place of <router-view> in the layout.

It is also worth noting that you can create, and nest as many layouts as you want.

Theming in Vuetify 3

Themes allow you to customize your application's default colors, surfaces, and more. If your brand has specific colors and styling, you can theme your Vuetify application to resemble your brand through theming better. Additionally, Vuetify 3 allows you to modify your theme in real-time programmatically. [Vuetify 3] also comes with light and dark themes pre-installed.

Theming API

Vuetify 3 offers us 2 main APIs for working with themes:

  • useTheme is a composable that allows us to get information about the current theme and will enable us to modify the existing theme.
  • v-theme-provider is used in the <template> section of your Vue files to modify the theme of all of its children.

Updating the theme of our application

Updating the theme of our application is straightforward with Vuetify 3. Let's customize our application's primary and secondary colors. In the vuetify.ts file, modify the themes section to contain the following styles:

import { createVuetify } from 'vuetify'
import 'vuetify/styles'

const vuetify = createVuetify({
  theme: {
    themes: {
      light: {
        background: '#FFFFFF',
        surface: '#F2F5F8',
        primary: '#6200EE',
        secondary: '#03DAC6',
        error: '#B00020',
        info: '#2196F3',
        success: '#4CAF50',
        warning: '#FB8C00',

In this example, we're defining a custom 'light' theme. If you want to use a dark theme, or any other named theme, you can add those as additional properties within the themes object. For example:

themes: {
  light: { /* ... */ },
  dark: { /* ... */ },

And... That's it! By changing the various config options in the vuetify.ts file, you can modify how your application looks and feels to match your brand. If you'd like to learn more about themes and all the options you can provide, please check out the official Vuetify documentation.


In this article, we've gone through the concepts of layouts and theming in Vuetify 3. We've seen how layouts can reduce code duplication, and provide consistency across your application. We've looked at how Vuetify's theming features allow you to customize your application to match your brand's visual identity.

Understanding and utilizing these features effectively can significantly enhance the development experience and the end user's interaction with your application. Remember, a well-structured and visually appealing application not only attracts users, but also retains them. Happy coding!

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Improving the Performance of Vue 3 Applications Using v-memo and KeepAlive cover image

Improving the Performance of Vue 3 Applications Using v-memo and KeepAlive

Introduction When building a Vue application, you will encounter performance issues as your app grows, causing it to run slower than it should. Most performance problems in web apps arise from performing tasks repeatedly, even when there is no need to. For example, if you had a list of thousands of items, and had to re-render all items when a single thing changed, you'd quickly encounter performance problems. The ideal solution would be to update only what changes. For the most part, VueJS does this well, and Vue 3 came with many performance improvements out of the box. However, in more complex applications, we may need more fine-grained control over what gets re-rendered and what does not. In this article, we are going to look at how we can use v-memo`, and KeepAlive as solutions to performance problems in Vue applications. Two leading solutions to performance problems in Vue JS Memoization Memoization is like a superpower that helps your app remember things without having to constantly redo the same calculations, or processes over and over again. This allows the applications to perform faster and more efficiently, which is the ultimate goal for any web developer. Think about it; when you were a kid, your teachers always taught you to memorize things to save time. The same is valid for web applications. With memoization, they can remember the results of a calculation or process, so they don't have to waste time recalculating it every time they need the same result. For example, imagine you have a website that requires users to enter their birthdates to access certain content. If a user enters their birthdate for the first time, the application will calculate their age. With memoization, the next time the user comes to the website and enters their birthdate, the application can use the previously computed result instead of recalculating it from scratch. That's the beauty of memoization! It saves time and resources, and gives the user a faster and smoother experience. Try memoization if you want your web application to perform at its best. You'll be amazed at how much of a difference it makes! To take advantage of this in Vue, we'll be using the v-memo directive. More on this shortly. Caching Caching works by storing a copy of frequently used data and resources so that the application can access them quickly without having to go through the entire process of rendering and fetching the data from scratch. Think of it like this. When you visit a website for the first time, the browser has to fetch all the resources needed to display the page. But if you see the same website again, the browser can use the cached data, resulting in a faster and smoother experience for the user. We can take advantage of caching in our Vue applications using KeepAlive. Using v-memo V-memo was added in Vue 3.2 and, as far as I am aware, will not be back-ported to Vue 2. You can use v-memo by passing the directive to the element/component whose dependencies you want to memoize. In this case, this would be an element/component that would be computationally expensive to re-render each time a dependency changed. Here's an example of how you could use the v-memo directive: `html ` Note that it accepts an array of dependency values. And if every value in the array is the same as the last render, updates for the entire sub-tree will be skipped. The dependency values are those that's changes you'd like to be checked and memoized anytime they change. You can learn more about how v-memo works from the official Vue JS documentation. In this case, if the value of dependencyA or dependencyB changed, we would re-render the children of the div. However, if neither dependencyA nor dependencyB changed, we would skip the re-rendering process. This means that any computationally intensive tasks that were needed to occur in the re-render would not be triggered, and as a result, our application would perform better. Using KeepAlive Another alternative solution to performance issues is caching, where the KeepAlive option comes in. KeepAlive is a built-in vue component that allows us to cache component instances when dynamically switching between multiple components conditionally. Using KeepAlive in Vue 3 is straightforward. You can wrap the components you want to cache inside a KeepAlive component. Here's a sample code: `html import { ref, onMounted } from 'vue' import ComponentA from './ComponentA.vue' import ComponentB from './ComponentB.vue' const currentComponent = ref(ComponentA) onMounted(() => { setInterval(() => { currentComponent.value = currentComponent.value === ComponentA ? ComponentB : ComponentA }, 1000) }) ` In this example, we use the KeepAlive component to conditionally cache the ComponentA and ComponentB` instances, which are dynamically switched using the currentComponent reactive property. With the KeepAlive component, we can maintain the state of these components even when they are not active, which can lead to improved performance. Conclusion In this article, we talked about how we can improve the performance of our VueJS applications using memoization and caching. In the case of memoization, we can use the v-memo VueJS directive that was introduced in Vue 3.2. Alternatively, when we want to cache component instances, we can use the built-in Vue KeepAlive component. By utilizing both v-memo and KeepAlive, you can optimize the performance of your Vue 3 applications, resulting in faster and smoother user experiences. That being said, if you are looking to start a new Vue JS project and need help with how to structure your project, feel free to check out our Vue JS GitHub showcases, which showcase (no kidding) a mini-GitHub clone application built using Vue in different ways (e.g., using Nuxt, Quasar], Vite, etc.). Alternatively, if you are looking to start a new project without worrying about all the config required, you can check out the [Vue JS starter kit instead. Thanks for checking this out!...

Introducing the Vue 3 and XState kit for cover image

Introducing the Vue 3 and XState kit for is an open source community resource developed by This Dot Labs that provides code starter kits in a variety of web technologies, including React, Angular, Vue, etc. with the hope of enabling developers to bootstrap their projects quickly without having to spend time configuring tooling.* Intro Today, we’re delighted to announce a new starter kit featuring Vue and XState! In this blog post, we’ll dive into what’s included with the kit, how to get started using it, and what makes this kit unique. What’s included All of our kits strive to provide you with popular and reliable frameworks and libraries, along with recommended tooling all configured for you, and designed to help you spin your projects up faster. This kit includes: - Vue as the core JS framework - XState for managing our application’s state - CSS for styling - Cypress component testing - Vue Router to manage navigation between pages - Storybook for visual prototyping - ESlint and Prettier to lint and format your code How to get started using the kit To get started using this kit, we recommend the starter CLI tool. You can pass in the kit name directly, and the tool will guide you through naming your project, installing your dependencies, and running the app locally. Each kit comes with some sample components, so you can see how the provided tooling works together right away. `js // npm npm create @this-dot/create-starter -- --kit vue3-xstate-css // yarn yarn create @this-dot/create-starter --kit vue3-xstate-css ` Now let’s dive into some of the unique aspects of this kit. Vue 3 Vue is a very powerful JS framework. We chose to use Vue directly to highlight some of the features that make it such a joy to work with. One of our favorite features is Vue’s single file components (SFC). We can include our JavaScript, HTML, and CSS for each component all in the same file. This makes it easier to keep all related code directly next to each other, making it easier to debug issues, and allowing less file flipping. Since we’re in Vue 3, we’re also able to make use of the new Composition API, which looks and feels a bit more like vanilla JavaScript. You can import files, create functions, and do most anything you could in regular JavaScript within your component’s script tag. Any variable name you create is automatically available within your HTML template. Provide and Inject Another feature we got to use specifically in this starter kit is the new provide and inject functionality. You can read more details about this in the Vue docs, but this feature gives us a way to avoid prop drilling and provide values directly where they’re needed. In this starter kit, we include a “greeting” example, which makes an API call using a provided message, and shows the user a generated greeting. Initially, we provided this message as a prop through the router to the greeting component. This works, but it did require us to do a little more legwork to provide a fallback value, as well as needing our router to be aware of the prop. Using the provide / inject setup, we’re able to provide our message through the root level of the app, making it globally available to any child component. Then, when we need to use it in our `GreetView``` component, we inject the “key” we expect (our message), and it provides a built-in way for us to provide a default value to use as a fallback. Now our router doesn’t need to do any prop handling! And our component consistently works with the provided value or offers our default if something goes wrong. ` // src/main.ts const app = createApp(App); app.provide('query', 'from This Dot Labs!'); ` ` // src/views/GreetView.vue import { inject } from 'vue'; const providedQuery = inject('query', ''); const { state } = useMachine(greetMachine(providedQuery)); ` Using XState If you haven’t had a chance to look into XState before, we highly recommend checking out their documentation. They have a great intro to state machines and state charts that explains the concepts really well. One of the biggest mindset shifts that happens when you work with state machines is that they really help you think through how your application should work. State machines make you think explicitly through the different modes or “states” your application can get into, and what actions or side effects should happen in those states. By thinking directly through these, it helps you avoid sneaky edge cases and mutations you don’t expect. Difference between Context and State One of the parts that can be a little confusing at first is the difference between “state” and “context” when it comes to state machines. It can be easy to think of state as any values you store in your application that you want to persist between components or paths, and that can be accurate. However, with XState, a “state” is really more the idea of what configurations your app can be in. A common example is a music player. Your player can be in an “off” state, a “playing” state, or a “paused” state. These are all different modes, if you will, that can happen when your music player is interacted with. They can be values in a way, but they’re really more like the versions of your interface that you want to exist. You can transition between states, but when you go back to a specific state, you expect everything to behave the same way each time. States can trigger changes to your data or make API calls, but each time you enter or leave a state, you should be able to see the same actions occur. They give you stability and help prevent hidden edge cases. Values that we normally think of as state, things like strings or numbers or objects, that might change as your application is interacted with. These are the values that are stored in the “context” within XState. Our context values are the pieces of our application that are quantitative and that we expect will change as our application is working. ` export const counterMachine = createMachine( { id: 'Counter', initial: 'active', context: { count: 0, }, states: { active: { on: { INC: { actions: 'increment' }, DEC: { actions: 'decrement' }, RESET: { actions: 'reset' }, }, }, }, }, { actions: { increment: assign({ count: (context) => context.count + 1 }), decrement: assign({ count: (context) => context.count - 1 }), reset: assign({ count: (context) => (context.count = 0) }), }, } ); ` Declaring Actions and Services When we create a state machine with XState, it accepts two values- a config object and an options object. The config tells us what the machine does. This is where we define our states and transitions. In the options object, we can provide more information on how the machine does things, including logic for guards, actions, and effects. You can write your actions and effect logic within the state that initiates those calls, which can be great for getting the machine working in the beginning. However, it’s recommended to make those into named functions within the options object, making it easier to debug issues and improving the readability for how our machine works. Cypress Testing The last interesting thing we’d like to talk about is our setup for using component testing in Cypress! To use their component testing feature, they provide you with a mount command, which handles mounting your individual components onto their test runner so you can unit test them in isolation. While this works great out of the box, there’s also a way to customize the mount command if you need to! This is where you’d want to add any configuration your application needs to work properly in a testing setup. Things like routing and state management setups would get added to this function. Since we made use of Vue’s provide and inject functions, we needed to add the provided value to our `mount``` command in order for our greeting test to properly work. With that set up, we can allow it to provide our default empty string for tests that don’t need to worry about injecting a value (or when we specifically want to test our default value), and then we can inject the value we want in the tests that do need a specific value! ` // cypress/support/component.ts Cypress.Commands.add('mount', (component, options = {}) => { = || {}; = || {}; return mount(component, options); }); ` Conclusion We hope you enjoy using this starter kit! We’ve touched a bit on the benefits of using Vue 3, how XState keeps our application working as we expect, and how we can test our components with Cypress. Have a request or a question about a [] project? Reach out in the issues to make your requests or ask us your questions. The project is 100% open sourced so feel free to hop in and code with us!...

Converting Your Vue 2 Mixins into Composables Using the Composition API cover image

Converting Your Vue 2 Mixins into Composables Using the Composition API

Introduction There are two main ways to add additional functionality to our Vue components: mixins and composables. Adding mixins is similar to adding properties through the options API because you can add them by creating and "injecting" the properties into your Vue components. However, this comes with a couple of problems, the primary one being that mixins can lead to messy code and conflicting names. This is because they dump their properties into the component. With the composition API that was introduced in Vue 3, we can mitigate the problems we just mentioned by using composables. The Vue team created composables as a better alternative to mixins that allow for greater code reuse and structure. Additionally, they are designed specifically for each component, and don't cause any naming issues. We'll see more of this later on. In this article, we'll show you how to take your mixins and turn them into composables for even more functionality and ease of maintenance. The Structure of a Mixin Now, before we say goodbye to mixins and embrace composables, we need to understand the main differences between the two. In a way, a mixin is like a bag of magic tricks you can easily add to your components to do their magic. However, having all these tricks in her one backpack can make it challenging to keep track of what's happening, especially if multiple components use the same mixin. For example; if there was a data` property called `message` in our `mixin`, and a property by the same name (`message`) in our component, the mixin `message` would clash with our component's `message` (which would have been declared in the `data` section of our component). We will be going over composables next. But before we do, here's an example of a mixin: `javascript export const testMixin = { data() { return { message: "This message is in our mixin!" }; }, methods: { printMessage() { console.log('Message:',this.message); } } }; ` In this example, the mixin` contains a `data` function that returns an object with a `message` property, as well as a `printMessage` method that logs the given message to the console. To use this mixin in a component, the component would need to import the mixin, and add it to its `mixins` option: `javascript import testMixin from './testMixin.js'; export default { mixins: [testMixin], // The rest of your code }; ` Creating the Composable Composables are a new feature that was added within the Vue 3 composition API. Composables are also called composition functions. They allow us to add functionality to components in a cleaner, more maintainable way. Instead of merging properties and methods directly into the component like in a mixin, composables are explicitly tailored to the component, and return a value or a set of reactive properties. What this means is that with composables, you can group all the functionality that is related together (rather than in separate data`, `computed`, and `methods` sections). Again, you also don't have name collisions anymore because each call to a composable will give you an object that is not tied to your existing component. That means that you can resolve any naming conflicts at development time rather than breaking your application because of the same thing happening at runtime as is the case with mixins. Here is a simple composable that achieves the same result as the mixin we had created earlier. `javascript import { ref } from 'vue'; export const useMessage = () => { const message = ref("This message is in our composable!"); const printMessage = () => { console.log(message.value); }; return { message, printMessage }; }; ` Here's how the composable can be used in a .vue` file component: `js import { useMessage } from './useMessage'; const { message, printMessage } = useMessage(); // You can now access the message and printMessage in your template after extracting them from the useMessage composable ` With this setup, the component will access the reactive message` property and the `printMessage` method returned by the composable. Notice that composables allow us to add functionality to components more intuitively and easier to understand without the clutter of a mixin's structure. In general, the naming convention for composables is useX`, where `X` is the domain our composable is meant to represent. In this case, since our functionality is around our message, our composable is called `useMessage`. Conclusion In this article, we've explored how to convert Vue 2 mixins to Composition API composables. We started by understanding the structure of a simple Vue 2 mixin, which can make components challenging to maintain due to how they merge properties and methods directly into the component. Next, we looked at how to create a composable in Vue.js 3's Composition API, which allows us to add functionality to cleaner and more maintainable components. Composables are explicitly tailored to the component and return a value or a set of reactive properties, making them easier to understand and use in components. With this understanding of mixins and composables, developers can now confidently convert their Vue 2 mixins to Vue 3 composables, taking advantage of the new and improved functionality offered by the Composition API. By doing so, developers can create more manageable components to maintain, test, and debug, resulting in a better user experience for their applications. Also, if you are looking to start a new Vue JS project and are not sure of how to structure your project, feel free to check out our Vue JS GitHub showcases which showcase (no kidding) a mini-GitHub clone application built using Vue in different ways (eg. using Nuxt, Quasar, Vite, etc). Alternatively, if you are simply looking to start a new project without worrying about all the config required to do so, you can check out the Vue JS starter kit instead. Either way, thanks for stopping by!...

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....