Pure CSS Collapsible Left Nav Bar
July 29, 2019
Creating a collapsible left navigation bar was a scary thought when I first did it a while back. Turns out, it's easy! Don't let yourself be frightened by transitions, disappearing text, or other parts. Approach the problem one thing at a time.
Essentially all you need for a generic collapsible navigation bar is this:
- Create a standard left nav bar
- Add a button which can toggle state. i.e. collapsed, not collapsed.
- Add collapsed class to tags when state is collapsed
- For the navbar, use a smaller collapsed width when the collapsed class is present
Here is the styling used for my left navigation bar. The styling is pretty generic and will be applied to a tag with the class left-nav-section
. See the notes about what each property does for you below.
.left-nav-section { background-color: aquamarine; /* make it easier to see nav */ position: fixed; /* avoids issues with nav bars that arise when it is part of the document flow */ height: 100%; /* take full height of page */ width: 250px; /* default width */ top: 0; /* stick fixed tag to top of the page */ left: 0; /* stick fixed tag to left of the page */ padding: 15px; /* keep children from edges */ /* Collapsible Related Properties! */ white-space: nowrap; /* don't let text wrap */ overflow: hidden; /* don't allow text to extend beyond nav bar */ transition: width 1s ease; /* when width styling changes, transition over a second */ }
and when it is collapsed, we want the width to be changed to a smaller value. The styling below will be applied with a class name left-nav-section collapsed
.
.left-nav-section.collapsed { width: 75px; }
I created my left navigation bar using React. There are three parts to this: 1) Parent component 2) Left Nav Bar 3) App Content
Here is my Parent Component:
class App extends Component { constructor(props) { super(props); this.toggleCollapse = this.toggleCollapse.bind(this); this.state = { collapsed: false }; } toggleCollapse() { this.setState(prevState => ({ collapsed: !prevState.collapsed })); } render() { return ( <> <LeftNav collapsed={this.state.collapsed} toggleCollapse={this.toggleCollapse} /> <Main collapsed={this.state.collapsed} /> </> ); } }
All you really need to take away from this is that there is state collapsed
at the root so that both my app and nav bar can receive the collapsed state via a prop. It's far more likely this will be stored in Redux or some other state management tool, so don't read into having this component too much.
My left nav bar then looks something like this:
const LeftNav = ({ collapsed, toggleCollapse }) => { const collapsedClass = collapsed ? " collapsed" : ""; return ( <section className={`left-nav-section${collapsedClass}`}> <p>Link 1</p> <p>Link 2</p> <p>Link 3</p> <button onClick={toggleCollapse}>collapse</button> </section> ); };
As you can see, if collapsed
is false
, then the className
will simply be left-nav-section
, but if it is true
, then the className
will be left-nav-section collapsed
. By adding the collapsed, the width will be overridden to 75px
as specified in the CSS above, and the component will transition between states as it is told to do.
There is still one more thing that needs to happen. Because the navigation bar is fixed
, the navigation bar changing in width will not affect the app content; we need the move it directly. The CSS and React code are done very similarly, and look like this:
const Main = ({ collapsed }) => ( <div className={`main${collapsed ? " collapsed" : ""}`}> <h1>App Content!</h1> </div> ); :::css .main { position: relative; margin-left: 250px; transition: margin-left 1s ease; } .main.collapsed { margin-left: 75px; }
It's very likely your nav bar will be a bit more complicated. For example you may want to fade text in or out, or perhaps remove an image that was at the top left of the nav bar. Don't let this freak you out, as all you really need to do is focus on your two states: 1) Not Collapsed. 2) Collapsed. Make the styles for each case, and add transitions when necessary.
If you're making this in React, read this!! 👇
As a final note, if you're dealing with dynamic content and use a map
over and array to create your elements,, rerenders can be very frustrating and if your component unmounts and remounts the transitions will fail, because HTML transitions can no longer know to occur if the component unmounts. Often this can be avoided by making the key consistent through renders. If you put key={uuidv4()}
you're going to have a bad time. There is a library called react-transition-group which is also very helpful for making transitions in react if the unmounts are unavoidable. It will apply various classNames at different points of the rendering process which make it much easier to apply transition changes between states.
Good luck and please leave a comment if you have questions.