DIY Projects

How to create a beautiful Web Interface for projects ESP8266 / ESP32 with Bootstrap

In the previous tutorial, we saw how to create a web interface to access the web server of an ESP8266 / ESP32. In this tutorial, I propose to discover how to use the Bootstrap framework to create a better interface for your projects of connected objects. We are not going to go very far in programming HTML, there is enough to hold a whole blog on the subject.


The objective of this tutorial is to set up the framework and test some elements (button, table, badge, interaction with Arduino code from the Web interface). To go further in HTML, here are some very well-made blogs: Alsa Création (in French), w3schools.

Note. You can follow the same procedure if you are using a Shield Ethernet or WiFi for Arduino. The libraries are identical

What is Bootstrap?

Bootstrap is a responsive HMTL framework, meaning that the display adapts to the size of the screen. Bootstrap is an open source project very popular among web designers and webapp (it was the most popular project on GitHub in 2014). It was initiated by a developer working at Tweeter.

This may seem difficult at first sight but in fact it is very simple to implement. Do not forget that the power of an ESP8266 is very limited, we are not going to make interfaces very complicated. The essential part of the “programming” HTML will be to indicate the classes (class in English) that the rendering engine must use to display an element.

Let’s take the example of a button. To display a button, you must do this as HTML

<button type="button">ON</button>

Which gives this. Not difficult to do better!

Now, go to the bootstrap site and go down to the examples in the glyphicon section. To change the appearance of an HTML element, you must assign Bootstrap classes to it.

For example, we will add the classes btn, btn-success (green color), btn-lg (large). Here is what it gives. More fun no!

That’s nothing very complicated. What is most difficult is to have an overview of all the available classes and the rendering obtained. Fortunately, there are sites that can make models online.

How to create an online interface template?

By downloading boostrap builder online, you will find many sites that allow you to make models and get the HTML code. Can not test them all, I tested layouit !. You can also read this article which quickly presents 7 solutions online.

Layouit! Is simple and effective, the elements are deposited on the page. It is a free service, so it is quite limited. Not all items are configurable. For example, it is impossible to change the number of rows and columns of a table, add a badge to a cell … It will have to be done by hand at the time of coding.

For each item, the different styles available are accessible from menus and checkboxes

Once your artwork is complete, to retrieve the HTML code, click Download (on the top bar) and then Continue non logged. There is no need to retrieve code and resources since they will be downloaded directly from the internet. Simply copy the code to the clipboard.

Warning, you are not going to get the HTML code made for your application, simply structure it with the classes that match your choices. Then it will be necessary to adapt the code and “populate” the elements with your data.

How to adapt the HTML code for the Arduino program?

Here is a small example of an interface composed only of a jumbotron, a frame containing title, a summary and an action button.

Here is the HTML code generated by layoutit!

<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <div class="jumbotron">
                    Demo Bootstrap ESP8266 / ESP32
                    Il fait 22°C
                    <a class="btn btn-primary btn-large" href="#">Nouvelle mesure</a>

I recall that the idea is to dynamically construct the HTML (a simple test) of the page by integrating the value of the variables.

The best thing is to create strings that contain recurring elements, this is not mandatory and can make it difficult to focus because it is more difficult to keep the code tree. It will also be necessary to replace all double quotation marks (“) with a single (‘).

String row = "<div class='row'>";
String col_md_12 = "<div class='col-md-12'>";

It remains only to assemble the chain including the variable of temperature. Some channels may be compacted, but it should be kept in mind that in case of modification or addition of elements, it will be more difficult to develop. Any open beacon to be closed, it is better to keep a certain tree to find it.

String page = "<div class='container-fluid'>";
page += "<div class='row'";
page +=   "div class='col-md-12'";
page +=     "<div class='jumbotron'>";
page +=       "<h2>Demo Bootstrap ESP8266</h2>";
page +=          "<p>Il fait";
page +=          t;
page +=          "°C</p>";
page +=          <p><a class='btn btn-primary btn-large' href='#'>Nouvelle mesure</a></p>";
page +=     "</div>";
page +=   "</div>";
page += "</div>";

How to add the Bootstrap framework to an ESP8266 Arduino project

We will now go back to the heart of the matter.

There are two ways to add the Bootstrap framework to an Arduino / ESP8266 project. The first is to embark all the necessary in the memory of the ESP8266. This will be the theme of the next tutorial on the subject. Here we will retrieve the sources from the internet. This forces the ESP8266 to be connected to the internet. For this to work, it can not be used as an access point.

