Framework7 Side Panels Deep Dive — Advanced Navigation, Gestures, and Behaviors
In this tutorial, you will learn about Framework7 Side Panels Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.
Framework7 side panels provide advanced navigation drawer capabilities with configurable swipe thresholds, reveal and cover modes, panel resizing, nested panels, and platform-adaptive behaviors.
What You'll Learn
- Panel sizing and custom widths
- Swipe gesture configuration
- Nested and stacked panels
- Panel animations and transitions
- Responsive panel behavior
Why It Matters
Side panels are a primary navigation pattern in mobile apps. Advanced panel features — like resizable panels on tablets, nested panels for multi-level menus, and swipe gesture customization — create professional navigation experiences.
Real-World Use
A tablet-optimized email app with a resizable left panel for folder list, a swipeable right panel for contact info, a nested sub-panel for folder management, and responsive behavior that shows panels permanently on tablets.
Panel Architecture
flowchart TD
A[Advanced Panels] --> B[Sizing]
A --> C[Gestures]
A --> D[Nested]
A --> E[Responsive]
B --> F[Fixed Width]
B --> G[Percentage]
B --> H[Resizable]
C --> I[Threshold]
C --> J[Edge Only]
D --> K[Sub Panel]
D --> L[Stacked]
E --> M[Permanent]
E --> N[Collapsible]
style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
Panel Sizing
<!-- Fixed width panel -->
<div class="panel panel-left panel-cover" style="width:300px">
<!-- Content -->
</div>
<!-- Percentage width panel -->
<div class="panel panel-left panel-reveal" data-width="80">
<!-- Takes 80% of screen width -->
</div>
<!-- Panel with min/max constraints -->
<div class="panel panel-left panel-cover" style="width:320px; max-width:90%">
</div>
// Set panel width programmatically
var panel = app.panel.get('.panel-left');
panel.setWidth(280); // px
// Get panel width
var width = panel.getWidth();
console.log('Panel width:', width);
// Resizable panel (tablet only)
var panel = app.panel.create({
el: '.panel-left',
resizable: true,
minWidth: 200,
maxWidth: 400,
on: {
resize: function(panel, width) {
console.log('Panel resized to:', width);
}
}
});
Expected output: Panel appears at the specified width. On tablets with resizable enabled, a drag handle appears at the panel edge for resizing.
Swipe Gesture Configuration
<!-- Panel with custom swipe settings via data attributes -->
<div class="panel panel-left panel-reveal"
data-swipe="true"
data-swipe-only-close="false"
data-swipe-threshold="50"
data-swipe-active-area="30">
</div>
// Programmatic swipe configuration
var panel = app.panel.create({
el: '.panel-left',
swipe: true,
swipeOnlyClose: false, // true = swipe only closes, never opens
swipeThreshold: 50, // px before panel snaps open
swipeActiveArea: 30, // px from edge to trigger swipe
swipeBackButton: true, // Enable swipe-back gesture for navigation
on: {
swipe: function(panel, progress) {
// progress: 0 to 1
console.log('Swipe progress:', progress);
}
}
});
// Enable/disable swipe
panel.enableSwipe();
panel.disableSwipe();
// Set swipe threshold dynamically
panel.setSwipeThreshold(80);
Expected output: Swiping from the left edge opens the panel with configurable threshold and active area. The swipe progress event fires continuously during the gesture.
Nested Panels
<!-- First level panel -->
<div class="panel panel-left panel-cover" id="main-panel">
<div class="page">
<div class="page-content">
<div class="list">
<ul>
<li class="item-divider">Main Menu</li>
<li>
<a href="#" class="item-link item-content" id="open-sub-panel">
<div class="item-media"><i class="icon f7-icons">folder</i></div>
<div class="item-inner">
<div class="item-title">Projects</div>
<div class="item-after"><i class="icon f7-icons">chevron-right</i></div>
</div>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Second level sub-panel -->
<div class="panel panel-left panel-cover panel-nested" id="sub-panel">
<div class="page">
<div class="navbar">
<div class="navbar-inner">
<div class="left">
<a href="#" class="link back-to-main">
<i class="icon icon-back"></i>
<span>Back</span>
</a>
</div>
<div class="title">Projects</div>
</div>
</div>
<div class="page-content">
<div class="list">
<ul>
<li><a href="#" class="item-link item-content"><div class="item-inner"><div class="item-title">Project Alpha</div></div></a></li>
<li><a href="#" class="item-link item-content"><div class="item-inner"><div class="item-title">Project Beta</div></div></a></li>
</ul>
</div>
</div>
</div>
</div>
// Open sub-panel when clicking in main panel
$$('#open-sub-panel').on('click', function() {
app.panel.close('.panel-left');
app.panel.open('#sub-panel');
});
// Back button returns to main panel
$$('.back-to-main').on('click', function() {
app.panel.close('#sub-panel');
app.panel.open('.panel-left');
});
Expected output: Clicking "Projects" in the main panel opens a sub-panel that slides in from the same edge. The back button closes the sub-panel and reopens the main panel.
Panel Animations
// Custom panel animation duration
var panel = app.panel.create({
el: '.panel-left',
animate: true,
animateDuration: 400, // ms
// Custom CSS animation classes
animateClasses: {
open: 'panel-custom-open',
close: 'panel-custom-close'
}
});
// Open with custom animation
panel.open({ animate: true, velocity: 300 });
// Close with animation
panel.close({ animate: true });
// Panel events for animation synchronization
panel.on('open', function() {
console.log('Panel started opening');
});
panel.on('opened', function() {
console.log('Panel fully opened');
// Start content animation
$$('.panel-content').addClass('fade-in');
});
panel.on('close', function() {
console.log('Panel started closing');
});
panel.on('closed', function() {
console.log('Panel fully closed');
});
/* Custom panel animations */
.panel-custom-open {
transition-duration: 500ms;
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.panel-custom-close {
transition-duration: 300ms;
transition-timing-function: ease-in;
}
Expected output: Panel opens and closes with custom timing functions and durations. The opened event fires after the animation completes, useful for triggering content animations.
Responsive Panel Behavior
<div class="panel panel-left panel-cover"
id="responsive-panel"
data-swipe="true">
</div>
// Responsive panel: show permanently on tablets, swipe on phones
var app = new Framework7({
on: {
init: function() {
var panel = this.panel.get('.panel-left');
if (this.device.tablet) {
// Show as permanent sidebar on tablets
panel.open({ animate: false });
panel.disableSwipe();
// Add resize handle
panel.resizable = true;
} else {
// Swipeable drawer on phones
panel.enableSwipe();
panel.resizable = false;
}
},
resize: function() {
// Handle orientation change
var width = window.innerWidth;
var panel = this.panel.get('.panel-left');
if (width >= 768 && !panel.opened) {
panel.open({ animate: false });
panel.disableSwipe();
} else if (width < 768 && panel.opened) {
panel.close({ animate: false });
panel.enableSwipe();
}
}
}
});
// Check panel state
if (panel.opened) {
console.log('Panel is open');
}
Expected output: On tablets (768px+), the panel shows permanently as a sidebar. On phones, it works as a swipeable drawer. Orientation changes toggle between modes.
Panel with Nested Views
<div class="panel panel-left panel-cover" id="menu-panel">
<div class="view" id="panel-view">
<div class="page">
<div class="navbar">
<div class="navbar-inner">
<div class="title">Menu</div>
</div>
</div>
<div class="page-content">
<div class="list">
<ul>
<li><a href="/dashboard/" class="item-link item-content panel-close">
<div class="item-inner"><div class="item-title">Dashboard</div></div>
</a></li>
<li><a href="/users/" class="item-link item-content panel-close">
<div class="item-inner"><div class="item-title">Users</div></div>
</a></li>
<li><a href="/settings/" class="item-link item-content panel-close">
<div class="item-inner"><div class="item-title">Settings</div></div>
</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
// Initialize panel with its own view
var app = new Framework7({
panel: {
left: {
el: '#menu-panel',
view: '#panel-view'
}
},
views: [
{ el: '#panel-view', routes: [/* panel routes */] }
]
});
// Navigate within the panel view
var panelView = app.views.get('#panel-view');
panelView.router.navigate('/settings/');
Expected output: The panel contains its own view with navigation capability. Links inside the panel navigate within the panel view, while panel-close links close the panel and navigate the main view.
Common Mistakes
Not handling panel z-index stacking - Multiple panels stack in DOM order. The last panel in the DOM appears on top. Manage z-index with CSS if needed.
Using nested panels without proper close/open sequencing - Opening a sub-panel while a main panel is open requires closing the main panel first. Always sequence panel operations.
Ignoring swipe gesture conflicts - Swipeable panels can conflict with horizontal carousels or swipeable list items. Disable panel swipe on pages with horizontal scrolling.
Not testing panel behavior on different devices - Panels behave differently on phones (overlay) vs tablets (permanent). Test both orientations and device sizes.
Forgetting to set panel width on reveal mode - Reveal mode pushes the view. If the panel width is not set, the view shifts by an unpredictable amount. Always set width explicitly.
Practice Questions
- How do you make a panel resizable by the user?
- What is the swipe threshold and how does it affect panel opening?
- How do you create nested panels (sub-panels)?
- How do you make a panel permanent on tablets and swipeable on phones?
- How do you navigate within a panel using its own view?
Challenge: Build a responsive email app layout with: a left panel for folders (Inbox, Sent, Drafts, Trash) that is permanent on tablets and swipeable on phones, a sub-panel for folder management (create, rename, delete), resizable panel on tablets with drag handle, swipe gesture that closes panel when swiping right on the main content, and responsive behavior that collapses the panel on orientation change.
FAQ
Mini Project
Build a responsive news app with: a left panel (categories, saved articles, settings) that is permanent on tablets (resizable, 300px default) and swipeable on phones (cover mode, 80% width), a nested sub-panel for category management, smooth animations with custom timing, viewport-based responsive behavior, and a right panel for article previews with swipe-to-close.
What's Next
Panels manage navigation. Learn how Framework7 Data Table and Grid displays tabular data with sorting, pagination, and responsive columns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro