v5.0

Bootstrap 5 responsive admin template

documented by Sean Ngu

Updated on: 27/March/2025
By: Sean Ngu

Thank you for purchasing my theme. I'd be glad to help you if you have any questions relating to this theme. No guarantees, but I'll do my best to assist.

Follow the following step to install the laravel in your localhost
You may refer to their official documentation for how to setup the development environment.
Setup Guide

<!-- run the following command --> 
cd /your-path-url/template_vue
npm install
npm run dev

<!-- browse the url --> 
http://localhost:5173/

Make sure node.js >= v22.x and npm >= v11.x has been installed on your localhost / server

File structure overview for Vue Version

vue_studio_v5.0/
├──template_vue_startup/      // version without demo pages
└──template_vue/              // version include all demo pages
    ├── .eslintrc.cjs
    ├── .gitignore
    ├── .vscode/
    ├── cypress/
    ├── cypress.json
    ├── env.d.ts
    ├── index.html
    ├── package.json
    ├── public/
    ├── README.md
    ├── src/
    │   ├── App.vue
    │   ├── assets/
    │   ├── components/
    │   ├── composables/
    │   ├── main.ts
    │   ├── router/
    │   ├── scss/
    │   ├── stores/
    │   └── views/
    ├── tsconfig.app.json
    ├── tsconfig.json
    ├── tsconfig-vite-config.json
    ├── tsconfig.vitest.json
    └── vite.config.ts

Below is the code from App.vue which include the app header, sidebar, content, footer and theme panel. You may remove the component if you are not using it.

<script setup lang="ts">
import { getCurrentInstance, onMounted } from 'vue';
import { RouterLink, RouterView } from 'vue-router';
import { useAppOptionStore } from '@/stores/app-option';
import { ProgressFinisher, useProgress } from '@marcoschulte/vue3-progress';
import AppSidebar from '@/components/app/Sidebar.vue';
import AppHeader from '@/components/app/Header.vue';
import AppTopNav from '@/components/app/TopNav.vue';
import AppFooter from '@/components/app/Footer.vue';
import AppThemePanel from '@/components/app/ThemePanel.vue';
import router from './router';

const appOption = useAppOptionStore();
const internalInstance = getCurrentInstance();

const progresses = [] as ProgressFinisher[];

router.beforeEach(async (to, from) => {
  progresses.push(useProgress().start());
  appOption.appSidebarMobileToggled = false;
  document.body.scrollTop = 0;
  document.documentElement.scrollTop = 0;
  
  var targetElm = [].slice.call(document.querySelectorAll('.app-sidebar .menu-submenu'));
  targetElm.map(function(elm) {
    elm.style.display = '';
  });
})
router.afterEach(async (to, from) => {
  progresses.pop()?.finish();
})

document.querySelector('body').classList.add('app-init');
</script>

<template>
  <div class="app" v-bind:class="{ 
    'app-header-menu-search-toggled': appOption.appHeaderSearchToggled,
    'app-sidebar-minified': appOption.appSidebarMinified,
    'app-sidebar-collapsed': appOption.appSidebarCollapsed,
    'app-sidebar-mobile-toggled': appOption.appSidebarMobileToggled,
    'app-sidebar-mobile-closed': appOption.appSidebarMobileClosed,
    'app-content-full-height': appOption.appContentFullHeight,
    'app-content-full-width': appOption.appSidebarHide,
    'app-with-top-nav': appOption.appTopNav,
    'app-without-sidebar': appOption.appSidebarHide,
    'app-without-header': appOption.appHeaderHide,
    'app-boxed-layout': appOption.appBoxedLayout,
    'app-footer-fixed': appOption.appFooterFixed,
  }">
    <vue3-progress-bar />
    <app-header v-if="!appOption.appHeaderHide" />
    <app-top-nav v-if="appOption.appTopNav" />
    <app-sidebar v-if="!appOption.appSidebarHide" />
    <div class="app-content" v-bind:class="appOption.appContentClass">
      <router-view></router-view>
    </div>
    <app-footer v-if="appOption.appFooter" />
    <app-theme-panel />
  </div>
</template>

List of components inside the components folder

/src/components/
├── app/
│   ├── Footer.vue
│   ├── Header.vue
│   ├── NavScrollTo.vue
│   ├── Sidebar.vue
│   ├── SidebarNav.vue
│   ├── TopNav.vue
│   ├── TopNavNav.vue
│   └── ThemePanel.vue
├── bootstrap/
│   ├── Card.vue
│   ├── CardBody.vue
│   ├── CardExpandToggler.vue
│   ├── CardFooter.vue
│   ├── CardGroup.vue
│   ├── CardHeader.vue
│   └── CardImgOverlay.vue
└── plugins/
    ├── Apexcharts.vue
    ├── Chartjs.vue
    ├── Datepicker.vue
    ├── Highlightjs.vue
    ├── QuillEditor.vue
    ├── TagsInput.vue
    ├── Typeahead.vue
    ├── VueSelect.vue
    └── VueTable.vue

