Black Lives Matter. Please consider donating to Black Girls Code today.
Dash HoloViews is now available! Check out the docs.

Holy Grail Layout with Dash Bootstrap Components

I have gone a long way utilizing https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/ to create several multi-page apps. These are formatted like the MENU and CONTENT blocks in the image below. However the layout that I and probably many others need to utilize is https://en.wikipedia.org/wiki/Holy_grail_(web_design) or basically:

Screen Shot 2020-06-09 at 11.20.15 AM

Couldn’t this be possible in the same manner as the sidebar example creating five well oriented CSS elements instead of two? The content within the header, ad, and footer columns would probably be fairly static but certainly required. I am a CSS rookie (aren’t we all)

PS The GitHub page link in https://dash-bootstrap-components.opensource.faculty.ai/examples/ needs to be corrected. (thanks for making this correction)

1 Like

So I began with the code from https://github.com/facultyai/dash-bootstrap-components/blob/master/examples/multi-page-apps/simple_sidebar.py and ran with it. I have a good start but it needs some CSS tweaks so that the footer does not hard-align to the bottom of the browser, but the whole page expands correctly as content is added to the content and footer sections. You can see from this image that I have 25 lines of content which are visible by scrollbar that only scrolls the Content section. I have five lines added to the footer which roll off and are not visible. Of course this makes sense to me based how the _STYLE definitions seem hard and fixed.

Here is an image of the finished product with me scrolling a little to capture the Content frame overflow:

And here is my code. I am hoping and experienced CSSer can make the tweaks so that the page size expands to handle a variable amount of content within the Content and Footer frames. The footer frame should have whatever variable sizing is needed to fit its content and if the Content frame has many lines of content, the page would expand, the footer would be pushed off the bottom and the scrollbar would apply to the entire page.

I hope this would be a helpful addition to https://github.com/facultyai/dash-bootstrap-components/tree/master/examples/multi-page-apps once these final adjustments are made. I believe it would probably be using something besides position: fixed on the bottom settings and a hard footer_height definition, but everything I experimented with made the format worse.

import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])

header_height, footer_height = "6rem", "10rem"
sidebar_width, adbar_width = "12rem", "12rem"

HEADER_STYLE = {
    "position": "fixed",
    "top": 0,
    "left": 0,
    "right": 0,
    "height": header_height,
    "padding": "2rem 1rem",
    "background-color": "white",
}

SIDEBAR_STYLE = {
    "position": "fixed",
    "top": header_height,
    "left": 0,
    "bottom": footer_height,
    "width": sidebar_width,
    "padding": "1rem 1rem",
    "background-color": "lightgreen",
}

ADBAR_STYLE = {
    "position": "fixed",
    "top": header_height,
    "right": 0,
    "bottom": footer_height,
    "width": adbar_width,
    "padding": "1rem 1rem",
    "background-color": "lightblue",
}

FOOTER_STYLE = {
    "position": "fixed",
    "bottom": 0,
    "left": 0,
    "right": 0,
    "height": footer_height,
    "padding": "1rem 1rem",
    "background-color": "gray",
}

CONTENT_STYLE = {
    "margin-top": header_height,
    "margin-left": sidebar_width,
    "margin-right": adbar_width,
    "margin-bottom": footer_height,
    "padding": "1rem 1rem",
}

header = html.Div([
    html.H2("Header")], style=HEADER_STYLE
)

fdivs = [html.H2("Footer")]
for f in range(5):
    fdivs.append(html.P(f'Footer line {f}'))
footer = html.Div(fdivs, style=FOOTER_STYLE)

adbar = html.Div([
    html.H2("Adbar"),
    html.P('Advertisements go here')], style=ADBAR_STYLE
)

sidebar = html.Div([
    html.H2("Sidebar"),
    html.Hr(),
    html.P("A simple sidebar layout with navigation links", className="lead"),
    dbc.Nav([
            dbc.NavLink("Page 1", href="/page-1", id="page-1-link"),
            dbc.NavLink("Page 2", href="/page-2", id="page-2-link"),
            dbc.NavLink("Page 3", href="/page-3", id="page-3-link"),
        ], vertical=True, pills=True,
    )],
    style=SIDEBAR_STYLE
)

cdivs = [html.H2("Content"),
         html.Div(id="page-content")]
