Folding the Flat with CSS
Tags: react, tutorial, ux, css
Published: 2019-07-23 10:11:46

A pure CSS approach that folds offscreen panel flat.


In this article we’ll create an offscreen panel with folds effect and elastic motion content from within.

I saw tons of cool demos over the years on the internet that has been done with Canvas, Threejs and all these heavy techniques . I’ve looking for a simpler way since then!

I had that task the other day to build a chat panel for the project am working on that appears by hitting the “Send a Message” button on the user’s profile, so users can communicate with each other. After getting the main functionality done and fully working, based on our workflow I had to forward to the QA team after getting done so they start testing! But I did not forward for some reasons 🤷‍♂️. Since I had enough time I wanted to make that chat a foldable animated panel with a less effort 🦸‍♂️. A few moments later after scratching my beard I decided to use a little bit CSS `transition` and [clip-path](https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path) `polygon(...)` to accomplish this feature/task.


## Demo

I'm using a [CRA](https://github.com/facebook/create-react-app) boilerplate in this demo to create a [React](https://reactjs.org/) component. But you can stick with whatever stack you're comfortable with.

Demo Link: https://elmahdim.github.io/OffScreenPanel/

## OffScreenPanel Component


```javascript
import React, { useState } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import styles from './OffScreenPanel.module.css';
export const OffScreenPanel = (props) => {
  const [open, toggleVisibility] = useState(false);
  const toggle = () => toggleVisibility(!open);
  return (
    <div className={classNames('offScreenPanel', {
      [styles.open]: open
    })}>
      <button type="button" className={styles.button} onClick={toggle}>
        ....
      </button>
      <div className={styles.panel}>
        <div className={styles.body}>{open && props.children}</div>
      </div>
      <div role="presentation" className={styles.overlay} onClick={toggle} />
    </div >
  );
};
OffScreenPanel.propTypes = {
  children: PropTypes.any,
};
export default OffScreenPanel;
```

The code above represents a functional `OffScreenPanel` component that using [Hooks](https://reactjs.org/docs/hooks-intro.html) and [CSS Modules Stylesheet](https://facebook.github.io/create-react-app/docs/adding-a-css-modules-stylesheet).


## Plain HTML

The main elements we need to fold/unfold our panel without React.


```html
<div class="offScreenPanel open">
  <div class="panel">
    <div class="body">...</div>
  </div>
  <div role="presentation" class="overlay"></div>
</div>
```

The class `open` is toggleable (via JavaScript) on the `offScreenPanel` element. It has no associated styles.

The `panel` element is responsable to fold/unfold its layout. We have two options to achieve this effect: add two more extra elements, or use CSS pseudo element!

I'm going to pick the second option which is using pseudo element (`::before`, `::after`). It makes our markup cleaner and less HTML codes.

The inner content will be wrapped by the element `body`.


## Styles

```css
/*
 * panel:
 * is out of screen (offset right) by default
 */
.panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 450px;
  bottom: 0;
  z-index: 2;
  transform: translateX(450px);
}

/*
 * panel:
 * on open we set its horizontally offset to "0"
 */
.open .panel {
  transform: translateX(0);
  transition: all 400ms ease;
}

/*
 * panel - the folding element [[]]
 * make each element half width of its parent (panel) size
 */
.panel::before,
.panel::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 225px;
  transition: all 400ms ease-out;
}

/*
 * panel - the folding element []]
 */
.panel::before {
  clip-path: polygon(100% 10%, 100% 0, 100% 100%, 100% 90%);
  left: 0;
}

/*
 * panel - the folding element []]
 */
.panel::after {
  background: #f0f0f0 linear-gradient(to right, #f7f7f7 0%, #fff 100%);
  clip-path: polygon(100% 50%, 100% 0, 100% 100%, 100% 50%);
  right: 0;
}

/*
 * panel - the folding element []]
 */
.open .panel::before,
.open .panel::after {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

/*
 * panel - the folding element [[]
 * giving the left panel a paper book like background,
 * off-white and light grey
 */
.open .panel::before {
  transition-delay: 400ms;
  background: #f0f0f0 linear-gradient(to right, #f3f3f3 0%, #f1f1f1 48%, #f1f1f1 100%);
}

/*
 * body, one thin line centered by default
 */
.body {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  left: 0;
  background-color: #fff;
  transition: all 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
  z-index: 1;
  clip-path: polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%);
}

/*
 * body, folded and fits its parent with on open
 */
.open .panel .body {
  transition-delay: 0.8s;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
}

/*
 * overlay, hidden by default. to overlap the content behind
 * and closes the panel on click outside
 */
.overlay {
  background-color: rgba(0, 0, 0, 0.2);
  position: fixed;
  visibility: hidden;
  top: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  opacity: 0;
}

/*
 * overlay, visible on open
 */
.open .panel + .overlay {
  opacity: 1;
  visibility: visible;
  transition: all 400ms ease;
}
```

Here's how it looks on both default and open states

![Panels](https://thepracticaldev.s3.amazonaws.com/i/97ju0eebj21fot8fgihc.png)



---

[LinkedIn](https://www.linkedin.com/in/elmahdim/) | [Twitter](https://twitter.com/_elmahdim)