CoreUI Free Vue Admin Template - Development Guide
April 1, 2026 · View on GitHub
A comprehensive guide for developers working with the CoreUI Free Vue Admin Template. This guide covers setup, development workflows, common patterns, and best practices.
Table of Contents
- Prerequisites
- Getting Started
- Project Structure
- Development Workflow
- Creating Components
- Adding New Pages
- Working with Routes
- State Management
- Styling Components
- Working with Forms
- Data Visualization
- Code Quality
- Testing
- Build and Deployment
- Troubleshooting
- Best Practices
Prerequisites
Required Software
- Node.js: Version 16 or higher (18+ recommended)
- npm: Version 7+ or yarn: Version 1.22+
- Git: For version control
Recommended Tools
- Visual Studio Code with extensions:
- Volar (Vue Language Features)
- ESLint
- Prettier
- Auto Import
- GitLens
- Vue DevTools browser extension
Knowledge Requirements
- JavaScript ES6+ features (arrow functions, destructuring, modules)
- Vue 3 fundamentals (Composition API, reactive, ref, computed)
- HTML5 and CSS3
- Sass/SCSS basics
- Git version control
Getting Started
Installation
- Clone the repository (or download the source):
git clone https://github.com/coreui/coreui-free-vue-admin-template.git
cd coreui-free-vue-admin-template
- Install dependencies:
npm install
# or
yarn install
- Start the development server:
npm run dev
# or
yarn dev
- Open your browser to http://localhost:3000
The development server includes:
- Hot Module Replacement (HMR) for instant updates
- Automatic browser refresh on file changes
- Error overlay for compile-time and runtime errors
Available Scripts
| Command | Description |
|---|---|
npm run dev | Start development server on port 3000 |
npm run build | Build optimized production bundle |
npm run preview | Preview production build locally |
npm run lint | Run ESLint to check code quality |
Project Structure
Source Code Organization
IMPORTANT: Always edit source files in src/, never modify compiled files in dist/.
src/
├── assets/ # Static assets (images, logos)
├── components/ # Reusable UI components
├── layouts/ # Layout wrapper components
├── views/ # Page/route components
├── router/ # Router configuration
├── stores/ # Pinia stores (optional)
├── scss/ # Global styles and themes
├── App.vue # Root component
├── main.js # Application entry point
└── _nav.js # Navigation menu configuration
Key Files
App.vue: Main application component with router-viewmain.js: Vue app creation, plugins, and mountingrouter/index.js: Route definitions and configuration_nav.js: Navigation menu structure for sidebar
Development Workflow
Daily Development Process
- Start development server:
npm run dev - Make changes to source files in
src/ - View changes instantly in browser (HMR)
- Check console for errors or warnings
- Run linter before committing:
npm run lint - Test in both themes (light and dark)
- Commit changes with conventional commit messages
Hot Module Replacement (HMR)
Vite provides instant feedback:
- Vue SFC changes: Component updates without page reload
- CSS/SCSS changes: Style updates without reload
- Configuration changes: Require server restart
Development Server Features
Port: 3000 (configurable in vite.config.mjs)
Features:
- Fast cold start (~500ms)
- Instant HMR (<50ms)
- Error overlay with stack traces
- Network access for mobile testing
Access from mobile:
# Find your local IP
# Windows: ipconfig
# Mac/Linux: ifconfig
# Access from mobile device:
http://192.168.1.X:3000
Creating Components
Component Structure
Composition API with script setup:
<template>
<CCard>
<CCardHeader>{{ name }}</CCardHeader>
<CCardBody>
<img v-if="avatar" :src="avatar" :alt="name" />
<p>{{ email }}</p>
</CCardBody>
</CCard>
</template>
<script setup>
/**
* UserCard component displays user information in a card format
*/
import { defineProps } from 'vue'
const props = defineProps({
/** User's full name */
name: {
type: String,
required: true,
},
/** User's email address */
email: {
type: String,
required: true,
},
/** Optional avatar URL */
avatar: {
type: String,
default: null,
},
})
</script>
<style scoped>
/* Component-specific styles */
</style>
Component Best Practices
- Keep components focused: One responsibility per component
- Use prop validation: Define types and required props
- Add JSDoc comments: Document component purpose and props
- Extract logic: Use composables for reusable logic
- Name meaningfully: Clear, descriptive component names
Composables (Custom Hooks)
Extract reusable logic into composables:
// composables/useFetch.js
import { ref, watch } from 'vue'
/**
* Custom composable for fetching data from an API
* @param {String} url - API endpoint URL
* @returns {Object} { data, loading, error, refetch }
*/
export function useFetch(url) {
const data = ref(null)
const loading = ref(true)
const error = ref(null)
const fetchData = async () => {
try {
loading.value = true
const response = await fetch(url)
data.value = await response.json()
error.value = null
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
watch(() => url, fetchData, { immediate: true })
return { data, loading, error, refetch: fetchData }
}
Usage:
<script setup>
import { useFetch } from '@/composables/useFetch'
const { data, loading, error } = useFetch('/api/users')
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>{{ data }}</div>
</template>
Adding New Pages
Step-by-Step Process
1. Create the page component in src/views/[feature]/:
<!-- src/views/products/Products.vue -->
<template>
<CRow>
<CCol :xs="12">
<CCard class="mb-4">
<CCardHeader>
<strong>Products</strong>
</CCardHeader>
<CCardBody>
<!-- Your content here -->
</CCardBody>
</CCard>
</CCol>
</CRow>
</template>
<script setup>
// Component logic
</script>
2. Add route to src/router/index.js:
{
path: '/products',
name: 'Products',
component: () => import('@/views/products/Products.vue'),
}
3. Add navigation item to src/_nav.js (optional):
{
component: 'CNavItem',
name: 'Products',
to: '/products',
icon: 'cil-basket',
}
4. Test the page:
- Navigate to
http://localhost:3000/#/products - Check that navigation highlights correctly
- Verify breadcrumb displays properly
Page Templates
List Page:
<template>
<CCard>
<CCardHeader>Items</CCardHeader>
<CCardBody>
<CTable>
<CTableHead>
<CTableRow>
<CTableHeaderCell>Name</CTableHeaderCell>
<CTableHeaderCell>Status</CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
<CTableRow v-for="item in items" :key="item.id">
<CTableDataCell>{{ item.name }}</CTableDataCell>
<CTableDataCell>{{ item.status }}</CTableDataCell>
</CTableRow>
</CTableBody>
</CTable>
</CCardBody>
</CCard>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const items = ref([])
onMounted(async () => {
// Fetch items
})
</script>
Detail Page:
<template>
<CSpinner v-if="!item" />
<CCard v-else>
<CCardHeader>{{ item.name }}</CCardHeader>
<CCardBody>
<p>{{ item.description }}</p>
</CCardBody>
</CCard>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const item = ref(null)
onMounted(async () => {
// Fetch item by route.params.id
})
</script>
Working with Routes
Route Configuration
Routes are defined in src/router/index.js:
const routes = [
{
path: '/',
redirect: '/dashboard',
name: 'Home',
component: DefaultLayout,
children: [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/Dashboard.vue'),
},
],
},
]
Dynamic Routes
With URL parameters:
// Route definition
{
path: '/products/:id',
name: 'Product Detail',
component: () => import('@/views/products/ProductDetail.vue'),
}
// Component usage
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const productId = route.params.id
</script>
Programmatic Navigation
Using useRouter hook:
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const goToProducts = () => {
router.push('/products')
}
const goBack = () => {
router.back()
}
</script>
<template>
<CButton @click="goToProducts">View Products</CButton>
<CButton @click="goBack">Go Back</CButton>
</template>
Protected Routes
Add authentication logic in router/index.js:
router.beforeEach((to, from, next) => {
const isAuthenticated = localStorage.getItem('token')
if (to.meta.requiresAuth && !isAuthenticated) {
next('/pages/login')
} else {
next()
}
})
State Management
Using Pinia
Creating a store:
// stores/main.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useStore = defineStore('main', () => {
// State
const sidebarVisible = ref(true)
const theme = ref('light')
// Getters
const isDark = computed(() => theme.value === 'dark')
// Actions
function toggleSidebar() {
sidebarVisible.value = !sidebarVisible.value
}
function setTheme(newTheme) {
theme.value = newTheme
}
return { sidebarVisible, theme, isDark, toggleSidebar, setTheme }
})
Using the store:
<script setup>
import { useStore } from '@/stores/main'
const store = useStore()
// Read state
console.log(store.sidebarVisible)
console.log(store.isDark)
// Call actions
store.toggleSidebar()
store.setTheme('dark')
</script>
Local Component State
ref for simple state:
<script setup>
import { ref } from 'vue'
const count = ref(0)
const isOpen = ref(false)
const formData = ref({ name: '', email: '' })
</script>
reactive for complex state:
<script setup>
import { reactive } from 'vue'
const state = reactive({
count: 0,
step: 1,
history: [],
})
</script>
Styling Components
Using CoreUI Components
ALWAYS use CoreUI Vue components from @coreui/vue:
<template>
<CRow>
<CCol :md="6">
<CCard>
<CCardHeader>Card Title</CCardHeader>
<CCardBody>
<CButton color="primary">Click Me</CButton>
</CCardBody>
</CCard>
</CCol>
</CRow>
</template>
Bootstrap Utilities
Use Bootstrap utility classes for quick styling:
<template>
<CCard class="mb-4 shadow-sm">
<CCardBody class="p-4 d-flex justify-content-between align-items-center">
<span class="text-muted">Left</span>
<span class="fw-bold">Right</span>
</CCardBody>
</CCard>
</template>
Common utilities:
- Spacing:
m-3,mt-2,mb-4,p-3,px-4,py-2 - Display:
d-flex,d-none,d-block,d-inline - Flexbox:
justify-content-between,align-items-center - Text:
text-center,text-muted,fw-bold,fs-5
Custom Styles
Component-level SCSS (scoped):
<template>
<div class="my-component">
<h1 class="my-component__title">Title</h1>
</div>
</template>
<style scoped lang="scss">
.my-component {
padding: 1rem;
background-color: var(--cui-light);
&__title {
color: var(--cui-primary);
font-size: 1.5rem;
}
}
</style>
CSS Custom Properties
Use CoreUI CSS variables for theming:
<template>
<div :style="{
backgroundColor: 'var(--cui-primary)',
color: 'var(--cui-white)',
padding: 'var(--cui-spacer-3)',
}">
Styled with CSS variables
</div>
</template>
Common variables:
- Colors:
--cui-primary,--cui-secondary,--cui-success,--cui-danger - Background:
--cui-body-bg,--cui-light,--cui-dark - Text:
--cui-body-color,--cui-text-muted - Spacing:
--cui-spacer-1through--cui-spacer-5
Conditional Styling
Use class or style bindings:
<template>
<button
:class="['btn', { 'btn-primary': isPrimary, 'btn-secondary': !isPrimary, 'active': isActive }]"
>
Button
</button>
</template>
<script setup>
import { ref } from 'vue'
const isPrimary = ref(true)
const isActive = ref(false)
</script>
Working with Forms
Form Component Example
<template>
<CCard>
<CCardHeader>Contact Form</CCardHeader>
<CCardBody>
<CForm
class="row g-3"
:noValidate="true"
:validated="validated"
@submit.prevent="handleSubmit"
>
<CCol :md="6">
<CFormLabel for="name">Name</CFormLabel>
<CFormInput
type="text"
id="name"
v-model="formData.name"
required
/>
</CCol>
<CCol :md="6">
<CFormLabel for="email">Email</CFormLabel>
<CFormInput
type="email"
id="email"
v-model="formData.email"
required
/>
</CCol>
<CCol :xs="12">
<CFormLabel for="message">Message</CFormLabel>
<CFormTextarea
id="message"
:rows="3"
v-model="formData.message"
required
/>
</CCol>
<CCol :xs="12">
<CButton color="primary" type="submit">
Submit
</CButton>
</CCol>
</CForm>
</CCardBody>
</CCard>
</template>
<script setup>
import { ref } from 'vue'
const formData = ref({
name: '',
email: '',
message: '',
})
const validated = ref(false)
const handleSubmit = (event) => {
const form = event.target
if (!form.checkValidity()) {
validated.value = true
return
}
// Submit form data
console.log('Form data:', formData.value)
}
</script>
Form Validation
HTML5 validation:
<CFormInput
type="email"
required
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
/>
Custom validation:
<script setup>
import { ref } from 'vue'
const errors = ref({})
const validate = () => {
const newErrors = {}
if (!formData.value.name) {
newErrors.name = 'Name is required'
}
if (!formData.value.email.includes('@')) {
newErrors.email = 'Invalid email address'
}
errors.value = newErrors
return Object.keys(newErrors).length === 0
}
const handleSubmit = () => {
if (validate()) {
// Submit form
}
}
</script>
Data Visualization
Using Chart.js with CoreUI
<template>
<CCard>
<CCardHeader>Sales Overview</CCardHeader>
<CCardBody>
<CChartLine
:data="chartData"
:options="chartOptions"
style="height: 300px"
/>
</CCardBody>
</CCard>
</template>
<script setup>
import { CChartLine } from '@coreui/vue-chartjs'
const chartData = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: 'Sales 2024',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
borderColor: 'rgba(220, 53, 69, 1)',
data: [40, 20, 12, 39, 10, 40, 39],
},
],
}
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
},
},
}
</script>
Chart Types
CoreUI provides Vue wrappers for Chart.js:
CChartLine- Line chartsCChartBar- Bar chartsCChartDoughnut- Doughnut chartsCChartPie- Pie chartsCChartPolarArea- Polar area chartsCChartRadar- Radar charts
Code Quality
ESLint Configuration
The project uses ESLint with Vue and Prettier plugins:
# Check for issues
npm run lint
# Auto-fix issues (when possible)
npm run lint -- --fix
Code Style Guidelines
JavaScript:
- No semicolons (enforced by Prettier)
- Single quotes for strings
- 2-space indentation
- Arrow functions preferred
- Destructuring when possible
Vue:
- Composition API with script setup
- Props defined with defineProps()
- Meaningful component names
- Scoped styles
File naming:
- PascalCase for components:
UserCard.vue - camelCase for utilities:
dateHelper.js - kebab-case for styles:
user-card.scss
Pre-commit Checks
Recommended: Set up pre-commit hooks with Husky:
npm install --save-dev husky lint-staged
# Add to package.json
{
"lint-staged": {
"src/**/*.{js,vue}": ["eslint --fix", "prettier --write"]
}
}
Testing
Manual Testing Checklist
Before committing changes:
- Test in both light and dark themes
- Test responsive design (mobile, tablet, desktop)
- Check browser console for errors
- Verify all links and navigation work
- Test form validation and submission
- Check accessibility (keyboard navigation, screen readers)
Browser Testing
Test in modern browsers:
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Responsive Testing
Test at common breakpoints:
- Mobile: 375px, 414px
- Tablet: 768px, 1024px
- Desktop: 1366px, 1920px
Tip: Use Chrome DevTools device toolbar (Cmd/Ctrl + Shift + M)
Build and Deployment
Production Build
Create optimized production build:
npm run build
Output in dist/ directory:
- Minified JavaScript bundles
- Extracted and minified CSS
- Optimized assets
- Source maps
Build Analysis
Check bundle size:
npm run build
# Output shows:
# - Total bundle size
# - Individual chunk sizes
# - Asset sizes
Preview Production Build
Test production build locally:
npm run preview
Opens preview server at http://localhost:4173
Deployment Platforms
Static hosting (builds to static files):
-
Netlify:
- Connect GitHub repository
- Build command:
npm run build - Publish directory:
dist
-
Vercel:
- Import Git repository
- Framework preset: Vite
- Build command:
npm run build
-
GitHub Pages:
- Build locally:
npm run build - Push
dist/folder togh-pagesbranch
- Build locally:
-
AWS S3 + CloudFront:
- Upload
dist/contents to S3 bucket - Configure CloudFront distribution
- Upload
Environment Variables
Create .env file (not committed to Git):
VITE_API_URL=https://api.example.com
VITE_APP_NAME=My App
Usage in code:
const apiUrl = import.meta.env.VITE_API_URL
IMPORTANT: Only variables prefixed with VITE_ are exposed to the app.
Troubleshooting
Common Issues
Problem: Port 3000 already in use
Solution:
# Kill process on port 3000
# Mac/Linux:
lsof -ti:3000 | xargs kill
# Windows:
netstat -ano | findstr :3000
taskkill /PID <PID> /F
# Or change port in vite.config.mjs
Problem: Module not found errors
Solution:
# Clear node_modules and reinstall
rm -rf node_modules package-lock.json
npm install
Problem: Styles not updating
Solution:
- Clear browser cache
- Hard refresh (Cmd/Ctrl + Shift + R)
- Restart dev server
Problem: HMR not working
Solution:
- Check file is saved
- Restart dev server
- Check for syntax errors in console
Problem: Build fails with memory error
Solution:
# Increase Node memory limit
NODE_OPTIONS="--max-old-space-size=4096" npm run build
Debugging Tips
Vue DevTools:
- Inspect component hierarchy
- View props and state
- Profile component renders
- Inspect Pinia stores
Console logging:
console.log('Variable:', variable)
console.table(arrayOfObjects)
console.error('Error:', error)
Best Practices
Performance
- Lazy load routes: Use dynamic imports for code splitting
- Use computed: For derived values instead of methods
- Optimize re-renders: Use v-memo for expensive renders
- Virtualize long lists: Use libraries like vue-virtual-scroller
- Optimize images: Use WebP format, lazy loading
Accessibility
- Semantic HTML: Use proper heading hierarchy (h1-h6)
- ARIA labels: Add aria-label for icon buttons
- Keyboard navigation: Ensure all interactive elements are keyboard-accessible
- Color contrast: Meet WCAG AA standards (4.5:1 for text)
- Form labels: Associate all form inputs with labels
Security
- Validate input: Sanitize user input before rendering
- Use HTTPS: Always serve over HTTPS in production
- Content Security Policy: Configure CSP headers
- Dependency audits: Run
npm auditregularly - Environment variables: Never commit secrets to Git
Code Organization
- Single Responsibility: One component does one thing well
- DRY Principle: Don't Repeat Yourself - extract reusable code
- Consistent naming: Follow naming conventions throughout
- File organization: Group related files together
- Documentation: Comment complex logic and add JSDoc
Git Workflow
Commit messages (Conventional Commits):
feat: add user profile page
fix: resolve navigation bug on mobile
docs: update README with deployment instructions
style: format code with Prettier
refactor: extract form validation logic
test: add tests for UserCard component
chore: update dependencies
Branch naming:
feature/user-profile
fix/navigation-bug
refactor/form-validation
docs/deployment-guide
This guide covers the essential workflows and patterns for developing with the CoreUI Free Vue Admin Template. For additional questions, consult the CoreUI Vue Documentation or the Vue 3 Documentation.
Happy coding! 🚀