diff --git a/crates/engine/src/cmd/shape.rs b/crates/engine/src/cmd/shape.rs
--- a/crates/engine/src/cmd/shape.rs
+++ b/crates/engine/src/cmd/shape.rs
@@ -121,7 +121,7 @@ pub fn specs() -> Vec<CommandSpec> {
             "Shape Effects",
             ["Shape Format", "Shape Styles"],
             None,
-            "{shadow?: none|outer|inner|perspective|{color, blur, dist, dir}, glow?: none|{color, radius}, softEdges?: pt, reflection?: none|tight|half|full, bevel?: none|circle|…, threeD?: {camera?: preset, rotX?, rotY?, rotZ?: deg, z?: pt, bevelTop?, bevelBottom?: none|circle|…, bevelTopW?, bevelTopH?, bevelBottomW?, bevelBottomH?, depth?, contour?: pt, material?, lighting?, reset?: true}, shadowProps?: {color?, transparency?: 0..1, blur?: pt, dist?: pt, dir?: deg}, glowProps?: {color?, transparency?, radius?: pt}, reflectionProps?: {transparency?, size?: 0..1, dist?: pt, blur?: pt}, reset?, ids?} (the *Props keys adjust the current effect, adding a default one when there is none)",
+            "{shadow?: none|outer|inner|perspective|{color, blur, dist, dir}, glow?: none|{color, radius}, softEdges?: pt, reflection?: none|tight|half|full, bevel?: none|circle|…, threeD?: {camera?: preset, rotX?, rotY?, rotZ?: deg, z?: pt, bevelTop?, bevelBottom?: none|circle|…, bevelTopW?, bevelTopH?, bevelBottomW?, bevelBottomH?, depth?, contour?: pt, material?, lighting?, reset?: true}, shadowProps?: {color?, transparency?: 0..1, size?: 0.01..2 (1 = 100%), blur?: pt, dist?: pt, dir?: deg}, glowProps?: {color?, transparency?, radius?: pt}, reflectionProps?: {transparency?, size?: 0..1, dist?: pt, blur?: pt}, reset?, ids?} (the *Props keys adjust the current effect, adding a default one when there is none)",
             has_selection,
             effects
         ),