for n in range(25):
    cdivs.append(html.P(f'Content line {n}'))

content = html.Div(cdivs, style=CONTENT_STYLE)

app.layout = html.Div([dcc.Location(id="url"),
                       header, sidebar, adbar, content, footer])


# this callback uses the current pathname to set the active state of the
# corresponding nav link to true, allowing users to tell see page they are on
@app.callback(
    [Output(f"page-{i}-link", "active") for i in range(1, 4)],
    [Input("url", "pathname")],
)
def toggle_active_links(pathname):
    if pathname == "/":
        # Treat page 1 as the homepage / index
        return True, False, False
    return [pathname == f"/page-{i}" for i in range(1, 4)]


@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def render_page_content(pathname):
    if pathname in ["/", "/page-1"]:
        return html.P("This is the content of page 1!")
    elif pathname == "/page-2":
        return html.P("This is the content of page 2. Yay!")
    elif pathname == "/page-3":
        return html.P("Oh cool, this is page 3!")
    # If the user tries to reach a different page, return a 404 message
    return dbc.Jumbotron(
        [
            html.H1("404: Not found", className="text-danger"),
            html.Hr(),
            html.P(f"The pathname {pathname} was not recognised..."),
        ]
    )


if __name__ == "__main__":
    app.run_server(port=8888)
    

with help from @tcbegley I am starting from scratch using a completely different model than the strategy used by the GitHub Sidebar Example.

Hi @marketemp,
You might find this tutorial on Dash Bootstrap useful.

1 Like

Hi @marketemp, @tcbegley,

would you mind sharing this different approach/model you started from scratch with? I am also struggling with this topic.

Thanks a lot!

hi Jans,

here is what I came up with. I have moved on a long way from it but you can give it a shot.

holygrail.py

import dash_html_components as html

import dash_bootstrap_components as dbc

app = dash.Dash(__name__, suppress_callback_exceptions=True,
                external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    html.Header(
        html.H3('Header'),
        className="bg-secondary text-white text-center py-4",
    ),
    html.Div([
        html.Nav(
            html.H3('Navbar'),
            className='bg-success holygrail-nav col-md-2 col-lg-2 col-xxl-1 bg-inverse'
        ),
        html.Main(
            html.H3('Main'),
            className='holygrail-main col-md-8 col-lg-8 col-xxl-10'
        ),
        html.Aside(
            html.H3('Sidebar'),
            className='bg-primary holygrail-aside col-md-2 col-lg-2 col-xxl-1 bg-inverse'
        ),
    ], className='holygrail-body no-gutters'),
    html.Footer(
        html.H3('Footer'),
        className="bg-dark text-inverse text-center py-4",
    ),
], className='holygrail')

if __name__ == "__main__":
    app.run_server(host='0.0.0.0', port=9876)

holygrail.css in assets

html {
  height: 100%;
}

.holygrail {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.holygrail-body {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}

@media screen and (min-width: 768px) { /* @include media-breakpoint-up(lg) */
  .holygrail-body {
      flex-direction: row;
  }
  .holygrail-main {
      flex: 1;
  }
  .holygrail-nav, .holygrail-aside {
      min-height: 100vh;
      flex: 0 0 auto;
  }
}

/* Other styles */
.block {
  padding: 3rem 0;
}

.text-inverse {
  color: white;
}

Good luck…

Hi @marketemp,

thanks for your reply, highly appreciated!

It’s great that the footer is not fixed to the screen bottom but pushed below it with a long content section.

However, with a short content section the footer is not fixed at the screen bottom, but in the middle of the screen (at least with me).

I played around with min-height: 100%; in the holygrail-body class, but without success yet.

How did you handle that?

Thanks again!

Hi, I never handled that properly and also struggled with it. Unless I was initially starting a new page, I always had enough in the body to make this irrelevant, but it definitely needs to be solved. I suspect there is a CSSy way to do it, something like minLength, but I am a rookie in that dept.

I tried using height=‘100%’ on the body div but then once the body was too long it pushed right through the footer :grinning:

Hi @marketemp: if you are just getting into this, you may also want to take a look at css-grid (also here). CSS-grid can make for a more intuitive layout and can be made responsive as well.

i seemed to fix this adding the min-height setting to the css:

  .holygrail-side, .holygrail-aside {
    flex: 0 0 auto;
    min-height: 100vh;
  }