diff --git a/crates/epub/src/css.rs b/crates/epub/src/css.rs
--- a/crates/epub/src/css.rs
+++ b/crates/epub/src/css.rs
@@ -150,6 +150,7 @@ pub(crate) fn stylesheet_with(doc: &Document, generics: &BTreeMap<String, String
         if let Some(sz) = c.size {
             let _ = write!(rule, "font-size: {:.3}em; ", sz / base);
         }
+        attrs_decor_css(c, &mut rule);
         let (tag, class) = export_mapping(st, &cs.name, true, &cc);
         if let Some(selector) = class_selector(tag, &class) {
             let _ = writeln!(css, "{selector} {{ {rule}}}");
@@ -158,6 +159,29 @@ pub(crate) fn stylesheet_with(doc: &Document, generics: &BTreeMap<String, String
     css
 }
 
+/// Tracking, capitalization and line decorations of a style or override, as CSS declarations.
+fn attrs_decor_css(c: &CharAttrs, out: &mut String) {
+    if let Some(t) = c.tracking
+        && t != 0.0
+    {
+        let _ = write!(out, "letter-spacing: {:.3}em; ", t / 1000.0);
+    }
+    match c.capitalization {
+        Some(designcraft_doc::Capitalization::AllCaps) => out.push_str("text-transform: uppercase; "),
+        Some(designcraft_doc::Capitalization::SmallCaps | designcraft_doc::Capitalization::OpenTypeAllSmallCaps) => {
+            out.push_str("font-variant: small-caps; ")
+        }
+        _ => {}
+    }
+    // One declaration: a later `text-decoration` would replace the earlier one.
+    match (c.underline == Some(true), c.strikethrough == Some(true)) {
+        (true, true) => out.push_str("text-decoration: underline line-through; "),
+        (true, false) => out.push_str("text-decoration: underline; "),
+        (false, true) => out.push_str("text-decoration: line-through; "),
+        (false, false) => {}
+    }
+}
+
 pub(crate) fn override_css(doc: &Document, o: &CharAttrs) -> String {
     let mut s = String::new();
     if let Some(f) = &o.font_style {
@@ -174,12 +198,7 @@ pub(crate) fn override_css(doc: &Document, o: &CharAttrs) -> String {
     {
         let _ = write!(s, "color: {h}; ");
     }
-    if o.underline == Some(true) {
-        s.push_str("text-decoration: underline; ");
-    }
-    if o.capitalization == Some(designcraft_doc::Capitalization::AllCaps) {
-        s.push_str("text-transform: uppercase; ");
-    }
+    attrs_decor_css(o, &mut s);
     s.trim_end().to_string()
 }
 
diff --git a/crates/epub/src/tests.rs b/crates/epub/src/tests.rs
--- a/crates/epub/src/tests.rs
+++ b/crates/epub/src/tests.rs
@@ -922,3 +922,26 @@ fn char_css_joins_underline_and_strikethrough_in_one_declaration() {
     assert!(css(false, true).contains("text-decoration: line-through;"));
     assert!(!css(false, false).contains("text-decoration"));
 }
+
+#[test]
+fn character_styles_keep_decorations_caps_and_tracking() {
+    let mut d = Document::new(&NewDocument::default());
+    d.styles_mut().character.push(designcraft_doc::CharacterStyle {
+        name: "Under".into(),
+        based_on: None,
+        chars: CharAttrs {
+            underline: Some(true),
+            strikethrough: Some(true),
+            capitalization: Some(designcraft_doc::Capitalization::AllCaps),
+            tracking: Some(100.0),
+            ..Default::default()
+        },
+        shortcut: String::new(),
+    });
+    let css = css::stylesheet(&d);
+    assert!(css.contains("text-decoration: underline line-through;"), "{css}");
+    assert!(css.contains("text-transform: uppercase;"), "{css}");
+    assert!(css.contains("letter-spacing: 0.100em;"), "{css}");
+    let o = CharAttrs { strikethrough: Some(true), ..Default::default() };
+    assert_eq!(css::override_css(&d, &o), "text-decoration: line-through;");
+}
