This tutorial shows how to build an ESP32 web server with a slider to control the LED brightness. You’ll learn how to add a slider to your web server projects, get its value and save it in a variable that the ESP32 can use. We’ll use that value to control the duty cycle of a PWM signal and change the brightness of an LED. Instead of an LED you can control a servo motor, for example.

Updated 13 March 2025
Additionally, you can also modify the code in this tutorial to add slider to your projects to set a threshold value or any other value that you need to use in your code.

Before proceeding with this project, make sure you check the following prerequisites.
We’ll program the ESP32 boards using Arduino IDE, so before proceeding with this tutorial, make sure you have the ESP32 board installed in your Arduino IDE.
We’ll build the web server using the following libraries:
You can install these libraries in the Arduino Library Manager. Open the Library Manager by clicking on the Library icon at the left sidebar.
Search for ESPAsyncWebServer and install the ESPAsyncWebServer by ESP32Async.

Then, install the AsyncTCP library. Search for AsyncTCP and install the AsyncTCP by ESP32Async.

The following code controls the brightness of the ESP32 built-in LED using a slider on a web server. In other words, you can change the PWM duty cycle with a slider. This can be useful to control the LED brightness or control a servo motor, for example.
Copy the code to your Arduino IDE. Insert your network credentials and the code will work straight away.
/********* Rui Santos & Sara Santos - Random Nerd Tutorials Complete project details at https://RandomNerdTutorials.com/esp32-web-server-slider-pwm/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. *********/ // Import required libraries #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; const int output = 2; String sliderValue = "0"; // setting PWM properties const int freq = 5000; const int ledChannel = 0; const int resolution = 8; const char* PARAM_INPUT = "value"; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); const char index_html[] PROGMEM = R"rawliteral( <!DOCTYPE HTML><html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP Web Server</title> <style> html {font-family: Arial; display: inline-block; text-align: center;} h2 {font-size: 2.3rem;} p {font-size: 1.9rem;} body {max-width: 400px; margin:0px auto; padding-bottom: 25px;} .slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C; outline: none; -webkit-transition: .2s; transition: opacity .2s;} .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;} .slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; } </style> </head> <body> <h2>ESP Web Server</h2> <p><span id="textSliderValue">%SLIDERVALUE%</span></p> <p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="255" value="%SLIDERVALUE%" step="1" class="slider"></p> <script> function updateSliderPWM(element) { var sliderValue = document.getElementById("pwmSlider").value; document.getElementById("textSliderValue").innerHTML = sliderValue; console.log(sliderValue); var xhr = new XMLHttpRequest(); xhr.open("GET", "/slider?value="+sliderValue, true); xhr.send(); } </script> </body> </html> )rawliteral"; // Replaces placeholder with button section in your web page String processor(const String& var){ //Serial.println(var); if (var == "SLIDERVALUE"){ return sliderValue; } return String(); } void setup(){ // Serial port for debugging purposes Serial.begin(115200); // configure LED PWM functionalitites ledcAttachChannel(output, freq, resolution, ledChannel); ledcWrite(output, sliderValue.toInt()); // Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Print ESP Local IP Address Serial.println(WiFi.localIP()); // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, "text/html", index_html, processor); }); // Send a GET request to <ESP_IP>/slider?value=<inputMessage> server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest *request) { String inputMessage; // GET input1 value on <ESP_IP>/slider?value=<inputMessage> if (request->hasParam(PARAM_INPUT)) { inputMessage = request->getParam(PARAM_INPUT)->value(); sliderValue = inputMessage; ledcWrite(output, sliderValue.toInt()); } else { inputMessage = "No message sent"; } Serial.println(inputMessage); request->send(200, "text/plain", "OK"); }); // Start server server.begin(); } void loop() { }
Continue reading to learn how the code works or skip to the next section.
First, import the required libraries. The WiFi, ESPAsyncWebServerand the ESPAsyncTCP are needed to build the web server.
#include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h>
Insert your network credentials in the following variables, so that the ESP32 can connect to your local network.
const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";
We’ll control the brightness of the ESP32 built-in LED. The built-in LED corresponds to GPIO 2. Save the GPIO we want to control on the output variable.
The sliderValue variable will hold the slider value. At start, it is set to zero.
String sliderValue = "0";
The following lines define the PWM properties to control the LED.
// setting PWM properties const int freq = 5000; const int ledChannel = 0; const int resolution = 8;
We’ll use 8-bit resolution, which means you can control the LED brightness using a value from 0 to 255.
To learn more about PWM properties with the ESP32, read our guide: ESP32 PWM with Arduino IDE (Analog Output).
The PARAM_INPUT variable will be used to “search” for the slider value on the request received by the ESP32 when the slider is moved. (Remember: the ESP32 will receive a request like this GET/slider?value=SLIDERVALUE)
const char* PARAM_INPUT = "value";
It will search for value on the URL and get the value assigned to it.
Let’s now proceed to the web server page.

