Update: 2023-01-09

Esp8266 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 Esp8266 Individual Button Modular(Tab)
// created: Jens Fleischer, 2020-04-10
// last mod: Jens Fleischer, 2020-06-18
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Device an D0 = GPIO 16
// Device an D1 = GPIO  5
// Device an D2 = GPIO  4
// Device an D3 = GPIO  0
// Device an D4 = GPIO  2
// Device an D5 = GPIO 14
// Device an D6 = GPIO 12
// Device an D7 = GPIO 13
// Device an D8 = GPIO 15
// Software: Esp8266 Arduino Core 2.4.2 - 3.1.0
// Getestet auf: Nodemcu, Wemos D1 Mini Pro
/******************************************************************
  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 <ESP8266WebServer.h> muss im Haupttab aufgerufen werden.
// Die Funktionalität des ESP8266 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[] {"Beleuchtung", "Pumpe", "Filter", "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[] {D2, D6, D7, D8};               // Pin's eintragen die du verwenden möchtest

void individualButton() {                                 // Die Funktion "individualButton();" muss im Setup aufgerufen werden.
  for (; sizeof(buttonPin) != sizeof(NAME) / sizeof(NAME[0]); Serial.println("Anzahl Pin und Namen der Button stimmen nicht überein."), delay(2e3));
  for (auto& pin : buttonPin) {
    digitalWrite(pin, !level);
    pinMode(pin, OUTPUT);
  }
  // Button Namen senden wenn diese nicht aus dem LocalStorage des Browsers geladen werden können.
  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
      Serial.printf("Button %ld ist %s\n", 1 + server.argName(0).toInt(), digitalRead(buttonPin[server.argName(0).toInt()]) == level ? "AN" : "AUS");
    }
    // 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 Esp8266 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="style.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>
	  window.addEventListener('DOMContentLoaded', () => {
	    var name = JSON.parse(localStorage.getItem('button'));
		if (!name) {
          fetch('/press', {
          }).then(resp => {
            return resp.json();
          }).then(arr => {
	        localStorage.setItem('button', JSON.stringify(arr));
			dom(arr);
	      });
        }
		else {
		  dom(name);
		}
	  });
	  function dom(names) {
	  	var buf = '';
	    names.forEach(el => {
		  length = Math.max(length, el.length);
		  buf += '<button class="button">' + el + '<span></span></button>';
        });
	    document.querySelector('body').insertAdjacentHTML('beforeend', buf);
		send(names);
		document.querySelectorAll('button').forEach((el, i) => {
          el.addEventListener('click', () => {
            let formData = new FormData();
            formData.append(i, 0);
            send(names,formData);
          });
        });
	  }
      function send(arr, arg) {
        fetch('/press', {
          method: 'post',
          body: arg
        }).then(resp => {
          return resp.json();
        }).then(str => {
		  if (str.length != arr.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>