diff --git a/src/input.rs b/src/input.rs
index 8881d91471b0576dc7e5e404c7810eea42f78933..af519006b5854b1ea2910a5e96b2ab5e6d934516 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -232,7 +232,6 @@ pub(crate) fn input_kb(
     mut font_system: ResMut<CosmicFontSystem>,
     mut is_deleting: Local<bool>,
     mut edits_duration: Local<Option<Duration>>,
-    mut undoredo_duration: Local<Option<Duration>>,
     _channel: Option<Res<WasmPasteAsyncChannel>>,
 ) {
     for (mut editor, mut edit_history, attrs, max_lines, max_chars, entity, readonly_opt) in
@@ -248,11 +247,7 @@ pub(crate) fn input_kb(
 
         let now_ms = get_timestamp();
 
-        #[cfg(target_os = "macos")]
-        let command = keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]);
-
-        #[cfg(not(target_os = "macos"))]
-        let command = keys.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
+        let command = keypress_command(&keys);
 
         #[cfg(target_arch = "wasm32")]
         let command = if web_sys::window()
@@ -402,84 +397,6 @@ pub(crate) fn input_kb(
             return;
         }
 
-        // redo
-        #[cfg(not(target_os = "windows"))]
-        let requested_redo = command && shift && keys.just_pressed(KeyCode::Z) && !readonly;
-        #[cfg(target_os = "windows")]
-        let requested_redo = command && keys.just_pressed(KeyCode::Y);
-
-        if requested_redo {
-            let edits = &edit_history.edits;
-            if edits.is_empty() {
-                return;
-            }
-            if edit_history.current_edit == edits.len() - 1 {
-                return;
-            }
-            let idx = edit_history.current_edit + 1;
-            if let Some(current_edit) = edits.get(idx) {
-                editor.0.buffer_mut().lines.clear();
-                for line in current_edit.lines.iter() {
-                    let mut line_text = String::new();
-                    let mut attrs_list = AttrsList::new(attrs.as_attrs());
-                    for (text, attrs) in line.iter() {
-                        let start = line_text.len();
-                        line_text.push_str(text);
-                        let end = line_text.len();
-                        attrs_list.add_span(start..end, attrs.as_attrs());
-                    }
-                    editor.0.buffer_mut().lines.push(BufferLine::new(
-                        line_text,
-                        attrs_list,
-                        Shaping::Advanced,
-                    ));
-                }
-                editor.0.set_cursor(current_edit.cursor);
-                editor.0.buffer_mut().set_redraw(true);
-                edit_history.current_edit += 1;
-            }
-            *undoredo_duration = Some(Duration::from_millis(now_ms as u64));
-            evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
-            return;
-        }
-        // undo
-        let requested_undo = command && keys.just_pressed(KeyCode::Z) && !readonly;
-
-        if requested_undo {
-            let edits = &edit_history.edits;
-            if edits.is_empty() {
-                return;
-            }
-            if edit_history.current_edit == 0 {
-                return;
-            }
-            let idx = edit_history.current_edit - 1;
-            if let Some(current_edit) = edits.get(idx) {
-                editor.0.buffer_mut().lines.clear();
-                for line in current_edit.lines.iter() {
-                    let mut line_text = String::new();
-                    let mut attrs_list = AttrsList::new(attrs.as_attrs());
-                    for (text, attrs) in line.iter() {
-                        let start = line_text.len();
-                        line_text.push_str(text);
-                        let end = line_text.len();
-                        attrs_list.add_span(start..end, attrs.as_attrs());
-                    }
-                    editor.0.buffer_mut().lines.push(BufferLine::new(
-                        line_text,
-                        attrs_list,
-                        Shaping::Advanced,
-                    ));
-                }
-                editor.0.set_cursor(current_edit.cursor);
-                editor.0.buffer_mut().set_redraw(true);
-                edit_history.current_edit -= 1;
-            }
-            *undoredo_duration = Some(Duration::from_millis(now_ms as u64));
-            evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
-            return;
-        }
-
         let mut is_clipboard = false;
         #[cfg(not(target_arch = "wasm32"))]
         {
@@ -586,7 +503,12 @@ pub(crate) fn input_kb(
             }
         }
 
-        if !is_edit || readonly {
+        // skip event + history if undo/redo keys pressed
+
+        let requested_redo = keypress_redo(&keys);
+        let requested_undo = command && keys.just_pressed(KeyCode::Z);
+
+        if !is_edit || readonly || requested_redo || requested_undo {
             return;
         }
 
@@ -606,6 +528,116 @@ pub(crate) fn input_kb(
     }
 }
 
