diff --git a/crates/io/src/svg_read.rs b/crates/io/src/svg_read.rs
--- a/crates/io/src/svg_read.rs
+++ b/crates/io/src/svg_read.rs
@@ -589,8 +589,9 @@ fn num(e: &BytesStart, name: &[u8], fallback: f64) -> f64 {
     attr(e, name).as_deref().and_then(parse_length_mm).unwrap_or(fallback)
 }
 
-/// A bare number, or a number with an absolute CSS/SVG unit: millimetres here, everything
-/// else (px, the default; pt, pc, in, cm) through the CSS reference pixel (96 per inch).
+/// A bare number or `px` (returned as is, in CSS pixels), or a number with an absolute CSS/SVG
+/// unit (mm, cm, in, pt, pc) converted to millimetres. Relative units (`%`, `em`, `ex`) and
+/// unknown ones have no absolute size and give `None`.
 fn parse_length_mm(s: &str) -> Option<f64> {
     let s = s.trim();
     let cut = s.find(|c: char| !(c.is_ascii_digit() || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')).unwrap_or(s.len());
@@ -606,7 +607,7 @@ fn parse_length_mm(s: &str) -> Option<f64> {
         "in" => Some(value * 25.4),
         "pt" => Some(value * 25.4 / 72.0),
         "pc" => Some(value * 25.4 / 6.0),
-        _ => Some(value * 25.4 / 96.0),
+        _ => None,
     }
 }
 
@@ -1077,6 +1078,16 @@ mod tests {
         }
     }
 
+    #[test]
+    fn relative_units_carry_no_absolute_length() {
+        assert_eq!(parse_length_mm("100%"), None);
+        assert_eq!(parse_length_mm("2em"), None);
+        assert_eq!(parse_length_mm("1ex"), None);
+        assert_eq!(parse_length_mm("10mm"), Some(10.0));
+        assert_eq!(parse_length_mm("100"), Some(100.0));
+        assert_eq!(parse_length_mm("100px"), Some(100.0));
+    }
+
     #[test]
     fn compact_arc_flags_read_as_single_characters() {
         let spaced = arc_of("M10 10 a20 20 0 0 1 20 20");
diff --git a/crates/io/src/tests.rs b/crates/io/src/tests.rs
--- a/crates/io/src/tests.rs
+++ b/crates/io/src/tests.rs
@@ -2328,6 +2328,15 @@ fn svgz_gunzips_to_the_same_drawing() {
     assert_eq!(plain.model.iter().map(|e| &e.kind).collect::<Vec<_>>(), packed.model.iter().map(|e| &e.kind).collect::<Vec<_>>());
 }
 
+#[test]
+fn svg_percentage_root_width_is_ignored() {
+    let body = "<line x1=\"0\" y1=\"0\" x2=\"1000\" y2=\"0\"/></svg>";
+    let with = read(format!("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" viewBox=\"0 0 1000 500\">{body}").as_bytes(), "a.svg").unwrap();
+    let without = read(format!("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1000 500\">{body}").as_bytes(), "b.svg").unwrap();
+    assert_eq!(with.model.iter().map(|e| &e.kind).collect::<Vec<_>>(), without.model.iter().map(|e| &e.kind).collect::<Vec<_>>());
+    assert_eq!(with.model.len(), 1);
+}
+
 #[test]
 fn svg_read_dispatches_by_extension() {
     let svg = format!("{SVG_PAGE}<circle cx=\"5\" cy=\"5\" r=\"1\"/></svg>");
