Development of tabs and accordions on Vue.js for 1C-Bitrix is an architectural decision that directly affects performance, SEO indexing, and UX. An incorrect approach leads to HTML duplication, slow loading, and inability to deep-link. We create components that save up to 30% development time and reduce HTML code by 2-3 times.
In a standard Bitrix template with 5 tabs, the DOM renders 5 times more content than needed — increasing load time by 200–500 ms on mobile devices. jQuery implementations via .show()/.hide() do not update the URL, making deep linking impossible. Our stack: Vue.js, PHP 8.1+, information blocks v2.0, tagged caching. We have specialized in this combination for over 6 years and completed 45+ projects with custom interfaces.
How to implement lazy loading for tabs?
Tabs are built with a pair of components: TabGroup manages the active state, TabPanel renders the content of the active tab. The key decision is how to store the active tab:
- ref() inside the component — if URL is not needed
- useRoute from Vue Router — for SPA pages with full routing
- URLSearchParams via native history.pushState — for Bitrix pages without Vue Router when deep linking is needed
// Сохранение активного таба в URL без Vue Router const activeTab = ref(new URLSearchParams(location.search).get('tab') || 'description'); watch(activeTab, (tab) => { const url = new URL(location.href); url.searchParams.set('tab', tab); history.replaceState({}, '', url); }); This allows users to copy a link to a specific tab — critical for product cards with 'Specifications', 'Reviews', 'Delivery' tabs.
Accordions require smooth height animation, which is non-trivial in CSS: height: auto does not animate. Solution via Vue <Transition> with JavaScript hooks:
function onEnter(el) { el.style.height = '0'; el.offsetHeight; // force reflow el.style.height = el.scrollHeight + 'px'; } function onAfterEnter(el) { el.style.height = 'auto'; } function onLeave(el) { el.style.height = el.scrollHeight + 'px'; el.offsetHeight; el.style.height = '0'; } This gives native height animation without libraries, working with any content inside. Code savings compared to jQuery solutions — up to 50%.
How to integrate tabs with Bitrix data?
Tab content from information blocks. Product card with tabs: 'Description' — DETAIL_TEXT from b_iblock_element, 'Specifications' — properties from b_iblock_element_property, 'Reviews' — data from the reviews information block. In result_modifier.php, an array is formed:
$arResult['TABS'] = [ ['id' => 'description', 'title' => 'Описание', 'content' => $arResult['DETAIL_TEXT']], ['id' => 'props', 'title' => 'Характеристики', 'props' => $arResult['DISPLAY_PROPERTIES']], ['id' => 'reviews', 'title' => 'Отзывы', 'lazy' => true, 'endpoint' => '/api/reviews/?id=' . $arResult['ID']], ]; The 'Reviews' tab is marked with lazy: true — content is loaded via AJAX only on first open. The loading state of the component is a spinner during the request.
Accordion FAQ from information block. The bitrix:news.list component template outputs questions and answers. The Vue component mounts onto the container, parses existing HTML via querySelectorAll (progressive enhancement) or receives data via data-items JSON.
Why choose Vue over jQuery for tabs?
Comparison:
| Feature | jQuery | Vue.js |
|---|---|---|
| HTML code | Duplicated blocks for each state | Single template with reactive data |
| Performance | Always renders all content | Only active tab rendered (v-if) |
| Maintainability | Hard to maintain with 5+ tabs | Easy to extend: add a tab — new entry in array |
| URL synchronization | Requires additional workarounds | Built-in via URLSearchParams |
Vue solution reduces HTML code size by 2-3 times and simplifies maintenance. According to our tests, switching between 10 tabs on Vue is 25% faster than on jQuery.
Case study: tabs on a pricing page with dynamic period switching
In one project, our client, a SaaS product on Bitrix, faced a problem: the pricing page with tabs 'Monthly Payment' / 'Yearly Payment'. When switching the tab, prices on all tariff cards had to change without page reload.
jQuery solution required duplicating all HTML with prices — one set for monthly prices, a second for yearly, and toggling visibility. With 6 tariffs — 12 HTML blocks, hard to maintain.
Vue solution: one component PricingTabs, data from window.BX_STATE.pricing:
const period = ref('monthly'); // 'monthly' | 'yearly' const plans = computed(() => rawPlans.value.map(plan => ({ ...plan, price: period.value === 'yearly' ? Math.round(plan.yearlyPrice / 12) : plan.monthlyPrice, badge: period.value === 'yearly' ? `Экономия ${plan.yearlySaving}%` : null })) ); Prices are stored in JSON, computed reactively when the period changes. Price change animation — <Transition name="price-flip"> with CSS rotateX. Data in PHP is filled from the 'Tariffs' information block with fields MONTHLY_PRICE, YEARLY_PRICE. Development took 2 days. Contact us to discuss a similar case for your project.
How to ensure accessibility and SEO for tabs?
Tabs without ARIA is a common mistake. Correct markup:
<div role="tablist"> <button role="tab" :aria-selected="activeTab === 'desc'" :aria-controls="'panel-desc'"> Описание </button> </div> <div role="tabpanel" id="panel-desc" :hidden="activeTab !== 'desc'"> <!-- контент --> </div> The hidden attribute instead of v-show with display: none — screen readers correctly ignore hidden panels. Keyboard navigation between tabs — ArrowLeft/ArrowRight via onKeydown.
SEO aspect: search engines index tab content if it is present in HTML. With v-if (lazy rendering), the first tab is rendered server-side via the Bitrix template, the rest load via AJAX. For Googlebot this is acceptable — it executes JavaScript but with a delay. For critical SEO content (product specifications) — always render in HTML, hide via hidden. Read more about ARIA at Wikipedia: ARIA and MDN documentation.
Common mistakes when developing tabs and accordions
- Using v-show instead of v-if for hidden tabs — content remains in DOM and slows rendering. Choose v-if for lazy loading.
- Forgot hidden attribute: screen reader may read hidden panels. Always add
:hidden="activeTab !== 'desc'". - Accordion animation via CSS height: doesn't work for auto. Use Vue Transition JavaScript hooks.
What's included in development and timeline?
- Analysis: determining data sources, prototyping components
- Design: architecture of Vue components, contracts with backend
- Implementation: development of tabs/accordions on Vue, integration with information blocks and APIs
- Testing: cross-browser, accessibility, performance
- Documentation: component description, usage examples
- Support: 3-month warranty
| Component | Approximate Timeline |
|---|---|
| Tabs with static content + URL saving | 1-2 days |
| Tabs with lazy loading | 2-3 days |
| Accordion with animation | 1 day |
| Linked tabs + dynamic data (as in pricing example) | 3-5 days |
Timelines are determined by the number of data sources, animation requirements, and complexity of integration with Bitrix components. Get a consultation for your project — we'll select the optimal architectural solution.
Order turnkey tab development — we'll prepare a commercial proposal within 1 business day. Contact us to discuss the details of your project.

