Detailed guide: Creating an adaptive sidebar for COTONTI CMF
How to turn an ordinary sidebar into a modern adaptive sidebar, which is always visible on wide screens, and smoothly moves out on mobile devices at the touch of a hamburger button.
Introduction
In this article, we will step by step analyze how to turn an ordinary side panel into a modern adaptive sidebar that is always visible on wide screens and smoothly slides out on mobile devices when a hamburger button is pressed. All solutions are based on a real project "COTONTI CMF" – an exchange monitor service. We won't just copy the code; we'll understand the logic behind every decision so you can apply it in your own projects.
By the end of this read, you will know precisely:
- How to design the HTML structure for a flexible sidebar.
- How to use CSS variables and media queries to implement responsiveness and theme switching.
- How to split JavaScript into independent modules to avoid confusion.
- How to handle opening/closing the sidebar, blocking scrolling, and working with the overlay.
We will focus all our attention on the sidebar – its markup, styling, and behavioral logic. A secondary but important part will be the light/dark theme switching, as it directly affects the sidebar's appearance. Other modules (tabs, swap buttons, demo search) will be mentioned briefly – they are not the focus of the article.
1. Architecture and General Concept
Before writing code, let's imagine how the page should work.
On desktop (screen width greater than 1024 pixels) we see:
- A header with a logo, navigation, and buttons.
- Below the header – the main area divided into two parts: a sidebar 300px wide on the left, and the main content on the right.
- The sidebar is stuck to the top and does not scroll with the page – it stays in place thanks to
position: sticky. - The hamburger (three bars) is hidden.
On a mobile device (width ≤1024px) the behavior changes:
- The header becomes more compact, navigation disappears, and the hamburger appears.
- The sidebar is no longer part of the flow – it is fixed over the content but initially shifted off the left edge of the screen.
- When the hamburger is pressed, the sidebar smoothly slides in from the left, and the main content is dimmed by a semi-transparent overlay.
- The sidebar can be closed in three ways: the "Collapse" button, clicking on the overlay, or pressing the Escape key.
- After closing, the sidebar slides back, the overlay disappears, and the page scrolling is restored.
Additionally, the whole page supports two color schemes – dark (default) and light. The choice is saved in localStorage.
This is exactly the UX we will implement. The key idea is one HTML code, with all behavior determined by CSS media queries and a small JavaScript module. No duplication of the sidebar for mobile and desktop versions.
2. Preparing the File Structure
For ease of maintenance, we will separate the code into individual files:
project/
index.html
css/
styles.css
js/
theme.js
sidebar.js
tabs.js
swap.js
search-demo.js
The main heroes of our article are index.html, css/styles.css, and js/sidebar.js. We will touch on theme.js and tabs.js in the context of the sidebar. The rest (swap.js, search-demo.js) will be left without detailed analysis – they solve local tasks and do not affect responsiveness.
3. HTML Markup: Framework and Key Elements
Let's start with index.html. It contains all the semantic structure but not a single line of styles or scripts (except external Bootstrap and Font Awesome libraries).
3.1. Header and Hamburger
The header (<header class="header">) is divided into three logical parts:
- Left: hamburger button + logo.
- Center: navigation menu (hidden on mobile using classes
d-none d-lg-flex). - Right: theme button and "Sign In" button.
The hamburger code:
<button class="hamburger" id="hamburgerBtn">
<i class="fas fa-bars"></i>
</button>
Initially, the hamburger is hidden via CSS (display: none), and only the media query for width ≤1024px makes it visible. We will attach the click handler for opening/closing the sidebar to this element.
3.2. Overlay – Background Dimming
Immediately after the header, but before the main layout, there is an empty <div>:
<div class="sidebar-overlay" id="sidebarOverlay"></div>
It will only be shown in the mobile version when the sidebar is open. In desktop mode it is permanently hidden (display: none). Clicking on it will also close the sidebar.
3.3. Main Layout Container and Sidebar
The main container <div class="layout"> unites the sidebar and the main content. It is a flex container that arranges its children in a row on desktop and in a column on mobile.
The sidebar itself (<aside class="sidebar" id="sidebar">) contains several internal blocks:
- Chips (tab buttons) "All Currencies" and "Favorites".
- Search block with two fields and a swap button.
- Container with the currency list.
- Placeholder for empty favorites.
- "Collapse" button (shown only on mobile).
The crucial identifier id="sidebar" allows JavaScript to easily find the element.
Note the initial class visible on the favorites placeholder – we use it to control visibility via JS.
3.4. Main Content and Footer
Inside <main class="main-content"> is everything to the right of the sidebar: headings, calculator, blog. The calculator layout is not critical for the sidebar topic.
The footer is standard, sticking to the bottom of the page thanks to flex.
4. CSS: Sidebar Styling and Responsiveness
All styles are collected in css/styles.css. We grouped them by meaning and provided comments. Let's examine the key points responsible for the sidebar's behavior.
4.1. CSS Variables and Themes
At the very beginning of the file, we define variables for the dark and light themes. The switching is based on the data-bs-theme attribute on the <html> element:
:root,
[data-bs-theme="dark"] {
--sidebar-bg: #1b1e22;
/* ... */
}
[data-bs-theme="light"] {
--sidebar-bg: #f8f9fa;
/* ... */
}
The sidebar itself uses the variable:
.sidebar {
background: var(--sidebar-bg);
/* ... */
}
Thus, changing the attribute automatically updates the background and other colors without reloading the page.
4.2. Default Sidebar Styles for Desktop
By default, the sidebar behaves as sticky:
.sidebar {
width: 300px;
position: sticky;
top: 56px; /* header height */
height: calc(100vh - 56px);
overflow-y: auto;
transition: transform 0.3s ease;
/* ... */
}
Why sticky and not fixed? Because we need the sidebar to participate in the flex container flow and not overlap the content. Sticky works exactly like that: the element behaves as a normal block until it reaches the specified top, then it "sticks". As a result, the content on the right never goes under the sidebar.
4.3. Media Query: Mobile Version
When the screen width is 1024px or less, a media query kicks in and completely overrides the sidebar's behavior:
@media (max-width: 1024px) {
.sidebar {
position: fixed;
top: 56px;
left: 0;
height: calc(100vh - 56px);
transform: translateX(-100%);
width: 280px;
z-index: 1060;
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-close-mobile { display: block; }
.layout { flex-direction: column; }
/* ... */
}
What's happening here:
- The sidebar becomes
fixedto sit above the content. - Initially, it is shifted off the left edge by 100% of its width using
transform: translateX(-100%). This hides it visually. - When JavaScript adds the class
open,transform: translateX(0)is applied, and the sidebar smoothly slides in. The smoothness is provided bytransition: transform 0.3s easedefined in the base styles. - We increase
z-indexto 1060 (the overlay has 1050) so the sidebar is above the dimming. - The close button, previously hidden (
display: none), now appears. - The main container
layoutswitches to a vertical direction – the content goes down.
Thus, the same HTML element adapts to different devices purely through CSS.
4.4. Overlay
The overlay is initially hidden and activated by adding the active class via JavaScript:
.sidebar-overlay {
display: none;
position: fixed;
top: 56px; left: 0;
width: 100%; height: calc(100% - 56px);
background: rgba(0,0,0,0.4);
z-index: 1050;
}
.sidebar-overlay.active {
display: block;
}
When we click on it, we remove the active class, which hides the dimming.
4.5. Additional Responsiveness for Very Narrow Screens
A second media query (max-width: 600px) rearranges the calculator fields into a column. It is not directly related to the sidebar but demonstrates the flexibility of the approach.
5. JavaScript: Sidebar Control Logic
Now let's move on to what makes the sidebar open and close. The code is in js/sidebar.js and is a pure module with no dependencies on other scripts.
5.1. Initialization and Element Selection
Everything starts after the DOM is fully loaded:
document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const hamburgerBtn = document.getElementById('hamburgerBtn');
const closeBtn = document.getElementById('sidebarCloseBtn');
if (!sidebar || !hamburgerBtn) return;
// ... functions and handlers
});
We check for the presence of key elements, and if they don't exist, we simply exit – this allows the script to be used on pages without a sidebar.
5.2. Open and Close Functions
function openSidebar() {
sidebar.classList.add('open');
if (overlay) overlay.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeSidebar() {
sidebar.classList.remove('open');
if (overlay) overlay.classList.remove('active');
document.body.style.overflow = '';
}
The logic is simple:
- Add/remove the
openclass – this triggers the CSS animation for sliding in. - Add/remove the
activeclass on the overlay. - Block/unblock page scrolling by setting
overflowonbody. This is crucial so that the content under the overlay does not scroll.
5.3. Event Handlers
The hamburger gets a toggleSidebar function that switches the state:
hamburgerBtn.addEventListener('click', () => {
sidebar.classList.contains('open') ? closeSidebar() : openSidebar();
});
The close button and clicking on the overlay call closeSidebar().
Additionally, we listen for the Escape key press on the whole page:
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && sidebar.classList.contains('open')) {
closeSidebar();
}
});
This allows closing the sidebar without a mouse – improving accessibility.
5.4. Resize Handler – An Important Detail
Imagine: a user opened the sidebar on a mobile device (width 600px), then rotated the device or resized the window to 1200px. If nothing is done, the sidebar will remain in the open state, but because the media query no longer applies transform, the sidebar will snap to its usual place and be visible. However, body will still have overflow: hidden, and the page will stop scrolling. To prevent this, we added:
window.addEventListener('resize', () => {
if (window.innerWidth > 1024) {
closeSidebar();
}
});
When transitioning to desktop mode, we forcibly close the sidebar, remove the overlay, and restore scrolling. This makes the interface reliable.
5.5. Why We Don't Use Bootstrap for the Sidebar
Many projects use Bootstrap's offcanvas component, but in our case we needed a more flexible solution: the sidebar on desktops must be sticky, not just hidden. Standard offcanvas is always fixed and requires additional wrapping. Our approach is lighter and gives full control over animation and behavior.
6. Theme Switching and Its Connection to the Sidebar
Although theme switching is not the main focus of the article, it directly affects the sidebar, so let's examine the key points.
The task of the theme.js module is to manage the data-bs-theme attribute on the <html> element and save the choice in localStorage. Why <html> and not body? Because the CSS variables responsible for colors are tied to this attribute:
[data-bs-theme="light"] {
--sidebar-bg: #f8f9fa;
}
When the user clicks the theme button, we read the current attribute, invert it, and update:
html.setAttribute('data-bs-theme', newTheme);
localStorage.setItem('index-mono-theme', newTheme);
That's it! All elements, including the sidebar, instantly recolor thanks to the variables.
At startup, we check the saved value or system preferences to immediately set the correct theme without flickering. For this, the <html> already has data-bs-theme="dark" written in, and JS, if needed, changes it to light. This guarantees that the page will be dark until the script runs.
The button icon (moon/sun) is also updated in the setTheme function.
This approach isolates the theme logic from other modules and allows easy reuse.
7. Tabs in the Sidebar (tabs.js) – Briefly
The tabs.js file handles switching between "All Currencies" and "Favorites". It's part of the sidebar, but its implementation is simple and does not affect responsiveness. When a chip button is clicked, we:
- Move the
activeclass to the appropriate button. - Show/hide the currencies container (
allCurrenciesContainer). - Show/hide the "No favorite directions" placeholder using the
visibleclass.
No complex logic, just visibility toggling. It's only important to check that all elements exist.
8. Other Modules: swap.js and search-demo.js
These files solve local tasks and do not require a detailed examination in an article about the sidebar.
swap.js contains a function that swaps the values of two fields (by their id) – this is for user convenience. Swap buttons exist both in the sidebar and in the main calculator.
search-demo.js is a temporary placeholder that shows an alert when the "Find" button is pressed. In the future, an AJAX request can be added here.
Their presence demonstrates how new features can be added without affecting the core sidebar logic.
9. Why This Separation Matters
Extracting styles into a separate file and splitting JavaScript into modules provides several advantages:
- HTML cleanliness – the markup remains semantic and easy to read.
- Reusability – the
theme.jsmodule can be included on any page that needs a theme. - Ease of maintenance – to fix a sidebar bug, you go to a single file
sidebar.js. - Parallel work – several developers can simultaneously edit different modules without conflicts.
Our project doesn't use bundlers (Webpack, Vite) or ES6 imports, but that's a conscious decision – for a small service, simple separation with defer is sufficient.
10. Conclusion
We have traveled the entire path from idea to a fully functional adaptive sidebar. On wide screens it is always visible, on mobile it slides out on demand. The color theme switches without reloading. The code is divided into logical parts, and each line has a clear explanation.
You can apply this approach in any project: an online store, admin panel, landing page. The main thing is to understand that responsiveness is built into CSS, and JavaScript only manages states.
Now you have not just a set of files, but a deep understanding of how to create a modern, convenient, and maintainable interface. Happy developing!
Reviews
No reviews yet
Comments (0)
Page Discussion in Telegram
Related Posts
Content author
Offline
Sodium Carbonate
Last logged: 2026-07-11 19:44
Чем могу помочь?
Оказываю весь спектр услуг по CMF Cotonti. Разработка открытых и закрытых корпоративных интернет порталов, небольших социальных сетей, торговые площадки, маркетплейсы, биржи фриланса, каталоги товаров оптовых поставщиков, интернет-магазин под заказ, чтобы делать совместные покупки и групповые совместные продажи от имени нескольких продавцов.
Разработки на GitHub бесплатно
Telegram
- Page published: 2026-06-14 01:41
- Last update: 2026-06-14 01:43
- Language:
Русский