Update: 2020-06-16

Esp32 Individual Button als Arduino Tab.

Achtung!
Nach dem ersten Aufruf der Webseite werden die Beschriftungen der Schaltflächen aus dem Local Storage des Browsers gelesen.
Eine nachträgliche Änderung im Sketch wird entweder durch löschen der Bezeichnungen im Local Storage oder durch ändern der Anzahl der Namen und Pin im Sketch übernommen.

Button.ino

// ****************************************************************
// Sketch Esp32 Individual Button Modular(Tab)
// created: Jens Fleischer, 2020-04-13
// last mod: Jens Fleischer, 2020-04-13
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Device an T2 = GPIO  2
// Device an T6 = GPIO 14
// Device an T7 = GPIO 27
// Device an T8 = GPIO 33
// Device an T9 = GPIO 32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von Individual Button sollte als Tab eingebunden werden.
// #include <WebServer.h> muss im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// Die Funktion "individualButton();" muss im Setup aufgerufen werden.
// Die Anzahl der Namen und Pin müssen übereinstimmen.
/**************************************************************************************/

const char* const PROGMEM NAME[] {"Stimmungslicht", "Pumpe", "Filter", "Sternenhimmel", "Lüftung"};
const auto level = HIGH;                                  // LOW für LOW aktive Relais/Led oder HIGH für HIGH aktive (zB. SSR, Mosfet) einstellen
const uint8_t buttonPin[] {2, 14, 27, 33, 32};            // Pin's eintragen die du verwenden möchtest

void individualButton() {                                // Die Funktion "setupButton();" muss im Setup aufgerufen werden.
  while (delay(2e3), sizeof(buttonPin) != sizeof(NAME) / sizeof(NAME[0])) Serial.println("Anzahl Pin und Namen der Button stimmen nicht überein.");
  for (auto& pin : buttonPin) {
    digitalWrite(pin, !level);
    pinMode(pin, OUTPUT);
  }
  // Button Namen senden falls diese nicht im LocalStorage des Browsers gefunden werden
  server.on("/press", HTTP_GET, []() {
    String temp = "[";
    for (auto& el : NAME) {
      if (temp != "[") temp += ',';
      temp += (String)"\"" + el + "\"";
    }
    temp += "]";
    server.send(200, "application/json", temp);
  });
  server.on("/press", HTTP_POST, []() {
    if (server.args()) {
      // Pin umschalten
      digitalWrite(buttonPin[server.argName(0).toInt()], !digitalRead(buttonPin[server.argName(0).toInt()]));
      // Serielle Ausgabe zur Kontrolle
      DEBUG_F("Button %2d ist %s\n", 1 + server.argName(0).toInt(), digitalRead(buttonPin[server.argName(0).toInt()]) == level ? "ON" : "OFF");
    }
    // Http Antwort
    String temp = "\"";
    for (byte i = 0; i < sizeof(buttonPin); i++) temp += digitalRead(buttonPin[i]) == level;
    temp += "\"";
    server.send(200, "application/json", temp);
  });
}

Die Webseite zum Esp32 Individual Button Tab.

button.html

<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="style32.css">
	<title>Individual Button</title>
	<style>
	  button:active {
        transition: all .5s ease;
        transform: translate(2%, 15%);
        box-shadow: none;
      }
	  button {
        transition: all .5s ease;
      }
	</style>
    <script>
	  var btnName = JSON.parse(localStorage.getItem('button'));
	  window.addEventListener('DOMContentLoaded', () => {
		if (!btnName) {
          fetch('/press', {
          }).then(resp => {
            return resp.json();
          }).then(arr => {
	        localStorage.setItem('button', JSON.stringify(arr));
		    btnName = arr;
			dom();
	      });
        }
		else {
		  dom();
		}
		send();
	  });
	  function dom() {
	  	var buf = '';
	    btnName.forEach(el => {
		  length = Math.max(length, el.length);
		  buf += '<button class="button">' + el + '<span></span></button>';
        });
	    document.querySelector('body').insertAdjacentHTML('beforeend', buf);
		document.querySelectorAll('button').forEach((el, i) => {
          el.addEventListener('click', () => {
            let formData = new FormData();
            formData.append(i, 0);
            send(formData);
          });
        });
	  }
      function send(arg) {
        fetch('/press', {
          method: 'post',
          body: arg
        }).then(resp => {
          return resp.json();
        }).then(str => {
		  if (str.length != btnName.length) localStorage.clear();
          document.querySelectorAll('button').forEach((el, i) => {
		    el.style.minWidth = 0.8 * length + 'em';
		    el.style.backgroundColor = str[i] != 0 ? '#3f0' : '#6c5';
		    el.lastElementChild.innerHTML = str[i] != 0 ? ' AN' : ' AUS';
		  });
        });
      } 
    </script>
  </head>
  <body>
    <h1>SmartHome</h1>
  </body>
</html>