CSS Flexbox with Sidebars Toggle

This post documents how to create a fully responsive flexbox content layout that contains a pure CSS open/close toggle for the left and right sidebars. The CSS is built from Sass using Webpack with Autoprefixer. For development, a Webpack Dev Server is included.

Features

  • Pure CSS Sidebar toggle
  • Sass CSS preprocessor
  • PostCSS Autoprefixer
  • CSSnano
  • Webpack 4
  • Webpack Dev Server

responsive sidebars in flexbox

Getting Started

Create an index.html file in the root of the project with the following HTML. This markup creates a a layout with header, content and footer containers. The content container includes aside containers for the sidebars and checkbox inputs to toggle the sidebars open or closed.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" media="all" href="/dist/app.css">
</head>
<body>
    <div class="site">
        <header class="header">Header</header>
        <div class="content">
            <article class="main">
                <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>
            </article>
            <input type="checkbox" id="sidebar-1-toggle">
            <label for="sidebar-1-toggle"></label>
            <aside class="sidebar sidebar-1">
                <div>Sidebar 1</div>
            </aside>
            <input type="checkbox" id="sidebar-2-toggle">
            <label for="sidebar-2-toggle"></label>
            <aside class="sidebar sidebar-2">
                <div>Sidebar 2</div>
            </aside>
        </div>
        <footer class="footer">Footer</footer>
    </div>
</body>
</html>
  • Using Emmet, which is built into VS Code, you can create the index.html content by entering an exclamation mark on the first line then select the tab key.

Create a src/sass folder in the project. And in the sass folder create a style.scss file with the following css.

style.scss
body {
  min-width: 400px;
}

.site {
  max-width: 1440px;
  margin: 0 auto;
  position: relative;
  font-weight: bold;
  text-align: center;
}

.content {
  display: flex;
}

.header {
  background: tomato;
  padding: 10px;
}

.footer {
  background: lightgreen;
  padding: 10px;
}

.main {
  flex: 1;
  min-width: 0;
  text-align: left;
  background: deepskyblue;
  padding: 10px;
}

.sidebar > div {
  padding: 10px;
}

.sidebar-1 {
  background: gold;
}

.sidebar-2 {
  background: hotpink;
}

@media all and (max-width: 800px) {
  .content {
    flex-wrap: wrap;
  }
  .main {
    flex: 1 1 100%;
  }
}

@media all and (min-width: 801px) {
  .sidebar-1 { order: 1; }
  .main    { order: 2; }
  .sidebar-2 { order: 3; }
}

Webpack

Webpack is going to be used to process the Sass as its saved into CSS and serve the HTML locally.

  • Pre-requisite – the Webpack module bundler example here requires Node.js.

Use your favorite Node.js bash command line to setup the project. Navigate to the project root and enter npm init. Accepting the defaults is fine.

npm init

Now we’re ready to install Webpack and other devDependencies into our project. In case you’re wondering, the projects node_modules folder is where all of these packages get installed. For more information, visit npmjs.com and/or watch this introduction to npm video.

Install Webpack and its Command Line Interface.

npm install -D webpack webpack-cli

Create a config folder for the webpack configuration files.

In this config folder, there will be three webpack configuration files, base.config.js, dev.config.js and prod.config.js.

First, create the config/base.config.js which holds the configuration common to both dev and production.

base.config.js
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  context: path.resolve(__dirname, '../src'),
  entry: {
    app: './js/index.js'
  },
  plugins: [
    new MiniCssExtractPlugin
  ],
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          {
            loader: 'postcss-loader' },
          {
            loader: 'sass-loader' }
        ]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      }
    ]
  }
}

The mini-css-extract-plugin extracts the css from the bundle and outputs the css file after the loaders process the sass.

Install the plugin and loaders for the base configuration.

npm install -D mini-css-extract-plugin

npm install -D babel-loader @babel/core @babel/preset-env

npm install -D autoprefixer style-loader css-loader postcss-loader

npm install -D node-sass sass-loader

Next, create the config/dev.config.js.

dev.config.js
const baseConfig = require('./base.config.js');
const merge = require('webpack-merge');
const writeFilePlugin = require('write-file-webpack-plugin');

module.exports = merge(baseConfig, {
  devtool: 'eval-source-map',
  plugins: [
    new writeFilePlugin()
  ]
});

Install the webpack-dev-server, write-file-webpack-plugin and webpack-merge.

npm install -D webpack-dev-server write-file-webpack-plugin

npm install -D webpack-merge