This template used mitt as event bus to emit events between component. Emitter files can be found via /composables/useEmitter.ts

import useEmitter from '@/composables/useEmitter';

const emitter = useEmitter();

// emit event
emitter.emit('my-event', true);

// event listener
this.emitter.on('my-event', (evt) => {
  // do something
});

This template used pinia to create the store and share the states variable between the components. Store files can be found via /src/stores/

/src/stores/
├─ app-option.ts            // global app option states
├─ app-sidebar-menu.ts      // global app sidebar menu list
├─ app-top-nav-menu.ts      // global app top nav menu list
└─ app-variable.ts          // global app variable (fetched from css / font variables)

You can use the global app option from /stores/app-option.ts

import { useAppOptionStore } from '@/stores/app-option';

const appOption = useAppOptionStore();

export default {
  mounted() {
    // available app option
    appOption.appThemeClass= '';
    appOption.appBoxedLayout= true;
    appOption.appHeaderHide = true;
    appOption.appHeaderSearchToggled= true;
    appOption.appSidebarMinified = true;
    appOption.appSidebarMobileToggled = true;
    appOption.appSidebarMobileClosed = true;
    appOption.appSidebarHide = true;
    appOption.appContentFullHeight = true;
    appOption.appContentClass = '';
    appOption.appTopNav = true;
    appOption.appFooter = true;
    appOption.appFooterFixed = true;
    appOption.appThemePanelToggled = true;
    appOption.appVh100 = true;
  },
  beforeUnmount() {
    // set to default before leave the page
    appOption.appBoxedLayout = false;
  }
}

You can use the global app variables (css color / font family) from /stores/app-variable.ts

import { defineStore } from "pinia";