We will therefore indicate in the header of the HTML page the resources to download on the Internet.

Note. Only the bootstrap.min.css style sheet is required, but if you need to create interactions with Arduino code or use some Bootstrap components with Javascript, you have to install everything.

<link rel='stylesheet' href=''
<script src=''></script>
<script src=''></script>"

If security is an obsession, you can even add a source integrity check.

<link rel='stylesheet' href='' integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src='' integrity='sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

Then you have to tell the web browser that the site is suitable for the mobile. All other settings are explained here (see paragraph Mobile First).

<meta name="viewport" content="width=device-width, initial-scale=1">

It’s over for the header. Let’s move to the body of the HTML page. All contents of the page must be included in a container. We will therefore add a div and indicate the container-fluid class. The fluid option means that the page takes up all available space on the screen

<div class="container-fluid">

Bootstrap has been designed to handle display blocks in the form of grids. One can have for example a display on three columns. Depending on the type of screen, or if you resize the browser window, the columns will begin to reduce and then place one below the other (on a smartphone). To do this, we must create a row in which to place our elements, for example a table.

Here, I suggest creating a line that contains

The HTML code that displays. It remains to integrate the measures in the final code.

<div class='container-fluid'>
 <div class='row'>
  <div class='col-md-12'>
    <h1>Demo Webserver ESP8266 + Bootstrap</h1>
    <h3>Mini station météo</h3>
    <ul class='nav nav-pills'>
      <li class='active'>
        <a href='#'> <span class='badge pull-right'>22°C</span> Température</a>
        <a href='#'> <span class='badge pull-right'>33%</span> Humidité</a>
        a href='#'> <span class='badge pull-right'> 980mbar</span> Pression atmosphérique</a>

Interaction with Arduino code from the web interface

There are three main methods for interacting with the Arduino code. The first method is to add javascript code in the HTML page. It is a very good solution but you will have to master an additional language. This solution will therefore be left aside.

The second solution is to pass the parameters in a URL, that is to connect a function that monitors the page concerned. In this case, this solution is not very well adapted because it will be necessary to manage the reloading of the main page, which is not going to give some very elegant.

We will therefore use the third method which consists in sending an HTTP request of type POST. Said like that, it’s Chinese, but in fact it’s quite simple. POST requests are sent, for example, when you call the submit function (already used in the previous tutorial). This is the function that is called when validating the filling of a form (creation of account, payment by credit board …). One can easily see what happens by opening the development tools proposed by Chrome (the equivalent exists on all browsers).

We will therefore start by modifying the elements with which we want to interact in the Arduino code. These are mostly buttons in general. For a button to “post” information, it must be included in a form. The form has two parameters:

<form action='/' method='POST'>

Then for a button, we add the type submit. To change the status of an output, the two buttons (ON and OFF) will be given the same name, and the value will be loaded. This gives the following code.

<form action='/' method='POST'>
  <button type='button submit' name='D6' value='1' class='btn btn-success btn-lg'>ON</button>
<form action='/' method='POST'>
  <button type='button submit' name='D6' value='0' class='btn btn-danger btn-lg'>OFF</button>

In the development tools, go to the Network tab and press an ON button that activates output D6 to see the request sent by the page. The Form Data section at the bottom of the page contains the data sent by the page when the button is pressed.

And when you turn it off

Now rated Arduino, we have to do 3 things:

In the handleRoot function, a test is added which verifies the presence of argument D6. If it is present, execute the handleD6 function.

