DEV Community

William Baker
William Baker

Posted on

Getting Started with React Offcanvas: Building Side Panels

React Offcanvas is a simple library for creating offcanvas (side panel) components in React applications. It provides an easy way to display content in a slide-out panel that overlays or pushes the main content, perfect for navigation menus, filters, and additional information panels. This guide walks through setting up and creating offcanvas panels using React Offcanvas with React, from installation to a working implementation. This is part 43 of a series on using React Offcanvas with React.

Prerequisites

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Basic knowledge of React hooks (useState)
  • Familiarity with JavaScript/TypeScript

Installation

Install React Offcanvas using your preferred package manager:

npm install react-offcanvas
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add react-offcanvas
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add react-offcanvas
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "react-offcanvas": "^0.4.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

React Offcanvas requires minimal setup. Import the component and styles:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

Let's create a simple offcanvas panel. Create a new file src/OffcanvasExample.jsx:

// src/OffcanvasExample.jsx
import React, { useState } from 'react';
import Offcanvas from 'react-offcanvas';

function OffcanvasExample() {
  const [isOpen, setIsOpen] = useState(false);

  const toggleOffcanvas = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div style={{ padding: '20px' }}>
      <h2>Offcanvas Example</h2>
      <button
        onClick={toggleOffcanvas}
        style={{
          padding: '10px 20px',
          backgroundColor: '#007bff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer'
        }}
      >
        Open Offcanvas
      </button>
      <Offcanvas
        width={300}
        transitionDuration={300}
        isMenuOpen={isOpen}
        effect="overlay"
        position="right"
      >
        <div style={{ padding: '20px' }}>
          <h3>Offcanvas Panel</h3>
          <p>This is the offcanvas content.</p>
          <button
            onClick={toggleOffcanvas}
            style={{
              padding: '8px 16px',
              backgroundColor: '#dc3545',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer',
              marginTop: '20px'
            }}
          >
            Close
          </button>
        </div>
      </Offcanvas>
    </div>
  );
}

export default OffcanvasExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import OffcanvasExample from './OffcanvasExample';
import './App.css';