export function generateVariables() {
  return {
    font: {
      bodyFontFamily: (getComputedStyle(document.body).getPropertyValue('--bs-body-font-family')).trim(),
      bodyFontSize: (getComputedStyle(document.body).getPropertyValue('--bs-body-font-size')).trim(),
      bodyFontWeight: (getComputedStyle(document.body).getPropertyValue('--bs-body-font-weight')).trim(),
      bodyLineHeight: (getComputedStyle(document.body).getPropertyValue('--bs-body-line-height')).trim()
    },
    color: {
      theme: (getComputedStyle(document.body).getPropertyValue('--bs-theme')).trim(),
      themeRgb: (getComputedStyle(document.body).getPropertyValue('--bs-theme-rgb')).trim(),
      themeColor: (getComputedStyle(document.body).getPropertyValue('--bs-theme-color')).trim(),
      themeColorRgb: (getComputedStyle(document.body).getPropertyValue('--bs-theme-color-rgb')).trim(),
      
      default: (getComputedStyle(document.body).getPropertyValue('--bs-default')).trim(),
      defaultRgb: (getComputedStyle(document.body).getPropertyValue('--bs-default-rgb')).trim(),
      
      primary: (getComputedStyle(document.body).getPropertyValue('--bs-primary')).trim(),
      primaryRgb: (getComputedStyle(document.body).getPropertyValue('--bs-primary-rgb')).trim(),
      primaryBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-primary-bg-subtle')).trim(),
      primaryText: (getComputedStyle(document.body).getPropertyValue('--bs-primary-text')).trim(),
      primaryBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-primary-border-subtle')).trim(),
      
      secondary: (getComputedStyle(document.body).getPropertyValue('--bs-secondary')).trim(),
      secondaryRgb: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-rgb')).trim(),
      secondaryBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-bg-subtle')).trim(),
      secondaryText: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-text')).trim(),
      secondaryBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-border-subtle')).trim(),
      
      success: (getComputedStyle(document.body).getPropertyValue('--bs-success')).trim(),
      successRgb: (getComputedStyle(document.body).getPropertyValue('--bs-success-rgb')).trim(),
      successBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-success-bg-subtle')).trim(),
      successText: (getComputedStyle(document.body).getPropertyValue('--bs-success-text')).trim(),
      successBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-success-border-subtle')).trim(),
      
      warning: (getComputedStyle(document.body).getPropertyValue('--bs-warning')).trim(),
      warningRgb: (getComputedStyle(document.body).getPropertyValue('--bs-warning-rgb')).trim(),
      warningBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-warning-bg-subtle')).trim(),
      warningText: (getComputedStyle(document.body).getPropertyValue('--bs-warning-text')).trim(),
      warningBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-warning-border-subtle')).trim(),
      
      info: (getComputedStyle(document.body).getPropertyValue('--bs-info')).trim(),
      infoRgb: (getComputedStyle(document.body).getPropertyValue('--bs-info-rgb')).trim(),
      infoBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-info-bg-subtle')).trim(),
      infoText: (getComputedStyle(document.body).getPropertyValue('--bs-info-text')).trim(),
      infoBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-info-border-subtle')).trim(),
      
      danger: (getComputedStyle(document.body).getPropertyValue('--bs-danger')).trim(),
      dangerRgb: (getComputedStyle(document.body).getPropertyValue('--bs-danger-rgb')).trim(),
      dangerBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-danger-bg-subtle')).trim(),
      dangerText: (getComputedStyle(document.body).getPropertyValue('--bs-danger-text')).trim(),
      dangerBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-danger-border-subtle')).trim(),
      
      light: (getComputedStyle(document.body).getPropertyValue('--bs-light')).trim(),
      lightRgb: (getComputedStyle(document.body).getPropertyValue('--bs-light-rgb')).trim(),
      lightBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-light-bg-subtle')).trim(),
      lightText: (getComputedStyle(document.body).getPropertyValue('--bs-light-text')).trim(),
      lightBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-light-border-subtle')).trim(),
      
      dark: (getComputedStyle(document.body).getPropertyValue('--bs-dark')).trim(),
      darkRgb: (getComputedStyle(document.body).getPropertyValue('--bs-dark-rgb')).trim(),
      darkBgSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-dark-bg-subtle')).trim(),
      darkText: (getComputedStyle(document.body).getPropertyValue('--bs-dark-text')).trim(),
      darkBorderSubtle: (getComputedStyle(document.body).getPropertyValue('--bs-dark-border-subtle')).trim(),
      
      inverse: (getComputedStyle(document.body).getPropertyValue('--bs-inverse')).trim(),
      inverseRgb: (getComputedStyle(document.body).getPropertyValue('--bs-inverse-rgb')).trim(),
      
      white: (getComputedStyle(document.body).getPropertyValue('--bs-white')).trim(),
      whiteRgb: (getComputedStyle(document.body).getPropertyValue('--bs-white-rgb')).trim(),
      
      black: (getComputedStyle(document.body).getPropertyValue('--bs-black')).trim(),
      blackRgb: (getComputedStyle(document.body).getPropertyValue('--bs-black-rgb')).trim(),
      
      teal: (getComputedStyle(document.body).getPropertyValue('--bs-teal')).trim(),
      tealRgb: (getComputedStyle(document.body).getPropertyValue('--bs-teal-rgb')).trim(),
      
      indigo: (getComputedStyle(document.body).getPropertyValue('--bs-indigo')).trim(),
      indigoRgb: (getComputedStyle(document.body).getPropertyValue('--bs-indigo-rgb')).trim(),
      
      purple: (getComputedStyle(document.body).getPropertyValue('--bs-purple')).trim(),
      purpleRgb: (getComputedStyle(document.body).getPropertyValue('--bs-purple-rgb')).trim(),

      yellow: (getComputedStyle(document.body).getPropertyValue('--bs-yellow')).trim(),
      yellowRgb: (getComputedStyle(document.body).getPropertyValue('--bs-yellow-rgb')).trim(),
      
      pink: (getComputedStyle(document.body).getPropertyValue('--bs-pink')).trim(),
      pinkRgb: (getComputedStyle(document.body).getPropertyValue('--bs-pink-rgb')).trim(),
      
      cyan: (getComputedStyle(document.body).getPropertyValue('--bs-cyan')).trim(),
      cyanRgb: (getComputedStyle(document.body).getPropertyValue('--bs-cyan-rgb')).trim(),
      
      gray100: (getComputedStyle(document.body).getPropertyValue('--bs-gray-100')).trim(),
      gray200: (getComputedStyle(document.body).getPropertyValue('--bs-gray-200')).trim(),
      gray300: (getComputedStyle(document.body).getPropertyValue('--bs-gray-300')).trim(),
      gray400: (getComputedStyle(document.body).getPropertyValue('--bs-gray-400')).trim(),
      gray500: (getComputedStyle(document.body).getPropertyValue('--bs-gray-500')).trim(),
      gray600: (getComputedStyle(document.body).getPropertyValue('--bs-gray-600')).trim(),
      gray700: (getComputedStyle(document.body).getPropertyValue('--bs-gray-700')).trim(),
      gray800: (getComputedStyle(document.body).getPropertyValue('--bs-gray-800')).trim(),
      gray900: (getComputedStyle(document.body).getPropertyValue('--bs-gray-900')).trim(),
      gray100Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-100-rgb')).trim(),
      gray200Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-200-rgb')).trim(),
      gray300Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-300-rgb')).trim(),
      gray400Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-400-rgb')).trim(),
      gray500Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-500-rgb')).trim(),
      gray600Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-600-rgb')).trim(),
      gray700Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-700-rgb')).trim(),
      gray800Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-800-rgb')).trim(),
      gray900Rgb: (getComputedStyle(document.body).getPropertyValue('--bs-gray-900-rgb')).trim(),
      
      muted: (getComputedStyle(document.body).getPropertyValue('--bs-muted')).trim(),
      mutedRgb: (getComputedStyle(document.body).getPropertyValue('--bs-muted-rgb')).trim(),
      
      emphasisColor: (getComputedStyle(document.body).getPropertyValue('--bs-emphasis-color')).trim(),
      emphasisColorRgb: (getComputedStyle(document.body).getPropertyValue('--bs-emphasis-color-rgb')).trim(),
      
      bodyBg: (getComputedStyle(document.body).getPropertyValue('--bs-body-bg')).trim(),
      bodyBgRgb: (getComputedStyle(document.body).getPropertyValue('--bs-body-bg-rgb')).trim(),
      
      bodyColor: (getComputedStyle(document.body).getPropertyValue('--bs-body-color')).trim(),
      bodyColorRgb: (getComputedStyle(document.body).getPropertyValue('--bs-body-color-rgb')).trim(),
      
      componentBg: (getComputedStyle(document.body).getPropertyValue('--bs-component-bg')).trim(),
      componentBgRgb: (getComputedStyle(document.body).getPropertyValue('--bs-component-bg-rgb')).trim(),
      
      headingColor: (getComputedStyle(document.body).getPropertyValue('--bs-heading-color')).trim(),
      
      secondaryColor: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-color')).trim(),
      secondaryColorRgb: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-color-rgb')).trim(),
      secondaryBg: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-bg')).trim(),
      secondaryBgRgb: (getComputedStyle(document.body).getPropertyValue('--bs-secondary-bg-rgb')).trim(),
      
      tertiaryColor: (getComputedStyle(document.body).getPropertyValue('--bs-tertiary-color')).trim(),
      tertiaryColorRgb: (getComputedStyle(document.body).getPropertyValue('--bs-tertiary-color-rgb')).trim(),
      tertiaryBg: (getComputedStyle(document.body).getPropertyValue('--bs-tertiary-bg')).trim(),
      tertiaryBgRgb: (getComputedStyle(document.body).getPropertyValue('--bs-tertiary-bg-rgb')).trim(),
      
      linkColor: (getComputedStyle(document.body).getPropertyValue('--bs-link-color')).trim(),
      linkColorRgb: (getComputedStyle(document.body).getPropertyValue('--bs-link-color-rgb')).trim(),
      linkHoverColor: (getComputedStyle(document.body).getPropertyValue('--bs-link-hover-color')).trim(),
      linkHoverColorRgb: (getComputedStyle(document.body).getPropertyValue('--bs-link-hover-color-rgb')).trim(),
      
      borderColor: (getComputedStyle(document.body).getPropertyValue('--bs-border-color')).trim(),
      borderColorTranslucent: (getComputedStyle(document.body).getPropertyValue('--bs-border-color-translucent')).trim(),
    }
  };
}

export const useAppVariableStore = defineStore("appVariable", () => {
  return generateVariables();
});

Set the app sidebar menu list from from /stores/app-sidebar-menu.ts

// single level structure
{
  'url': '/',
  'icon': 'fa fa-bome',
  'text': 'Dashboard'
},
 
// multi level structure 
{
  'icon': 'fa fa-envelope',
  'text': 'Email',
  'children': [{
    'url': '/email/inbox',
    'action': 'Inbox',
    'text': 'Inbox'
  }, {
    'url': '/email/compose',
    'action': 'Compose',
    'text': 'Compose'
  }, {
    'url': '/email/detail',
    'action': 'Detail',
    'text': 'Detail'
  }]
}

The default theme color is set to the blue color. You may change it from /src/scss/_variables.scss

// LINE 92
$theme:        $blue !default;

Besides that, you can also set the theme color by adding the value theme-{ color-name } to /src/stores/app-option.ts

// LINE 7
appThemeClass: 'theme-blue',
***IMPORTANT***
You might need to remove the <app-theme-panel /> component from /src/App.vue.

This is because app-theme-panel will store a localStorage variable and append the theme class automatically when page load if you have select any theme color before from theme panel.

If you wish to enable the dark mode instead, you can easily do so by adding the data-bs-theme="dark" attribute from /template_vue/index.html. This will disable the default dark mode and enable the light mode theme.

