Skip to content

themeix/ghost-dynamic-dropdown-mega-menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 

Repository files navigation

ThemeixMenu - Ghost Dynamic Dropdown & Mega Menu

A sophisticated JavaScript solution for creating dynamic dropdown and mega menus for Ghost CMS. This module provides flexible menu configuration, responsive design, accessibility features, and extensive customization options.

Version

Current Version: 2.0.0
Release Date: July 9, 2026
License: MIT License
Author: Themeix (https://themeix.com)
Repository: https://github.com/themeix/ghost-dynamic-dropdown-mega-menu

Installation

Basic Setup

  1. Include the JavaScript and CSS files in your Ghost theme:
<!-- In your default.hbs -->
<link rel="stylesheet" href="{{asset 'css/themeix-menu.css'}}">
<script src="{{asset 'js/themeix-menu.js'}}"></script>
  1. Initialize the menu system:
// Initialize with default settings
ThemeixMenu.init();

Advanced Setup with Configuration

// Initialize with custom configuration
ThemeixMenu.init({
    targetElement: '.gh-navigation-menu',
    mobileBreakpoint: 768,
    animationDuration: 300,
    closeOnClickOutside: true,
    closeOnEscape: true,
    keyboardNavigation: true,
    mouseDelay: 150,
    childPrefix: '-',
    debug: false
});

Core Configuration Options

Default Configuration

const defaultConfig = {
    targetElement: '.gh-navigation-menu',      // CSS selector for navigation element
    mobileBreakpoint: 768,                     // Pixel width for mobile switch
    animationDuration: 300,                    // Animation duration in milliseconds
    closeOnClickOutside: true,                 // Close menus when clicking outside
    closeOnEscape: true,                       // Close menus on Escape key press
    keyboardNavigation: true,                  // Enable keyboard navigation
    mouseDelay: 150,                           // Delay before hover activation (ms)
    childPrefix: '-',                          // Prefix for nested menu items
    jsonConfigPath: null,                      // Path to external JSON config
    preferCodeInjection: true,                 // Prefer Code Injection config over JSON file
    defaultMenuSettings: {                     // Default settings for menu items
        columns: 3,                            // Number of columns in mega menu
        width: 'auto',                         // Menu width
        alignment: 'left',                     // Menu alignment
        animation: 'slide'                     // Animation type
    },
    autoInjectClasses: true,                   // Automatically inject CSS classes
    autoInjectId: true,                        // Automatically inject element ID
    debug: false                               // Enable verbose logging
};

Configuration Priorities

The configuration system follows this priority order:

  1. Code Injection (Highest Priority) - window.themeixMenuConfig
  2. External JSON File - Loaded from jsonConfigPath
  3. Initialization Config - Passed to ThemeixMenu.init()
  4. Default Configuration - Built-in defaults

Menu Structure

Ghost Navigation Integration

The menu system integrates with Ghost's navigation structure using dash prefixes to indicate nesting levels:

Home
Products
- Software
-- Applications
--- Mobile Apps
- Hardware
Services
About Us

Each dash prefix represents one nesting level:

  • No dash: Top-level menu item
  • - : First-level submenu
  • -- : Second-level submenu
  • --- : Third-level submenu

Menu Item Types

  1. Link: Simple menu item without children
  2. Dropdown: Menu item with basic dropdown submenu
  3. Mega: Large multi-column menu with grouped content

JSON Configuration

Complete JSON Configuration Example

// In Ghost Code Injection (Site Footer)
<script>
window.themeixMenuConfig = {
    useGhostChildren: false,  // Use only JSON, ignore Ghost navigation
    globalSettings: {
        mobileBreakpoint: 768,
        animationDuration: 300,
        defaultMenuSettings: {
            columns: 4,
            width: '900px',
            alignment: 'center',
            animation: 'slide'
        }
    },
    menus: [
        {
            match: {
                url: '/products',
                title: 'Products',
                pattern: '^/product.*'
            },
            type: 'mega',
            icon: 'star',
            badge: 'New',
            settings: {
                columns: 3,
                width: '1000px',
                alignment: 'center',
                animation: 'slide'
            },
            groups: [
                {
                    title: 'Software',
                    links: [
                        {
                            title: 'Applications',
                            url: '/products/applications',
                            icon: 'book',
                            badge: 'Popular',
                            description: 'Our main applications'
                        },
                        {
                            title: 'Mobile Apps',
                            url: '/products/mobile',
                            icon: 'search',
                            description: 'iOS and Android apps'
                        }
                    ]
                },
                {
                    title: 'Hardware',
                    links: [
                        {
                            title: 'Devices',
                            url: '/products/devices',
                            icon: 'settings'
                        }
                    ]
                }
            ]
        },
        {
            match: { url: '/services' },
            type: 'dropdown',
            settings: {
                width: '300px',
                alignment: 'right',
                animation: 'fade'
            }
        }
    ]
};
</script>

JSON Configuration Properties

Top-Level Properties

  • useGhostChildren (boolean): Whether to merge with Ghost navigation or use only JSON
  • globalSettings (object): Global settings applied to all menus
  • menus (array): Array of menu configuration objects

Menu Item Properties

  • match (object): Criteria for matching Ghost navigation items
    • url (string): Match by URL path
    • title (string): Match by menu item title
    • label (string): Match by menu item label
    • pattern (string): Regular expression pattern for URL matching
  • type (string): Menu type - 'link', 'dropdown', or 'mega'
  • icon (string|object): Icon configuration
  • badge (string): Badge text
  • description (string): Menu item description
  • settings (object): Menu-specific settings
  • groups (array): Group configuration for mega menus

Icon System

Built-in SVG Icons

The menu system includes several built-in SVG icons:

const availableIcons = [
    'book',       // Documentation/book icon
    'megaphone',  // Announcement/marketing icon
    'palette',    // Design/creative icon
    'star',       // Rating/favorite icon
    'heart',      // Like/love icon
    'search',     // Search functionality icon
    'user',       // User/account icon
    'settings'    // Settings/configuration icon
];

Icon Configuration Options

Simple String (Built-in Icon)

{
    icon: 'star'
}

Sprite Icon

{
    icon: {
        sprite: '/assets/icons/sprite.png',
        width: '20px',
        height: '20px',
        x: '0px',
        y: '0px',
        spriteSize: 'auto'
    }
}

Image Icon

{
    icon: {
        image: '/assets/icons/custom-icon.png',
        width: '16px',
        height: '16px',
        alt: 'Custom Icon'
    }
}

CSS Variables

Menu Container Variables

:root {
    --tdgm-menu-width: 100%;           /* Main menu width */
    --tdgm-menu-gap: 2rem;             /* Gap between menu items */
    --tdgm-menu-radius: 8px;           /* Border radius */
    --tdgm-menu-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);  /* Box shadow */
    --tdgm-menu-background: #ffffff;   /* Background color */
    --tdgm-menu-color: #333333;        /* Text color */
    --tdgm-menu-hover-color: #666666;  /* Hover text color */
    --tdgm-menu-active-color: #000000; /* Active text color */
    
    /* Spacing */
    --menu-padding: 0.75rem 1rem;      /* Menu item padding */
    --menu-font-size: inherit;         /* Font size */
    --menu-font-weight: 500;           /* Font weight */
    --menu-border: 1px solid #e5e5e5;  /* Border styling */
    --menu-transition: all 0.3s ease;  /* Transition effect */
    
    /* Gaps */
    --tdgm-menu-gap-items: 0.5rem;     /* Gap between items */
    --tdgm-menu-gap-header: 1.5rem;    /* Gap for headers */
    --tdgm-menu-gap-group: 2rem;       /* Gap between groups */
}