function App() {
  return (
    <div className="App">
      <OffcanvasExample />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates a basic offcanvas panel that slides in from the right when the button is clicked.

Understanding the Basics

React Offcanvas provides several key features:

  • Offcanvas component: The main component that renders the side panel
  • isMenuOpen prop: Controls whether the offcanvas is open or closed
  • width prop: Sets the width of the offcanvas panel
  • position prop: Controls which side the panel slides in from (left, right, top, bottom)
  • effect prop: Controls how the panel appears (overlay, push, etc.)
  • transitionDuration: Controls animation speed

Key concepts:

  • State Management: Use useState to track whether the offcanvas is open or closed
  • Toggle Function: Create a function to toggle the offcanvas state
  • Positioning: Choose which side the panel slides in from
  • Effects: Different effects like overlay, push, or reveal
  • Styling: Customize the appearance with inline styles or CSS classes

Here's an example with different positions and effects:

// src/MultipleOffcanvasExample.jsx
import React, { useState } from 'react';
import Offcanvas from 'react-offcanvas';

function MultipleOffcanvasExample() {
  const [leftOpen, setLeftOpen] = useState(false);
  const [rightOpen, setRightOpen] = useState(false);
  const [topOpen, setTopOpen] = useState(false);

  return (
    <div style={{ padding: '20px' }}>
      <h2>Multiple Offcanvas Examples</h2>
      <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', marginTop: '20px' }}>
        <button
          onClick={() => setLeftOpen(!leftOpen)}
          style={{
            padding: '10px 20px',
            backgroundColor: '#28a745',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Open Left
        </button>
        <button
          onClick={() => setRightOpen(!rightOpen)}
          style={{
            padding: '10px 20px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Open Right
        </button>
        <button
          onClick={() => setTopOpen(!topOpen)}
          style={{
            padding: '10px 20px',
            backgroundColor: '#ffc107',
            color: 'black',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Open Top
        </button>
      </div>

      <Offcanvas
        width={250}
        isMenuOpen={leftOpen}
        effect="overlay"
        position="left"
      >
        <div style={{ padding: '20px' }}>
          <h3>Left Panel</h3>
          <p>This panel slides in from the left.</p>
          <button onClick={() => setLeftOpen(false)}>Close</button>
        </div>
      </Offcanvas>

      <Offcanvas
        width={300}
        isMenuOpen={rightOpen}
        effect="overlay"
        position="right"
      >
        <div style={{ padding: '20px' }}>
          <h3>Right Panel</h3>
          <p>This panel slides in from the right.</p>
          <button onClick={() => setRightOpen(false)}>Close</button>
        </div>
      </Offcanvas>

      <Offcanvas
        width="100%"
        isMenuOpen={topOpen}
        effect="overlay"
        position="top"
      >
        <div style={{ padding: '20px' }}>
          <h3>Top Panel</h3>
          <p>This panel slides in from the top.</p>
          <button onClick={() => setTopOpen(false)}>Close</button>
        </div>
      </Offcanvas>
    </div>
  );
}

export default MultipleOffcanvasExample;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a navigation menu with an offcanvas panel:

// src/NavigationWithOffcanvas.jsx
import React, { useState } from 'react';
import Offcanvas from 'react-offcanvas';

function NavigationWithOffcanvas() {
  const [isOpen, setIsOpen] = useState(false);

  const menuItems = [
    { id: 1, label: 'Home', href: '#home' },
    { id: 2, label: 'About', href: '#about' },
    { id: 3, label: 'Services', href: '#services' },
    { id: 4, label: 'Portfolio', href: '#portfolio' },
    { id: 5, label: 'Contact', href: '#contact' }
  ];

  const toggleMenu = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div>
      <nav style={{
        backgroundColor: '#2c3e50',
        padding: '15px 30px',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
      }}>
        <div style={{ color: 'white', fontSize: '24px', fontWeight: 'bold' }}>
          MyApp
        </div>
        <button
          onClick={toggleMenu}
          style={{
            padding: '8px 16px',
            backgroundColor: 'transparent',
            border: '2px solid white',
            color: 'white',
            borderRadius: '4px',
            cursor: 'pointer',
            fontSize: '16px'
          }}
        >
          ☰ Menu
        </button>
      </nav>

      <Offcanvas
        width={280}
        isMenuOpen={isOpen}
        effect="overlay"
        position="right"
        transitionDuration={300}
      >
        <div style={{
          padding: '30px 20px',
          height: '100%',
          backgroundColor: '#34495e',
          color: 'white'
        }}>
          <div style={{
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            marginBottom: '30px',
            paddingBottom: '20px',
            borderBottom: '2px solid rgba(255, 255, 255, 0.3)'
          }}>
            <h2 style={{ margin: 0 }}>Navigation</h2>
            <button
              onClick={toggleMenu}
              style={{
                background: 'none',
                border: 'none',
                color: 'white',
                fontSize: '24px',
                cursor: 'pointer',
                padding: '5px 10px'
              }}
            >
              ×
            </button>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
            {menuItems.map(item => (
              <a
                key={item.id}
                href={item.href}
                onClick={toggleMenu}
                style={{
                  color: 'white',
                  textDecoration: 'none',
                  padding: '15px',
                  borderRadius: '8px',
                  transition: 'background-color 0.3s',
                  fontSize: '18px'
                }}
                onMouseEnter={(e) => e.target.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'}
                onMouseLeave={(e) => e.target.style.backgroundColor = 'transparent'}
              >
                {item.label}
              </a>
            ))}
          </div>
        </div>
      </Offcanvas>

      <div style={{ padding: '40px' }}>
        <h1>Welcome to My App</h1>
        <p>Click the menu button to open the offcanvas navigation panel.</p>
      </div>
    </div>
  );
}

export default NavigationWithOffcanvas;
Enter fullscreen mode Exit fullscreen mode

Now create a filter panel component:

// src/FilterPanel.jsx
import React, { useState } from 'react';
import Offcanvas from 'react-offcanvas';

function FilterPanel() {
  const [isOpen, setIsOpen] = useState(false);
  const [filters, setFilters] = useState({
    category: '',
    priceRange: '',
    sortBy: ''
  });

  const handleFilterChange = (name, value) => {
    setFilters(prev => ({ ...prev, [name]: value }));
  };

  const applyFilters = () => {
    console.log('Applied filters:', filters);
    setIsOpen(false);
  };

  const clearFilters = () => {
    setFilters({ category: '', priceRange: '', sortBy: '' });
  };

  return (
    <div style={{ padding: '20px' }}>
      <button
        onClick={() => setIsOpen(true)}
        style={{
          padding: '10px 20px',
          backgroundColor: '#007bff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer'
        }}
      >
        Open Filters
      </button>

      <Offcanvas
        width={320}
        isMenuOpen={isOpen}
        effect="overlay"
        position="right"
      >
        <div style={{ padding: '30px 20px', height: '100%' }}>
          <div style={{
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            marginBottom: '30px',
            paddingBottom: '20px',
            borderBottom: '2px solid #ddd'
          }}>
            <h2 style={{ margin: 0 }}>Filters</h2>
            <button
              onClick={() => setIsOpen(false)}
              style={{
                background: 'none',
                border: 'none',
                fontSize: '24px',
                cursor: 'pointer',
                padding: '5px 10px'
              }}
            >
              ×
            </button>
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
            <div>
              <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
                Category
              </label>
              <select
                value={filters.category}
                onChange={(e) => handleFilterChange('category', e.target.value)}
                style={{
                  width: '100%',
                  padding: '8px',
                  border: '1px solid #ddd',
                  borderRadius: '4px'
                }}
              >
                <option value="">All Categories</option>
                <option value="electronics">Electronics</option>
                <option value="clothing">Clothing</option>
                <option value="books">Books</option>
                <option value="home">Home & Garden</option>
              </select>
            </div>

            <div>
              <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
                Price Range
              </label>
              <select
                value={filters.priceRange}
                onChange={(e) => handleFilterChange('priceRange', e.target.value)}
                style={{
                  width: '100%',
                  padding: '8px',
                  border: '1px solid #ddd',
                  borderRadius: '4px'
                }}
              >
                <option value="">All Prices</option>
                <option value="0-50">$0 - $50</option>
                <option value="50-100">$50 - $100</option>
                <option value="100-200">$100 - $200</option>
                <option value="200+">$200+</option>
              </select>
            </div>

            <div>
              <label style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
                Sort By
              </label>
              <select
                value={filters.sortBy}
                onChange={(e) => handleFilterChange('sortBy', e.target.value)}
                style={{
                  width: '100%',
                  padding: '8px',
                  border: '1px solid #ddd',
                  borderRadius: '4px'
                }}
              >
                <option value="">Default</option>
                <option value="price-asc">Price: Low to High</option>
                <option value="price-desc">Price: High to Low</option>
                <option value="name-asc">Name: A to Z</option>
                <option value="name-desc">Name: Z to A</option>
              </select>
            </div>
          </div>

          <div style={{
            display: 'flex',
            gap: '10px',
            marginTop: '30px',
            paddingTop: '20px',
            borderTop: '2px solid #ddd'
          }}>
            <button
              onClick={applyFilters}
              style={{
                flex: 1,
                padding: '12px',
                backgroundColor: '#007bff',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer',
                fontSize: '16px'
              }}
            >
              Apply Filters
            </button>
            <button
              onClick={clearFilters}
              style={{
                flex: 1,
                padding: '12px',
                backgroundColor: '#6c757d',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer',
                fontSize: '16px'
              }}
            >
              Clear
            </button>
          </div>
        </div>
      </Offcanvas>
    </div>
  );
}

export default FilterPanel;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import NavigationWithOffcanvas from './NavigationWithOffcanvas';
import FilterPanel from './FilterPanel';
import './App.css';

function App() {
  return (
    <div className="App">
      <NavigationWithOffcanvas />
      <div style={{ padding: '40px' }}>
        <FilterPanel />
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Navigation menu with offcanvas panel
  • Filter panel with form controls
  • Multiple offcanvas panels
  • Different positions (left, right, top)
  • State management for open/close
  • Custom styling and theming

Common Issues / Troubleshooting

  1. Offcanvas not appearing: Make sure the isMenuOpen prop is set to true and the component is properly rendered. Check that you're managing the state correctly with useState.

  2. Panel not closing: Ensure you have a close button or function that sets isMenuOpen to false. The offcanvas won't close automatically unless you handle the state.

  3. Styling issues: If the offcanvas doesn't look right, check for CSS conflicts. You can customize the appearance using inline styles on the content div inside the Offcanvas component.

  4. Position not working: Make sure you're using a valid position value: 'left', 'right', 'top', or 'bottom'. The default is usually 'left'.

  5. Animation not smooth: Adjust the transitionDuration prop to control animation speed. Higher values (in milliseconds) create slower animations.

  6. Content overflow: If content is too long, add scrolling to the offcanvas content div using overflow-y: auto in the style.

Next Steps

Now that you have a basic understanding of React Offcanvas:

  • Learn about different effects (overlay, push, reveal)
  • Explore advanced positioning options
  • Implement keyboard navigation for accessibility
  • Add animations and transitions
  • Integrate with React Router for navigation
  • Learn about other offcanvas libraries (react-sidebar, react-slide-menu)
  • Check the official repository: https://github.com/vutran/react-offcanvas
  • Look for part 44 of this series for more advanced topics

Summary

You've successfully set up React Offcanvas in your React application and created side panels for navigation and filters. React Offcanvas provides a simple solution for displaying content in slide-out panels with smooth animations.

SEO Keywords

react-offcanvas
React side panel
react-offcanvas tutorial
React slide-out menu
react-offcanvas installation
React offcanvas component
react-offcanvas example
React side navigation
react-offcanvas setup
React panel component
react-offcanvas customization
React overlay panel
react-offcanvas positioning
React side menu
react-offcanvas getting started

Top comments (0)