Please note that once you add the data-bs-theme="dark" attribute, the dark mode theme will persist even if the user refreshes the page or navigates to a different page within the application.

Here's an example:

change from
<!DOCTYPE html>
<html lang="en">
</html>
to
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
</html>

With this example, the data-bs-theme="dark" attribute has been added to html tag. This will enable the dark mode to the entire page.

OR

If you want to apply dark / light mode to a single component only, you can add the data-bs-theme="dark|light" attribute to that component.

<div class="card" data-bs-theme="dark">
  <div class="card-body">
    <!-- your component content here -->
  </div>
</div>

To enable RTL mode, follow these steps:

  1. Navigate to the /src/scss/_variables.scss file in your code editor.
  2. Find the $enable-rtl variable and change its value to true:
    $enable-rtl: true;
    
    This will enable RTL mode for your application.

Global Variables

The /src/scss/_variables.scss file contains the variables that control the styles of your application.

To edit these variables, navigate to the /src/scss/_variables.scss file in your code editor and modify the values of the variables to suit your needs.

<!-- global variable -->
/src/scss/_variables.scss

Dark Mode Variables

The /src/scss/_variables-dark.scss file contains the variables that control the styles for dark mode. These variables are used in conjunction with the variables in the /src/scss/_variables.scss file to provide different values for light and dark modes.

<!-- dark mode variable -->
/src/scss/_variables-dark.scss

We have created the common re-usable card bootstrap component for this template. You may found the card component via /src/components/bootstrap/

//usage 
<card>
  <card-header>...</card-header>
  <card-body>...</card-body>
  <card-footer>...</card-footer>
</card>

You may use the default bootstrap data attribute like data-bs-toggle="dropdown" OR import the required modules from bootstrap.

// usage example
import { ScrollSpy } from 'bootstrap';

new ScrollSpy(document.body, {
  target: '#sidebar-bootstrap',
  offset: 200
})

I've used the following images, icons or other files as listed.

Plugins

  1. ApexCharts: https://apexcharts.com/
  2. Axios: https://github.com/axios/axios
  3. Bootstrap: http://getbootstrap.com/
  4. Bootstrap Icons: https://icons.getbootstrap.com/
  5. Chart.js: https://chartjs.org
  6. Datatables: https://datatables.net/
  7. FontAwesome: https://fontawesome.com/
  8. Fullcalendar: https://fullcalendar.io/
  9. Highlight.js: https://highlightjs.org/
  10. Iconify: https://icon-sets.iconify.design/
  11. jsvectormap: https://github.com/themustafaomar/jsvectormap
  12. lity: https://sorgalla.com/lity/
  13. maska: https://github.com/beholdr/maska
  14. masonry: https://masonry.desandro.com/
  15. mitt: https://github.com/developit/mitt
  16. moment: http://momentjs.com/
  17. Perfect Scrollbar: https://github.com/mercs600/vue3-perfect-scrollbar
  18. Picmo: https://picmojs.com/
  19. Pinia: https://pinia.vuejs.org/
  20. PhotoSwipe: https://photoswipe.com/
  21. Popper.js: https://popper.js.org/
  22. Vue.js: https://vuejs.org/
  23. Vue Colorpicker: https://caohenghu.github.io/vue-colorpicker/
  24. Vue Colorpicker: https://caohenghu.github.io/vue-colorpicker/
  25. Vue Countdown: https://fengyuanchen.github.io/vue-countdown/
  26. Vue Quill: https://vueup.github.io/vue-quill/
  27. Vue Select: https://vue-select.org/
  28. Vue 3 Datepicker: https://vue3datepicker.com/
  29. Vue 3 Google Map: https://github.com/inocan-group/vue3-google-map
  30. Vue 3 Progress: https://github.com/marcoschulte/vue3-progress
  31. Vue 3 Simple Typeahead: https://github.com/frikinside/vue3-simple-typeahead
  32. Vue 3 Tags Input: https://github.com/sipec/vue-tags-input
  33. Vue 3 Table Lite: https://github.com/linmasahiro/vue3-table-lite

Photos

  1. Unsplash: https://unsplash.com/
  2. Freepik: https://www.freepik.com/