void handleRoot(){ 
  if ( server.hasArg("D6") ) {
  } else {
    server.send ( 200, "text/html", getPage() );

The value of the argument is retrieved and the associated processing is started

void handleD6() {
  String D6Value = server.arg("D6"); 
  updateGPIO(1, D6Value); 

Display of accented or special characters

If you are using Firefox or Safari (on iOS) you will most likely experience problems displaying accented characters. I tried to solve the problem both on the ESP8266 side (lang and charset parameters) and on the browser side (option to display accented characters), without success. The only solution that works (I’m a taker if you find anything else) is to use the HTML code corresponding to the accented or special character. Here is a small table with the most common. There are plenty of sources on the internet.

HTML code Character
&agrave; à
&acirc; â
&eacute; é
&egrave; è
&ecirc; ê
&ugrave; ù
&ucirc; û
&ccedil; ç
&deg; °

This gives, for example, temperature,, Temp&eacute;rature. The best is to do a search / replace when all is finished

Assembly of the project

It’s time to get down to business and realize our first interface.

Equipment used

For this demo, I simply assembled a DHT22 and a BMP180 to retrieve measurements. I also connected Led to simulate GPIO activation.

Any ESP8266 ESP-12 module, for example Wemos D1 Mini
  Atmospheric pressure


Temperature and humidity sensor

DHT11 or DHT22

Monochrome OLED display 168×64 pixels 0.96″


Jumper Dupont
Led (optional, follow the WiFi activity)
Resistor 220Ω (optional)


Here is a boardping between Arduino and ESP8266 pins. You can also add a OLED SDSD1306 display if you want.

Component Pin Arduino Pin ESP8266 Pin (Wemos D1 mini)
Data GPIO-14 G5
BMP180 VDC 5V 5V
OLED SSD1306 0.96 “ VDC 5V 5V
SCK GPIO-5 [] D1
Led WiFi activity Pole + GPIO-12 D6
Pole- GND G

Arduino Code

Here’s the final code of the project you just have to paste into a new Arduino skit. You must define the ssid and password variables to access your WiFi network.


#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DHT.h>
#include <Adafruit_BMP085.h>

#define ssid      "xxx"       // WiFi SSID
#define password  "xxxxxxxx"  // WiFi password
#define DHTTYPE   DHT22       // DHT type (DHT11, DHT22)
#define DHTPIN    D4          // Broche du DHT / DHT Pin
const uint8_t GPIOPIN[4] = {D5,D6,D7,D8};  // Led
float   t = 0 ;
float   h = 0 ;
float   p = 0;
String  etatGpio[4] = {"OFF","OFF","OFF","OFF"};

// Création des objets / create Objects
Adafruit_BMP085 bmp;
ESP8266WebServer server ( 80 );

String getPage(){
  String page = "<html lang='fr'><head><meta http-equiv='refresh' content='60' name='viewport' content='width=device-width, initial-scale=1'/>";
  page += "<link rel='stylesheet' href=''><script src=''></script><script src=''></script>";
  page += "<title>ESP8266 Demo -</title></head><body>";
  page += "<div class='container-fluid'>";
  page +=   "<div class='row'>";
  page +=     "<div class='col-md-12'>";
  page +=       "<h1>Demo Webserver ESP8266 + Bootstrap</h1>";
  page +=       "<h3>Mini station m&eacute;t&eacute;o</h3>";
  page +=       "<ul class='nav nav-pills'>";
  page +=         "<li class='active'>";
  page +=           "<a href='#'> <span class='badge pull-right'>";
  page +=           t;
  page +=           "</span> Temp&eacute;rature</a>";
  page +=         "</li><li>";
  page +=           "<a href='#'> <span class='badge pull-right'>";
  page +=           h;
  page +=           "</span> Humidit&eacute;</a>";
  page +=         "</li><li>";
  page +=           "<a href='#'> <span class='badge pull-right'>";
  page +=           p;
  page +=           "</span> Pression atmosph&eacute;rique</a></li>";
  page +=       "</ul>";
  page +=       "<table class='table'>";  // Tableau des relevés
  page +=         "<thead><tr><th>Capteur</th><th>Mesure</th><th>Valeur</th><th>Valeur pr&eacute;c&eacute;dente</th></tr></thead>"; //Entête
  page +=         "<tbody>";  // Contenu du tableau
  page +=           "<tr><td>DHT22</td><td>Temp&eacute;rature</td><td>"; // Première ligne : température
  page +=             t;
  page +=             "&deg;C</td><td>";
  page +=             "-</td></tr>";
  page +=           "<tr class='active'><td>DHT22</td><td>Humidit&eacute;</td><td>"; // 2nd ligne : Humidité
  page +=             h;
  page +=             "%</td><td>";
  page +=             "-</td></tr>";
  page +=           "<tr><td>BMP180</td><td>Pression atmosph&eacute;rique</td><td>"; // 3ème ligne : PA (BMP180)
  page +=             p;
  page +=             "mbar</td><td>";
  page +=             "-</td></tr>";
  page +=       "</tbody></table>";
  page +=       "<h3>GPIO</h3>";
  page +=       "<div class='row'>";
  page +=         "<div class='col-md-4'><h4 class ='text-left'>D5 ";
  page +=           "<span class='badge'>";
  page +=           etatGpio[0];
  page +=         "</span></h4></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D5' value='1' class='btn btn-success btn-lg'>ON</button></form></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D5' value='0' class='btn btn-danger btn-lg'>OFF</button></form></div>";
  page +=         "<div class='col-md-4'><h4 class ='text-left'>D6 ";
  page +=           "<span class='badge'>";
  page +=           etatGpio[1];
  page +=         "</span></h4></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D6' value='1' class='btn btn-success btn-lg'>ON</button></form></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D6' value='0' class='btn btn-danger btn-lg'>OFF</button></form></div>";
  page +=         "<div class='col-md-4'><h4 class ='text-left'>D7 ";
  page +=           "<span class='badge'>";
  page +=           etatGpio[2];
  page +=         "</span></h4></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D7' value='1' class='btn btn-success btn-lg'>ON</button></form></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D7' value='0' class='btn btn-danger btn-lg'>OFF</button></form></div>";
  page +=         "<div class='col-md-4'><h4 class ='text-left'>D8 ";
  page +=           "<span class='badge'>";
  page +=           etatGpio[3];
  page +=         "</span></h4></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D8' value='1' class='btn btn-success btn-lg'>ON</button></form></div>";
  page +=         "<div class='col-md-4'><form action='/' method='POST'><button type='button submit' name='D8' value='0' class='btn btn-danger btn-lg'>OFF</button></form></div>";
  page +=       "</div>";
  page +=     "<br><p><a href=''></p>
  page += "</div></div></div>";
  page += "</body></html>";
  return page;
void handleRoot(){ 
  if ( server.hasArg("D5") ) {
  } else if ( server.hasArg("D6") ) {
  } else if ( server.hasArg("D7") ) {
  } else if ( server.hasArg("D8") ) {
  } else {
    server.send ( 200, "text/html", getPage() );

void handleD5() {
  String D5Value; 

void handleD6() {
  String D6Value; 

void handleD7() {
  String D7Value; 

void handleD8() {
  String D8Value; 

void updateGPIO(int gpio, String DxValue) {
  Serial.println("Update GPIO "); Serial.print(GPIOPIN[gpio]); Serial.print(" -> "); Serial.println(DxValue);
  if ( DxValue == "1" ) {
    digitalWrite(GPIOPIN[gpio], HIGH);
    etatGpio[gpio] = "On";
    server.send ( 200, "text/html", getPage() );
  } else if ( DxValue == "0" ) {
    digitalWrite(GPIOPIN[gpio], LOW);
    etatGpio[gpio] = "Off";
    server.send ( 200, "text/html", getPage() );
  } else {
    Serial.println("Err Led Value");

void setup() {
  for ( int x = 0 ; x < 5 ; x++ ) { 
  Serial.begin ( 115200 );
  // Initialisation du BMP180 / Init BMP180
  if ( !bmp.begin() ) {
    Serial.println("BMP180 KO!");
  } else {
    Serial.println("BMP180 OK");
  WiFi.begin ( ssid, password );
  // Attente de la connexion au réseau WiFi / Wait for connection
  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 ); Serial.print ( "." );
  // Connexion WiFi établie / WiFi connexion is OK
  Serial.println ( "" ); 
  Serial.print ( "Connected to " ); Serial.println ( ssid );
  Serial.print ( "IP address: " ); Serial.println ( WiFi.localIP() );

  // On branche la fonction qui gère la premiere page / link to the function that manage launch page 
  server.on ( "/", handleRoot );

  Serial.println ( "HTTP server started" );

void loop() {
  // put your main code here, to run repeatedly:
  t = dht.readTemperature();
  h = dht.readHumidity();
  p = bmp.readPressure() / 100.0F;


Here is the interface you will get by entering the IP address of the Wemos D1 Mini. The page is configured to automatically update every 60 seconds (content parameter = ’60 ‘in the page header).

There is still much to discover, it is impossible to detail everything in a tutorial, but you now have the keys to create beautiful HTML interfaces for your projects ESP8266. Feel free to share screenshots of your achievements in the comments!

Note. All the tutorial was developed on the IDE Arduino 1.8.1 installed on an Orange Pi Plus 2e (presented here) running under Armbian (Ubuntu 16.04 LTS). Follow this tutorial to learn how to install the Arduino IDE on Linux (ARM or x86).


Click to rate this post!
[Total: 3 Average: 5]
Exit mobile version