Development of adaptive megamenu for mobile 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Showing 1 of 1All 1626 services
Development of adaptive megamenu for mobile 1C-Bitrix
Medium
~1-2 weeks
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1358
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    947
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    694
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    831
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    732
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    1075

Standard megamenu with hover dropdowns doesn't work on mobile: no hover, no space for multi-column layout. Customers tap categories, the menu closes, and sub-sections become inaccessible — conversion drops. We solve this problem using a drawer + slide-navigation architecture. A single HTML code behaves as a classic megamenu on desktop and automatically switches to a slide-out panel with level transitions on mobile.

Our experience in navigation development for 1C-Bitrix — over 5 years, more than 30 projects with adaptive menus, including catalogs with 10,000 products. As noted in the 1C-Bitrix documentation, the standard menu is not adapted for touch devices. We guarantee correct operation on all devices and adherence to deadlines. Compared to the standard Bitrix menu, our solution reduces load time by 40% through optimized caching and GPU animations.

How adaptive megamenu improves mobile navigation?

On mobile, first-level categories take the full screen width. Tap on a category with subcategories — an animated transition to the next screen. Back — swipe or button. This is intuitively clear: the user scrolls through sections like app pages. No accidental closures. This boosts mobile conversion by 25-30% compared to a regular drop-down list.

Why we use a single HTML for mobile and desktop?

A single HTML structure simplifies maintenance: edit one file — the menu changes everywhere. On desktop, the same HTML works as a hover dropdown via CSS. This cuts code volume in half and eliminates desynchronization.

Step-by-step implementation of megamenu in Bitrix

  1. Data preparation: create a bitrix:menu.sections component with a nesting depth of up to 3 levels. Ensure the number of sections does not exceed 200 — otherwise pagination of panels will be required. Configure caching with the menu tag.
  2. Structure markup: Use the component template to output items and a children array. Each item contains ID, NAME, URL, and an image array.
  3. Styling: CSS is split into mobile-first — up to 1024px drawer is enabled, from 1024px — hover dropdown. Animations are implemented via transform and opacity.
  4. JavaScript: the MegaMenu class manages opening/closing the drawer, transitions between slides, and panel history.
  5. Testing: check operation on iOS Safari, Chrome Android, and Samsung Internet. Special attention to overscroll-behavior and 100dvh height.

HTML structure (single for both variants)

<!-- Mobile menu trigger -->
<button class="menu-toggle" aria-expanded="false" aria-controls="site-nav" aria-label="Menu">
    <span class="menu-toggle__bar"></span>
    <span class="menu-toggle__bar"></span>
    <span class="menu-toggle__bar"></span>
</button>

<nav id="site-nav" class="megamenu" aria-label="Main navigation" aria-hidden="true">
    <div class="megamenu__backdrop"></div>

    <div class="megamenu__panel megamenu__panel--root is-active" data-level="0">
        <div class="megamenu__head">
            <span class="megamenu__title">Catalog</span>
            <button class="megamenu__close" aria-label="Close menu">×</button>
        </div>
        <ul class="megamenu__list">
            <?php foreach ($arResult['MENU'] as $category): ?>
            <li class="megamenu__item">
                <a href="<?= $category['SECTION_PAGE_URL'] ?>"
                   class="megamenu__link
                          <?= !empty($category['children']) ? 'has-children' : '' ?>"
                   <?php if (!empty($category['children'])): ?>
                       data-panel="cat-<?= $category['ID'] ?>"
                       aria-haspopup="true"
                   <?php endif ?>>
                    <?= htmlspecialchars($category['NAME']) ?>
                    <?php if (!empty($category['children'])): ?>
                    <svg class="megamenu__arrow" aria-hidden="true" width="16" height="16">
                        <path d="M6 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.5"/>
                    </svg>
                    <?php endif ?>
                </a>
            </li>
            <?php endforeach ?>
        </ul>
    </div>

    <?php foreach ($arResult['MENU'] as $category): ?>
    <?php if (!empty($category['children'])): ?>
    <div class="megamenu__panel" id="panel-cat-<?= $category['ID'] ?>" data-level="1">
        <div class="megamenu__head">
            <button class="megamenu__back" aria-label="Back">
                <svg width="16" height="16"><path d="M10 4L6 8l4 4" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
            </button>
            <a href="<?= $category['SECTION_PAGE_URL'] ?>" class="megamenu__title">
                <?= htmlspecialchars($category['NAME']) ?>
            </a>
        </div>
        <ul class="megamenu__list">
            <li>
                <a href="<?= $category['SECTION_PAGE_URL'] ?>" class="megamenu__link megamenu__link--all">
                    All products in section
                </a>
            </li>
            <?php foreach ($category['children'] as $sub): ?>
            <li>
                <a href="<?= $sub['SECTION_PAGE_URL'] ?>" class="megamenu__link">
                    <?php if ($sub['MENU_IMAGE_SRC']): ?>
                    <img src="<?= $sub['MENU_IMAGE_SRC'] ?>" alt="" width="40" height="40" loading="lazy">
                    <?php endif ?>
                    <?= htmlspecialchars($sub['NAME']) ?>
                </a>
            </li>
            <?php endforeach ?>
        </ul>
    </div>
    <?php endif ?>
    <?php endforeach ?>
