The Flask Mega-Tutorial, Part XII: Facelift

(Great news! There is a new version of this tutorial!)

This is the twelfth article in the series in which I document my experience writing web applications in Python using the Flask microframework.

The goal of the tutorial series is to develop a decently featured microblogging application that demonstrating total lack of originality I have decided to call microblog.

NOTE: This article was revised in September 2014 to be in sync with current versions of Python and Flask.

Here is an index of all the articles in the series that have been published to date:


If you have been playing with the microblog application you must have noticed that we haven't spent too much time on its looks. Up to now the templates we put together were pretty basic, with absolutely no styling. This was useful, because we did not want the distraction of having to write good looking HTML when we were coding.

But we've been hard at work coding for a while now, so today we are taking a break and will see what we can do to make our application look a bit more appealing to our users.

This article is going to be different than previous ones because writing good looking HTML/CSS is a vast topic that falls outside of the intended scope of this series. There won't be any detailed HTML or CSS, we will just discuss basic guidelines and ideas so on how to approach the task.

How do we do this?

While we can argue that coding is hard, our pains are nothing compared to those of web designers, who have to write templates that have a nice and consistent look on a list of web browsers, most with obscure bugs or quirks. And in this modern age they not only need to make their designs look good on regular browsers but also on the resource limited browsers of tablets and smartphones.

Unfortunately, learning HTML, CSS and Javascript and on top of that being aware of the idiosyncrasies of each web browser is a task of uncertain dimensions. We really do not have time (or interest) to do that. We just want our application to look decent without having to invest a lot of energy.

So how can we approach the task of styling microblog with these constraints?

Introducing Bootstrap

Our good friends at Twitter have released an open source web framework called Bootstrap that might be our winning ticket.

Bootstrap is a collection of CSS and Javascript utilities for the most common types of web pages. If you want to see the kind of pages that can be designed with this framework here are some examples.

These are some of the things Bootstrap is good at:

  • Similar looks in all major web browsers
  • Handling of desktop, tablet and phone screen sizes
  • Customizable layouts
  • Fully styled navigation bars
  • Fully styled forms
  • And much, much more...

Bootstrapping microblog

Before we can add Bootstrap to our application we have to install the Bootstrap CSS, Javascript and image files in a place where our web server can find them.

In Flask applications the app/static folder is where regular files go. The web server knows to go look for files in these location when a URL has a /static prefix.

For example, if we store a file named image.png in /app/static then in an HTML template we can display the image with the following tag:

<img src="/static/image.png" />

We will install the Bootstrap framework according to the following structure:


Then in the head section of our base template we load the framework according to the instructions:

<!DOCTYPE html>
<html lang="en">
    <link href="/static/css/bootstrap.min.css" rel="stylesheet" media="screen">
    <link href="/static/css/bootstrap-responsive.min.css" rel="stylesheet">
    <script src="http://code.jquery.com/jquery-latest.js"></script>
    <script src="/static/js/bootstrap.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

The link and script tags load the CSS and Javascript files that come with Bootstrap. Note that one of the required Javascript files is jQuery, which is used by some of the Bootstrap plugins.

The meta tag enables Bootstrap's responsive mode, which scales the page appropriately for desktops, tablets and smartphones.

With these changes incorporated into our base.html template we are ready to start implementing Bootstrap, which simply involves changing the HTML in our templates.

The changes that we will make are:

We will not discuss the specific changes to achieve the above since these are pretty simple. For those interested, the actual changes can be viewed in diff form on this github commit. The Bootstrap reference documentation will be useful when trying to analyze the new microblog templates.

Note: At the time this tutorial was written the current version of Bootstrap was 2.3.2. Twitter has released a new major version since then, and with it several of the CSS classes have changed. Visit the Bootstrap site for more information.

Final words

Today we've made the promise to not write a single line of code, and we stuck to it. All the improvements we've made were done with edits to the template files.

To give you an idea of the magnitude of the transformation, here are a few before and after screenshots. Click on the images to enlarge.

The updated application can be downloaded below:

Download microblog-0.12.zip.

