How to Toggle a Sidebar in React
By Braincuber Team
Published on January 17, 2026
Every dashboard, admin panel, and complex web app eventually needs one: a collapsible sidebar. It's the universal solution for balancing navigation access with screen real estate. When your user needs to focus on content, they collapse it. When they need to navigate, they expand it. Simple concept, but the implementation details matter.
In this tutorial, we'll build a smooth, animated sidebar toggle in React. Not just a show/hide flip—we're adding CSS transitions for a polished feel, proper state management, and a responsive design that works across devices. By the end, you'll have a reusable pattern you can drop into any project.
What You'll Build: A responsive sidebar with smooth slide-in/slide-out animations, a hamburger toggle button, navigation links, and proper state management using React's useState hook.
Why Toggle Sidebars Matter
Space Efficiency
Maximize content area when navigation isn't needed. Critical for data-heavy dashboards and editing interfaces.
Mobile Friendly
On smaller screens, a fixed sidebar eats precious space. Collapsible sidebars are essential for responsive design.
User Control
Let users choose their preferred layout. Some like navigation visible; others prefer a clean workspace.
Professional Polish
Smooth animations signal quality. A jarring show/hide feels broken; a slide transition feels intentional.
Prerequisites
- Node.js (v16+) installed
- Basic React knowledge (components, JSX)
- Understanding of CSS Flexbox
- A code editor (VS Code recommended)
Step 1: Project Setup
Create a fresh React application or use an existing one:
# Create new React app npx create-react-app sidebar-demo cd sidebar-demo # Start development server npm start
Step 2: Build the Sidebar Component
Replace the contents of src/App.js with our sidebar implementation. We're using inline styles for simplicity, but you can easily extract these to a CSS file or use styled-components:
import React, { useState } from 'react';
function App() {
const [isOpen, setIsOpen] = useState(true);
const toggleSidebar = () => {
setIsOpen(!isOpen);
};
// Navigation items
const navItems = [
{ icon: '📊', label: 'Dashboard', href: '#' },
{ icon: '📁', label: 'Projects', href: '#' },
{ icon: '👥', label: 'Team', href: '#' },
{ icon: '📅', label: 'Calendar', href: '#' },
{ icon: '⚙️', label: 'Settings', href: '#' },
];
return (
<div style={{ display: 'flex', minHeight: '100vh', background: '#f3f4f6' }}>
{/* Sidebar */}
<aside
style={{
width: isOpen ? '260px' : '0px',
overflow: 'hidden',
background: 'linear-gradient(180deg, #1e293b 0%, #0f172a 100%)',
transition: 'width 0.3s ease-in-out',
display: 'flex',
flexDirection: 'column',
}}
>
{/* Logo Area */}
<div style={{ padding: '24px', borderBottom: '1px solid #334155' }}>
<h2 style={{ color: '#fff', fontSize: '1.5rem', fontWeight: 700, margin: 0 }}>
MyApp
</h2>
</div>
{/* Navigation Links */}
<nav style={{ padding: '16px 0', flex: 1 }}>
{navItems.map((item, index) => (
<a
key={index}
href={item.href}
style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
padding: '12px 24px',
color: '#cbd5e1',
textDecoration: 'none',
transition: 'background 0.2s',
whiteSpace: 'nowrap',
}}
onMouseEnter={(e) => e.target.style.background = '#334155'}
onMouseLeave={(e) => e.target.style.background = 'transparent'}
>
<span style={{ fontSize: '1.25rem' }}>{item.icon}</span>
<span>{item.label}</span>
</a>
))}
</nav>
{/* User Profile */}
<div style={{ padding: '16px 24px', borderTop: '1px solid #334155' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style={{
width: '40px',
height: '40px',
borderRadius: '50%',
background: '#3b82f6',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontWeight: 600,
}}>
JD
</div>
<div style={{ whiteSpace: 'nowrap' }}>
<div style={{ color: '#fff', fontWeight: 500 }}>John Doe</div>
<div style={{ color: '#94a3b8', fontSize: '0.875rem' }}>Admin</div>
</div>
</div>
</div>
</aside>
{/* Main Content */}
<main style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
{/* Header */}
<header style={{
background: '#fff',
padding: '16px 24px',
display: 'flex',
alignItems: 'center',
gap: '16px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
}}>
{/* Toggle Button */}
<button
onClick={toggleSidebar}
style={{
background: 'transparent',
border: 'none',
cursor: 'pointer',
padding: '8px',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<svg width="24" height="24" fill="none" stroke="#1e293b" strokeWidth="2">
<path d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<h1 style={{ margin: 0, fontSize: '1.25rem', color: '#1e293b' }}>Dashboard</h1>
</header>
{/* Content */}
<div style={{ padding: '24px', flex: 1 }}>
<div style={{
background: '#fff',
borderRadius: '12px',
padding: '32px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
}}>
<h2 style={{ marginTop: 0, color: '#1e293b' }}>Welcome to Your Dashboard</h2>
<p style={{ color: '#64748b', lineHeight: 1.6 }}>
Click the hamburger menu icon in the header to toggle the sidebar.
The sidebar smoothly transitions thanks to CSS transitions on the width property.
</p>
</div>
</div>
</main>
</div>
);
}
export default App;
How It Works
State Management with useState
We track whether the sidebar is open with a boolean state:
const [isOpen, setIsOpen] = useState(true);
Starting with true means the sidebar is visible on page load.
Toggle Function
A simple function flips the state:
const toggleSidebar = () => setIsOpen(!isOpen);
Attached to the hamburger button's onClick event.
CSS Transition Magic
The smooth animation comes from this single CSS property:
transition: 'width 0.3s ease-in-out'
Combined with overflow: hidden, the content fades naturally as width shrinks.
Conditional Width
Instead of conditionally rendering, we change the width:
width: isOpen ? '260px' : '0px'
This preserves the DOM element, enabling smooth CSS transitions.
Alternative: Transform-Based Animation
For better performance (especially on mobile), you can use transform instead of width:
// Sidebar styles with transform
<aside
style={{
width: '260px',
position: 'fixed',
left: 0,
top: 0,
height: '100vh',
transform: isOpen ? 'translateX(0)' : 'translateX(-100%)',
transition: 'transform 0.3s ease-in-out',
background: 'linear-gradient(180deg, #1e293b 0%, #0f172a 100%)',
zIndex: 1000,
}}
>
{/* Sidebar content */}
</aside>
{/* Add left margin to main content when sidebar is open */}
<main style={{ marginLeft: isOpen ? '260px' : '0', transition: 'margin 0.3s ease-in-out' }}>
{/* Main content */}
</main>
Performance Tip: transform and opacity are GPU-accelerated properties. They don't trigger layout recalculations, making them smoother than animating width or margin.
Best Practices
Sidebar UX Tips:
- Remember State: Save the open/closed preference to localStorage so it persists across sessions.
- Keyboard Accessible: Ensure the toggle button is focusable and works with Enter/Space keys.
- Overlay on Mobile: On small screens, consider having the sidebar overlay the content with a backdrop.
- Avoid Layout Shift: If using
widthanimation, ensure your main content handles the resize gracefully.
Conclusion
A collapsible sidebar is one of the most versatile UI patterns in web development. With React's state management and a few lines of CSS for transitions, you can create a polished, professional navigation experience. The key ingredients: useState for tracking visibility, conditional styling for the width/transform, and transition for smooth animations.
Key Takeaway: Use useState to track open/closed state, apply width or transform based on state, add transition for smooth animations. For mobile-first apps, prefer transform: translateX() for GPU-accelerated performance.