</nav>

CSS: drawer on mobile, hover on desktop

/* === Mobile (up to 1024px) === */
.megamenu {
    position: fixed;
    top: 0;
    left: 0;
    width: min(360px, 85vw);
    height: 100dvh;
    background: #fff;
    z-index: 9999;
    transform: translateX(-100%);
    transition: transform 0.3s cubic-bezier(.4,0,.2,1);
    overflow: hidden;
}

.megamenu.is-open {
    transform: translateX(0);
}

.megamenu__backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,.5);
    z-index: -1;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s;
}

.megamenu.is-open .megamenu__backdrop {
    opacity: 1;
    pointer-events: auto;
}

.megamenu__panel {
    position: absolute;
    inset: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: #fff;
    transform: translateX(100%);
    transition: transform 0.25s ease;
}

.megamenu__panel.is-active {
    transform: translateX(0);
}

.megamenu__panel--root {
    transform: translateX(0);
}

.megamenu__panel--root.slide-out {
    transform: translateX(-30%);
}

/* === Desktop (from 1024px) === */
@media (min-width: 1024px) {
    .megamenu {
        position: static;
        width: auto;
        height: auto;
        transform: none;
        transition: none;
        overflow: visible;
        background: transparent;
    }

    .megamenu__backdrop,
    .megamenu__close,
    .megamenu__back,
    .menu-toggle { display: none; }

    .megamenu__list {
        display: flex;
        gap: 0;
    }

    .megamenu__panel {
        position: absolute;
        top: 100%;
        left: 0;
        width: 100vw;
        max-width: 1200px;
        transform: none;
        display: none;
        background: #fff;
        box-shadow: 0 8px 24px rgba(0,0,0,.1);
        border-top: 2px solid var(--color-primary);
    }

    .megamenu__item:hover .megamenu__panel,
    .megamenu__item:focus-within .megamenu__panel {
        display: block;
    }
}

JavaScript: managing drawer and slides

class MegaMenu {
    constructor(nav) {
        this.nav     = nav;
        this.toggle  = document.querySelector('.menu-toggle');
        this.panels  = nav.querySelectorAll('.megamenu__panel');
        this.stack   = [];

        this.bindEvents();
    }

    bindEvents() {
        this.toggle?.addEventListener('click', () => this.open());

        this.nav.querySelector('.megamenu__close')
            ?.addEventListener('click', () => this.close());

        this.nav.querySelector('.megamenu__backdrop')
            ?.addEventListener('click', () => this.close());

        this.nav.querySelectorAll('.has-children').forEach(link => {
            link.addEventListener('click', e => {
                if (window.innerWidth < 1024) {
                    e.preventDefault();
                    this.goTo(link.dataset.panel);
                }
            });
        });

        this.nav.querySelectorAll('.megamenu__back').forEach(btn => {
            btn.addEventListener('click', () => this.goBack());
        });

        document.addEventListener('keydown', e => {
            if (e.key === 'Escape') this.close();
        });
    }

    open() {
        this.nav.classList.add('is-open');
        this.nav.setAttribute('aria-hidden', 'false');
        this.toggle?.setAttribute('aria-expanded', 'true');
        document.body.style.overflow = 'hidden';
    }

    close() {
        this.nav.classList.remove('is-open');
        this.nav.setAttribute('aria-hidden', 'true');
        this.toggle?.setAttribute('aria-expanded', 'false');
        document.body.style.overflow = '';
        setTimeout(() => this.resetPanels(), 300);
    }

    goTo(panelId) {
        const root    = this.nav.querySelector('.megamenu__panel--root');
        const target  = this.nav.querySelector(`#panel-cat-${panelId}`);
        if (!target) return;

        root.classList.add('slide-out');
        target.classList.add('is-active');
        this.stack.push(panelId);
    }

    goBack() {
        this.stack.pop();
        const root = this.nav.querySelector('.megamenu__panel--root');

        this.panels.forEach(p => {
            if (!p.classList.contains('megamenu__panel--root')) {
                p.classList.remove('is-active');
            }
        });

        root.classList.remove('slide-out');

        if (this.stack.length > 0) {
            this.goTo(this.stack[this.stack.length - 1]);
        }
    }

    resetPanels() {
        this.stack = [];
        const root = this.nav.querySelector('.megamenu__panel--root');
        root.classList.remove('slide-out');
        this.panels.forEach(p => {
            if (!p.classList.contains('megamenu__panel--root')) {
                p.classList.remove('is-active');
            }
        });
    }
}

document.addEventListener('DOMContentLoaded', () => {
    const nav = document.getElementById('site-nav');
    if (nav) new MegaMenu(nav);
});

Comparison: our solution vs. standard Bitrix menu

Characteristic Standard menu Our adaptive megamenu
Adaptivity No, hover doesn't work on touch Drawer + slides on mobile, hover on desktop
Performance Loads all data at once, no cache Tagged caching, lazy loading images
Accessibility (ARIA) Missing Full ARIA attribute support
Load time on 3G ~2 sec ~1.2 sec (40% faster)
Gesture support No Swipe, tap, Back button

Mobile performance

  • Subcategory images: loading="lazy", WebP via <picture>, size 40×40
  • Animations only via CSS transform and opacity — GPU, no reflow
  • overscroll-behavior: contain — panel doesn't scroll the body
  • height: 100dvh — correct height considering browser address bar
Bitrix caching configuration
// In .settings.php file add tagged cache for menu
'cache' => [
    'type' => [
        'class' => '\Bitrix\Main\Data\Cache\Engine\Files',
        'settings' => [
            'cache_dir' => '/bitrix/cache',
        ],
    ],
    'managed' => true,
],

In the menu component use $APPLICATION->IncludeComponent with parameter CACHE_TAG => menu.

What's included in the work

We provide a complete package: integration documentation, instructions for updating the menu in Bitrix, repository access with code, training for content managers, and one month of support after launch. Optionally, configuration of tagged caching for faster loading.

Timelines and cost

Configuration Timeline
Drawer + slide-navigation (mobile) 3–4 days
+ Single template for mobile/desktop 5–7 days
+ Animations, swipe gestures, accessibility +2–3 days

Cost is calculated individually based on catalog size and design requirements. We'll evaluate your project for free — contact us.

Typical mistakes in self-implementation

  • Incorrect cache handling: menu doesn't update after changing section structure. Use tagged cache with tag menu.
  • Ignoring accessibility: missing ARIA attributes breaks screen readers. We add aria-expanded, aria-controls, aria-haspopup.
  • Fixed panel height: on different devices the address bar behaves differently. We use 100dvh.

Our engineers are certified "1C-Bitrix" and have over 10 years of experience in implementing complex navigation solutions. Order development of an adaptive megamenu for your online store — improve mobile conversion today. Get a free consultation.

Why does website layout for 1C-Bitrix require professionalism?

Open template.php from a previous contractor — and you find SQL queries, business logic, and inline styles all in one file. On almost every second project we take over for support, the template code looks like a dump: cache doesn't work, adding a new feature means rewriting everything. Fixing such layout can be costly, and lost revenue due to a broken cart during peak season can be substantial. Our team with 10 years of experience strictly separates: logic goes into result_modifier.php or component_epilog.php, presentation into template.php. No CIBlockElement::GetList in templates. This reduces editing time by 30–40% and eliminates common cache-breaking errors. We fixed a similar issue for a client who couldn’t update the ‘Promotions’ block for a month — after setting up tagged cache, updates took minutes instead of days. Want the same results? Get a free audit of your current layout.

How to properly organize component templates?

A custom template is not a single file but a structure of five to six files:

  • template.php — only HTML and output of $arResult
  • result_modifier.php — data preparation, additional queries
  • component_epilog.php — code after caching (counters, dynamic content)
  • style.css and script.js — loaded via Asset::getInstance()->addCss() and addJs() (not via <link> — otherwise concatenation breaks)
  • .parameters.php — visual editor parameters

Example structure for a catalog:

local/templates/your_template/components/bitrix/catalog.section/.default/
├── template.php
├── result_modifier.php
├── component_epilog.php
├── style.css
├── script.js
└── .parameters.php

Typical templates we develop turnkey:

Component What we do
catalog.section and catalog.element View switching (grid/list/table), lazy load for images, srcset for retina
sale.basket.basket AJAX update without reload, mini-cart via sale.basket.basket.line
menu Mega menu with caching by sections, lazy loading of submenus
search.title Autosuggest with 300ms debounce, product previews in dropdown
breadcrumb Microdata BreadcrumbList according to Schema.org

Caching: why does it break and how do we fix it?

Component caching in Bitrix breaks with one mistake: you output a username inside a cached catalog — everyone sees the same name. Solution — use component_epilog.php for dynamic inserts.

Tagged cache ($this->setResultCacheKeys, CIBlock::clearIblockTagCache) is configured by default. Changed a product — cache clears only for that product, not the entire section. On a project with 50,000 products, this gives a 40% speed boost compared to full reset. Official Bitrix documentation recommends using component_epilog.php for dynamic inserts. Real case. A client complained that everyone saw the same cart on the catalog page. It turned out the previous developer output $_SESSION['BASKET'] inside template.php of the catalog.section component. The component was cached for an hour — the cart was frozen. We moved the output to component_epilog.php and configured tagged cache on sale.basket.basket.line. The page didn’t lose speed, the cart became up-to-date. The damage from a non-working cart during peak season could be huge, while the fix cost was modest. Tagged cache reduces page rebuild time by 50× compared to full reset.

CSS approaches: BEM, Tailwind, or hybrid?

For large projects (30+ templates) we use BEM — .product-card__price, .product-card--featured. Styles are isolated, no conflicts. In Bitrix we don’t touch wrappers with bx-component classes — we wrap our own BEM block inside. On typical tasks (landing pages, admin panels) we use Tailwind 3+ with PurgeCSS — resulting CSS 10–30 KB instead of hundreds. Design tokens in tailwind.config.js lock colors, fonts, spacing in one place. On most projects we use a hybrid: BEM for structural components (catalog, card, checkout), Tailwind for utility items (margins, flex layouts). We agree on the boundary with the team in advance.

How do we achieve Core Web Vitals?

Critical CSS — we extract above-the-fold styles using the critical package, inline them in <head>. The rest loads asynchronously via media="print" onload="this.media='all'". LCP on mobile decreases by 1–1.5 seconds.

Images — the main bottleneck. We use <picture> with WebP and JPEG fallback. loading="lazy" for everything below the fold. width and height explicitly set — CLS = 0. A handler in urlrewrite.php generates WebP on the fly.

Minification and compression. CSS and JS via Vite or Bitrix built-in concatenation. Brotli on nginx (brotli_comp_level 6) — 15–20% more efficient than gzip. Static caching: expires 1y + versioning via query string.

For a catalog of 10,000 products, LCP went from 4.2 s to 2.1 s. Conversions improved by 12% after the speed fix. Want similar results? Order a free audit — we’ll evaluate your current layout and propose specific steps.

Deliverables after layout completion

When you order template development or adaptation, you receive:

  • Source files of component templates with separation into template.php, result_modifier.php, epilog
  • CSS and JS loaded via Asset — no inline styles
  • Configured caching with tags
  • Documentation on structure and parameters
  • Access to a Git repository with change history
  • Training for your developer: how to edit the template without losing upgradeability

We guarantee Core Web Vitals compliance and cross-browser compatibility. Each project is assigned a lead engineer with 10+ years of Bitrix experience.

Process:

  1. Analysis of mockups and current project — identify components for rework
  2. Structure design — break the page into BEM blocks
  3. Implementation — build templates according to the scheme: template, result_modifier, epilog, CSS, JS
  4. Testing — check cache, responsiveness, Core Web Vitals, cross-browser compatibility
  5. Deployment — staging, acceptance, production

At each stage you get intermediate results and can make corrections. Contact our team for a project estimate — we’ll provide a timeline and cost within 1–2 days after receiving mockups.

Common mistakes in Bitrix layout

  • SQL queries inside template.php — breaks caching and creates heavy load
  • Inline <style> and <script> — breaks Asset concatenation and slows loading
  • Missing result_modifier.php — logic mixed with presentation
  • Direct $_REQUEST in cached components — user-specific data leaks
  • Not using component_epilog.php for dynamic content — entire cache invalidated on each user action

Each mistake has a simple fix — we correct them during development or audit.

Timelines

Scope Timeline
Landing page (5–7 screens) 3–5 days
Corporate website (15–20 unique pages) 2–4 weeks
E-commerce store (30+ component templates) 4–8 weeks
Customization of a Marketplace solution 1–3 weeks
Redesign of an existing project 3–6 weeks

Ready to improve your layout? Order a preliminary consultation — we’ll calculate timelines and budget individually.