The web page for this project is pretty simple. It contains one heading, one paragraph and one input of type range.
Let’s see how the web page is created.
All the HTML text with styles included is stored in the index_html variable. Now we’ll go through the HTML text and see what each part does.
The following <meta> tag makes your web page responsive in any browser.
<meta name="viewport" content="width=device-width, initial-scale=1">Between the <title> </title> tags goes the title of our web server. The title is the text that shows up on the web browser tab.
Between the <style></style> tags, we add some CSS to style the web page.
<style> html {font-family: Arial; display: inline-block; text-align: center;} h2 {font-size: 2.3rem;} p {font-size: 1.9rem;} body {max-width: 400px; margin:0px auto; padding-bottom: 25px;} .slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C; outline: none; -webkit-transition: .2s; transition: opacity .2s;} .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;} .slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; } </style>
Basically, we’re setting the HTML page to display the text with Arial font in block without margin, and aligned at the center.
html {font-family: Arial; display: inline-block; text-align: center;}The following lines set the font size for the heading (h2) and paragraph (p).
h2 {font-size: 2.3rem;}
p {font-size: 1.9rem;}Set the HTML body properties.
body {max-width: 400px; margin:0px auto; padding-bottom: 25px;}The following lines customize the slider:
.slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C;
outline: none; -webkit-transition: .2s; transition: opacity .2s;}
.slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;}
.slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; } Inside the <body></body> tags is where we add the web page content.
The <h2></h2> tags add a heading to the web page. In this case, the “ESP Web Server” text, but you can add any other text.
<h2>ESP Web Server</h2>
The first paragraph will contain the current slider value. That particular HTML tag has the id textSliderValue assign to it, so that we can reference it later.
<p><span id="textSliderValue">%SLIDERVALUE%</span></p>
The %SLIDERVALUE% is a placeholder for the slider value. This will be replaced by the ESP32 by an actual value when it sends it to the browser. This is useful to show the current value when you access the browser for the first time.
To create a slider in HTML you use the <input> tag. The <input> tag specifies a field where the user can enter data.
There are a wide variety of input types. To define a slider, use the “type” attribute with the “range” value. In a slider, you also need to define the minimum and the maximum range using the “min” and “max” attributes (in this case, 0 and 255, respectively).
<p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="255" value="%SLIDERVALUE%" step="1" class="slider"></p>
You also need to define other attributes like:
Next, you need to add some JavaScript code to your HTML file using the <script> and </script> tags. You need to add the updateSliderPWM() function that will make a request to the ESP32 with the current slider value.
<script> function updateSliderPWM(element) { var sliderValue = document.getElementById("pwmSlider").value; document.getElementById("textSliderValue").innerHTML = sliderValue; console.log(sliderValue); var xhr = new XMLHttpRequest(); xhr.open("GET", "/slider?value="+sliderValue, true); xhr.send(); } </script>
This next line gets the current slider value by its id and saves it in the sliderValue JavaScript variable. Previously, we’ve assigned the id of the slider to pwmSlider. So, we get it as follows:
var sliderValue = document.getElementById("pwmSlider").value;After that, we set the slider label (whose id is textSliderValue) to the value saved on the sliderValue variable.
Finally, make an HTTP GET request.
var xhr = new XMLHttpRequest();
xhr.open("GET", "/slider?value="+sliderValue, true);
xhr.send();For example, when the slider is at 0, you make an HTTP GET request on the following URL:
http://ESP-IP-ADDRESS/slider?value=0
And when the slider value is 200, you’ll have a request on the follow URL.
http://ESP-IP-ADDRESS/slider?value=200
This way, when the ESP32 receives the GET request, it can retrieve the value parameter in the URL and control the PWM signal accordingly as we’ll se in the next sections
Now, we need to create the processor() function, that will replace the placeholders in our HTML text with the current slider value when you access it for the first time in a browser.
// Replaces placeholder with button section in your web page String processor(const String& var){ //Serial.println(var); if (var == "SLIDERVALUE"){ return sliderValue; } return String(); }
When the web page is requested, we check if the HTML has any placeholders. If it finds the %SLIDERVALUE% placeholder, we return the value saved on the sliderValue variable.
In the setup(), initialize the Serial Monitor for debugging purposes.
Serial.begin(115200);
Configure the LEDC pin with the PWM properties defined earlier.
// configure LED PWM functionalitites ledcAttachChannel(output, freq, resolution, ledChannel);
Set the duty cycle of the PWM signal to the value saved on the sliderValue (when the ESP32 starts, it is set to 0).
ledcWrite(output, sliderValue.toInt());
Connect to your local network and print the ESP32 IP address.
// Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Print ESP Local IP Address Serial.println(WiFi.localIP());
Finally, add the next lines of code to handle the web server.
// Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html, processor); }); // Send a GET request to <ESP_IP>/slider?value=<inputMessage> server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest *request) { String inputMessage; // GET input1 value on <ESP_IP>/slider?value=<inputMessage> if (request->hasParam(PARAM_INPUT)) { inputMessage = request->getParam(PARAM_INPUT)->value(); sliderValue = inputMessage; ledcWrite(output, sliderValue.toInt()); } else { inputMessage = "No message sent"; } Serial.println(inputMessage); request->send(200, "text/plain", "OK"); });
When we make a request on the root URL, we send the HTML text that is stored on the index_html variable. We also need to pass the processorfunction, that will replace all the placeholders with the right values.
// Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html, processor); });
We need another handler that will save the current slider value and set he LED brightness accordingly.
server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest *request) { String inputMessage; // GET input1 value on <ESP_IP>/slider?value=<inputMessage> if (request->hasParam(PARAM_INPUT)) { inputMessage = request->getParam(PARAM_INPUT)->value(); sliderValue = inputMessage; ledcWrite(output, sliderValue.toInt()); } else { inputMessage = "No message sent"; } Serial.println(inputMessage); request->send(200, "text/plain", "OK"); });
Basically, we get the slider value on the following lines:
if (request->hasParam(PARAM_INPUT)) { inputMessage = request->getParam(PARAM_INPUT)->value(); sliderValue = inputMessage;
Then, update the LED brightness (PWM duty cycle) using the ledcWrite() function that accepts as arguments the LEDC pin you want to control and the value.
ledcWrite(output, sliderValue.toInt());
Lastly, start the server.
server.begin();
Because this is an asynchronous web server, we don’t need to write anything in the loop().
void loop(){ }
That’s pretty much how the code works.
Now, upload the code to your ESP32. Make sure you have the right board and COM port selected.
After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 reset button. The ESP32 IP address should be printed in the serial monitor.

Open a browser and type the ESP32 IP address. Your web server should display the slider and its current value.

Move the slider and see the ESP32 built-in LED increasing and decreasing its brightness.
Copyright ©2025. All Rights Reserved Emblab THE RAVE INNOVATION