The write-file-webpack-plugin, like the mini-css-extract-plugin, outputs the css file for the in-memory webpack dev server. However, this plugin does not do anything unless it is used with the webpack-dev-server.

We are using webpack-merge to combine our separate webpack configuration modules.

We need a small configuration file for postcss. In the project root, create a file named postcss.config.js.

postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

Create the config/prod.config.js.

prod.config.js
const baseConfig = require('./base.config.js');
const merge = require('webpack-merge');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");

module.exports = merge(baseConfig, {
  optimization: {
      minimizer: [
        new OptimizeCSSAssetsPlugin({})
      ]
  },
  plugins: [
    new UglifyJsPlugin({
      uglifyOptions: { output: { comments: false } }
    })
  ]
});

Install the optimize-css-assets-webpack-plugin and uglifyjs-webpack-plugin to make our client side assets as small as possible for production.

npm install -D optimize-css-assets-webpack-plugin

npm install -D uglifyjs-webpack-plugin

Supported Browsers

Add a browserslist property to the package.json file to define which vender prefixes to with the generated CSS. In this example, we target browsers greater than 2% according to global usage statistics and Internet Explorer versions greater than 9. [more info]

package.json
{
  "name": "blank",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "browserslist": [
    "> 2%",
    "ie > 9"
  ],

  ...
}

npm-run-script

Add these webpack commands to the scripts property. These npm script commands are what we will run to kickoff the webpack dev server and watch or prod webpack build.

package.json
  ...

  "browserslist": [
    "> 2%",
    "ie > 9"
  ],
  "scripts": {
    "dev": "webpack-dev-server --mode=development --watch --progress --colors --config config/dev.config.js --open",
    "build": "webpack --mode=production --progress --hide-modules --config config/prod.config.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

  ...
}

Entrypoint JavaScript

Create a src/js/index.js javascript entrypoint file to import the style Sass for the webpack bundle.

index.js
import style from '../sass/style.scss'

Sidebar Toggle Sass

Create these three Sass partials for the sidebar toggle, src/sass/_toggle.scss, src/sass/_default-open.scss and src/sass/_default-closed.scss.

This is the _toggle.scss partial imported by the style.scss file.

_toggle.scss
/**
 * Sidebar toggle
 */

// uncomment for default closed sidebars
// @import "default-closed";
// uncomment for default open sidebars
@import "default-open";

/**
 * checkboxes open/close the sidebars
 * #sidebar-1-toggle and #sidebar-2-toggle
 */
 #sidebar-1-toggle, #sidebar-2-toggle {
  display: none;
}

label,
label {
  position: absolute;
  top: 0;
  display: block;
  padding: .25em 10px;
  font-size: 1.5em;
  cursor: pointer;
}

label {
  left: 0;
}

label {
  right: 0;
}

@media all and (max-width: 800px) {
  label,
  label {
    display: none;
  }
  .sidebar-1,
  .sidebar-2 {
    display: block;
    width: 50%;
  }
}

This is the _default-open.scss partial imported by the _toggle.scss.

_default-open.scss
/**
 * Sidebars open by default
 */

.sidebar {
  width: 20%;
}

label,
label {
  &:after {
    content: '\2013';
  }
}

#sidebar-1-toggle:checked ~ label,
#sidebar-2-toggle:checked ~ label {
  &:after {
    content: '+';
  }
}

#sidebar-1-toggle:checked ~ .sidebar-1,
#sidebar-2-toggle:checked ~ .sidebar-2 {
  display: none;
  width: 0;
}

This is the _default-closed.scss partial imported by the _toggle.scss file when uncommented.

_default-closed.scss
/**
 * Sidebars closed by default
 */

.sidebar {
  display: none;
}

label,
label {
  &:after {
    content: '+';
  }
}

#sidebar-1-toggle:checked ~ label,
#sidebar-2-toggle:checked ~ label {
  &:after {
    content: '\2013';
  }
}

#sidebar-1-toggle:checked ~ .sidebar-1,
#sidebar-2-toggle:checked ~ .sidebar-2 {
  display: block;
  width: 20%;
}

Webpack Dev Server

For development, use npm run dev to launch the dev server, open the browser, watch for changes and build incrementally when changes are saved.

npm run dev

Now test the development server and file watch build. Edit the _toggle.scss file and comment @import "default-open";, uncomment @import "default-closed";. Save the file, check the terminal output and refresh the browser.

For production, cancel the dev task Ctrl + C and run the build.

npm run build 
Source Code
Loading Disqus Comments ...
Loading Facebook Comments ...