diff --git a/Cargo.lock b/Cargo.lock
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1610,8 +1610,10 @@ dependencies = [
  "designcraft-render",
  "designcraft-tools",
  "egui",
+ "egui-wgpu",
  "egui_kittest",
  "log",
+ "pollster 1.0.1",
  "serde",
  "serde_json",
  "tempfile",
diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml
--- a/crates/ui-egui/Cargo.toml
+++ b/crates/ui-egui/Cargo.toml
@@ -41,6 +41,8 @@ designcraft-idml = { workspace = true }
 zip = { workspace = true }
 # Headless (offscreen wgpu) rendering of the whole window for UI screenshots: `examples/ui_shot.rs`.
 egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
+egui-wgpu = { version = "0.37", default-features = false }
+pollster = "1.0"
 designcraft-fonts = { workspace = true, features = ["testing"] }
 
 [build-dependencies]
diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs
--- a/crates/ui-egui/src/canvas/tests.rs
+++ b/crates/ui-egui/src/canvas/tests.rs
@@ -750,7 +750,7 @@ fn the_paste_in_place_shortcut_pastes_in_place_and_plain_paste_offsets() {
     let clip = app.object_clip_text.clone().expect("the copy's placeholder text");
     let mut h = harness(app);
     let paste = |h: &mut Harness<'static, DesignApp>, modifiers: egui::Modifiers| {
-        // As egui-winit delivers ⌃⌥⇧V: the chord's paste and the V release, no V press; here the
+        // As egui-winit delivers Cmd+Alt+Shift+V: the chord's paste and the V release, no V press; here the
         // modifiers are released within the same frame.
         let release = egui::Event::Key { key: egui::Key::V, physical_key: None, pressed: false, repeat: false, modifiers };
         let none = egui::Event::ModifiersChanged(egui::Modifiers::NONE);
@@ -761,12 +761,17 @@ fn the_paste_in_place_shortcut_pastes_in_place_and_plain_paste_offsets() {
         assert_ne!(pasted.0, id);
         st.doc.item(pasted).unwrap().bounds()
     };
-    let linux = egui::Modifiers { alt: true, ctrl: true, shift: true, mac_cmd: false, command: true };
-    assert_eq!(paste(&mut h, linux), original, "Paste in Place");
-    let moved = paste(&mut h, egui::Modifiers::COMMAND);
+    let command = if cfg!(target_os = "macos") {
+        egui::Modifiers::MAC_CMD | egui::Modifiers::COMMAND
+    } else {
+        egui::Modifiers::CTRL | egui::Modifiers::COMMAND
+    };
+    let in_place = command | egui::Modifiers::ALT | egui::Modifiers::SHIFT;
+    assert_eq!(paste(&mut h, in_place), original, "Paste in Place");
+    let moved = paste(&mut h, command);
     assert!((moved.x0 - original.x0 - 12.0).abs() < 1e-9 && (moved.y0 - original.y0 - 12.0).abs() < 1e-9, "{moved:?}");
     // The modifiers went down in an earlier frame than the paste.
-    h.event_modifiers(egui::Event::Paste(clip.clone()), linux);
+    h.event_modifiers(egui::Event::Paste(clip.clone()), in_place);
     h.run_steps(2);
     let st = h.state().session.doc().unwrap();
     let pasted = *st.selection.items.first().expect("the copy is selected");
@@ -1042,7 +1047,20 @@ fn live_corner_widget_and_diamonds_draw() {
     app.run("frame.create", json!({"rect": [100, 100, 300, 250], "content": "graphic"})).unwrap();
     let id = app.session.doc().unwrap().doc.spreads[0].items[0].id;
     app.run("selection.set", json!({"ids": [id.0]})).unwrap();
-    let mut h = Harness::builder().with_size(vec2(1440.0, 900.0)).with_pixels_per_point(1.0).wgpu().build_ui_state(
+    let setup = egui_kittest::wgpu::default_wgpu_setup();
+    let instance = pollster::block_on(setup.new_instance());
+    let config = egui_wgpu::WgpuConfiguration { wgpu_setup: setup, ..Default::default() };
+    let renderer = match pollster::block_on(egui_wgpu::RenderState::create(&config, &instance, None, egui_wgpu::RendererOptions::PREDICTABLE)) {
+        Ok(state) => egui_kittest::wgpu::WgpuTestRenderer::from_render_state(state),
+        Err(egui_wgpu::WgpuError::CustomNativeAdapterSelectionError(reason))
+            if reason == "No adapter found" && (cfg!(target_os = "freebsd") || std::env::var_os("CI").is_none()) =>
+        {
+            eprintln!("skipped: no offscreen GPU adapter");
+            return;
+        }
+        Err(error) => panic!("failed to initialize offscreen renderer: {error}"),
+    };
+    let mut h = Harness::builder().with_size(vec2(1440.0, 900.0)).with_pixels_per_point(1.0).renderer(renderer).build_ui_state(
         |ui, app: &mut DesignApp| {
             let ctx = ui.ctx().clone();
             app.logic(&ctx);
@@ -1059,20 +1077,13 @@ fn live_corner_widget_and_diamonds_draw() {
     };
     let widget = screen(&h, 300.0, 100.0) + vec2(0.0, 11.5);
     let diamond = screen(&h, 100.0, 100.0) + vec2(12.0, 0.0);
-    let yellow = |h: &mut Harness<'_, DesignApp>, p: Pos2| -> Option<bool> {
-        let img = match h.render() {
-            Ok(img) => img,
-            Err(e) => {
-                eprintln!("skipped: no offscreen renderer ({e})");
-                return None;
-            }
-        };
+    let yellow = |h: &mut Harness<'_, DesignApp>, p: Pos2| -> bool {
+        let img = h.render().expect("render UI screenshot");
         let px = img.get_pixel(p.x.round() as u32, p.y.round() as u32).0;
-        Some(px[0] > 200 && px[1] > 180 && px[2] < 100)
+        px[0] > 200 && px[1] > 180 && px[2] < 100
     };
-    let Some(w) = yellow(&mut h, widget) else { return };
-    assert!(w, "no widget at {widget:?}");
-    assert_eq!(yellow(&mut h, diamond), Some(false));
+    assert!(yellow(&mut h, widget), "no widget at {widget:?}");
+    assert!(!yellow(&mut h, diamond));
     h.hover_at(widget);
     h.step();
     for pressed in [true, false] {
@@ -1080,8 +1091,8 @@ fn live_corner_widget_and_diamonds_draw() {
         h.step();
     }
     h.run_steps(3);
-    assert_eq!(yellow(&mut h, diamond), Some(true), "no diamond at {diamond:?}");
-    assert_eq!(yellow(&mut h, widget), Some(false));
+    assert!(yellow(&mut h, diamond), "no diamond at {diamond:?}");
+    assert!(!yellow(&mut h, widget));
 }
 
 /// A harness whose frames are 1 ms apart, so two clicks make a double-click.
diff --git a/crates/ui-egui/src/menus/tests.rs b/crates/ui-egui/src/menus/tests.rs
--- a/crates/ui-egui/src/menus/tests.rs
+++ b/crates/ui-egui/src/menus/tests.rs
@@ -901,6 +901,10 @@ fn middle_drag_pans_with_any_tool() {
     assert_eq!(items(&app), items0, "the tool saw nothing");
 }
 
+fn command_modifier() -> egui::Modifiers {
+    if cfg!(target_os = "macos") { egui::Modifiers::MAC_CMD | egui::Modifiers::COMMAND } else { egui::Modifiers::CTRL | egui::Modifiers::COMMAND }
+}
+
 #[test]
 fn layout_menu_goes_through_pages_and_spreads_with_their_shortcuts() {
     let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
@@ -941,7 +945,7 @@ fn layout_menu_goes_through_pages_and_spreads_with_their_shortcuts() {
     assert!(at("view.nextSpread") < at("view.previousSpread"));
     assert!(at("view.previousSpread") < at("view.goToPage"));
     let shift = egui::Modifiers::SHIFT;
-    let cmd_shift = egui::Modifiers { shift: true, command: true, ctrl: true, ..Default::default() };
+    let cmd_shift = command_modifier() | egui::Modifiers::SHIFT;
     let alt = egui::Modifiers::ALT;
     // Pages 1 | 2-3 | 4-5 | 6-7.
     frame(&mut app, key(egui::Key::PageDown, shift));
@@ -1010,7 +1014,7 @@ fn go_back_and_go_forward_retrace_the_pages_viewed() {
     assert_eq!(at("view.goBack"), at("view.goToPage") + 1);
     assert_eq!(at("view.goForward"), at("view.goBack") + 1);
     assert_eq!(can(&app), (false, false), "one page viewed: nowhere to go");
-    let cmd = egui::Modifiers { command: true, ctrl: true, ..Default::default() };
+    let cmd = command_modifier();
     let cmd_shift = egui::Modifiers { shift: true, ..cmd };
     let shift = egui::Modifiers::SHIFT;
     // Pages viewed: 1, 5, 7.
@@ -1096,7 +1100,7 @@ fn next_spread_at_the_last_spread_is_no_jump() {
         ]
     };
     let page = |app: &crate::DesignApp| crate::canvas::current_page(app).unwrap();
-    let cmd = egui::Modifiers { command: true, ctrl: true, ..Default::default() };
+    let cmd = command_modifier();
     let alt = egui::Modifiers::ALT;
     frame(&mut app, vec![]);
     frame(&mut app, vec![]);
@@ -1140,7 +1144,7 @@ fn go_back_returns_to_the_view_a_jump_left_and_scrolling_adds_no_entry() {
         let st = app.session.active().unwrap();
         crate::canvas::current_slot(app, &designcraft_tools::layout::CanvasLayout::new(&st.doc, false)).unwrap()
     };
-    let cmd = egui::Modifiers { command: true, ctrl: true, ..Default::default() };
+    let cmd = command_modifier();
     let alt = egui::Modifiers::ALT;
     frame(&mut app, vec![]);
     frame(&mut app, vec![]);
@@ -1212,7 +1216,7 @@ fn go_back_after_presentation_returns_to_the_last_spread_presented() {
         let st = app.session.active().unwrap();
         crate::canvas::current_slot(app, &designcraft_tools::layout::CanvasLayout::new(&st.doc, false)).unwrap()
     };
-    let cmd = egui::Modifiers { command: true, ctrl: true, ..Default::default() };
+    let cmd = command_modifier();
     frame(&mut app, vec![]);
     frame(&mut app, vec![]);
     // Scrolled off the page's centre, so the place Go Back returns to is not just the page.
diff --git a/crates/ui-egui/src/panels/styles.rs b/crates/ui-egui/src/panels/styles.rs
--- a/crates/ui-egui/src/panels/styles.rs
+++ b/crates/ui-egui/src/panels/styles.rs
@@ -246,7 +246,10 @@ mod tests {
         app.run("style.character.create", json!({"name": "Emphasis", "chars": {"underline": true}})).unwrap();
         app.run("style.character.apply", json!({"name": "Emphasis"})).unwrap();
         app.run("type.char", json!({"fill": "[Paper]"})).unwrap();
-        let mut h = Harness::builder().with_size(vec2(460.0, 300.0)).build_ui_state(|ui, app: &mut DesignApp| paragraph(app, ui), app);
+        let mut h = Harness::builder()
+            .with_size(vec2(460.0, 300.0))
+            .with_context_setup(|ctx| ctx.set_fonts(crate::theme::font_definitions(&[], "")))
+            .build_ui_state(|ui, app: &mut DesignApp| paragraph(app, ui), app);
         h.run_steps(3);
         let at = h.get_all_by_label("Header+").next_back().unwrap().rect().center();
         h.event(egui::Event::PointerMoved(at));
@@ -281,7 +284,10 @@ mod tests {
         app.run("type.para", json!({"spaceAfter": 6})).unwrap();
         let a = app.run("type.selectionAttrs", json!({})).unwrap();
         assert_eq!((a["paraOverrides"].as_u64(), a["charOverrides"].as_u64()), (Some(0), Some(0)));
-        let mut h = Harness::builder().with_size(vec2(360.0, 240.0)).build_ui_state(|ui, app: &mut DesignApp| paragraph(app, ui), app);
+        let mut h = Harness::builder()
+            .with_size(vec2(360.0, 240.0))
+            .with_context_setup(|ctx| ctx.set_fonts(crate::theme::font_definitions(&[], "")))
+            .build_ui_state(|ui, app: &mut DesignApp| paragraph(app, ui), app);
         h.run_steps(3);
         assert!(h.query_by_label("Body+").is_none());
         assert!(h.query_all_by_label("Body").next().is_some());
diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs
--- a/crates/ui-egui/src/theme.rs
+++ b/crates/ui-egui/src/theme.rs
@@ -452,9 +452,11 @@ pub(crate) fn add_system_fallbacks(fonts: &mut FontDefinitions, system: &[System
     for (family, stack) in fonts.families.iter_mut() {
         // The Arabic families keep their own order (see `font_definitions`).
         let arabic = matches!(family, FontFamily::Name(n) if n.starts_with("arabic"));
-        let at = if arabic { stack.len() } else { stack.iter().position(|n| n.starts_with("craft-")).unwrap_or(stack.len()) };
+        let mono_fallback = |stack: &[String]| stack.iter().position(|n| n == "mono").filter(|&i| i > 0).unwrap_or(stack.len());
+        let at = if arabic { mono_fallback(stack) } else { stack.iter().position(|n| n.starts_with("craft-")).unwrap_or(mono_fallback(stack)) };
         stack.splice(at..at, preferred.iter().cloned());
-        stack.extend(rest.iter().cloned());
+        let at = mono_fallback(stack);
+        stack.splice(at..at, rest.iter().cloned());
     }
 }
 
@@ -528,6 +530,12 @@ pub(crate) fn font_definitions(craft: &'static [designcraft_fonts::CraftFont], l
         let mut seen = std::collections::HashSet::new();
         fonts.families.insert(FontFamily::Name(family.into()), stack.filter(|n| seen.insert(n.clone())).collect());
     }
+    // The bundled mono face supplies shortcut symbols such as ⇥ that the UI face lacks.
+    for stack in fonts.families.values_mut() {
+        if !stack.iter().any(|name| name == "mono") {
+            stack.push("mono".into());
+        }
+    }
     fonts
 }
 
@@ -603,6 +611,48 @@ mod japanese_font_tests {
         ctx
     }
 
+    #[test]
+    fn keyboard_shortcut_symbols_are_available_without_extra_fonts() {
+        let defs = super::font_definitions(&[], "ja");
+        assert_eq!(defs.families[&egui::FontFamily::Proportional][0], "ui");
+        assert_eq!(defs.families[&egui::FontFamily::Name("semibold".into())][0], "ui-semibold");
+        let ctx = ctx_with(&[]);
+        ctx.fonts_mut(|fonts| {
+            for family in families() {
+                let font = egui::FontId::new(13.0, family);
+                for ch in "⌃⌥⇧⌘⇥".chars() {
+                    assert!(fonts.has_glyph(&font, ch), "missing {ch} in {font:?}");
+                }
+            }
+        });
+    }
+
+    #[test]
+    fn installed_script_fonts_precede_the_mono_fallback_without_craft_fonts() {
+        let face = |family: &str, script| {
+            super::SystemFont::new(script, family.into(), designcraft_fonts::testing::font_with(family, &['日', '｝']).unwrap(), 0)
+        };
+        let system = [face("DC Ja", "Jpan"), face("DC Hans", "Hans"), face("DC Arabic", "Arab"), face("DC Tamil", "Taml")];
+        for (lang, preferred, other) in [("ja", "system-DC Ja", "system-DC Hans"), ("zh", "system-DC Hans", "system-DC Ja")] {
+            let mut defs = super::font_definitions(&[], lang);
+            super::add_system_fallbacks(&mut defs, &system, lang);
+            for family in [
+                egui::FontFamily::Proportional,
+                egui::FontFamily::Name("semibold".into()),
+                egui::FontFamily::Name("arabic".into()),
+                egui::FontFamily::Name("arabic-semibold".into()),
+            ] {
+                let stack = &defs.families[&family];
+                let at = |name: &str| stack.iter().position(|n| n == name).unwrap();
+                assert!(at(preferred) < at(other), "{lang} {family:?}: {stack:?}");
+                for name in [preferred, other, "system-DC Arabic", "system-DC Tamil"] {
+                    assert!(at(name) < at("mono"), "{lang} {family:?}: {name} follows mono: {stack:?}");
+                }
+            }
+            assert_eq!(defs.families[&egui::FontFamily::Monospace][0], "mono");
+        }
+    }
+
     /// A newly supplied script must remain usable without any matching installed system font.
     /// Previously craft's script coverage suppressed system discovery while these faces were
     /// omitted from the actual UI font definitions, so a Hebrew tab panicked on its first glyph.
@@ -654,7 +704,10 @@ mod japanese_font_tests {
         let mut defs = super::font_definitions(&[], "ja");
         super::add_system_fallbacks(&mut defs, &system, "ja");
         for family in families() {
-            assert_eq!(defs.families[&family].last().map(String::as_str), Some("system-DC UI Japanese"), "{family:?}");
+            let stack = &defs.families[&family];
+            let system_at = stack.iter().position(|n| n == "system-DC UI Japanese").unwrap();
+            let mono_at = stack.iter().position(|n| n == "mono").unwrap();
+            assert!(if family == egui::FontFamily::Monospace { mono_at < system_at } else { system_at < mono_at }, "{family:?}: {stack:?}");
         }
         let ctx = egui::Context::default();
         ctx.set_fonts(defs);
@@ -721,7 +774,9 @@ mod japanese_font_tests {
         for (lang, first) in [("ja", "DC Ja"), ("zh", "DC Hans"), ("zh-hant", "DC Hant"), ("ko", "DC Ko"), ("", "DC Ja")] {
             let mut defs = super::font_definitions(&[], lang);
             // Stand-in for a built-in CJK face (craft-fonts may be absent in this build).
-            defs.families.entry(egui::FontFamily::Proportional).or_default().push("craft-Stand-in".into());
+            let stack = defs.families.entry(egui::FontFamily::Proportional).or_default();
+            let at = stack.iter().position(|name| name == "mono").unwrap_or(stack.len());
+            stack.insert(at, "craft-Stand-in".into());
             super::add_system_fallbacks(&mut defs, &system, lang);
             let stack = &defs.families[&egui::FontFamily::Proportional];
             let at = |n: &str| stack.iter().position(|x| x == n).unwrap();
@@ -761,7 +816,9 @@ mod japanese_font_tests {
         assert_eq!(picked, [("Jpan", "Noto Sans CJK JP"), ("Taml", "Nirmala UI"), ("Thai", "Noto Sans Thai")]);
 
         let mut defs = super::font_definitions(&[], "ja");
-        defs.families.entry(egui::FontFamily::Proportional).or_default().push("craft-Stand-in".into());
+        let stack = defs.families.entry(egui::FontFamily::Proportional).or_default();
+        let at = stack.iter().position(|name| name == "mono").unwrap_or(stack.len());
+        stack.insert(at, "craft-Stand-in".into());
         super::add_system_fallbacks(&mut defs, &system, "ja");
         let stack = &defs.families[&egui::FontFamily::Proportional];
         let at = |n: &str| stack.iter().position(|x| x == n).unwrap();
diff --git a/crates/ui-egui/src/toolbar/tests.rs b/crates/ui-egui/src/toolbar/tests.rs
--- a/crates/ui-egui/src/toolbar/tests.rs
+++ b/crates/ui-egui/src/toolbar/tests.rs
@@ -197,7 +197,8 @@ fn the_tools_panel_lays_out_like_indesigns() {
     for double in [false, true] {
         let h = test_window::open(app(double), vec2(1400.0, 900.0));
         let _ = rect(&h, "Gradient Swatch Tool (G)");
-        let _ = rect(&h, "Gradient Feather Tool (Shift+G)");
+        let feather = if cfg!(target_os = "macos") { "Gradient Feather Tool (⇧G)" } else { "Gradient Feather Tool (Shift+G)" };
+        let _ = rect(&h, feather);
         let _ = rect(&h, "View Options");
         let split = ["Content Placer Tool (B)", "Apply Color (,)", "Apply Gradient (.)", "Normal"];
         for label in split {