Badge Variables

:root {
    --tdgm-badge-background: #ff6b6b;  /* Badge background */
    --tdgm-badge-color: #ffffff;       /* Badge text color */
    --tdgm-badge-padding: 0.25rem 0.5rem;  /* Badge padding */
    --tdgm-badge-radius: 4px;          /* Badge border radius */
    --tdgm-badge-font-size: 0.75rem;   /* Badge font size */
}

Icon Variables

:root {
    --tdgm-icon-size: 1.25rem;         /* Icon size */
    --tdgm-icon-gap: 0.5rem;           /* Gap between icon and text */
    --tdgm-icon-color: inherit;        /* Icon color */
    --tdgm-icon-hover-color: inherit;  /* Icon hover color */
}

Submenu Variables

:root {
    --tdgm-dropdown-width: 250px;      /* Dropdown width */
    --tdgm-mega-width: 700px;          /* Mega menu width */
    --tdgm-submenu-background: #f8f8f8;  /* Submenu background */
    --tdgm-submenu-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);  /* Submenu shadow */
    --tdgm-submenu-border-radius: 8px;  /* Submenu border radius */
    --tdgm-submenu-padding: 1rem;      /* Submenu padding */
    --tdgm-submenu-hover-background: #f8f9fa;  /* Hover background */
    --tdgm-submenu-border: 1px solid #e5e5e5;  /* Submenu border */
    --tdgm-submenu-link-padding: 0.5rem 0.75rem;  /* Link padding */
    --tdgm-submenu-link-gap: 0.25rem;  /* Link gap */
    --tdgm-submenu-hover-color: var(--ghost-accent-color);  /* Hover color */
}