diff --git a/crates/engine/src/cmd/shape/appearance.rs b/crates/engine/src/cmd/shape/appearance.rs
--- a/crates/engine/src/cmd/shape/appearance.rs
+++ b/crates/engine/src/cmd/shape/appearance.rs
@@ -397,6 +397,12 @@ pub(super) fn effects(s: &mut Session, p: &Value) -> Result<Value> {
             if let Some(d) = num(v, "dir") {
                 x.dir = d.rem_euclid(360.0);
             }
+            // PowerPoint's Size: 0–200%, both axes; a zero scale would collapse the shadow's matrix.
+            if let Some(z) = num(v, "size") {
+                let z = z.clamp(0.01, 2.0);
+                x.sx = z;
+                x.sy = z;
+            }
             *slot = Some(x);
         }
         if let Some(v) = &glow_props {
diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs
--- a/crates/engine/src/tests.rs
+++ b/crates/engine/src/tests.rs
@@ -2320,6 +2320,15 @@ fn shape_effects_props_adjust_shadow_glow_and_reflection() {
     let f = fx(&s);
     assert!(f.outer_shadow.is_none());
     assert_eq!(f.inner_shadow.unwrap().blur, 3.0);
+    // Size scales both axes (PowerPoint's 0–200%); out-of-range values clamp.
+    s.execute("shape.effects", &json!({"shadow": "inner"})).unwrap();
+    s.execute("shape.effects", &json!({"shadowProps": {"size": 1.5}})).unwrap();
+    let sw = fx(&s).inner_shadow.unwrap();
+    assert_eq!((sw.sx, sw.sy), (1.5, 1.5));
+    s.execute("shape.effects", &json!({"shadowProps": {"size": 9}})).unwrap();
+    assert_eq!(fx(&s).inner_shadow.unwrap().sx, 2.0);
+    s.execute("shape.effects", &json!({"shadowProps": {"size": 0}})).unwrap();
+    assert_eq!(fx(&s).inner_shadow.unwrap().sy, 0.01);
     // Glow: size, colour, transparency; size 0 removes it.
     s.execute("shape.effects", &json!({"glowProps": {"radius": 12, "transparency": 0.5}})).unwrap();
     let g = fx(&s).glow.unwrap();
diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs
--- a/crates/ui-egui/src/panels.rs
+++ b/crates/ui-egui/src/panels.rs
@@ -14,6 +14,7 @@ mod animation;
 mod background;
 mod comments;
 mod designer;
+mod effect_presets;
 mod format_shape;
 mod smartart;
 mod thesaurus;
@@ -121,6 +122,9 @@ pub mod thumbs;
 #[cfg(test)]
 mod background_tests;
 
+#[cfg(test)]
+mod effect_presets_tests;
+
 #[cfg(test)]
 mod pattern_fill_tests;
 
diff --git a/crates/ui-egui/src/panels/effect_presets.rs b/crates/ui-egui/src/panels/effect_presets.rs
new file mode 100644
--- /dev/null
+++ b/crates/ui-egui/src/panels/effect_presets.rs
@@ -0,0 +1,202 @@
+//! Format Shape ▸ Effects: preset tiles that draw what each Shadow, Glow and Soft Edges preset does
+//! to a small card, so the choice is made by looking rather than by reading names.
+
+use deckcraft_model::style::Effects;
+use egui::{Color32, CornerRadius, Painter, Rect, Role, Sense, Stroke, StrokeKind, Ui, WidgetInfo, pos2, vec2};
+
+use crate::theme::Tokens;
+
+const TILE: egui::Vec2 = vec2(46.0, 38.0);
+const SHADOWS: [(&str, &str); 6] = [
+    ("none", "No shadow"),
+    ("outer", "Outer shadow"),
+    ("bottom", "Bottom shadow"),
+    ("center", "Centered shadow"),
+    ("inner", "Inner shadow"),
+    ("perspective", "Perspective shadow"),
+];
+/// The glow radii (pt) the ribbon's Glow menu offers.
+pub(super) const GLOWS: [f64; 4] = [5.0, 8.0, 11.0, 18.0];
+/// The soft-edge radii (pt) the ribbon's Soft Edges menu offers; 0 is "none".
+pub(super) const SOFT_EDGES: [f64; 6] = [0.0, 1.0, 2.5, 5.0, 10.0, 25.0];
+
+/// The `shape.effects` shadow preset id that `fx`'s current shadow came from.
+pub(super) fn shadow_preset_id(fx: &Effects) -> &'static str {
+    if fx.inner_shadow.is_some() {
+        return "inner";
+    }
+    match &fx.outer_shadow {
+        None => "none",
+        Some(s) if s.align == "bl" => "perspective",
+        Some(s) if s.dist == 0.0 => "center",
+        Some(s) if s.dir == 90.0 => "bottom",
+        Some(_) => "outer",
+    }
+}
+
+/// A row of shadow preset tiles; returns the id of the one clicked.
+pub(super) fn shadow_row(ui: &mut Ui, current: &str) -> Option<&'static str> {
+    let mut picked = None;
+    ui.horizontal_wrapped(|ui| {
+        for (id, label) in SHADOWS {
+            if tile(ui, label, id == current, |p, r| paint_shadow(p, r, id)).clicked() {
+                picked = Some(id);
+            }
+        }
+    });
+    picked
+}
+
+/// A row of glow tiles (the first clears it); returns the radius clicked, 0 for none.
+pub(super) fn glow_row(ui: &mut Ui, current: f64) -> Option<f64> {
+    let mut picked = None;
+    ui.horizontal_wrapped(|ui| {
+        for r in std::iter::once(0.0).chain(GLOWS) {
+            let label = if r == 0.0 { "No glow".to_string() } else { format!("{r} pt glow") };
+            if tile(ui, &label, r == current, |p, rect| paint_glow(p, rect, r)).clicked() {
+                picked = Some(r);
+            }
+        }
+    });
+    picked
+}
+
+/// A row of soft-edge tiles (the first clears it); returns the radius clicked, 0 for none.
+pub(super) fn soft_edge_row(ui: &mut Ui, current: f64) -> Option<f64> {
+    let mut picked = None;
+    ui.horizontal_wrapped(|ui| {
+        for r in SOFT_EDGES {
+            let label = if r == 0.0 { "No soft edges".to_string() } else { format!("{r} pt soft edges") };
+            if tile(ui, &label, r == current, |p, rect| paint_soft(p, rect, r)).clicked() {
+                picked = Some(r);
+            }
+        }
+    });
+    picked
+}
+
+fn tile(ui: &mut Ui, label: &str, selected: bool, paint: impl FnOnce(&Painter, Rect)) -> egui::Response {
+    let t = Tokens::get(ui.ctx());
+    let (rect, resp) = ui.allocate_exact_size(TILE, Sense::click());
+    resp.widget_info(|| WidgetInfo::selected(Role::Button, true, selected, label));
+    let p = ui.painter();
+    let round = CornerRadius::same(5);
+    p.rect_filled(rect, round, if resp.hovered() { t.hover } else { t.field_bg });
+    paint(&p.with_clip_rect(rect.shrink(1.0)), rect);
+    let stroke = if selected { Stroke::new(2.0, t.accent) } else { Stroke::new(1.0, t.border) };
+    p.rect_stroke(rect, round, stroke, StrokeKind::Inside);
+    resp.on_hover_text(label)
+}
+
+/// The sample card every preview draws on, centred in `tile`.
+fn card(tile: Rect) -> Rect {
+    Rect::from_center_size(tile.center(), vec2(22.0, 16.0))
+}
+
+fn card_fill(p: &Painter, r: Rect) {
+    p.rect_filled(r, CornerRadius::same(2), Tokens::get(p.ctx()).accent);
+}
+
+/// `layers` rounded rects growing from `r` by up to `spread`, each faint, so the edge falls off.
+fn halo(p: &Painter, r: Rect, spread: f32, color: Color32, layers: u32) {
+    for i in (1..=layers).rev() {
+        let k = i as f32 / layers as f32;
+        let a = (f32::from(color.a()) * (1.0 - k * 0.8) / layers as f32 * 2.0).min(255.0) as u8;
+        p.rect_filled(
+            r.expand(spread * k),
+            CornerRadius::same((2.0 + spread * k) as u8),
+            Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), a),
+        );
+    }
+}
+
+fn paint_shadow(p: &Painter, tile: Rect, id: &str) {
+    let r = card(tile);
+    let ink = Color32::from_rgba_unmultiplied(0, 0, 0, 120);
+    match id {
+        "outer" => halo(p, r.translate(vec2(3.0, 3.0)), 3.0, ink, 4),
+        "bottom" => halo(p, r.translate(vec2(0.0, 4.0)), 3.0, ink, 4),
+        "center" => halo(p, r, 5.0, ink, 5),
+        "perspective" => {
+            let base = r.max.y + 1.0;
+            let slant = [pos2(r.min.x - 6.0, base), pos2(r.max.x - 8.0, base), pos2(r.max.x - 2.0, base + 5.0), pos2(r.min.x - 12.0, base + 5.0)];
+            p.add(egui::Shape::convex_polygon(slant.to_vec(), Color32::from_rgba_unmultiplied(0, 0, 0, 90), Stroke::NONE));
+        }
+        _ => {}
+    }
+    card_fill(p, r);
+    if id == "inner" {
+        for i in 0..4 {
+            let a = 110 - i * 25;
+            p.rect_stroke(
+                r.shrink(i as f32),
+                CornerRadius::same(2),
+                Stroke::new(1.0, Color32::from_rgba_unmultiplied(0, 0, 0, a as u8)),
+                StrokeKind::Inside,
+            );
+        }
+    }
+    if id == "none" {
+        p.line_segment([tile.left_bottom() + vec2(6.0, -6.0), tile.right_top() + vec2(-6.0, 6.0)], Stroke::new(1.5, Tokens::get(p.ctx()).text_faint));
+    }
+}
+
+fn paint_glow(p: &Painter, tile: Rect, radius: f64) {
+    let r = card(tile);
+    if radius > 0.0 {
+        // 18 pt fills the tile's margin; smaller radii scale down.
+        halo(p, r, (radius as f32 / 18.0 * 9.0).max(2.0), Tokens::get(p.ctx()).accent.gamma_multiply(0.9), 6);
+    }
+    card_fill(p, r);
+    if radius == 0.0 {
+        p.line_segment([tile.left_bottom() + vec2(6.0, -6.0), tile.right_top() + vec2(-6.0, 6.0)], Stroke::new(1.5, Tokens::get(p.ctx()).text_faint));
+    }
+}
+
+fn paint_soft(p: &Painter, tile: Rect, radius: f64) {
+    let t = Tokens::get(p.ctx());
+    let r = card(tile).expand(2.0);
+    card_fill(p, r);
+    // Rings of the tile's own background, opaque at the edge and clear inward, feather the rim.
+    let depth = (radius as f32 / 25.0 * 7.0).round().max(if radius > 0.0 { 1.0 } else { 0.0 }) as u32;
+    let bg = t.field_bg;
+    for i in 0..depth {
+        let a = (255.0 * (1.0 - i as f32 / depth as f32)) as u8;
+        let c = Color32::from_rgba_unmultiplied(bg.r(), bg.g(), bg.b(), a);
+        p.rect_stroke(r.shrink(i as f32), CornerRadius::same(2), Stroke::new(1.0, c), StrokeKind::Inside);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use deckcraft_model::style::Shadow;
+    use deckcraft_model::{ColorRef, SchemeSlot};
+
+    fn shadow(dist: f64, dir: f64, align: &str, inner: bool) -> Shadow {
+        Shadow {
+            color: ColorRef::scheme(SchemeSlot::Dk1),
+            blur: 4.0,
+            dist,
+            dir,
+            inner,
+            sx: 1.0,
+            sy: 1.0,
+            kx: 0.0,
+            ky: 0.0,
+            align: align.into(),
+            rotate_with_shape: false,
+        }
+    }
+
+    #[test]
+    fn shadow_preset_id_names_each_preset() {
+        let with = |o: Option<Shadow>, i: Option<Shadow>| Effects { outer_shadow: o, inner_shadow: i, ..Effects::default() };
+        assert_eq!(shadow_preset_id(&with(None, None)), "none");
+        assert_eq!(shadow_preset_id(&with(Some(shadow(3.0, 45.0, "tl", false)), None)), "outer");
+        assert_eq!(shadow_preset_id(&with(Some(shadow(3.0, 90.0, "tl", false)), None)), "bottom");
+        assert_eq!(shadow_preset_id(&with(Some(shadow(0.0, 45.0, "tl", false)), None)), "center");
+        assert_eq!(shadow_preset_id(&with(Some(shadow(0.0, 90.0, "bl", false)), None)), "perspective");
+        assert_eq!(shadow_preset_id(&with(None, Some(shadow(3.0, 45.0, "tl", true)))), "inner");
+    }
+}
diff --git a/crates/ui-egui/src/panels/effect_presets_tests.rs b/crates/ui-egui/src/panels/effect_presets_tests.rs
new file mode 100644
--- /dev/null
+++ b/crates/ui-egui/src/panels/effect_presets_tests.rs
@@ -0,0 +1,73 @@
+//! Format Shape ▸ Effects: the Shadow, Glow and Soft Edges preset tiles.
+
+use deckcraft_engine::Session;
+use deckcraft_model::style::Effects;
+use egui::accesskit::Toggled;
+use egui_kittest::Harness;
+use egui_kittest::kittest::{NodeT, Queryable};
+use serde_json::json;
+
+use crate::{Services, SlideApp};
+
+fn harness() -> Harness<'static, SlideApp> {
+    let mut s = Session::with_new();
+    let r = s.execute("shape.insert", &json!({"preset": "rect", "rect": [80.0, 80.0, 200.0, 120.0]})).unwrap();
+    let id = r.get("id").and_then(|v| v.as_u64()).unwrap();
+    s.execute("edit.select", &json!({"ids": [id]})).unwrap();
+    let mut app = SlideApp::new(s, Services::default());
+    app.ui.pane = Some("format".into());
+    app.ui.format_tab = "effects".into();
+    let mut h = Harness::builder().with_size(egui::vec2(1800.0, 1100.0)).build_ui_state(
+        |ui, app: &mut SlideApp| {
+            app.logic(ui.ctx());
+            app.ui(ui);
+        },
+        app,
+    );
+    h.run_steps(3);
+    h
+}
+
+fn effects(h: &Harness<'_, SlideApp>) -> Effects {
+    h.state().session.active().unwrap().selected_shapes()[0].effects.clone().unwrap_or_default()
+}
+
+fn click(h: &mut Harness<'_, SlideApp>, label: &str) {
+    h.get_by_label(label).click();
+    h.run_steps(3);
+}
+
+#[test]
+fn preset_tiles_set_shadow_glow_and_soft_edges() {
+    let mut h = harness();
+    click(&mut h, "Bottom shadow");
+    let sw = effects(&h).outer_shadow.unwrap();
+    assert_eq!((sw.dir, sw.dist), (90.0, 3.0));
+    // The Size slider appears once there is a shadow.
+    assert!(h.query_all_by_label("Size").count() >= 2);
+
+    click(&mut h, "Inner shadow");
+    let fx = effects(&h);
+    assert!(fx.outer_shadow.is_none() && fx.inner_shadow.is_some());
+    click(&mut h, "No shadow");
+    let fx = effects(&h);
+    assert!(fx.outer_shadow.is_none() && fx.inner_shadow.is_none());
+
+    click(&mut h, "18 pt glow");
+    assert_eq!(effects(&h).glow.unwrap().radius, 18.0);
+    click(&mut h, "No glow");
+    assert!(effects(&h).glow.is_none());
+
+    click(&mut h, "10 pt soft edges");
+    assert_eq!(effects(&h).soft_edge, Some(10.0));
+    click(&mut h, "No soft edges");
+    assert_eq!(effects(&h).soft_edge, None);
+}
+
+#[test]
+fn the_current_preset_tile_is_marked_selected() {
+    let mut h = harness();
+    click(&mut h, "5 pt glow");
+    assert_eq!(h.get_by_label("5 pt glow").accesskit_node().toggled(), Some(Toggled::True));
+    assert_eq!(h.get_by_label("11 pt glow").accesskit_node().toggled(), Some(Toggled::False));
+}
diff --git a/crates/ui-egui/src/panels/format_shape.rs b/crates/ui-egui/src/panels/format_shape.rs
--- a/crates/ui-egui/src/panels/format_shape.rs
+++ b/crates/ui-egui/src/panels/format_shape.rs
@@ -1,3 +1,4 @@
+use super::effect_presets;
 use super::*;
 
 /// Percent transparency (0 = opaque) of a solid fill, the same number `shape.fill`'s