+fn keypress_command(keys: &Input<KeyCode>) -> bool {
+    #[cfg(target_os = "macos")]
+    let command = keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]);
+
+    #[cfg(not(target_os = "macos"))]
+    let command = keys.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
+
+    #[cfg(target_arch = "wasm32")]
+    let command = if web_sys::window()
+        .unwrap()
+        .navigator()
+        .user_agent()
+        .unwrap_or("NoUA".into())
+        .contains("Macintosh")
+    {
+        keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight])
+    } else {
+        command
+    };
+
+    command
+}
+
+fn keypress_redo(keys: &Input<KeyCode>) -> bool {
+    let command = keypress_command(keys);
+    let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
+
+    #[cfg(not(target_os = "windows"))]
+    let requested_redo = command && shift && keys.just_pressed(KeyCode::Z);
+
+    // TODO: windows OS detection for wasm here
+    #[cfg(target_os = "windows")]
+    let requested_redo = command && keys.just_pressed(KeyCode::Y);
+
+    requested_redo
+}
+
+pub(crate) fn undo_redo(
+    active_editor: Res<Focus>,
+    keys: Res<Input<KeyCode>>,
+    mut editor_q: Query<
+        (&mut CosmicEditor, &CosmicAttrs, &mut CosmicEditHistory),
+        Without<ReadOnly>,
+    >,
+    mut evw_changed: EventWriter<CosmicTextChanged>,
+) {
+    if active_editor.0.is_none() {
+        return;
+    }
+
+    let entity = active_editor.0.unwrap();
+
+    let (mut editor, attrs, mut edit_history) = editor_q.get_mut(entity).unwrap();
+    let command = keypress_command(&keys);
+
+    let attrs = &attrs.0;
+
+    let requested_redo = keypress_redo(&keys);
+    let requested_undo = command & keys.just_pressed(KeyCode::Z);
+
+    if !(requested_redo || requested_undo) {
+        return;
+    }
+
+    let edits = &edit_history.edits;
+
+    if edits.is_empty() {
+        return;
+    }
+
+    // use not redo rather than undo, cos undo will be true when redo is
+    if !requested_redo && edit_history.current_edit == 0 {
+        return;
+    }
+
+    if requested_redo && edit_history.current_edit == edits.len() - 1 {
+        return;
+    }
+
+    let index = if requested_redo {
+        edit_history.current_edit + 1
+    } else {
+        edit_history.current_edit - 1
+    };
+
+    if let Some(current_edit) = edits.get(index) {
+        editor.0.buffer_mut().lines.clear();
+        for line in current_edit.lines.iter() {
+            let mut line_text = String::new();
+            let mut attrs_list = AttrsList::new(attrs.as_attrs());
+            for (text, attrs) in line.iter() {
+                let start = line_text.len();
+                line_text.push_str(text);
+                let end = line_text.len();
+                attrs_list.add_span(start..end, attrs.as_attrs());
+            }
+            editor.0.buffer_mut().lines.push(BufferLine::new(
+                line_text,
+                attrs_list,
+                Shaping::Advanced,
+            ));
+        }
+        editor.0.set_cursor(current_edit.cursor);
+        editor.0.set_select_opt(None); // prevent auto selection of redo-inserted text
+        editor.0.buffer_mut().set_redraw(true);
+        edit_history.current_edit = index;
+        evw_changed.send(CosmicTextChanged((entity, editor.get_text())));
+    }
+}
+
 #[cfg(target_arch = "wasm32")]
 #[wasm_bindgen]
 pub fn write_clipboard_wasm(text: &str) {
diff --git a/src/lib.rs b/src/lib.rs
index d103cf79ed42b7acf0799d53d511b649cb6b7933..ba9ce10a6bf9b53d1e364f87189eee93c4b908f2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23,7 +23,7 @@ use cosmic_text::{
 use cursor::{change_cursor, hover_sprites, hover_ui};
 pub use cursor::{TextHoverIn, TextHoverOut};
 use image::{imageops::FilterType, GenericImageView};
-use input::{input_kb, input_mouse, ClickTimer};
+use input::{input_kb, input_mouse, undo_redo, ClickTimer};
 #[cfg(target_arch = "wasm32")]
 use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel};
 
@@ -423,6 +423,7 @@ impl Plugin for CosmicEditPlugin {
                 init_history,
                 input_kb,
                 input_mouse,
+                undo_redo,
                 blink_cursor,
                 freeze_cursor_blink,
                 hide_inactive_or_readonly_cursor,