Arrow Variables

:root {
    --tdgm-arrow-size: 0.3em;          /* Arrow size */
    --tdgm-arrow-width: 0.3em;         /* Arrow width */
    --tdgm-arrow-color: currentColor;  /* Arrow color */
    --tdgm-arrow-hover-color: inherit; /* Arrow hover color */
    --tdgm-arrow-transform: rotate(180deg);  /* Arrow rotation */
    --tdgm-arrow-transition: transform 0.3s ease;  /* Arrow transition */
}

Mobile Menu Variables

:root {
    --tdgm-mobile-menu-background: #ffffff;  /* Mobile menu background */
    --tdgm-mobile-menu-width: 300px;         /* Mobile menu width */
    --tdgm-mobile-menu-padding: 1rem;        /* Mobile menu padding */
    --tdgm-mobile-transition: all 0.3s ease; /* Mobile transition */
}

Animation Types

The menu system supports four animation types:

1. Fade Animation

settings: {
    animation: 'fade'
}

Smooth fade-in/fade-out effect with opacity transitions.

2. Slide Animation

settings: {
    animation: 'slide'
}

Slides submenu down from top with vertical translation.

3. Scale Animation

settings: {
    animation: 'scale'
}

Scales submenu from 0.95 to 1.0 with scaling effect.

4. Flip Animation

settings: {
    animation: 'flip'
}

3D flip animation using perspective transforms.

Alignment Options

Left Alignment

settings: {
    alignment: 'left'
}

Aligns submenu to the left edge of the parent item.

Center Alignment

settings: {
    alignment: 'center'
}

Centers submenu relative to the viewport (uses fixed positioning).

Right Alignment

settings: {
    alignment: 'right'
}

Aligns submenu to the right edge of the parent item.

Mega Menu Configuration

Mega Menu Structure

{
    type: 'mega',
    settings: {
        columns: 4,              // Number of columns
        width: '1200px',         // Total width
        alignment: 'center'      // Alignment
    },
    groups: [
        {
            title: 'Group Title',
            links: [
                {
                    title: 'Link Title',
                    url: '/link-url',
                    icon: 'icon-name',
                    badge: 'Badge',
                    description: 'Link description'
                }
            ]
        }
    ]
}

Column Layouts

The system supports responsive column layouts:

/* 2 columns */
.mega-2-columns .themeix-mega-group {
    flex: 0 0 calc(50% - 1rem);
}

/* 3 columns */
.mega-3-columns .themeix-mega-group {
    flex: 0 0 calc(33.333% - 1.33rem);
}

/* 4 columns */
.mega-4-columns .themeix-mega-group {
    flex: 0 0 calc(25% - 1.5rem);
}

/* 5 columns */
.mega-5-columns .themeix-mega-group {
    flex: 0 0 calc(20% - 1.6rem);
}

API Methods

Initialization

ThemeixMenu.init(config)

Initialize the menu system with optional configuration.

ThemeixMenu.init({
    targetElement: '.gh-navigation-menu',
    mobileBreakpoint: 768,
    debug: true
});

ThemeixMenu.destroy()

Destroy the menu system and clean up event listeners.

ThemeixMenu.destroy();

ThemeixMenu.refresh()

Re-initialize the menu system (useful for dynamic content).

ThemeixMenu.refresh();

Menu Control

ThemeixMenu.open(menuId)

Open a specific menu by ID.

ThemeixMenu.open('menu-0');

