diff --git a/docs/widget-guide.md b/docs/widget-guide.md
index 7a1ed850..9bc271eb 100644
--- a/docs/widget-guide.md
+++ b/docs/widget-guide.md
@@ -206,6 +206,47 @@ To use an existing widget in your plugin's `config_schema.json`, simply add the
The widget will be automatically rendered when the plugin configuration form is loaded.
+## Labelling Enum Options (`x-options.labels`)
+
+A plain `enum` renders as a dropdown whose option text is the value with
+underscores replaced and title case applied — `day_first` becomes "Day First".
+That is fine for values that read as their own label, and wrong for values that
+do not: `vs` becomes "Vs", and `abbrev` says nothing about the `Sep 19` it
+actually produces.
+
+Supply `x-options.labels` to set the visible text. This is the same convention
+the `checkbox-group` widget uses:
+
+```json
+{
+ "date_format": {
+ "type": "string",
+ "enum": ["abbrev", "numeric", "day_first"],
+ "default": "abbrev",
+ "x-options": {
+ "labels": {
+ "abbrev": "Sep 19",
+ "numeric": "9/19",
+ "day_first": "19 Sep"
+ }
+ }
+ }
+}
+```
+
+Labels are **display only** — the stored value is still the enum value, so
+adding them never changes a saved config. The map may be partial: any value
+without a label keeps the humanised fallback. Older cores that predate this
+support ignore `x-options` and render the fallback for every option, so a
+plugin can ship labels without requiring a core upgrade.
+
+Array-table columns (`x-widget: array-table`) accept the same
+`x-options.labels` on a column definition, but their fallback is the **raw
+value** rather than the humanised one, because those columns hold values such
+as ticker symbols where `aapl` → "Aapl" would be wrong. Rows added in the
+browser use the labels too (`array-table.js`), so a column reads the same
+before and after a page reload.
+
## Marking Fields as Advanced (`x-advanced`)
Add `"x-advanced": true` to any top-level, non-object property to move it out
diff --git a/test/test_enum_option_labels.py b/test/test_enum_option_labels.py
new file mode 100644
index 00000000..1e8d248e
--- /dev/null
+++ b/test/test_enum_option_labels.py
@@ -0,0 +1,134 @@
+"""Guard: enum dropdowns in the plugin config form honour x-options.labels.
+
+The form derives an option's visible text from its value — underscores
+replaced, title case applied ("day_first" -> "Day First"). That cannot
+express every label a schema needs: "vs" reads as "Vs", and "abbrev" says
+nothing about the "Sep 19" it produces. Schemas can supply x-options.labels
+instead, the same convention the checkbox-group widget already uses.
+
+These tests extract the enum `.
+ENUM_BLOCK_RE = re.compile(
+ r"(\{%\s*set enum_labels\s*=.*?)", re.S
+)
+
+
+def _shipped_enum_block() -> str:
+ """Return the live enum