@@ -56,21 +57,10 @@ pub(super) fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
     match app.ui.format_tab.as_str() {
         "effects" => {
             ui.label(egui::RichText::new("Shadow").font(theme::bold(13.0)));
-            ui.horizontal_wrapped(|ui| {
-                for (l, v) in [
-                    ("None", "none"),
-                    ("Outer", "outer"),
-                    ("Bottom", "bottom"),
-                    ("Center", "center"),
-                    ("Inner", "inner"),
-                    ("Perspective", "perspective"),
-                ] {
-                    if ui.button(l).clicked() {
-                        run(app, "shape.effects", json!({"shadow": v}));
-                    }
-                }
-            });
             let fx = sh.effects.clone().unwrap_or_default();
+            if let Some(v) = effect_presets::shadow_row(ui, effect_presets::shadow_preset_id(&fx)) {
+                run(app, "shape.effects", json!({"shadow": v}));
+            }
             let shadow = fx.outer_shadow.as_ref().or(fx.inner_shadow.as_ref());
             if let Some(sw) = shadow {
                 color_button(app, ui, "Color", None, |c| ("shape.effects".into(), json!({"shadowProps": {"color": c.as_ref().map(cref_param)}})));
@@ -86,6 +76,7 @@ pub(super) fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
                     "transparency",
                     0.01,
                 );
+                fx_slider(app, ui, ("shadow", sh.id.0, "s"), "Size", sw.sx * 100.0, 1.0..=200.0, "%", "shadowProps", "size", 0.01);
                 fx_slider(app, ui, ("shadow", sh.id.0, "b"), "Blur", sw.blur, 0.0..=100.0, " pt", "shadowProps", "blur", 1.0);
                 fx_slider(app, ui, ("shadow", sh.id.0, "a"), "Angle", sw.dir, 0.0..=359.0, "°", "shadowProps", "dir", 1.0);
                 fx_slider(app, ui, ("shadow", sh.id.0, "d"), "Distance", sw.dist, 0.0..=200.0, " pt", "shadowProps", "dist", 1.0);
@@ -119,6 +110,9 @@ pub(super) fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
             ui.add_space(6.0);
             ui.label(egui::RichText::new("Glow").font(theme::bold(13.0)));
             let radius = fx.glow.as_ref().map_or(0.0, |g| g.radius);
+            if let Some(r) = effect_presets::glow_row(ui, radius) {
+                run(app, "shape.effects", json!({"glow": if r > 0.0 { json!(r) } else { Value::Null }}));
+            }
             fx_slider(app, ui, ("glow", sh.id.0, "r"), "Size", radius, 0.0..=150.0, " pt", "glowProps", "radius", 1.0);
             if let Some(g) = &fx.glow {
                 color_button(app, ui, "Color", None, |c| ("shape.effects".into(), json!({"glowProps": {"color": c.as_ref().map(cref_param)}})));
@@ -137,6 +131,9 @@ pub(super) fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
             }
             ui.label(egui::RichText::new("Soft Edges").font(theme::bold(13.0)));
             let mut s = sh.effects.as_ref().and_then(|e| e.soft_edge).unwrap_or(0.0);
+            if let Some(r) = effect_presets::soft_edge_row(ui, s) {
+                run(app, "shape.effects", json!({"softEdges": r}));
+            }
             if ui.add(egui::Slider::new(&mut s, 0.0..=50.0).text("size (pt)")).drag_stopped() {
                 run(app, "shape.effects", json!({"softEdges": s}));
             }
