diff --git a/tutorials/device-dashboard/events/Makefile b/tutorials/device-dashboard/events/Makefile
new file mode 100644
index 00000000..f28dc096
--- /dev/null
+++ b/tutorials/device-dashboard/events/Makefile
@@ -0,0 +1,28 @@
+# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
+
+PROG ?= example
+TOP ?= ../../..
+SOURCES = dashboard.c file_data.c $(TOP)/mongoose.c
+CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
+CFLAGS ?= -W -Wall -Wextra -Werror -Wundef -Wshadow -O0 $(CFLAGS_EXTRA)
+PFLAGS = -I$(TOP) -DMG_DATA_SIZE=64 -DMG_ENABLE_LINES=1 -DMAIN
+AFLAGS ?= -fsanitize=signed-integer-overflow,address,undefined,alignment
+
+all: $(PROG)
+ ./$(PROG)
+
+# Rebuild the program
+$(PROG): $(SOURCES) $(TOP)/mongoose.h
+ $(CC) $(SOURCES) $(CFLAGS) $(PFLAGS) $(AFLAGS) -o $@
+
+# Create an embedded filesystem containing compressed dashboard.html
+file_data.c: dashboard.html
+ node $(TOP)/resources/html2c.js dashboard.html -o file_data.c
+ @echo "On-flash size: `grep -oE '[0-9]+' file_data.c | wc -l`"
+
+# For development: rebuild and restart if any of the sources change
+watch:
+ node $(TOP)/resources/watch.js -w dashboard.c -w $(TOP)/mongoose.h -w dashboard.html -- make
+
+clean:
+ rm -rf $(PROG) *.exe *.obj *.dSYM mongoose file_data.c
diff --git a/tutorials/device-dashboard/events/dashboard.c b/tutorials/device-dashboard/events/dashboard.c
new file mode 100644
index 00000000..3cff03cd
--- /dev/null
+++ b/tutorials/device-dashboard/events/dashboard.c
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2026 Cesanta Software Limited
+// SPDX-License-Identifier: GPL-2.0-only or commercial
+
+#include "mongoose.h"
+
+struct event {
+ int index;
+ char message[100];
+};
+
+static struct event s_event;
+
+static void read_event(void) {
+ mg_snprintf(s_event.message, sizeof(s_event.message), "my ev %d",
+ s_event.index);
+}
+
+static void write_event(void) {
+ // Do nothing. Dashboard sets the s_event.index
+}
+
+static struct mg_field fields_event[] = {
+ {"index", MG_VAL_INT, &s_event.index, sizeof(s_event.index)},
+ {"message", MG_VAL_STR, &s_event.message, sizeof(s_event.message)},
+ {NULL, MG_VAL_INT, NULL, 0},
+};
+
+static struct mg_field_set field_set_event = {
+ "event", fields_event, read_event, write_event, 0, 0, NULL,
+};
+
+void mg_dash_init(struct mg_mgr *mgr) {
+ static struct mg_dash dash; // Important: keep it static!
+ MG_DASH_ADD_FIELD_SET(&dash, &field_set_event);
+ mg_mem_files = mg_packed_files;
+ mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &dash);
+}
+
+void mg_dash_poll(struct mg_mgr *mgr) {
+ (void) mgr;
+}
+
+// On desktop, build with -DMAIN flag to include main().
+// On embedded system, run this code in your main() function
+#ifdef MAIN
+int main(void) {
+ struct mg_mgr mgr;
+
+ mg_mgr_init(&mgr);
+ mg_dash_init(&mgr);
+
+ for (;;) {
+ mg_mgr_poll(&mgr, 1);
+ mg_dash_poll(&mgr);
+ }
+
+ return 0;
+}
+#endif
diff --git a/tutorials/device-dashboard/events/dashboard.html b/tutorials/device-dashboard/events/dashboard.html
new file mode 100644
index 00000000..6f2f4296
--- /dev/null
+++ b/tutorials/device-dashboard/events/dashboard.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+ Array Dashboard
+
+
+
+
+
+
+
+
+
+ Array read/write
+
+
+
+
+
+
+
+ | Index |
+ Message |
+
+
+
+
+
+
+
+