In the next chapter we will look at improving the formatting of dates and times in our application. I look forward to see you then!



  • #26 Manuel Godoy said 2015-09-12T07:48:52Z

    Hi Miguel,

    I would like the email to show with style in the user's mailbox. Can you implement this facelift in the email HTML from the previous part? So far, I copied the CSS on the HTML itself and it does help a bit, but the email definitely does not look as good as it would in the browser.

    Thanks a lot,


  • #27 Miguel Grinberg said 2015-09-13T02:04:58Z

    @Manuel: This is ultimately going to depend on how the email client displays the HTML, you don't really have a lot of control there. But yes, it is perfectly fine to include embedded CSS definitions in the emails, and you can also use Flask's render_template to generate the email bodies.

  • #28 Nate said 2015-12-07T19:56:32Z

    Thanks so much for this tutorial. I'm getting some strange behavior on Mac. The page will not load in Safari at all, but doesn't give any error. Just a white page. This is the stack trace: - - [07/Dec/2015 12:50:00] "GET / HTTP/1.1" 200 - - - [07/Dec/2015 12:50:00] "GET /static/js/bootstrap.min.js HTTP/1.1" 200 - - - [07/Dec/2015 12:50:00] "GET /static/css/bootstrap-responsive.min.css HTTP/1.1" 304 - - - [07/Dec/2015 12:50:00] "GET /static/css/bootstrap.min.css HTTP/1.1" 304 -

    The page loads in Chrome with the updated themes, but the links to the openid's are broken, ie clicking them does nothing and I can't log in. This seems to confirm that I have the bootstrap files in the right directory. I have the jquery file in my /microblog folder, so I'm wondering if that's in the wrong place? I know this is an old article but any help would be appreciated. Thanks!

  • #29 Miguel Grinberg said 2015-12-10T17:58:15Z

    @Nate: did you check for errors in the browser's debugging console? It looks like you are looking at output from the server, but you'll most likely find the problem if you look at the browser.

  • #30 windery said 2016-02-25T15:30:28Z

    Hi minguel, thanks to you nice tutorial! In this chapter I encountered a problem. In the index page, I clicked the Older Posts button to swich to the other pages of my posts. But it didn't work. Nothing happend and above the button a block turned blue which seemed bootstrap got some problem. And then I set the scaling from default 100%(maybe 125%) to 90%, widgets turned larger and it then worked. How can I fix it ? Is it caused because I removed the avatar and the search part of this application?

  • #31 Miguel Grinberg said 2016-02-26T19:07:26Z

    @windery: It would probably help to see images of what this looks like, I don't really understand what the problem is from your description. Could you post this as a stack overflow question with some images?

  • #32 James Perry said 2016-04-26T16:15:27Z


    Thank you for this tutorial. I don't know how recently Bootstrap changed, but they no longer have the png option for the img or the -responsive file. Can you help me update it?


  • #33 Miguel Grinberg said 2016-04-28T14:26:11Z

    @James: here is the structure of the current bootstrap version: http://getbootstrap.com/getting-started/#whats-included-precompiled. If you prefer to use the old version that matches this article, get the files from my github repository.

  • #34 Eric Ahlgren said 2016-11-15T20:49:48Z

    First of all thanks for this great tutorial! I can't tell what happened, but for some reason when I added the provider pngs to the login page the function "set_openid" now fails to run. When I hover over the images it indicates that it will run the function with the correct provider arguments, but when I click nothing happens. Do you know what might have caused this?


  • #35 Miguel Grinberg said 2016-11-15T20:55:10Z

    @Eric: I can't really tell, but if you look in your browser's javascript console you will probably see an error message that will give you some idea what's wrong.

  • #36 Eric Ahlgren said 2016-11-15T20:58:30Z

    @Miguel: Thank you! Here is the output of the debug console:

    [Error] SyntaxError: Unexpected identifier 'pr'. Expected ')' to end an argument list. (anonymous function) (login:38) [Error] ReferenceError: Can't find variable: set_openid Global Code (login:1)

  • #37 Eric Ahlgren said 2016-11-15T21:13:35Z

    @Miguel: I caught my mistake... very stupid. I copied the Bootstrap code from the GitHub repo and then did a find/delete on the "+" chars, forgetting there was string concatenation in the JS. It's working now, thanks for pointing me in the right direction.

  • #38 LookinForHelp said 2017-10-12T02:30:45Z

    bootstrap/ ├── css/ │ ├── bootstrap.css │ ├── bootstrap.min.css ├── js/ │ ├── bootstrap.js │ ├── bootstrap.min.js └── img/ ├── glyphicons-halflings.png └── glyphicons-halflings-white.png

    ^^^ This is the presented file hierarchy when downloading from getbootstrap(.)com. I have 2 questions:

    1) My understanding is we move these files, keeping the rest of the hierarchy intact, from bootstrap into 'app/static'. Is this correct?

    2) Instead of "bootstrap-responsive.min.css" like you have above, there is just "bootstrap.css" in the download from getbootstrap. Is this okay?

    Thank you!

  • #39 Miguel Grinberg said 2017-10-12T05:25:20Z

    @LookinForHelp: I think you are looking at a different (newer) version of Bootstrap than what I used when I wrote this article almost 5 years ago. The files that I used are in the GitHub repository, if you want to make the project work with the version that I used back then, you should get the files from GitHub.

  • #40 Tin Tun Naing said 2019-02-06T01:52:20Z

    I want to add options for users to choose themes from. Before going through this chapter, I added two css files in static/styles (one with color background as a default and the other with image background ), a Boolean check box form in form.py, and a function with decorator '@app.context_processor' in routes.py that talks with the newly added form . I also made changes in base.html accordingly.

    My goal was that if the user checked the box, then the background of the webpage would change from default (color background) to image background .

    However it only works on pages that have POST methods. And even on pages that have POST methods, whenever I refresh the page, I still have to re-check the check box form again. I realize that my approach may not have been the best. I have been reading about flask-themes but not sure how to work with it just yet. I really appreciate any suggestions.

  • #41 Miguel Grinberg said 2019-02-06T19:19:34Z

    @Tin: I'm not sure I fully understand what you did, but I would imagine you have to record the state of the check box in the user session so that it is remembered when the user navigates to another page? That may be what you are missing.

Leave a Comment