Developing a Mega Menu in 1C-Bitrix
The standard bitrix:menu component renders a flat list of links. A large online store requires something different: multi-column navigation across sections, quick access to the second and third hierarchy levels without extra clicks, support for thousands of categories without performance degradation. A mega menu is a separate component built on top of the infoblock section tree.
Data Source: Section Tree
The Bitrix section structure is stored in b_iblock_section. A mega menu needs two to three levels of nesting. The standard CIBlockSection::GetList method with INCLUDE_SUBSECTIONS = 'N' does not return a tree — only a flat list. We build the tree manually:
// /local/lib/Menu/MegaMenuBuilder.php
namespace Local\Menu;
class MegaMenuBuilder
{
public function build(int $iblockId, int $depth = 3): array
{
$cache = new \CPHPCache();
$cacheId = 'megamenu_' . $iblockId . '_' . LANGUAGE_ID;
if ($cache->InitCache(3600, $cacheId, '/megamenu/')) {
return $cache->GetVars()['menu'];
}
$raw = $this->fetchSections($iblockId, $depth);
$tree = $this->buildTree($raw);
$cache->StartDataCache();
$cache->EndDataCache(['menu' => $tree]);
return $tree;
}
private function fetchSections(int $iblockId, int $depth): array
{
$result = [];
$res = \CIBlockSection::GetList(
['LEFT_MARGIN' => 'ASC'],
[
'IBLOCK_ID' => $iblockId,
'ACTIVE' => 'Y',
'DEPTH_LEVEL' => [$depth], // up to the required depth
'GLOBAL_ACTIVE' => 'Y',
],
false,
['ID', 'NAME', 'CODE', 'DEPTH_LEVEL', 'IBLOCK_SECTION_ID',
'SECTION_PAGE_URL', 'PICTURE', 'UF_MENU_ICON', 'LEFT_MARGIN', 'RIGHT_MARGIN']
);
while ($section = $res->GetNext()) {
$result[$section['ID']] = $section;
}
return $result;
}
private function buildTree(array $flat, int $parentId = 0): array
{
$tree = [];
foreach ($flat as $section) {
if ((int)$section['IBLOCK_SECTION_ID'] === $parentId) {
$section['children'] = $this->buildTree($flat, (int)$section['ID']);
$tree[] = $section;
}
}
return $tree;
}
}
Mega Menu Component
The component is registered at /local/components/local/megamenu/. The template receives the section tree and renders HTML:
// /local/components/local/megamenu/class.php
class LocalMegaMenuComponent extends \CBitrixComponent
{
public function onPrepareComponentParams($params): array
{
$params['IBLOCK_ID'] = (int)($params['IBLOCK_ID'] ?? CATALOG_IBLOCK_ID);
$params['DEPTH'] = (int)($params['DEPTH'] ?? 3);
return $params;
}
public function executeComponent(): void
{
$builder = new \Local\Menu\MegaMenuBuilder();
$this->arResult['MENU'] = $builder->build(
$this->arParams['IBLOCK_ID'],
$this->arParams['DEPTH']
);
$this->setFrameMode(false); // disable Bitrix edit mode
$this->includeComponentTemplate();
}
}
Mega Menu Template
// /local/components/local/megamenu/templates/.default/template.php
/** @var array $arResult */
?>
<nav class="megamenu" aria-label="Catalogue navigation">
<ul class="megamenu__list">
<?php foreach ($arResult['MENU'] as $category): ?>
<li class="megamenu__item" data-id="<?= $category['ID'] ?>">
<a href="<?= htmlspecialchars($category['SECTION_PAGE_URL']) ?>"
class="megamenu__link">
<?php if ($category['UF_MENU_ICON']): ?>
<img src="<?= \CFile::GetPath($category['UF_MENU_ICON']) ?>"
alt="" class="megamenu__icon" loading="lazy">
<?php endif ?>
<span><?= htmlspecialchars($category['NAME']) ?></span>
</a>
<?php if (!empty($category['children'])): ?>
<div class="megamenu__dropdown">
<div class="megamenu__columns">
<?php foreach (array_chunk($category['children'], 8) as $col): ?>
<div class="megamenu__col">
<?php foreach ($col as $sub): ?>
<a href="<?= htmlspecialchars($sub['SECTION_PAGE_URL']) ?>"
class="megamenu__sublink">
<?= htmlspecialchars($sub['NAME']) ?>
</a>
<?php if (!empty($sub['children'])): ?>
<ul class="megamenu__tertiary">
<?php foreach (array_slice($sub['children'], 0, 5) as $third): ?>
<li>
<a href="<?= htmlspecialchars($third['SECTION_PAGE_URL']) ?>">
<?= htmlspecialchars($third['NAME']) ?>
</a>
</li>
<?php endforeach ?>
</ul>
<?php endif ?>
<?php endforeach ?>
</div>
<?php endforeach ?>
</div>
</div>
<?php endif ?>
</li>
<?php endforeach ?>
</ul>
</nav>
CSS: Opening Without JavaScript
The mega menu functions via CSS :hover as a baseline — a courtesy to users with slow JavaScript:
.megamenu__dropdown {
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: #fff;
border-top: 2px solid var(--color-primary);
box-shadow: 0 8px 24px rgba(0,0,0,.12);
display: none;
z-index: 1000;
}
.megamenu__item:hover .megamenu__dropdown,
.megamenu__item:focus-within .megamenu__dropdown {
display: block;
}
.megamenu__columns {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1.5rem;
padding: 1.5rem;
max-width: 1200px;
margin: 0 auto;
}
JavaScript: Close Delay and Accessibility
// 200ms close delay — prevents flickering when moving the mouse
document.querySelectorAll('.megamenu__item').forEach(item => {
let timer;
item.addEventListener('mouseenter', () => {
clearTimeout(timer);
item.querySelector('.megamenu__dropdown')?.style.setProperty('display', 'block');
});
item.addEventListener('mouseleave', () => {
timer = setTimeout(() => {
item.querySelector('.megamenu__dropdown')?.style.setProperty('display', 'none');
}, 200);
});
});
// Keyboard navigation
document.querySelectorAll('.megamenu__link').forEach(link => {
link.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
const dropdown = link.nextElementSibling;
if (dropdown) {
e.preventDefault();
dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
}
}
});
});
Caching and Invalidation
The section tree is cached for 3600 seconds. When any catalogue section is modified, the OnAfterIBlockSectionUpdate event handler clears the cache:
AddEventHandler('iblock', 'OnAfterIBlockSectionUpdate', function($fields) {
$cache = new \CPHPCache();
$cache->CleanDir('/megamenu/');
});
Performance
The section tree is built with a single query to b_iblock_section, and the result is cached. The mega menu generates no additional SQL queries on the page. With 500 sections across three nesting levels, the query takes 3–8 ms and the PHP tree construction takes another 2–5 ms. A full render of the cached mega menu takes less than 1 ms.
Implementation Timeline
| Configuration | Timeline |
|---|---|
| Basic mega menu (2 levels, hover) | 3–4 days |
| With third level, caching, invalidation | 5–7 days |
| Responsive version with mobile drawer | +3–4 days |
| With images, banners, promotions | +2–3 days |