ThemeixMenu.close(menuId)

Close a specific menu by ID, or all menus if no ID provided.

// Close specific menu
ThemeixMenu.close('menu-0');

// Close all menus
ThemeixMenu.close();

Event Management

ThemeixMenu.on(eventName, callback)

Subscribe to menu events.

ThemeixMenu.on('menu:init', (data) => {
    console.log('Menu initialized:', data);
});

ThemeixMenu.off(eventName, callback)

Unsubscribe from menu events.

ThemeixMenu.off('menu:init', callback);

ThemeixMenu.emit(eventName, data)

Emit custom events (mainly for internal use).

ThemeixMenu.emit('custom:event', { data: 'value' });

Information Methods

ThemeixMenu.getConfig()

Get current configuration.

const config = ThemeixMenu.getConfig();
console.log('Current config:', config);

ThemeixMenu.getMenus()

Get all menu items.

const menus = ThemeixMenu.getMenus();
console.log('All menus:', menus);

ThemeixMenu.getMenu(menuId)

Get a specific menu by ID.

const menu = ThemeixMenu.getMenu('menu-0');
console.log('Menu:', menu);

Events

Available Events

  • menu:init: Fired when menu system is initialized
  • Custom events can be added via the on() method

Event Usage Example

// Listen for menu initialization
ThemeixMenu.on('menu:init', (data) => {
    console.log('Menu initialized with', data.menus.length, 'items');
});

// Custom event handling
ThemeixMenu.on('menu:opened', (menuId) => {
    console.log('Menu opened:', menuId);
});

Accessibility Features

Keyboard Navigation

The menu system supports comprehensive keyboard navigation:

  • Arrow Down: Move focus to next item
  • Arrow Up: Move focus to previous item
  • Arrow Right: Open submenu
  • Arrow Left: Close submenu
  • Escape: Close all open menus

ARIA Attributes

Automatic ARIA attribute management:

  • aria-haspopup: Indicates menu has popup content
  • aria-expanded: Indicates popup state
  • aria-label: Provides descriptive labels
<a href="/products" aria-haspopup="true" aria-expanded="false" aria-label="Products">
    Products
</a>

Focus Management

The system manages focus states for keyboard accessibility:

// Focus can be managed through keyboard events
document.addEventListener('keydown', handleKeyboard);

Responsive Design

Mobile Breakpoint

The menu system switches between desktop and mobile modes at the configured breakpoint:

{
    mobileBreakpoint: 768  // Switch to mobile at 768px
}

Mobile Behavior

On mobile devices:

  • Hover interactions are replaced with click/tap
  • Submenus are positioned relatively
  • Menus take full width
  • Touch events are optimized to prevent double-tap delays

Mobile CSS Adjustments

@media (max-width: 767px) {
    .themeix-menu-container {
        display: none;
    }
    
    .themeix-submenu {
        position: relative !important;
        width: 100% !important;
        left: 0 !important;
        right: 0 !important;
        transform: none !important;
        margin: 0 !important;
        box-shadow: none !important;
    }
    
    .themeix-mega-groups {
        flex-direction: column;
        gap: 1rem;
        padding: 0;
    }
    
    .themeix-mega-group {
        min-width: 100% !important;
        max-width: 100%;
    }
}

Performance Optimization

Memory Management

The menu system uses several optimization techniques:

  1. WeakMap for Timer Storage: Prevents memory leaks from hover timers
  2. Map for Menu Lookup: O(1) menu item retrieval instead of O(n) array search
  3. Regex Compilation: Compiled once during initialization instead of per-item
// Efficient memory usage
const hoverTimers = new WeakMap();
let menusById = new Map();
let childPrefixRegex = null;  // Compiled once

Event Delegation

Efficient event handling through delegation and proper cleanup:

// Event listeners are properly managed
function destroy() {
    document.removeEventListener('click', handleClickOutside);
    document.removeEventListener('keydown', handleKeyboard);
    // Clean up all listeners
}

Lazy Loading

Configuration is loaded asynchronously when needed:

async function loadConfiguration() {
    // Priority 1: Code Injection
    if (window.themeixMenuConfig) {
        return window.themeixMenuConfig;
    }
    
    // Priority 2: JSON file
    if (config.jsonConfigPath) {
        return await loadJSONConfig(config.jsonConfigPath);
    }
    
    return null;
}

