Customization Guide

Learn how to customize and extend Gentelella Admin Template to match your brand and requirements

Table of contents

  1. Branding and Theming
    1. Color Scheme Customization
      1. Primary Colors
      2. Dark Theme Support
      3. Theme Toggle Implementation
    2. Logo and Branding
      1. Custom Logo Implementation
    3. Typography Customization
      1. Custom Font Integration
  2. Layout Customization
    1. Sidebar Modifications
      1. Collapsible Sidebar
      2. Custom Menu Items
    2. Header Customization
      1. Custom Navigation Bar
      2. Search Functionality
  3. Component Customization
    1. Custom Dashboard Widgets
      1. Widget Factory
      2. Widget Configuration
    2. Form Builder
      1. Dynamic Form Generator
  4. Advanced Customization
    1. Plugin System
      1. Plugin Architecture
      2. Example Plugin
  5. Next Steps

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

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">&times;</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


💡 Pro Tip: Start with small customizations and gradually build complexity. Always test your changes across different screen sizes and browsers to ensure compatibility.