diff --git a/ROADMAP.md b/ROADMAP.md
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -361,6 +361,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
 
 Newest first.
 
+- **2026-10-25** · Buttons and Forms gains the actions the Object States panel was missing a partner for (#1798): Go To State (object and state), Go To Next State and Go To Previous State (object, Loop), and Go To Destination (a text anchor). `button.set` takes `action: state|nextState|previousState|destination` with `object` (defaulting to the document's only multi-state object), `state` (index or name), `wrap` and `anchor`. Destination buttons export as PDF links to the anchor's page; state actions stay in the document until states export as optional content. No readiness percentage change.
 - **2026-10-24** · Renaming a paragraph or character style, or moving it into a style group, keeps its Export Tagging under the new name, and undo puts it back under the old one. Deleting a tagged style drops its tagging and leaves the replacement style's own tagging as it was. No readiness percentage change.
 - **2026-10-24** · Quick Apply and the application bar's search leave out the interface commands that failed or reset a setting when chosen there: those that need a workspace, a command and shortcut, a menu item, a panel, a window edge, an app page, a recent file or a swatch they can't give them, a Contextual Task Bar place, and Interface Color Theme, UI Scaling, Reset Shortcuts, the flattener preset, Screen Mode and Zoom To, which without a value switched to the dark theme, set 100%, cleared every custom shortcut or the preset, or went to Normal mode or 100% zoom. Each stays in the menu, panel or dialog that gives it the value. No readiness percentage change.
 - **2026-10-24** · EPUB Export Options › Split Document, a dialog before File › Export EPUB… saves (`document.epubSplit`, stored with the document): off, a single paragraph style, or based on paragraph style export tags, with Paragraph Style Options › Export Tagging's Split Document flag marking those styles. A reflowable EPUB is one content file unless Split Document is on; the split before each top-level h1 from #791 is gone. A split before a list item or preformatted line no longer leaves an unclosed list or block in a content file. IDML import and export keep the setting and the flag, and Load Styles the flag. No readiness percentage change.
diff --git a/crates/doc/src/item.rs b/crates/doc/src/item.rs
--- a/crates/doc/src/item.rs
+++ b/crates/doc/src/item.rs
@@ -972,6 +972,22 @@ pub enum ButtonAction {
     GoToNextPage,
     GoToPreviousPage,
     GoToUrl { url: String },
+    /// A text anchor (a Hyperlinks panel destination): its page.
+    GoToDestination { anchor: u64 },
+    /// Show state `state` (0-based) of the multi-state object `object`.
+    GoToState { object: ItemId, state: usize },
+    /// Show the state after the active one of `object`; `wrap` goes round from the last to the first.
+    GoToNextState {
+        object: ItemId,
+        #[serde(default)]
+        wrap: bool,
+    },
+    /// Show the state before the active one of `object`; `wrap` goes round from the first to the last.
+    GoToPreviousState {
+        object: ItemId,
+        #[serde(default)]
+        wrap: bool,
+    },
 }
 
 fn is_zero_usize(v: &usize) -> bool {
diff --git a/crates/engine/src/cmd/buttons.rs b/crates/engine/src/cmd/buttons.rs
--- a/crates/engine/src/cmd/buttons.rs
+++ b/crates/engine/src/cmd/buttons.rs
@@ -1,13 +1,33 @@
 //! Window › Interactive › Buttons and Forms: objects that act when clicked in an interactive PDF
-//! (go to a page, the next / previous / first / last page, or a URL).
+//! (go to a page, the next / previous / first / last page, a URL, a text anchor, or a state of a
+//! multi-state object).
 
-use designcraft_doc::ButtonAction;
+use designcraft_doc::{ButtonAction, ItemId};
 use serde_json::{Value, json};
 
 use super::{CommandSpec, bad, cmd, has_selection, targets};
-use crate::Result;
+use crate::{Result, Session};
 
-fn parse(p: &Value) -> Result<Option<ButtonAction>> {
+/// The multi-state object a state action targets: `object`, or the document's only one.
+fn state_object(s: &Session, p: &Value) -> Result<(ItemId, Vec<String>)> {
+    let d = &s.doc()?.doc;
+    let mso = |id: ItemId| d.item(id).filter(|it| !it.states.is_empty()).map(|it| (id, it.states.clone()));
+    if let Some(id) = super::id_param(p, "object") {
+        return mso(id).ok_or_else(|| bad("button.set", format!("{id} is not a multi-state object")));
+    }
+    let mut all = d.all_items().into_iter().filter_map(mso);
+    match (all.next(), all.next()) {
+        (Some(one), None) => Ok(one),
+        (None, _) => Err(bad("button.set", "no multi-state object in the document (Object States: Convert Selection to Multi-State Object)")),
+        _ => Err(bad("button.set", "`object` required: the document has several multi-state objects")),
+    }
+}
+
+fn wrap(p: &Value) -> bool {
+    p.get("wrap").and_then(Value::as_bool).unwrap_or(false)
+}
+
+fn parse(s: &Session, p: &Value) -> Result<Option<ButtonAction>> {
     let a = p.get("action").and_then(Value::as_str).unwrap_or("nextPage");
     Ok(Some(match a {
         "none" => return Ok(None),
@@ -19,6 +39,23 @@ fn parse(p: &Value) -> Result<Option<ButtonAction>> {
         "nextPage" => ButtonAction::GoToNextPage,
         "previousPage" => ButtonAction::GoToPreviousPage,
         "url" => ButtonAction::GoToUrl { url: p.get("url").and_then(Value::as_str).ok_or_else(|| bad("button.set", "`url` required"))?.to_string() },
+        "destination" => ButtonAction::GoToDestination {
+            anchor: p.get("anchor").and_then(Value::as_u64).ok_or_else(|| bad("button.set", "`anchor` (text anchor id) required"))?,
+        },
+        "state" => {
+            let (object, states) = state_object(s, p)?;
+            let state = match (p.get("state").and_then(Value::as_u64), p.get("state").and_then(Value::as_str)) {
+                (Some(i), _) => i as usize,
+                (None, Some(n)) => states.iter().position(|x| x == n).ok_or_else(|| bad("button.set", format!("no state `{n}`")))?,
+                _ => return Err(bad("button.set", "`state` (index or name) required")),
+            };
+            if state >= states.len() {
+                return Err(bad("button.set", format!("no state {state}")));
+            }
+            ButtonAction::GoToState { object, state }
+        }
+        "nextState" => ButtonAction::GoToNextState { object: state_object(s, p)?.0, wrap: wrap(p) },
+        "previousState" => ButtonAction::GoToPreviousState { object: state_object(s, p)?.0, wrap: wrap(p) },
         o => return Err(bad("button.set", format!("unknown action `{o}`"))),
     }))
 }
@@ -30,11 +67,11 @@ pub fn specs() -> Vec<CommandSpec> {
             "Convert to Button",
             ["Object", "Interactive"],
             None,
-            "{ids?, action: page|firstPage|lastPage|nextPage|previousPage|url|none, page?, url?} — the selected objects act on release in interactive PDF",
+            "{ids?, action: page|firstPage|lastPage|nextPage|previousPage|url|destination|state|nextState|previousState|none, page? (0-based), url?, anchor? (text anchor id), object? (multi-state object; default: the document's only one), state? (index or name), wrap?} — the selected objects act on release in interactive PDF (page, URL and destination actions export as PDF links; state actions are kept in the document for interactive export)",
             has_selection,
             |s, p| {
                 let ids = targets(s, p)?;
-                let action = parse(p)?;
+                let action = parse(s, p)?;
                 s.edit(|d, _| {
                     for id in &ids {
                         if let Some(it) = d.item_mut(*id) {
@@ -179,4 +216,58 @@ mod tests {
         assert_eq!(designcraft_render::pdf_page_count(&bytes), Some(3), "the update still reads");
         assert!(s.execute("button.list", &json!({})).unwrap().as_array().unwrap().is_empty());
     }
+
+    /// #1798: buttons reach a multi-state object's states and a text anchor, not only pages and URLs.
+    #[test]
+    fn buttons_go_to_states_and_destinations() {
+        let mut s = Session::new();
+        s.execute("file.new", &json!({"pages": 2, "facingPages": false})).unwrap();
+        let button = s.execute("frame.create", &json!({"rect": [72, 72, 200, 120]})).unwrap()["id"].clone();
+        // Without a multi-state object there is nothing to go to.
+        let e = s.execute("button.set", &json!({"ids": [button], "action": "nextState"})).expect_err("no multi-state object").to_string();
+        assert!(e.contains("no multi-state object"), "{e}");
+
+        let a = s.execute("frame.create", &json!({"rect": [100, 200, 200, 300]})).unwrap()["id"].clone();
+        let b = s.execute("frame.create", &json!({"rect": [100, 200, 200, 300]})).unwrap()["id"].clone();
+        let mso = s.execute("states.create", &json!({"ids": [a, b]})).unwrap()["id"].clone();
+
+        // The document's only multi-state object is the default target.
+        s.execute("button.set", &json!({"ids": [button], "action": "nextState", "wrap": true})).unwrap();
+        let act = s.execute("button.list", &json!({})).unwrap()[0]["action"].clone();
+        assert_eq!(act, json!({"kind": "goToNextState", "object": mso, "wrap": true}));
+        s.execute("button.set", &json!({"ids": [button], "action": "previousState"})).unwrap();
+        assert_eq!(s.execute("button.list", &json!({})).unwrap()[0]["action"]["wrap"], false, "wrap defaults to off");
+        // A state by name or by index; unknown states and objects are refused.
+        s.execute("button.set", &json!({"ids": [button], "action": "state", "object": mso, "state": "State 2"})).unwrap();
+        assert_eq!(s.execute("button.list", &json!({})).unwrap()[0]["action"], json!({"kind": "goToState", "object": mso, "state": 1}));
+        s.execute("button.set", &json!({"ids": [button], "action": "state", "state": 0})).unwrap();
+        assert_eq!(s.execute("button.list", &json!({})).unwrap()[0]["action"]["state"], 0);
+        assert!(s.execute("button.set", &json!({"ids": [button], "action": "state", "state": "State 9"})).is_err());
+        assert!(s.execute("button.set", &json!({"ids": [button], "action": "state", "state": 2})).is_err());
+        assert!(s.execute("button.set", &json!({"ids": [button], "action": "state", "object": button, "state": 0})).is_err(), "a plain frame is not a multi-state object");
+        assert!(s.execute("button.set", &json!({"ids": [button], "action": "state"})).is_err(), "`state` required");
+        // With two multi-state objects the target has to be named.
+        let c = s.execute("frame.create", &json!({"rect": [300, 200, 400, 300]})).unwrap()["id"].clone();
+        let d = s.execute("frame.create", &json!({"rect": [300, 200, 400, 300]})).unwrap()["id"].clone();
+        s.execute("states.create", &json!({"ids": [c, d]})).unwrap();
+        let e = s.execute("button.set", &json!({"ids": [button], "action": "nextState"})).expect_err("ambiguous").to_string();
+        assert!(e.contains("`object` required"), "{e}");
+        s.execute("button.set", &json!({"ids": [button], "action": "nextState", "object": mso})).unwrap();
+
+        // Go To Destination: a text anchor's page becomes the link target in the PDF.
+        let text = s.execute("frame.create", &json!({"spread": 1, "rect": [72, 72, 400, 200], "content": "text", "text": "Appendix"})).unwrap();
+        s.execute("text.select", &json!({"story": text["story"], "anchor": 0, "focus": 0})).unwrap();
+        let anchor = s.execute("anchor.create", &json!({"name": "Appendix"})).unwrap()["id"].clone();
+        assert!(s.execute("button.set", &json!({"ids": [button], "action": "destination"})).is_err(), "`anchor` required");
+        let links = |s: &mut Session| {
+            let r = s.execute("file.exportPdf", &json!({})).unwrap();
+            let bytes = super::super::file::base64_decode(r["base64"].as_str().unwrap());
+            String::from_utf8_lossy(&bytes).matches("/Link").count()
+        };
+        s.execute("button.clear", &json!({"ids": [button]})).unwrap();
+        let without = links(&mut s);
+        s.execute("button.set", &json!({"ids": [button], "action": "destination", "anchor": anchor})).unwrap();
+        assert_eq!(s.execute("button.list", &json!({})).unwrap()[0]["action"], json!({"kind": "goToDestination", "anchor": anchor}));
+        assert!(links(&mut s) > without, "the destination button exports a link annotation");
+    }
 }
diff --git a/crates/pdf/src/links.rs b/crates/pdf/src/links.rs
--- a/crates/pdf/src/links.rs
+++ b/crates/pdf/src/links.rs
@@ -107,6 +107,10 @@ pub(crate) fn annotations(doc: &Document, cache: &designcraft_compose::Cache, sh
             B::GoToLastPage => total.checked_sub(1),
             B::GoToNextPage => here.map(|h| h + 1).filter(|p| *p < total),
             B::GoToPreviousPage => here.and_then(|h| h.checked_sub(1)),
+            // A text anchor's page, as cross-references resolve it.
+            B::GoToDestination { anchor } => cache.xref_index(doc).page_index(*anchor),
+            // A PDF shows one state of a multi-state object (#1798: states as optional content to come).
+            B::GoToState { .. } | B::GoToNextState { .. } | B::GoToPreviousState { .. } => continue,
         };
         let Some(i) = page.and_then(|p| sheet_of_page(doc, sheets, p)) else { continue };
         let target = Target::Destination(XyzDestination::new(i, krilla::geom::Point::from_xy(0.0, 0.0)).into());
diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs
--- a/crates/ui-egui/src/i18n.rs
+++ b/crates/ui-egui/src/i18n.rs
@@ -3577,6 +3577,12 @@ const TABLE: &[(&str, [&str; 5])] = &[
     ("Go To Last Page", ["Go To Last Page", "Atteindre la dernière page", "Go To Last Page", "Go To Last Page", "转到最后一页"]),
     ("Go To Page", ["Go To Page", "Atteindre la page", "Go To Page", "Go To Page", "转到页面"]),
     ("Go To URL", ["Go To URL", "Atteindre l'URL", "Go To URL", "Go To URL", "转到网址"]),
+    ("Go To Destination", ["Gehe zu Ziel", "Atteindre la destination", "Ir a destino", "リンク先へ移動", "转到目标"]),
+    ("Go To State", ["Gehe zu Status", "Atteindre l'état", "Ir a estado", "ステートへ移動", "转到状态"]),
+    ("Go To Next State", ["Gehe zu nächstem Status", "Atteindre l'état suivant", "Ir a estado siguiente", "次のステートへ移動", "转到下一状态"]),
+    ("Go To Previous State", ["Gehe zu vorherigem Status", "Atteindre l'état précédent", "Ir a estado anterior", "前のステートへ移動", "转到上一状态"]),
+    ("State", ["Status", "État", "Estado", "ステート", "状态"]),
+    ("Destination", ["Ziel", "Destination", "Destino", "リンク先", "目标"]),
     ("Not a form field", ["Not a form field", "Pas un champ de formulaire", "Not a form field", "Not a form field", "非表单域"]),
     ("Text Field", ["Text Field", "Champ de texte", "Text Field", "Text Field", "文本域"]),
     ("Check Box", ["Check Box", "Case à cocher", "Check Box", "Check Box", "复选框"]),
diff --git a/crates/ui-egui/src/panels/interactive.rs b/crates/ui-egui/src/panels/interactive.rs
--- a/crates/ui-egui/src/panels/interactive.rs
+++ b/crates/ui-egui/src/panels/interactive.rs
@@ -352,7 +352,36 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) {
         Some(B::GoToNextPage) => ("nextPage", 0, String::new()),
         Some(B::GoToPreviousPage) => ("previousPage", 0, String::new()),
         Some(B::GoToUrl { url }) => ("url", 0, url.clone()),
+        Some(B::GoToDestination { .. }) => ("destination", 0, String::new()),
+        Some(B::GoToState { .. }) => ("state", 0, String::new()),
+        Some(B::GoToNextState { .. }) => ("nextState", 0, String::new()),
+        Some(B::GoToPreviousState { .. }) => ("previousState", 0, String::new()),
     };
+    // What a state or destination action points at (#1798).
+    let (object, state, wrap) = match &cur {
+        Some(B::GoToState { object, state }) => (Some(object.0), *state, false),
+        Some(B::GoToNextState { object, wrap } | B::GoToPreviousState { object, wrap }) => (Some(object.0), 0, *wrap),
+        _ => (None, 0, false),
+    };
+    let anchor = match &cur {
+        Some(B::GoToDestination { anchor }) => Some(*anchor),
+        _ => None,
+    };
+    // The document's multi-state objects (id, name, states) and text anchors (id, name).
+    let (msos, anchors): (Vec<(u64, String, Vec<String>)>, Vec<(u64, String)>) = app.session.active().map_or((vec![], vec![]), |d| {
+        let msos = d
+            .doc
+            .all_items()
+            .into_iter()
+            .filter_map(|i| {
+                let it = d.doc.item(i)?;
+                let name = if it.name.is_empty() { format!("Multi-state object {}", i.0) } else { it.name.clone() };
+                (!it.states.is_empty()).then(|| (i.0, name, it.states.clone()))
+            })
+            .collect();
+        let anchors = d.doc.stories.values().flat_map(|st| st.anchors.iter().map(|a| (a.id, a.name.clone()))).collect();
+        (msos, anchors)
+    });
     let labels = [
         ("none", "Not a button"),
         ("nextPage", "Go To Next Page"),
@@ -361,6 +390,10 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) {
         ("lastPage", "Go To Last Page"),
         ("page", "Go To Page"),
         ("url", "Go To URL"),
+        ("destination", "Go To Destination"),
+        ("state", "Go To State"),
+        ("nextState", "Go To Next State"),
+        ("previousState", "Go To Previous State"),
     ];
     let mut pick = None;
     ui.horizontal(|ui| {
@@ -377,7 +410,67 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) {
     });
     let mut params = None;
     if let Some(k) = pick {
-        params = Some(json!({"action": k, "page": page, "url": if url.is_empty() { "https://" } else { &url }}));
+        let mut p = json!({"action": k, "page": page, "url": if url.is_empty() { "https://" } else { &url }, "wrap": wrap});
+        // A state action starts on the current object (or the first), a destination on the first anchor.
+        if let Some(o) = object.or_else(|| msos.first().map(|m| m.0)) {
+            p["object"] = json!(o);
+        }
+        if k == "state" {
+            p["state"] = json!(if object.is_some() { state } else { 0 });
+        }
+        if let Some(a) = anchor.or_else(|| anchors.first().map(|a| a.0)) {
+            p["anchor"] = json!(a);
+        }
+        params = Some(p);
+    }
+    if matches!(kind, "state" | "nextState" | "previousState") {
+        let current = msos.iter().find(|m| Some(m.0) == object);
+        ui.horizontal(|ui| {
+            crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Object"));
+            egui::ComboBox::from_id_salt("button_state_object")
+                .selected_text(crate::rtl::widget(ui, current.map_or("", |m| m.1.as_str())))
+                .show_ui(ui, |ui| {
+                    for m in &msos {
+                        if ui.selectable_label(Some(m.0) == object, crate::rtl::widget(ui, m.1.as_str())).clicked() {
+                            params = Some(json!({"action": kind, "object": m.0, "state": 0, "wrap": wrap}));
+                        }
+                    }
+                });
+        });
+        if kind == "state" {
+            let states: &[String] = current.map_or(&[], |m| m.2.as_slice());
+            ui.horizontal(|ui| {
+                crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "State"));
+                egui::ComboBox::from_id_salt("button_state_index")
+                    .selected_text(crate::rtl::widget(ui, states.get(state).map_or("", String::as_str)))
+                    .show_ui(ui, |ui| {
+                        for (i, st) in states.iter().enumerate() {
+                            if ui.selectable_label(i == state, crate::rtl::widget(ui, st.as_str())).clicked() {
+                                params = Some(json!({"action": "state", "object": object, "state": i}));
+                            }
+                        }
+                    });
+            });
+        } else {
+            let mut w = wrap;
+            if ui.checkbox(&mut w, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Loop"))).changed() {
+                params = Some(json!({"action": kind, "object": object, "wrap": w}));
+            }
+        }
+    }
+    if kind == "destination" {
+        ui.horizontal(|ui| {
+            crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Destination"));
+            egui::ComboBox::from_id_salt("button_destination")
+                .selected_text(crate::rtl::widget(ui, anchors.iter().find(|a| Some(a.0) == anchor).map_or("", |a| a.1.as_str())))
+                .show_ui(ui, |ui| {
+                    for (aid, name) in &anchors {
+                        if ui.selectable_label(Some(*aid) == anchor, crate::rtl::widget(ui, name.as_str())).clicked() {
+                            params = Some(json!({"action": "destination", "anchor": aid}));
+                        }
+                    }
+                });
+        });
     }
     if kind == "page" {
         let total = app.session.active().map_or(1, |d| d.doc.page_count());
diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md
--- a/docs/target-app-parity.md
+++ b/docs/target-app-parity.md
@@ -1,6 +1,6 @@
 # DesignCraft parity with Adobe InDesign
 
-> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (Convert Variable to Text; list buttons with the default hanging indent; custom anchored objects honour Relative to Spine; PDF/X-4 visible unembedded font detection, #909; autoflow fills the following pages first; File ▸ Open Recent; .indd and .indt open through indd-utils; text threads by hand; image clipping paths from IDML; anchored objects wrap text; Tabs panel listed as present (#343); spring-loaded Zoom navigation; recovery checkpoint retention; cancelable macOS Dock Quit; Based on Parent; object styles carry effects; colour management on craft-cms, Coated FOGRA39 default CMYK; swatches on placed greyscale and bitmap images) · **Target:** Adobe InDesign 2026 (21.6.0.57, macOS)
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (button actions Go To State / Next State / Previous State / Destination, #1798; Convert Variable to Text; list buttons with the default hanging indent; custom anchored objects honour Relative to Spine; PDF/X-4 visible unembedded font detection, #909; autoflow fills the following pages first; File ▸ Open Recent; .indd and .indt open through indd-utils; text threads by hand; image clipping paths from IDML; anchored objects wrap text; Tabs panel listed as present (#343); spring-loaded Zoom navigation; recovery checkpoint retention; cancelable macOS Dock Quit; Based on Parent; object styles carry effects; colour management on craft-cms, Coated FOGRA39 default CMYK; swatches on placed greyscale and bitmap images) · **Target:** Adobe InDesign 2026 (21.6.0.57, macOS)
 
 The authoritative assessment of how close DesignCraft is to Adobe InDesign. [ROADMAP.md](../ROADMAP.md)
 summarizes it; [gaps.md](gaps.md) lists every known shortfall one at a time; the feature-by-feature
@@ -373,7 +373,7 @@ It is evidence of breadth: each item exists. It is not a claim of InDesign-level
 - Cell strokes: with cells selected (or the cursor in a cell) the Stroke panel edits the chosen cell edges, picked on a drawn edge proxy (top, bottom, left, right, inner horizontal, inner vertical); Table › Cell Options › Strokes and Fills… does the same with the cell fill. Weight, every stroke type, colour, tint, gap colour and gap tint draw on screen and in PDF and round-trip through IDML (`table.setCell` `stroke.edges`, `table.getCellStroke`).
 - Distribute Rows Evenly (`table.distributeRows`, Table menu and panel): selected rows receive equal fixed heights preserving their composed total height, with undo/redo. Row-spanning merges and rows outside the composed frames are rejected; shrinking tall cells can overset their text.
 - Object States (`states.create/show/rename/release/list`, Object States panel): multi-state objects show one state on screen and in PDF.
-- Buttons (`button.set/clear/list`, Buttons and Forms panel): go to page / next / previous / first / last / URL as PDF link annotations. Form fields too (`form.set/clear/list`: text fields, check boxes, combo and list boxes, signature fields; required, multiline, defaults) exported as AcroForm widgets through an incremental update (not in PDF/X or PDF/A).
+- Buttons (`button.set/clear/list`, Buttons and Forms panel): go to page / next / previous / first / last / URL / destination (a text anchor's page) as PDF link annotations; Go To State / Next State / Previous State (object, state, Loop) point at a multi-state object and are kept in the document (#1798; a PDF shows one state, so they export nothing yet). Form fields too (`form.set/clear/list`: text fields, check boxes, combo and list boxes, signature fields; required, multiline, defaults) exported as AcroForm widgets through an incremental update (not in PDF/X or PDF/A).
 - Power Zoom (Hand tool: press and hold still for half a second, or Alt-press): zoom out to the spread, aim the red view rectangle, release to zoom back in there.
 - Graphic cells (`table.placeGraphic`, `table.textCell`, Table › Convert Cell Type, Table panel): images in table cells, fitted or filled, clipped to the cell on screen and in PDF; IDML export writes them as an inline graphic in the cell.
 - Live Distribute (Space while dragging a selection handle; `transform.resize {distribute}`): objects keep their size and spread with the bounds.
