A comprehensive guide for developers working with the CoreUI Free React Admin Template. This guide covers setup, development workflows, common patterns, and best practices.
- 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
- Node.js: Version 16 or higher (18+ recommended)
- npm: Version 7+ or yarn: Version 1.22+
- Git: For version control
- Visual Studio Code with extensions:
- ESLint
- Prettier
- ES7+ React/Redux/React-Native snippets
- Auto Import
- GitLens
- React Developer Tools browser extension
- Redux DevTools browser extension
- JavaScript ES6+ features (arrow functions, destructuring, modules)
- React fundamentals (components, hooks, props, state)
- HTML5 and CSS3
- Sass/SCSS basics
- Git version control
- Clone the repository (or download the source):
git clone https://github.com/coreui/coreui-free-react-admin-template.git
cd coreui-free-react-admin-template- Install dependencies:
npm install
# or
yarn install- Start the development server:
npm start
# or
yarn start- 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
| Command | Description |
|---|---|
npm start |
Start development server on port 3000 |
npm run build |
Build optimized production bundle |
npm run serve |
Preview production build locally |
npm run lint |
Run ESLint to check code quality |
IMPORTANT: Always edit source files in src/, never modify compiled files in build/.
src/
├── assets/ # Static assets (images, logos)
├── components/ # Reusable UI components
├── layout/ # Layout wrapper components
├── views/ # Page/route components
├── scss/ # Global styles and themes
├── App.jsx # Root component with routing
├── index.jsx # Application entry point
├── routes.js # Route definitions
├── _nav.jsx # Navigation menu configuration
└── store.js # Redux store setup
App.jsx: Main application component, routing setup, theme initializationindex.jsx: ReactDOM render, Provider setup, store connectionroutes.js: Array of route configurations for protected routes_nav.jsx: Navigation menu structure for sidebarstore.js: Redux store with global state (theme, sidebar)
- Start development server:
npm start - 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
Vite provides instant feedback:
- JavaScript/JSX changes: Component updates without page reload
- CSS/SCSS changes: Style updates without reload
- Configuration changes: Require server restart
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:3000Functional components with Hooks:
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { CCard, CCardBody, CCardHeader } from '@coreui/react'
/**
* UserCard component displays user information in a card format
* @param {Object} props - Component props
* @param {string} props.name - User's full name
* @param {string} props.email - User's email address
* @param {string} [props.avatar] - Optional avatar URL
*/
const UserCard = ({ name, email, avatar }) => {
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
// Component lifecycle logic
console.log('UserCard mounted')
return () => {
// Cleanup logic
console.log('UserCard unmounted')
}
}, [])
return (
<CCard>
<CCardHeader>{name}</CCardHeader>
<CCardBody>
{avatar && <img src={avatar} alt={name} />}
<p>{email}</p>
</CCardBody>
</CCard>
)
}
UserCard.propTypes = {
name: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
avatar: PropTypes.string,
}
UserCard.defaultProps = {
avatar: null,
}
export default UserCard- Keep components focused: One responsibility per component
- Use PropTypes: Validate all props for runtime type checking
- Provide default props: Define sensible defaults
- Add JSDoc comments: Document component purpose and props
- Extract logic: Use custom hooks for reusable logic
- Name meaningfully: Clear, descriptive component names
Extract reusable logic into custom hooks:
import { useState, useEffect } from 'react'
/**
* Custom hook for fetching data from an API
* @param {string} url - API endpoint URL
* @returns {Object} { data, loading, error }
*/
const useFetch = (url) => {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
const response = await fetch(url)
const json = await response.json()
setData(json)
setError(null)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { data, loading, error }
}
export default useFetchUsage:
const MyComponent = () => {
const { data, loading, error } = useFetch('/api/users')
if (loading) return <CSpinner />
if (error) return <div>Error: {error}</div>
return <div>{JSON.stringify(data)}</div>
}1. Create the page component in src/views/[feature]/:
// src/views/products/Products.js
import React from 'react'
import {
CCard,
CCardBody,
CCardHeader,
CCol,
CRow,
} from '@coreui/react'
const Products = () => {
return (
<CRow>
<CCol xs={12}>
<CCard className="mb-4">
<CCardHeader>
<strong>Products</strong>
</CCardHeader>
<CCardBody>
{/* Your content here */}
</CCardBody>
</CCard>
</CCol>
</CRow>
)
}
export default Products2. Add route to src/routes.js:
import React from 'react'
const Products = React.lazy(() => import('./views/products/Products'))
const routes = [
// ... existing routes
{ path: '/products', name: 'Products', element: Products },
]
export default routes3. Add navigation item to src/_nav.jsx (optional):
import { cilBasket } from '@coreui/icons'
export default [
// ... existing items
{
component: CNavItem,
name: 'Products',
to: '/products',
icon: <CIcon icon={cilBasket} customClassName="nav-icon" />,
},
]4. Test the page:
- Navigate to
http://localhost:3000/#/products - Check that navigation highlights correctly
- Verify breadcrumb displays properly
List Page:
const ListPage = () => {
const [items, setItems] = useState([])
useEffect(() => {
// Fetch items
}, [])
return (
<CCard>
<CCardHeader>Items</CCardHeader>
<CCardBody>
<CTable>
<CTableHead>
<CTableRow>
<CTableHeaderCell>Name</CTableHeaderCell>
<CTableHeaderCell>Status</CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
{items.map(item => (
<CTableRow key={item.id}>
<CTableDataCell>{item.name}</CTableDataCell>
<CTableDataCell>{item.status}</CTableDataCell>
</CTableRow>
))}
</CTableBody>
</CTable>
</CCardBody>
</CCard>
)
}Detail Page:
const DetailPage = () => {
const { id } = useParams()
const [item, setItem] = useState(null)
useEffect(() => {
// Fetch item by id
}, [id])
if (!item) return <CSpinner />
return (
<CCard>
<CCardHeader>{item.name}</CCardHeader>
<CCardBody>
<p>{item.description}</p>
</CCardBody>
</CCard>
)
}Routes are defined in src/routes.js:
const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: '/dashboard', name: 'Dashboard', element: Dashboard },
{ path: '/users', name: 'Users', element: Users, exact: true },
{ path: '/users/:id', name: 'User Details', element: UserDetail },
]With URL parameters:
// Route definition
{ path: '/products/:id', name: 'Product Detail', element: ProductDetail }
// Component usage
import { useParams } from 'react-router-dom'
const ProductDetail = () => {
const { id } = useParams()
return <div>Product ID: {id}</div>
}Using useNavigate hook:
import { useNavigate } from 'react-router-dom'
const MyComponent = () => {
const navigate = useNavigate()
const goToProducts = () => {
navigate('/products')
}
const goBack = () => {
navigate(-1) // Go back one page
}
return (
<>
<CButton onClick={goToProducts}>View Products</CButton>
<CButton onClick={goBack}>Go Back</CButton>
</>
)
}Add authentication logic in DefaultLayout.js:
const DefaultLayout = () => {
const isAuthenticated = useSelector((state) => state.isAuthenticated)
const navigate = useNavigate()
useEffect(() => {
if (!isAuthenticated) {
navigate('/login')
}
}, [isAuthenticated, navigate])
return (
<div>
{/* Layout content */}
</div>
)
}Reading state with useSelector:
import { useSelector } from 'react-redux'
const MyComponent = () => {
const theme = useSelector((state) => state.theme)
const sidebarShow = useSelector((state) => state.sidebarShow)
return <div>Theme: {theme}</div>
}Updating state with useDispatch:
import { useDispatch } from 'react-redux'
const MyComponent = () => {
const dispatch = useDispatch()
const handleClick = () => {
dispatch({ type: 'set', theme: 'dark' })
}
return <CButton onClick={handleClick}>Dark Mode</CButton>
}useState for simple state:
const [count, setCount] = useState(0)
const [isOpen, setIsOpen] = useState(false)
const [formData, setFormData] = useState({ name: '', email: '' })useReducer for complex state:
const initialState = { count: 0, step: 1 }
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step }
case 'decrement':
return { ...state, count: state.count - state.step }
case 'setStep':
return { ...state, step: action.payload }
default:
return state
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
<p>Count: {state.count}</p>
<CButton onClick={() => dispatch({ type: 'increment' })}>+</CButton>
<CButton onClick={() => dispatch({ type: 'decrement' })}>-</CButton>
</>
)
}ALWAYS use CoreUI React components from @coreui/react:
import {
CButton,
CCard,
CCardBody,
CCardHeader,
CCol,
CRow,
} from '@coreui/react'
const MyComponent = () => (
<CRow>
<CCol md={6}>
<CCard>
<CCardHeader>Card Title</CCardHeader>
<CCardBody>
<CButton color="primary">Click Me</CButton>
</CCardBody>
</CCard>
</CCol>
</CRow>
)Use Bootstrap utility classes for quick styling:
<CCard className="mb-4 shadow-sm">
<CCardBody className="p-4 d-flex justify-content-between align-items-center">
<span className="text-muted">Left</span>
<span className="fw-bold">Right</span>
</CCardBody>
</CCard>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
Component-level SCSS:
// MyComponent.js
import './MyComponent.scss'
const MyComponent = () => (
<div className="my-component">
<h1 className="my-component__title">Title</h1>
</div>
)// MyComponent.scss
.my-component {
padding: 1rem;
background-color: var(--cui-light);
&__title {
color: var(--cui-primary);
font-size: 1.5rem;
}
}Use CoreUI CSS variables for theming:
<div style={{
backgroundColor: 'var(--cui-primary)',
color: 'var(--cui-white)',
padding: 'var(--cui-spacer-3)',
}}>
Styled with CSS variables
</div>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
Use the classnames utility:
import classNames from 'classnames'
const MyComponent = ({ isActive, isPrimary }) => {
const buttonClass = classNames('btn', {
'btn-primary': isPrimary,
'btn-secondary': !isPrimary,
'active': isActive,
})
return <button className={buttonClass}>Button</button>
}import React, { useState } from 'react'
import {
CButton,
CCard,
CCardBody,
CCardHeader,
CCol,
CForm,
CFormInput,
CFormLabel,
CFormTextarea,
CRow,
} from '@coreui/react'
const MyForm = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
})
const [validated, setValidated] = useState(false)
const handleChange = (e) => {
const { name, value } = e.target
setFormData(prev => ({
...prev,
[name]: value,
}))
}
const handleSubmit = (event) => {
event.preventDefault()
const form = event.currentTarget
if (form.checkValidity() === false) {
event.stopPropagation()
setValidated(true)
return
}
// Submit form data
console.log('Form data:', formData)
}
return (
<CCard>
<CCardHeader>Contact Form</CCardHeader>
<CCardBody>
<CForm
className="row g-3"
noValidate
validated={validated}
onSubmit={handleSubmit}
>
<CCol md={6}>
<CFormLabel htmlFor="name">Name</CFormLabel>
<CFormInput
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
</CCol>
<CCol md={6}>
<CFormLabel htmlFor="email">Email</CFormLabel>
<CFormInput
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</CCol>
<CCol xs={12}>
<CFormLabel htmlFor="message">Message</CFormLabel>
<CFormTextarea
id="message"
name="message"
rows="3"
value={formData.message}
onChange={handleChange}
required
/>
</CCol>
<CCol xs={12}>
<CButton color="primary" type="submit">
Submit
</CButton>
</CCol>
</CForm>
</CCardBody>
</CCard>
)
}
export default MyFormHTML5 validation:
<CFormInput
type="email"
required
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
/>Custom validation:
const [errors, setErrors] = useState({})
const validate = () => {
const newErrors = {}
if (!formData.name) {
newErrors.name = 'Name is required'
}
if (!formData.email.includes('@')) {
newErrors.email = 'Invalid email address'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = (e) => {
e.preventDefault()
if (validate()) {
// Submit form
}
}import React from 'react'
import { CCard, CCardBody, CCardHeader } from '@coreui/react'
import { CChartLine } from '@coreui/react-chartjs'
const Dashboard = () => {
return (
<CCard>
<CCardHeader>Sales Overview</CCardHeader>
<CCardBody>
<CChartLine
data={{
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],
},
],
}}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
},
},
}}
style={{ height: '300px' }}
/>
</CCardBody>
</CCard>
)
}CoreUI provides React wrappers for Chart.js:
CChartLine- Line chartsCChartBar- Bar chartsCChartDoughnut- Doughnut chartsCChartPie- Pie chartsCChartPolarArea- Polar area chartsCChartRadar- Radar charts
The project uses ESLint with React and Prettier plugins:
# Check for issues
npm run lint
# Auto-fix issues (when possible)
npm run lint -- --fixJavaScript:
- No semicolons (enforced by Prettier)
- Single quotes for strings
- 2-space indentation
- Arrow functions preferred
- Destructuring when possible
React:
- Functional components only
- Hooks at top level
- PropTypes for all components
- Meaningful component names
File naming:
- PascalCase for components:
UserCard.js - camelCase for utilities:
dateHelper.js - kebab-case for styles:
user-card.scss
Recommended: Set up pre-commit hooks with Husky:
npm install --save-dev husky lint-staged
# Add to package.json
{
"lint-staged": {
"src/**/*.{js,jsx}": ["eslint --fix", "prettier --write"]
}
}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)
Test in modern browsers:
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Test at common breakpoints:
- Mobile: 375px, 414px
- Tablet: 768px, 1024px
- Desktop: 1366px, 1920px
Tip: Use Chrome DevTools device toolbar (Cmd/Ctrl + Shift + M)
Create optimized production build:
npm run buildOutput in build/ directory:
- Minified JavaScript bundles
- Extracted and minified CSS
- Optimized assets
- Source maps
Check bundle size:
npm run build
# Output shows:
# - Total bundle size
# - Individual chunk sizes
# - Asset sizesTest production build locally:
npm run serveOpens preview server at http://localhost:4173
Static hosting (builds to static files):
-
Netlify:
- Connect GitHub repository
- Build command:
npm run build - Publish directory:
build
-
Vercel:
- Import Git repository
- Framework preset: Vite
- Build command:
npm run build
-
GitHub Pages:
- Build locally:
npm run build - Push
build/folder togh-pagesbranch
- Build locally:
-
AWS S3 + CloudFront:
- Upload
build/contents to S3 bucket - Configure CloudFront distribution
- Upload
Create .env file (not committed to Git):
VITE_API_URL=https://api.example.com
VITE_APP_NAME=My AppUsage in code:
const apiUrl = import.meta.env.VITE_API_URLIMPORTANT: Only variables prefixed with VITE_ are exposed to the app.
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.mjsProblem: Module not found errors
Solution:
# Clear node_modules and reinstall
rm -rf node_modules package-lock.json
npm installProblem: 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 buildReact DevTools:
- Inspect component hierarchy
- View props and state
- Profile component renders
Redux DevTools:
- Inspect Redux state
- Time-travel debugging
- Action history
Console logging:
console.log('Variable:', variable)
console.table(arrayOfObjects)
console.error('Error:', error)React error boundaries:
class ErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError(error) {
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>
}
return this.props.children
}
}- Lazy load routes: Use React.lazy() for code splitting
- Memoize expensive calculations: Use useMemo()
- Optimize re-renders: Use React.memo() for pure components
- Virtualize long lists: Use libraries like react-window
- Optimize images: Use WebP format, lazy loading
- 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
- 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
- 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
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 React Admin Template. For additional questions, consult the CoreUI React Documentation or the React Documentation.
Happy coding! 🚀