Browser Compatibility

Modern Browsers

  • Chrome/Edge (latest)
  • Firefox (latest)
  • Safari (latest)
  • Mobile browsers (iOS Safari, Chrome Mobile)

Required Features

  • ES6+ JavaScript support
  • CSS Grid and Flexbox
  • CSS Custom Properties (variables)
  • DOM Level 3 Events

Legacy Browser Support

For older browsers, you may need polyfills for:

  • Promise (for async/await)
  • WeakMap (for timer storage)
  • CSS Custom Properties

Troubleshooting

Debug Mode

Enable debug mode for detailed logging:

ThemeixMenu.init({
    debug: true
});

Common Issues

Menu Not Initializing

Problem: Menu doesn't appear or work Solution:

  • Check that the target element exists: document.querySelector('.gh-navigation-menu')
  • Verify CSS is loaded properly
  • Check browser console for errors

Submenus Not Opening

Problem: Submenus don't open on hover/click Solution:

  • Verify menu items have children (using dash prefixes)
  • Check that childPrefix matches your Ghost navigation structure
  • Ensure CSS variables are properly defined

Mobile Menu Issues

Problem: Mobile menu doesn't work correctly Solution:

  • Check mobileBreakpoint setting
  • Verify touch events are working
  • Check that mobile-specific CSS is loaded

Configuration Not Applied

Problem: JSON configuration doesn't affect menu Solution:

  • Check configuration priority (Code Injection > JSON file > init config)
  • Verify JSON structure is correct
  • Enable debug mode to see configuration loading

Advanced Usage

Dynamic Menu Updates

// Update menu configuration dynamically
function updateMenuConfiguration(newConfig) {
    ThemeixMenu.destroy();
    ThemeixMenu.init(newConfig);
}

Custom Event Handling

// Listen for menu state changes
ThemeixMenu.on('menu:init', handleMenuInit);
ThemeixMenu.on('menu:opened', handleMenuOpen);
ThemeixMenu.on('menu:closed', handleMenuClose);

function handleMenuInit(data) {
    console.log('Menu system initialized');
    // Perform initialization actions
}

function handleMenuOpen(menuId) {
    console.log('Menu opened:', menuId);
    // Track analytics or perform other actions
}

function handleMenuClose(menuId) {
    console.log('Menu closed:', menuId);
    // Clean up resources
}

Integration with Other Systems

// Integrate with analytics
ThemeixMenu.on('menu:opened', (menuId) => {
    if (typeof gtag !== 'undefined') {
        gtag('event', 'menu_open', {
            'menu_id': menuId
        });
    }
});

// Integrate with state management
if (typeof store !== 'undefined') {
    ThemeixMenu.on('menu:opened', (menuId) => {
        store.dispatch({
            type: 'MENU_OPENED',
            payload: { menuId }
        });
    });
}

Best Practices

Performance

  1. Minimize DOM Manipulation: Use the menu system's built-in methods
  2. Debounce Resize Events: The system already debounces resize handlers
  3. Use Efficient Selectors: The system uses optimized selectors internally

Accessibility

  1. Test Keyboard Navigation: Ensure all menu items are accessible via keyboard
  2. Check ARIA Attributes: Verify proper ARIA attributes are applied
  3. Test Screen Readers: Ensure compatibility with screen readers

Maintenance

  1. Keep Configuration Updated: Regularly update menu configuration
  2. Monitor Performance: Use debug mode to monitor performance
  3. Test Across Devices: Test on various screen sizes and devices

License

MIT License - Copyright 2026 Themeix

Support

Changelog

Version 2.0.0 (July 9, 2026)

  • Complete rewrite with modern JavaScript
  • Improved performance with WeakMap and Map data structures
  • Enhanced accessibility features
  • Better mobile responsiveness
  • Support for mega menus with column layouts
  • Multiple animation types
  • Comprehensive configuration system
  • Debug mode for troubleshooting

Credits

Developed by Themeix (https://themeix.com) for the Ghost CMS community.


For detailed implementation examples and additional documentation, visit the project repository.

About

No description, website, or topics provided.

Resources

Stars

16 stars

Watchers

2 watching

Forks

Packages

 
 
 

Contributors