Customization Guide
Learn how to customize and extend Gentelella Admin Template to match your brand and requirements
Table of contents
Branding and Theming
Color Scheme Customization
Primary Colors
Override Bootstrap variables in src/scss/variables.scss:
// Brand colors
$primary: #73879C; // Main brand color
$secondary: #6c757d; // Secondary color
$success: #26B99A; // Success actions
$info: #3498DB; // Information
$warning: #F39C12; // Warnings
$danger: #E74C3C; // Errors
// Sidebar colors
$sidebar-bg: #2A3F54;
$sidebar-text: #E7E7E7;
$sidebar-text-hover: #ffffff;
$sidebar-active-bg: $primary;
// Dashboard colors
$dashboard-bg: #F7F7F7;
$card-bg: #ffffff;
$card-border: #E6E9ED;
// Text colors
$text-primary: #73879C;
$text-secondary: #ABB1B7;
$text-dark: #566573;
Dark Theme Support
Create src/scss/themes/_dark.scss:
// Dark theme variables
[data-theme="dark"] {
--bs-body-bg: #1a1a1a;
--bs-body-color: #ffffff;
--bs-card-bg: #2d2d2d;
--bs-border-color: #404040;
// Sidebar dark theme
.left_col {
background: #0F1419;
.nav-link {
color: #CCCCCC;
&:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
}
&.active {
background: var(--bs-primary);
color: #ffffff;
}
}
}
// Cards and panels
.x_panel {
background: var(--bs-card-bg);
border: 1px solid var(--bs-border-color);
.x_title {
border-bottom: 1px solid var(--bs-border-color);
h2 {
color: var(--bs-body-color);
}
}
}
// Tables
.table {
--bs-table-bg: var(--bs-card-bg);
--bs-table-border-color: var(--bs-border-color);
color: var(--bs-body-color);
}
// Forms
.form-control {
background-color: #3d3d3d;
border-color: var(--bs-border-color);
color: var(--bs-body-color);
&:focus {
background-color: #4d4d4d;
border-color: var(--bs-primary);
}
}
}
Theme Toggle Implementation
// src/js/theme-toggle.js
class ThemeToggle {
constructor() {
this.theme = localStorage.getItem('theme') || 'light';
this.init();
}
init() {
// Apply saved theme
document.documentElement.setAttribute('data-theme', this.theme);
// Create toggle button
this.createToggleButton();
// Listen for toggle events
document.addEventListener('theme-toggle', this.toggle.bind(this));
}
createToggleButton() {
const button = document.createElement('button');
button.className = 'btn btn-outline-secondary theme-toggle';
button.innerHTML = this.theme === 'dark'
? '<i class="fa fa-sun"></i>'
: '<i class="fa fa-moon"></i>';
button.addEventListener('click', () => this.toggle());
// Add to navbar
const navbar = document.querySelector('.navbar-nav');
if (navbar) {
const li = document.createElement('li');
li.className = 'nav-item';
li.appendChild(button);
navbar.appendChild(li);
}
}
toggle() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', this.theme);
localStorage.setItem('theme', this.theme);
// Update button icon
const button = document.querySelector('.theme-toggle');
if (button) {
button.innerHTML = this.theme === 'dark'
? '<i class="fa fa-sun"></i>'
: '<i class="fa fa-moon"></i>';
}
// Trigger custom event
document.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme: this.theme }
}));
}
getTheme() {
return this.theme;
}
}
// Initialize theme toggle
new ThemeToggle();
Logo and Branding
Custom Logo Implementation
// src/scss/components/_logo.scss
.site_title {
display: flex;
align-items: center;
padding: 15px 20px;
color: $sidebar-text;
text-decoration: none;
.logo {
width: 32px;
height: 32px;
margin-right: 10px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.brand-text {
font-size: 18px;
font-weight: 600;
.brand-suffix {
font-size: 12px;
font-weight: 400;
opacity: 0.8;
display: block;
line-height: 1;
}
}
}
// Responsive logo
@media (max-width: 768px) {
.site_title {
.brand-text {
display: none;
}
}
}
<!-- Update logo in HTML files -->
<a href="index.html" class="site_title">
<div class="logo">
<img src="/images/logo.svg" alt="Your Brand">
</div>
<span class="brand-text">
Your Brand
<small class="brand-suffix">Admin Panel</small>
</span>
</a>
Typography Customization
Custom Font Integration
// src/scss/base/_typography.scss
// Import custom fonts
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
// Typography variables
$font-family-base: 'Inter', 'Segoe UI', Roboto, sans-serif;
$font-family-heading: 'Inter', 'Segoe UI', Roboto, sans-serif;
$font-family-monospace: 'SF Mono', Monaco, 'Cascadia Code', monospace;
// Font sizes
$font-size-xs: 0.75rem; // 12px
$font-size-sm: 0.875rem; // 14px
$font-size-base: 1rem; // 16px
$font-size-lg: 1.125rem; // 18px
$font-size-xl: 1.25rem; // 20px
// Font weights
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;
// Line heights
$line-height-tight: 1.25;
$line-height-normal: 1.5;
$line-height-relaxed: 1.75;
// Apply typography
body {
font-family: $font-family-base;
font-size: $font-size-base;
font-weight: $font-weight-normal;
line-height: $line-height-normal;
}
// Headings
h1, h2, h3, h4, h5, h6 {
font-family: $font-family-heading;
font-weight: $font-weight-semibold;
line-height: $line-height-tight;
margin-bottom: 0.5em;
}
h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.75rem; }
h4 { font-size: 1.5rem; }
h5 { font-size: 1.25rem; }
h6 { font-size: 1rem; }
// Code and monospace
code, pre {
font-family: $font-family-monospace;
}
Layout Customization
Sidebar Modifications
Collapsible Sidebar
// src/js/sidebar.js
class Sidebar {
constructor() {
this.sidebar = document.querySelector('.left_col');
this.mainContent = document.querySelector('.right_col');
this.toggleBtn = document.querySelector('.sidebar-toggle');
this.isCollapsed = localStorage.getItem('sidebar-collapsed') === 'true';
this.init();
}
init() {
// Apply saved state
if (this.isCollapsed) {
this.collapse();
}
// Create toggle button if it doesn't exist
if (!this.toggleBtn) {
this.createToggleButton();
}
// Add event listeners
this.toggleBtn?.addEventListener('click', () => this.toggle());
// Handle responsive behavior
this.handleResize();
window.addEventListener('resize', () => this.handleResize());
}
createToggleButton() {
const button = document.createElement('button');
button.className = 'btn btn-link sidebar-toggle';
button.innerHTML = '<i class="fa fa-bars"></i>';
// Add to navbar
const navbar = document.querySelector('.navbar');
if (navbar) {
navbar.insertBefore(button, navbar.firstChild);
}
this.toggleBtn = button;
}
toggle() {
if (this.isCollapsed) {
this.expand();
} else {
this.collapse();
}
}
collapse() {
this.sidebar?.classList.add('collapsed');
this.mainContent?.classList.add('sidebar-collapsed');
this.isCollapsed = true;
localStorage.setItem('sidebar-collapsed', 'true');
// Update toggle button icon
if (this.toggleBtn) {
this.toggleBtn.innerHTML = '<i class="fa fa-bars"></i>';
}
}
expand() {
this.sidebar?.classList.remove('collapsed');
this.mainContent?.classList.remove('sidebar-collapsed');
this.isCollapsed = false;
localStorage.setItem('sidebar-collapsed', 'false');
// Update toggle button icon
if (this.toggleBtn) {
this.toggleBtn.innerHTML = '<i class="fa fa-times"></i>';
}
}
handleResize() {
const width = window.innerWidth;
// Auto-collapse on mobile
if (width < 768) {
this.collapse();
} else if (width > 1200 && this.isCollapsed) {
this.expand();
}
}
}
// Initialize sidebar
new Sidebar();
// src/scss/components/_sidebar.scss
.left_col {
width: 230px;
transition: all 0.3s ease;
&.collapsed {
width: 70px;
.nav_title {
.brand-text {
display: none;
}
}
.main_menu_side {
.nav > li > a {
text-align: center;
padding: 12px 0;
.menu-text {
display: none;
}
.fa {
margin-right: 0;
}
}
.child_menu {
display: none !important;
}
}
}
}
.right_col {
margin-left: 230px;
transition: all 0.3s ease;
&.sidebar-collapsed {
margin-left: 70px;
}
}
@media (max-width: 768px) {
.left_col {
transform: translateX(-100%);
&.mobile-show {
transform: translateX(0);
}
}
.right_col {
margin-left: 0;
}
}
Custom Menu Items
// src/js/menu-builder.js
class MenuBuilder {
constructor(menuConfig) {
this.config = menuConfig;
this.menuContainer = document.querySelector('#sidebar-menu');
this.buildMenu();
}
buildMenu() {
if (!this.menuContainer) return;
this.menuContainer.innerHTML = '';
this.config.sections.forEach(section => {
const sectionElement = this.createSection(section);
this.menuContainer.appendChild(sectionElement);
});
}
createSection(section) {
const sectionDiv = document.createElement('div');
sectionDiv.className = 'menu_section';
if (section.title) {
const title = document.createElement('h3');
title.textContent = section.title;
sectionDiv.appendChild(title);
}
const menuList = document.createElement('ul');
menuList.className = 'nav side-menu';
section.items.forEach(item => {
const menuItem = this.createMenuItem(item);
menuList.appendChild(menuItem);
});
sectionDiv.appendChild(menuList);
return sectionDiv;
}
createMenuItem(item) {
const li = document.createElement('li');
const a = document.createElement('a');
// Set link properties
if (item.url) {
a.href = item.url;
}
// Add icon
if (item.icon) {
const icon = document.createElement('i');
icon.className = `fa fa-${item.icon}`;
a.appendChild(icon);
}
// Add text
const textSpan = document.createElement('span');
textSpan.className = 'menu-text';
textSpan.textContent = item.label;
a.appendChild(textSpan);
// Add submenu indicator
if (item.children && item.children.length > 0) {
const chevron = document.createElement('span');
chevron.className = 'fa fa-chevron-down';
a.appendChild(chevron);
// Create submenu
const submenu = this.createSubmenu(item.children);
li.appendChild(submenu);
}
// Add click handler for submenus
a.addEventListener('click', (e) => {
if (item.children && item.children.length > 0) {
e.preventDefault();
this.toggleSubmenu(li);
}
});
li.appendChild(a);
return li;
}
createSubmenu(items) {
const ul = document.createElement('ul');
ul.className = 'nav child_menu';
ul.style.display = 'none';
items.forEach(item => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = item.url || '#';
a.textContent = item.label;
li.appendChild(a);
ul.appendChild(li);
});
return ul;
}
toggleSubmenu(parentLi) {
const submenu = parentLi.querySelector('.child_menu');
const chevron = parentLi.querySelector('.fa-chevron-down, .fa-chevron-up');
if (submenu.style.display === 'none') {
submenu.style.display = 'block';
chevron.className = chevron.className.replace('chevron-down', 'chevron-up');
} else {
submenu.style.display = 'none';
chevron.className = chevron.className.replace('chevron-up', 'chevron-down');
}
}
}
// Menu configuration
const menuConfig = {
sections: [
{
title: 'General',
items: [
{
label: 'Dashboard',
icon: 'home',
children: [
{ label: 'Dashboard 1', url: 'index.html' },
{ label: 'Dashboard 2', url: 'index2.html' },
{ label: 'Dashboard 3', url: 'index3.html' }
]
},
{
label: 'Analytics',
icon: 'bar-chart-o',
url: 'analytics.html'
}
]
},
{
title: 'Forms',
items: [
{
label: 'Form Elements',
icon: 'edit',
url: 'form.html'
},
{
label: 'Form Validation',
icon: 'check-square-o',
url: 'form_validation.html'
}
]
}
]
};
// Initialize menu
new MenuBuilder(menuConfig);
Header Customization
Custom Navigation Bar
// src/scss/components/_navbar.scss
.nav_menu {
background: #ffffff;
border-bottom: 1px solid #E6E9ED;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
.navbar-nav {
align-items: center;
.nav-item {
margin: 0 5px;
.nav-link {
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s ease;
&:hover {
background: rgba(115, 135, 156, 0.1);
color: $primary;
}
}
// User dropdown
&.dropdown {
.dropdown-menu {
border: none;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-radius: 8px;
margin-top: 8px;
.dropdown-item {
padding: 12px 20px;
&:hover {
background: rgba(115, 135, 156, 0.1);
}
}
}
}
}
}
// Breadcrumb
.breadcrumb {
background: transparent;
margin: 0;
padding: 0;
.breadcrumb-item {
color: #566573;
&.active {
color: $primary;
font-weight: 500;
}
a {
color: #566573;
text-decoration: none;
&:hover {
color: $primary;
}
}
}
}
}
Search Functionality
// src/js/search.js
class GlobalSearch {
constructor() {
this.searchInput = document.getElementById('global-search');
this.searchResults = document.getElementById('search-results');
this.searchData = [];
this.init();
}
async init() {
if (!this.searchInput) return;
// Load search data
await this.loadSearchData();
// Add event listeners
this.searchInput.addEventListener('input',
this.debounce(this.handleSearch.bind(this), 300));
this.searchInput.addEventListener('focus', this.showResults.bind(this));
document.addEventListener('click', this.hideResults.bind(this));
}
async loadSearchData() {
// Load searchable content
this.searchData = [
{ title: 'Dashboard', url: 'index.html', category: 'Page' },
{ title: 'Form Elements', url: 'form.html', category: 'Page' },
{ title: 'Tables', url: 'tables.html', category: 'Page' },
{ title: 'Charts', url: 'chartjs.html', category: 'Page' },
// Add more searchable items
];
}
handleSearch(event) {
const query = event.target.value.toLowerCase().trim();
if (query.length < 2) {
this.hideResults();
return;
}
const results = this.searchData.filter(item =>
item.title.toLowerCase().includes(query) ||
item.category.toLowerCase().includes(query)
).slice(0, 10);
this.displayResults(results, query);
}
displayResults(results, query) {
if (!this.searchResults) return;
this.searchResults.innerHTML = '';
if (results.length === 0) {
const noResults = document.createElement('div');
noResults.className = 'search-no-results';
noResults.textContent = 'No results found';
this.searchResults.appendChild(noResults);
} else {
results.forEach(result => {
const item = this.createResultItem(result, query);
this.searchResults.appendChild(item);
});
}
this.showResults();
}
createResultItem(result, query) {
const item = document.createElement('a');
item.className = 'search-result-item';
item.href = result.url;
const title = document.createElement('div');
title.className = 'search-result-title';
title.innerHTML = this.highlightQuery(result.title, query);
const category = document.createElement('div');
category.className = 'search-result-category';
category.textContent = result.category;
item.appendChild(title);
item.appendChild(category);
return item;
}
highlightQuery(text, query) {
const regex = new RegExp(`(${query})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
showResults() {
if (this.searchResults) {
this.searchResults.style.display = 'block';
}
}
hideResults(event) {
if (event && this.searchInput.contains(event.target)) return;
if (this.searchResults) {
this.searchResults.style.display = 'none';
}
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
}
// Initialize search
new GlobalSearch();
Component Customization
Custom Dashboard Widgets
Widget Factory
// src/js/widgets/widget-factory.js
class WidgetFactory {
static createWidget(type, config) {
switch (type) {
case 'stat':
return new StatWidget(config);
case 'chart':
return new ChartWidget(config);
case 'list':
return new ListWidget(config);
case 'progress':
return new ProgressWidget(config);
default:
throw new Error(`Unknown widget type: ${type}`);
}
}
}
class BaseWidget {
constructor(config) {
this.config = config;
this.container = null;
}
render(container) {
this.container = container;
this.container.innerHTML = this.template();
this.afterRender();
}
template() {
return '<div>Base Widget</div>';
}
afterRender() {
// Override in subclasses
}
destroy() {
if (this.container) {
this.container.innerHTML = '';
}
}
}
class StatWidget extends BaseWidget {
template() {
return `
<div class="x_panel tile fixed_height_320">
<div class="x_title">
<h2>${this.config.title}</h2>
</div>
<div class="x_content">
<div class="widget-stat">
<div class="stat-icon">
<i class="fa fa-${this.config.icon}"></i>
</div>
<div class="stat-content">
<div class="stat-value">${this.config.value}</div>
<div class="stat-label">${this.config.label}</div>
${this.config.change ? `
<div class="stat-change ${this.config.change > 0 ? 'positive' : 'negative'}">
<i class="fa fa-${this.config.change > 0 ? 'arrow-up' : 'arrow-down'}"></i>
${Math.abs(this.config.change)}%
</div>
` : ''}
</div>
</div>
</div>
</div>
`;
}
}
class ChartWidget extends BaseWidget {
template() {
return `
<div class="x_panel">
<div class="x_title">
<h2>${this.config.title}</h2>
</div>
<div class="x_content">
<canvas id="chart-${this.config.id}" width="400" height="200"></canvas>
</div>
</div>
`;
}
afterRender() {
this.initChart();
}
async initChart() {
const { Chart } = await import('chart.js/auto');
const ctx = document.getElementById(`chart-${this.config.id}`);
new Chart(ctx, {
type: this.config.chartType || 'line',
data: this.config.data,
options: {
responsive: true,
maintainAspectRatio: false,
...this.config.options
}
});
}
}
Widget Configuration
// src/js/dashboard-config.js
const dashboardConfig = {
widgets: [
{
id: 'users-stat',
type: 'stat',
grid: { x: 0, y: 0, w: 3, h: 1 },
config: {
title: 'Total Users',
value: '2,564',
label: 'Active Users',
icon: 'users',
change: 12.5
}
},
{
id: 'revenue-stat',
type: 'stat',
grid: { x: 3, y: 0, w: 3, h: 1 },
config: {
title: 'Revenue',
value: '$52,147',
label: 'This Month',
icon: 'dollar',
change: -3.2
}
},
{
id: 'sales-chart',
type: 'chart',
grid: { x: 0, y: 1, w: 6, h: 2 },
config: {
title: 'Sales Overview',
chartType: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Sales',
data: [12, 19, 3, 5, 2, 3],
borderColor: '#73879C',
backgroundColor: 'rgba(115, 135, 156, 0.1)'
}]
}
}
}
]
};
// Initialize dashboard
class Dashboard {
constructor(config) {
this.config = config;
this.widgets = new Map();
this.container = document.getElementById('dashboard-container');
}
init() {
this.createGrid();
this.renderWidgets();
}
createGrid() {
this.container.className = 'dashboard-grid';
}
renderWidgets() {
this.config.widgets.forEach(widgetConfig => {
const widget = WidgetFactory.createWidget(
widgetConfig.type,
widgetConfig.config
);
const widgetContainer = this.createWidgetContainer(widgetConfig);
widget.render(widgetContainer);
this.widgets.set(widgetConfig.id, widget);
});
}
createWidgetContainer(config) {
const container = document.createElement('div');
container.className = 'dashboard-widget';
container.style.gridColumn = `${config.grid.x + 1} / ${config.grid.x + config.grid.w + 1}`;
container.style.gridRow = `${config.grid.y + 1} / ${config.grid.y + config.grid.h + 1}`;
this.container.appendChild(container);
return container;
}
}
// Initialize dashboard
new Dashboard(dashboardConfig).init();
Form Builder
Dynamic Form Generator
// src/js/forms/form-builder.js
class FormBuilder {
constructor(container, schema) {
this.container = container;
this.schema = schema;
this.fields = new Map();
}
build() {
const form = document.createElement('form');
form.className = 'dynamic-form';
form.setAttribute('data-validate', 'true');
this.schema.fields.forEach(fieldConfig => {
const field = this.createField(fieldConfig);
form.appendChild(field);
});
// Add submit button
if (this.schema.submit) {
const submitBtn = this.createSubmitButton(this.schema.submit);
form.appendChild(submitBtn);
}
this.container.appendChild(form);
this.initializeValidation();
return form;
}
createField(config) {
const fieldContainer = document.createElement('div');
fieldContainer.className = 'form-group row mb-3';
// Create label
if (config.label) {
const label = document.createElement('label');
label.className = 'col-form-label col-md-3 col-sm-3';
label.textContent = config.label;
label.setAttribute('for', config.name);
fieldContainer.appendChild(label);
}
// Create field wrapper
const fieldWrapper = document.createElement('div');
fieldWrapper.className = 'col-md-6 col-sm-6';
// Create field based on type
const field = this.createFieldByType(config);
fieldWrapper.appendChild(field);
// Add help text
if (config.help) {
const helpText = document.createElement('small');
helpText.className = 'form-text text-muted';
helpText.textContent = config.help;
fieldWrapper.appendChild(helpText);
}
fieldContainer.appendChild(fieldWrapper);
this.fields.set(config.name, field);
return fieldContainer;
}
createFieldByType(config) {
switch (config.type) {
case 'text':
case 'email':
case 'password':
case 'number':
return this.createInput(config);
case 'textarea':
return this.createTextarea(config);
case 'select':
return this.createSelect(config);
case 'checkbox':
return this.createCheckbox(config);
case 'radio':
return this.createRadioGroup(config);
case 'file':
return this.createFileInput(config);
case 'date':
return this.createDateInput(config);
default:
return this.createInput(config);
}
}
createInput(config) {
const input = document.createElement('input');
input.type = config.type || 'text';
input.name = config.name;
input.id = config.name;
input.className = 'form-control';
if (config.placeholder) input.placeholder = config.placeholder;
if (config.value) input.value = config.value;
if (config.required) input.required = true;
if (config.pattern) input.pattern = config.pattern;
if (config.min) input.min = config.min;
if (config.max) input.max = config.max;
return input;
}
createSelect(config) {
const select = document.createElement('select');
select.name = config.name;
select.id = config.name;
select.className = 'form-control';
if (config.multiple) {
select.multiple = true;
select.className += ' select2';
}
if (config.placeholder) {
const placeholderOption = document.createElement('option');
placeholderOption.value = '';
placeholderOption.textContent = config.placeholder;
placeholderOption.disabled = true;
placeholderOption.selected = true;
select.appendChild(placeholderOption);
}
if (config.options) {
config.options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.label;
if (option.selected) optionElement.selected = true;
select.appendChild(optionElement);
});
}
return select;
}
createTextarea(config) {
const textarea = document.createElement('textarea');
textarea.name = config.name;
textarea.id = config.name;
textarea.className = 'form-control';
textarea.rows = config.rows || 4;
if (config.placeholder) textarea.placeholder = config.placeholder;
if (config.value) textarea.value = config.value;
if (config.required) textarea.required = true;
return textarea;
}
getData() {
const data = {};
this.fields.forEach((field, name) => {
if (field.type === 'checkbox') {
data[name] = field.checked;
} else if (field.type === 'radio') {
const checked = document.querySelector(`input[name="${name}"]:checked`);
data[name] = checked ? checked.value : null;
} else {
data[name] = field.value;
}
});
return data;
}
setData(data) {
Object.entries(data).forEach(([name, value]) => {
const field = this.fields.get(name);
if (field) {
if (field.type === 'checkbox') {
field.checked = value;
} else {
field.value = value;
}
}
});
}
initializeValidation() {
// Initialize form validation if Parsley is available
if (window.Parsley) {
const form = this.container.querySelector('form');
$(form).parsley();
}
}
}
// Form schema example
const userFormSchema = {
fields: [
{
name: 'firstName',
type: 'text',
label: 'First Name',
placeholder: 'Enter first name',
required: true
},
{
name: 'email',
type: 'email',
label: 'Email Address',
placeholder: 'Enter email',
required: true
},
{
name: 'role',
type: 'select',
label: 'Role',
placeholder: 'Select role',
options: [
{ value: 'admin', label: 'Administrator' },
{ value: 'user', label: 'User' },
{ value: 'moderator', label: 'Moderator' }
],
required: true
},
{
name: 'bio',
type: 'textarea',
label: 'Biography',
placeholder: 'Tell us about yourself',
rows: 4
}
],
submit: {
text: 'Create User',
className: 'btn btn-primary'
}
};
// Usage
const formContainer = document.getElementById('form-container');
const formBuilder = new FormBuilder(formContainer, userFormSchema);
const form = formBuilder.build();
Advanced Customization
Plugin System
Plugin Architecture
// src/js/core/plugin-system.js
class PluginSystem {
constructor() {
this.plugins = new Map();
this.hooks = new Map();
}
registerPlugin(name, plugin) {
if (this.plugins.has(name)) {
console.warn(`Plugin ${name} already registered`);
return;
}
// Initialize plugin
if (typeof plugin.init === 'function') {
plugin.init(this);
}
this.plugins.set(name, plugin);
console.log(`Plugin ${name} registered successfully`);
}
getPlugin(name) {
return this.plugins.get(name);
}
addHook(hookName, callback, priority = 10) {
if (!this.hooks.has(hookName)) {
this.hooks.set(hookName, []);
}
this.hooks.get(hookName).push({ callback, priority });
// Sort by priority
this.hooks.get(hookName).sort((a, b) => a.priority - b.priority);
}
async executeHook(hookName, data = {}) {
if (!this.hooks.has(hookName)) {
return data;
}
const hooks = this.hooks.get(hookName);
let result = data;
for (const hook of hooks) {
try {
const hookResult = await hook.callback(result);
if (hookResult !== undefined) {
result = hookResult;
}
} catch (error) {
console.error(`Error in hook ${hookName}:`, error);
}
}
return result;
}
removeHook(hookName, callback) {
if (!this.hooks.has(hookName)) return;
const hooks = this.hooks.get(hookName);
const index = hooks.findIndex(hook => hook.callback === callback);
if (index > -1) {
hooks.splice(index, 1);
}
}
}
// Global plugin system instance
window.GentelellaPlugins = new PluginSystem();
Example Plugin
// src/js/plugins/notification-plugin.js
const NotificationPlugin = {
name: 'notifications',
init(pluginSystem) {
this.pluginSystem = pluginSystem;
this.notifications = [];
this.container = null;
this.createContainer();
this.bindHooks();
},
createContainer() {
this.container = document.createElement('div');
this.container.id = 'notification-container';
this.container.className = 'notification-container';
document.body.appendChild(this.container);
},
bindHooks() {
// Hook into form submissions
this.pluginSystem.addHook('form.submit.success', (data) => {
this.show('Form submitted successfully!', 'success');
return data;
});
this.pluginSystem.addHook('form.submit.error', (data) => {
this.show('Error submitting form', 'error');
return data;
});
},
show(message, type = 'info', duration = 5000) {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<i class="fa fa-${this.getIcon(type)}"></i>
<span>${message}</span>
<button class="notification-close">×</button>
</div>
`;
// Add close functionality
const closeBtn = notification.querySelector('.notification-close');
closeBtn.addEventListener('click', () => this.remove(notification));
// Auto remove after duration
setTimeout(() => this.remove(notification), duration);
this.container.appendChild(notification);
this.notifications.push(notification);
// Animate in
requestAnimationFrame(() => {
notification.classList.add('notification-show');
});
},
remove(notification) {
notification.classList.add('notification-hide');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
const index = this.notifications.indexOf(notification);
if (index > -1) {
this.notifications.splice(index, 1);
}
}, 300);
},
getIcon(type) {
const icons = {
success: 'check-circle',
error: 'exclamation-circle',
warning: 'exclamation-triangle',
info: 'info-circle'
};
return icons[type] || icons.info;
}
};
// Register plugin
window.GentelellaPlugins.registerPlugin('notifications', NotificationPlugin);
Next Steps
- API Integration - Connect with backend APIs
- Security Guide - Implement security best practices
- Testing Guide - Test your customizations
💡 Pro Tip: Start with small customizations and gradually build complexity. Always test your changes across different screen sizes and browsers to ensure compatibility.