diff --git a/python_payload/st3m/goose.py b/python_payload/st3m/goose.py
index 5b678a896d4b43b70eda7735bd55de4381cddc3f..5226841abc4fb0c9317d4efbc4816654c3d26182 100644
--- a/python_payload/st3m/goose.py
+++ b/python_payload/st3m/goose.py
@@ -17,7 +17,17 @@ if TYPE_CHECKING:
     class ABCBase(metaclass=ABCMeta):
         pass
 
-    from typing import List, Optional, Tuple, Dict, Any, Callable, Iterator, Generator, Union
+    from typing import (
+        List,
+        Optional,
+        Tuple,
+        Dict,
+        Any,
+        Callable,
+        Iterator,
+        Generator,
+        Union,
+    )
     from enum import Enum
 else:
     # We're in CPython or Micropython.
diff --git a/python_payload/st3m/settings.py b/python_payload/st3m/settings.py
index 69d2fbbae9b288165902d03aa5595ec971a01503..c74061baa78c640172c84040d360bb23a0e5e69d 100644
--- a/python_payload/st3m/settings.py
+++ b/python_payload/st3m/settings.py
@@ -11,8 +11,19 @@ request.
 import json
 
 from st3m import InputState, Responder, logging
-from st3m.goose import ABCBase, abstractmethod, Any, Dict, List, Optional, Callable
-from st3m.ui.menu import MenuController, MenuItem, MenuItemBack
+from st3m.goose import (
+    ABCBase,
+    abstractmethod,
+    Any,
+    Dict,
+    List,
+    Tuple,
+    Optional,
+    Callable,
+    Union,
+    TYPE_CHECKING,
+)
+from st3m.ui.menu import MenuController, MenuItem, MenuItemBack, MenuItemForeground
 from st3m.ui.elements.menus import SimpleMenu
 from st3m.ui.view import ViewManager
 from st3m.utils import lerp, ease_out_cubic, reduce
@@ -337,7 +348,9 @@ onoff_wifi = OnOffTunable("Enable WiFi", "system.wifi.enabled", False)
 str_wifi_ssid = StringTunable("WiFi SSID", "system.wifi.ssid", "Camp2023-open")
 str_wifi_psk = StringTunable("WiFi Password", "system.wifi.psk", None)
 str_hostname = StringTunable("Hostname", "system.hostname", "flow3r")
-all_settings: List[UnaryTunable] = [
+
+# List of all settings to be loaded/saved
+load_save_settings: List[UnaryTunable] = [
     onoff_show_tray,
     onoff_button_swap,
     onoff_debug,
@@ -348,6 +361,27 @@ all_settings: List[UnaryTunable] = [
     str_hostname,
 ]
 
+if TYPE_CHECKING:
+    MenuStructureEntry = Union[UnaryTunable, Tuple[str, List["MenuStructureEntry"]]]
+    MenuStructure = List[MenuStructureEntry]
+
+# WiFi submenu
+wifi_settings: "MenuStructure" = [
+    onoff_wifi,
+    str_wifi_ssid,
+    str_wifi_psk,
+    str_hostname,
+]
+
+# Main settings menu
+settings_menu_structure: "MenuStructure" = [
+    onoff_show_tray,
+    onoff_button_swap,
+    onoff_debug,
+    onoff_debug_touch,
+    ("Wifi", wifi_settings),
+]
+
 
 def load_all() -> None:
     """
@@ -362,7 +396,7 @@ def load_all() -> None:
         return
 
     log.info("Loaded settings from flash")
-    for setting in all_settings:
+    for setting in load_save_settings:
         setting.load(data)
 
 
@@ -383,7 +417,7 @@ def save_all() -> None:
     Save all settings to flash.
     """
     res: Dict[str, Any] = {}
-    for setting in all_settings:
+    for setting in load_save_settings:
         res = _update(res, setting.save())
     try:
         with open("/flash/settings.json", "w") as f:
@@ -395,12 +429,25 @@ def save_all() -> None:
     log.info("Saved settings to flash")
 
 
-def build_menu() -> SimpleMenu:
+def build_menu_recursive(items: "MenuStructure") -> SimpleMenu:
     """
-    Return a SimpleMenu for all settings.
+    Recursively build a menu for the given setting structure.
     """
     mib: MenuItem = SettingsMenuItemBack()
     positions: List[MenuItem] = [
         mib,
-    ] + [SettingsMenuItem(t) for t in all_settings]
+    ] + [
+        SettingsMenuItem(t)
+        if isinstance(t, UnaryTunable)
+        else MenuItemForeground(t[0], build_menu_recursive(t[1]))
+        for t in items
+    ]
+
     return SettingsMenu(positions)
+
+
+def build_menu() -> SimpleMenu:
+    """
+    Return a SettingsMenu for all settings.
+    """
+    return build_menu_recursive(settings_menu_structure)