Some background
For our active learning kits, such as our Shark Skull Active Learning Kit, we’ve created open-source guides that lead users through the various kit activities. Our first method for creating these guides was to write them in Google Docs, export the guides as PDFs, and upload these PDFs to our website. We also made the Google Doc publicly available so that anyone could create a copy and edit it to customize the guides (that’s the open-source part).
However, this method had a few challenges: making changes was time consuming (update Google doc, export PDF, upload PDF, update link), edits by other users were not available to other users since they were made to their own copy, and there was no way to add dynamic content (e.g., videos, collapsible elements, etc.) directly into the guides because these wouldn’t transfer over to a PDF.
For these reasons, we’ve decided to migrate our existing and future activity guides to BookStack. BookStack is a self-hosted, open-source platform created by Dan Brown for organizing hierarchical content. BookStack has some similarities to a Wiki (e.g., it’s free and open-source, multiple users can create and edit content with little to no coding skill needed, content is searchable). But we decided that BookStack would be a better fit than a Wiki for hosting our activity guides because of its built-in hierarchical system.
All content is added to the platform either as a page, chapter, book, or shelf (listed in increasing hierarchical position), which makes it easy for us to organize our activity guide content in a way that is intuitive for users. For example, each of our kits will have a “shelf,” with a “book” for each of the kit activities, and all the content for each activity will be organized into various “chapters” and “pages.” Another thing that we love about BookStack is the ability to use code to customize the functionality and layout of the platform and the community of users and developers actively improving the platform (yay for open-source!).
The hack and how it works
In keeping with this open-source culture of BookStack, I wanted to share some custom code (i.e., hack) that I have developed that allows users to dynamically show and hide the left sidebar, expanding the width of the main page content for full page viewing on midsize screens. This has been a feature request for BookStack (e.g., Dynamically show or hide sidebars #4461 and Make pages bigger #1757) but hasn’t been implemented as a core/included functionality.
You can see this hack in action on this this BookStack page from our own hosted BookStack instance (click on the button/text “Collapse left sidebar” at the top left of the screen) or in the images below. I’ve tested this custom code with the most recent version of BookStack, v25.11.4 (the most recent as of the original publication date of this post, November 28, 2025). But of course —standard disclosure— this custom code is not officially supported and may cause instability, introduce issues or conflict with future updates.

For this hack I used the standard method for BookStack, which is to add custom Javascript and CSS code to the “Custom HTML Head Content” in the Customization page of the Settings. The code adds a “Collapse left sidebar” button to all BookStack webpages (for pages, chapters, books, and shelves) that is only visible when the main webpage content is in the “two-column layout” (i.e., when there is a single sidebar to the left occupying 30% of the window width and a main content block occupying the remaining 70% of the window to the right).

Clicking on this button hides the left sidebar content while preserving the small SVG icon so that the user has a button to click to re-open the sidebar (I used the same SVG icon as the one used to toggle open/closed the right sidebar when editing pages on BookStack). Clicking on the button also creates a cookie that will register the most recent state of the left sidebar so that if you navigate to the next page in a book (or any other page for that matter), the left sidebar stays closed (or open).
A collapsible sidebar is not needed when viewing BookStack pages on a narrow screen (i.e., a tablet or mobile device) since BookStack automatically rearranges the window contents into single main content and “Info” columns (interchangeable by clicking on the corresponding header button) when the window width is less than 1001 pixels.

Likewise, a collapsible sidebar is not needed when viewing BookStack pages on a wide screen (in my opinion) because there is enough room to accommodate both a left and right sidebar while leaving plenty of space for the main content. At window widths greater than 1400 pixels, BookStack automatically rearranges the window contents into a three-column layout with a left sidebar, main content, and right sidebar.

When the window is either too narrow or too wide to necessitate a collapsible left sidebar, the custom code reopens the sidebar (restores the default formatting) so that the sidebar is visible in the info column (on narrow screens) or to the left (on wide screens). The custom code also hides the “Collapse left sidebar” button. If the user changes the window width to the middle (i.e., two-column format) size, the left sidebar will re-collapse (if it was previously collapsed). That is, the collapse status will update automatically with dynamic window width changes.
One issue with this code is that the collapsing of the sidebar occurs after all of the document objects have been loaded. So, if you have the left sidebar collapsed and navigate to another page, the sidebar will “flash” briefly and then collapse rather than being collapsed from the very start of the page loading. This would be great to fix in the future but the code in its current state is functional and I think the momentary flash of the sidebar is OK.
The code
Below is the custom code to be copied into the “Custom HTML Head Content” in the Customization page of the Settings.
Here’s the CSS code:
<style>
/* Styling for collapsible left sidebar */
@media screen and (min-width: 1401px), screen and (max-width: 1000px) {
/* Hide left sidebar collapse button when screen is too narrow or too wide */
.collapse-left-side-bar-button {
display: none;
}
.tri-layout-sides-content {
margin-top: 0px;
}
}
@media screen and (min-width: 1001px) and (max-width: 1400px) {
.tri-layout-sides-content {
top: -15px; /* Adjust from default of 0px for collapse button */
margin-top: -20px; /* Decreases gap between left sidebar collapse button and text */
}
}
.collapse-left-side-bar-button-div {
}
.collapse-left-side-bar-button-div-collapsed {
text-align: center;
}
.collapse-left-side-bar-button {
position: sticky;
outline: none;
margin: 50px 0px 10px -5px;
color: #777; /* rgb(117, 117, 117) Matches grayed out text in sidebar wo hover */
}
.collapse-left-side-bar-button-collapsed {
margin-left: auto;
margin-right: auto;
width: 100%;
}
.collapse-left-side-bar-button:hover {
color: #444; /* rgb(68, 68, 68) Matches darker text in sidebar w hover */
cursor: pointer;
}
.collapse-left-side-bar-svg {
float: left;
top: 3px;
}
.collapse-left-side-bar-svg-collapsed {
transform: rotate(180deg);
margin-inline-end: 0px;
float: none;
left: -1px;
}
.collapse-left-side-bar-button-text {
float: left;
}
.collapse-left-side-bar-button-text-collapsed {
display: none;
}
.tri-layout-container-collapsed {
grid-template-columns: 0.07fr 3fr !important;
grid-column-gap: 0px;
margin-left: 0px;
}
.tri-layout-sides-content-collapsed {
margin-top: -35px;
top: -35px;
}
.tri-layout-right-collapsed, .tri-layout-left-collapsed {
display: none;
}
</style>And here’s the Javascript code:
<!-- Make left sidebar collapsible -->
<script type="text/javascript">
// Declare global variables
let previousInnerWidth = window.innerWidth;
sidebarOpenedOnResize = false;
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Function that collapses and opens left sidebar
function toggleLeftSideBar() {
// Toggle collapsed class for sidebar container (changes width)
const tri_layout_container_matches = document.getElementsByClassName('tri-layout-container');
tri_layout_container_matches[0].classList.toggle('tri-layout-container-collapsed');
// Toggle collapsed class for sidebar content (shows/hides text)
const tri_layout_sides_content_matches = document.getElementsByClassName('tri-layout-sides-content');
tri_layout_sides_content_matches[0].classList.toggle('tri-layout-sides-content-collapsed');
// Toggle collapsed class for sidebar content (shows/hides text)
const tri_layout_right_matches = document.getElementsByClassName('tri-layout-right');
tri_layout_right_matches[0].classList.toggle('tri-layout-right-collapsed');
const tri_layout_left_matches = document.getElementsByClassName('tri-layout-left');
tri_layout_left_matches[0].classList.toggle('tri-layout-left-collapsed');
// Toggle collapsed class for button text (shows/hides text)
const button_text = document.getElementById('collapse-left-side-bar-button-text');
button_text.classList.toggle('collapse-left-side-bar-button-text-collapsed');
// Toggle collapsed class for button div
const button_div = document.getElementById('collapse-left-side-bar-button-div');
button_div.classList.toggle('collapse-left-side-bar-button-div-collapsed');
// Toggle true/false value for aria-expanded property of button
const button = document.getElementById('collapse-left-side-bar-button');
if(!button.ariaExpanded){
button.title = 'Open left sidebar';
}else{
button.title = 'Collapse left sidebar';
}
// Change value
button.ariaExpanded = button.ariaExpanded !== 'true';
button.classList.toggle('collapse-left-side-bar-button-collapsed');
// Make session cookie - whether left sidebar is expanded by click (not by window resize)
if(!sidebarOpenedOnResize){
document.cookie = "bookstack_leftsidebar_expanded=" + button.ariaExpanded + "; path=/"; // No expires attribute makes it a session cookie
}
// Toggle collapsed class for SVG element (rotates 0/180 deg)
const button_svg = document.getElementById('collapse-left-side-bar-svg');
button_svg.classList.toggle('collapse-left-side-bar-svg-collapsed');
}
//
window.addEventListener('resize', function() {
// Get window dimensions
const currentInnerWidth = window.innerWidth;
const button = document.getElementById('collapse-left-side-bar-button');
if(previousInnerWidth >= 1001 && previousInnerWidth <= 1400){
// Window was previously inside thresholds
if(currentInnerWidth < 1001 || currentInnerWidth > 1400){
// Window is now outside thresholds
if(button.ariaExpanded == 'false'){
sidebarOpenedOnResize = true;
toggleLeftSideBar()
//console.log('Left sidebar opened due to window width change (' + previousInnerWidth + ' --> ' + currentInnerWidth + ')');
}
}
}else{
// Window was previously outside thresholds
if(currentInnerWidth >= 1001 && currentInnerWidth <= 1400){
// Window is now inside thresholds
if(sidebarOpenedOnResize && button.ariaExpanded == 'true'){
sidebarOpenedOnResize = false;
toggleLeftSideBar()
//console.log('Left sidebar collapsed due to window width change (' + previousInnerWidth + ' --> ' + currentInnerWidth + ')');
}
}
}
// Set new previous window width
previousInnerWidth = currentInnerWidth;
});
document.addEventListener("DOMContentLoaded", function() {
// Add button to sidebar
const sidebar_contents_matches = document.getElementsByClassName('tri-layout-sides-content');
var sidebar_contents_div = sidebar_contents_matches[0];
if (sidebar_contents_div) { // If an element was found
// Create new div for button
const newButtonDiv = document.createElement('div');
newButtonDiv.id = 'collapse-left-side-bar-button-div';
newButtonDiv.classList.add('collapse-left-side-bar-button-div');
// Create new button
const newButton = document.createElement('button');
newButton.id = 'collapse-left-side-bar-button';
newButton.type = 'button';
newButton.refs = 'editor-toolbox@toggle';
newButton.title = 'Collapse left sidebar';
newButton.setAttribute('aria-expanded', 'true');
newButton.classList.add('toolbox-toggle');
newButton.classList.add('collapse-left-side-bar-button');
newButton.addEventListener('click', function() {
toggleLeftSideBar();
});
// Create SVG container for circle and triangle
const svgContainer = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgContainer.id = 'collapse-left-side-bar-svg';
svgContainer.classList.add('svg-icon');
svgContainer.classList.add('collapse-left-side-bar-svg');
svgContainer.role = 'presentation';
svgContainer.setAttribute('viewBox', '0 0 24 24');
svgContainer.setAttribute('data-icon', 'caret-left-circle');
// Create background circle and append to SVG container
const path1 = document.createElementNS("http://www.w3.org/2000/svg", 'path');
path1.setAttribute('d', 'M0 0h24v24H0z');
path1.setAttribute('fill', 'none');
svgContainer.appendChild(path1);
// Create triangle and append to SVG container
const path2 = document.createElementNS("http://www.w3.org/2000/svg", 'path');
path2.setAttribute('d', 'M12 22c5.52 0 10-4.48 10-10S17.52 2 12 2 2 6.48 2 12s4.48 10 10 10m2-14.5v9L8 12z');
svgContainer.appendChild(path2);
// Append the SVG container to the button
newButton.appendChild(svgContainer);
// Create div for button text
const newButtonText = document.createElement('div');
newButtonText.id = 'collapse-left-side-bar-button-text';
newButtonText.innerHTML = 'Collapse left sidebar';
newButtonText.classList.add('collapse-left-side-bar-button-text');
newButton.appendChild(newButtonText);
newButtonDiv.appendChild(newButton);
sidebar_contents_div.prepend(newButtonDiv);
}
const leftSidebarExpanded = getCookie('bookstack_leftsidebar_expanded');
if (typeof leftSidebarExpanded !== 'undefined') {
// Cookie for state of left sidebar collapse is defined
if(leftSidebarExpanded == 'false'){
// Left sidebar should be collapsed
if(window.innerWidth >= 1001 && window.innerWidth <= 1400){
// Window width is inside thresholds, collapse left sidebar
toggleLeftSideBar();
}else{
// Window width is outside thresholds, will collapse left sidebar
// if window is resized to within thresholds
sidebarOpenedOnResize = true;
}
}
}
});
</script>Here’s some additional CSS code that I use to further expand the main content block. It’s not necessary for the collapsible sidebar functionality but if you’re interested in maximizing the main content width, it’s probably something you’ll want to add.
<style>
/* Expand width of page content */
/* Allows middle (page) content to expand to fill width of screen on extra wide screens */
@media screen and (min-width: 1400px) {
.tri-layout-middle-contents {
max-width: 1700px;
}
}
.page-content {
max-width: 1600px; /* Allows page-content within middle content space to expand to
fill, leaving 50 px on each side as margin */
}
/* Between 1001px and 1400px (inclusive), the page will have a two-column layout with a single
sidebar on the left and page content on the right */
@media screen and (min-width: 1001px) and (max-width: 1400px) {
.tri-layout-container {
padding-right: 0px; /* This value is 24px by default for two-column layout.
Setting to 0 px gives page content a bit more width while keeping
symmetrical margins */
}
}
</style>If you have any suggestions for improvement or if you find any bugs, leave a comment below!
