diff --git a/src/input.rs b/src/input.rs
index 5c32b205864c174a19c729264ad420f13c797e2c..3e1b84ebf95d310d32fa158b3401d89edb4c6a8d 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -22,8 +22,8 @@ use wasm_bindgen_futures::JsFuture;
 use crate::{
     get_node_cursor_pos, get_timestamp, get_x_offset_center, get_y_offset_center,
     save_edit_history, CosmicAttrs, CosmicEditHistory, CosmicEditor, CosmicFontSystem,
-    CosmicMaxChars, CosmicMaxLines, CosmicText, CosmicTextChanged, CosmicTextPosition, Focus,
-    PasswordInput, ReadOnly, XOffset,
+    CosmicMaxChars, CosmicMaxLines, CosmicTextChanged, CosmicTextPosition, Focus, ReadOnly,
+    XOffset,
 };
 
 #[derive(Resource)]
@@ -49,14 +49,12 @@ pub(crate) fn input_mouse(
     buttons: Res<Input<MouseButton>>,
     mut cosmic_edit_query: Query<(
         &mut CosmicEditor,
-        &CosmicAttrs,
         &GlobalTransform,
         &CosmicTextPosition,
         Entity,
         &XOffset,
         Option<&mut Node>,
         Option<&mut Sprite>,
-        Option<&PasswordInput>,
     )>,
     mut font_system: ResMut<CosmicFontSystem>,
     mut scroll_evr: EventReader<MouseWheel>,
@@ -88,41 +86,13 @@ pub(crate) fn input_mouse(
     let primary_window = windows.single();
     let scale_factor = primary_window.scale_factor() as f32;
     let (camera, camera_transform) = camera_q.iter().find(|(c, _)| c.is_active).unwrap();
-    for (
-        mut editor,
-        attrs,
-        node_transform,
-        text_position,
-        entity,
-        x_offset,
-        node_opt,
-        sprite_opt,
-        password_opt,
-    ) in &mut cosmic_edit_query.iter_mut()
+    for (mut editor, node_transform, text_position, entity, x_offset, node_opt, sprite_opt) in
+        &mut cosmic_edit_query.iter_mut()
     {
         if active_editor.0 != Some(entity) {
             continue;
         }
 
-        // hijack buffer contents
-        let current_text = editor.get_text();
-        let current_select_opt = editor.0.select_opt().clone();
-        let current_cursor = editor.0.cursor().clone();
-
-        // intercept text for password inputs
-        if let Some(password) = password_opt {
-            if !current_text.is_empty() {
-                editor.set_text(
-                    CosmicText::OneStyle(format!("{}", password.0).repeat(current_text.len())),
-                    attrs.0.clone(),
-                    &mut font_system.0,
-                );
-
-                editor.0.set_select_opt(current_select_opt);
-                editor.0.set_cursor(current_cursor);
-            }
-        }
-
         let (width, height, is_ui_node) = match node_opt {
             Some(node) => (node.size().x, node.size().y, true),
             None => {
@@ -239,18 +209,6 @@ pub(crate) fn input_mouse(
                 }
             }
         }
-
-        // reset intercepted text
-        if password_opt.is_some() && !current_text.is_empty() {
-            editor.set_text(
-                crate::CosmicText::OneStyle(current_text),
-                attrs.0.clone(),
-                &mut font_system.0,
-            );
-
-            editor.0.set_select_opt(current_select_opt);
-            editor.0.set_cursor(current_cursor);
-        }
     }
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index 0fdbb84aca597e394cf8255675296d57ca996c8f..e356ba627ee31c906d214cf7ef52776d9dafba6f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21,7 +21,8 @@ use input::{input_kb, input_mouse, undo_redo, ClickTimer};
 use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel};
 use render::{
     blink_cursor, cosmic_edit_redraw_buffer, freeze_cursor_blink, hide_inactive_or_readonly_cursor,
-    on_scale_factor_change, set_initial_scale, CursorBlinkTimer, CursorVisibility, SwashCacheState,
+    hide_password_text, on_scale_factor_change, restore_password_text, set_initial_scale,
+    CursorBlinkTimer, CursorVisibility, PasswordStates, SwashCacheState,
 };
 
 #[cfg(feature = "multicam")]
@@ -314,23 +315,33 @@ impl Plugin for CosmicEditPlugin {
         .add_systems(
             Update,
             (
-                init_history,
-                input_kb,
-                input_mouse,
-                undo_redo,
-                blink_cursor,
-                freeze_cursor_blink,
-                hide_inactive_or_readonly_cursor,
-                clear_inactive_selection,
-                render::update_handle_ui,
-                render::update_handle_sprite,
+                (
+                    init_history,
+                    input_kb,
+                    undo_redo,
+                    blink_cursor,
+                    freeze_cursor_blink,
+                    hide_inactive_or_readonly_cursor,
+                    clear_inactive_selection,
+                    render::update_handle_ui,
+                    render::update_handle_sprite,
+                )
+                    .before(hide_password_text),
+                hide_password_text,
+                input_mouse.after(hide_password_text),
             ),
         )
         .add_systems(
             PostUpdate,
-            (cosmic_edit_redraw_buffer).after(TransformSystem::TransformPropagate),
+            (
+                restore_password_text,
+                cosmic_edit_redraw_buffer
+                    .after(TransformSystem::TransformPropagate)
+                    .before(restore_password_text),
+            ),
         )
         .init_resource::<Focus>()
+        .init_resource::<PasswordStates>()
         .insert_resource(CursorBlinkTimer(Timer::from_seconds(
             0.53,
             TimerMode::Repeating,
diff --git a/src/render.rs b/src/render.rs
index 80b63e021f651b80f9c1dbbb835f8bc5e9d69b54..83d84b5c998f76116616efa51f8d2a93189ec2cb 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -4,6 +4,7 @@ use bevy::{
     asset::HandleId,
     prelude::*,
     render::render_resource::Extent3d,
+    utils::HashMap,
     window::{PrimaryWindow, WindowScaleFactorChanged},
 };
 use cosmic_text::{Affinity, Edit, Metrics, SwashCache};
@@ -44,7 +45,6 @@ pub(crate) fn cosmic_edit_redraw_buffer(
         &mut XOffset,
         &CosmicMode,
         Option<&mut Placeholder>,
-        Option<&PasswordInput>,
     )>,
     mut font_system: ResMut<CosmicFontSystem>,
 ) {
@@ -64,7 +64,6 @@ pub(crate) fn cosmic_edit_redraw_buffer(
         mut x_offset,
         mode,
         mut placeholder_opt,
-        password_opt,
     ) in &mut cosmic_edit_query.iter_mut()
     {
         if !cosmic_editor.0.buffer().redraw() {
@@ -72,22 +71,6 @@ pub(crate) fn cosmic_edit_redraw_buffer(
         }
 
         let current_text = cosmic_editor.get_text();
-        let current_select_opt = cosmic_editor.0.select_opt().clone();
-        let current_cursor = cosmic_editor.0.cursor().clone();
-
-        // intercept text for password inputs
-        if let Some(password) = password_opt {
-            if !current_text.is_empty() {
-                cosmic_editor.set_text(
-                    CosmicText::OneStyle(format!("{}", password.0).repeat(current_text.len())),
-                    attrs.0.clone(),
-                    &mut font_system.0,
-                );
-
-                cosmic_editor.0.set_select_opt(current_select_opt);
-                cosmic_editor.0.set_cursor(current_cursor);
-            }
-        }
 
         // Check for placeholder, replace editor if found and buffer is empty
         let editor = if current_text.is_empty() && placeholder_opt.is_some() {
@@ -283,18 +266,6 @@ pub(crate) fn cosmic_edit_redraw_buffer(
         }
 
         editor.buffer_mut().set_redraw(false);
-
-        // reset intercepted text
-        if password_opt.is_some() && !current_text.is_empty() {
-            cosmic_editor.set_text(
-                crate::CosmicText::OneStyle(current_text),
-                attrs.0.clone(),
-                &mut font_system.0,
-            );
-
-            cosmic_editor.0.set_select_opt(current_select_opt);
-            cosmic_editor.0.set_cursor(current_cursor);
-        }
     }
 }
 
@@ -527,3 +498,55 @@ pub(crate) fn update_handle_sprite(
         *handle = canvas.0.clone_weak();
     }
 }
+
+#[derive(Resource, Default)]
+pub(crate) struct PasswordStates(pub HashMap<Entity, String>);
+
+pub(crate) fn hide_password_text(
+    mut editor_q: Query<(Entity, &mut CosmicEditor, &CosmicAttrs, &PasswordInput)>,
+    mut font_system: ResMut<CosmicFontSystem>,
+    mut password_input_states: ResMut<PasswordStates>,
+) {
+    for (entity, mut cosmic_editor, attrs, password) in editor_q.iter_mut() {
+        let text = cosmic_editor.get_text();
+        let select_opt = cosmic_editor.0.select_opt();
+        let cursor = cosmic_editor.0.cursor();
+
+        if !text.is_empty() {
+            cosmic_editor.set_text(
+                CosmicText::OneStyle(format!("{}", password.0).repeat(text.len())),
+                attrs.0.clone(),
+                &mut font_system.0,
+            );
+
+            cosmic_editor.0.set_select_opt(select_opt);
+            cosmic_editor.0.set_cursor(cursor);
+        }
+
+        password_input_states.0.insert(entity, text);
+    }
+}
+
+pub(crate) fn restore_password_text(
+    mut editor_q: Query<(Entity, &mut CosmicEditor, &CosmicAttrs), With<PasswordInput>>,
+    mut font_system: ResMut<CosmicFontSystem>,
+    password_input_states: Res<PasswordStates>,
+) {
+    for (entity, mut cosmic_editor, attrs) in editor_q.iter_mut() {
+        if let Some(text) = password_input_states.0.get(&entity) {
+            // reset intercepted text
+            if !text.is_empty() {
+                let cursor = cosmic_editor.0.cursor();
+                let select_opt = cosmic_editor.0.select_opt();
+                cosmic_editor.set_text(
+                    crate::CosmicText::OneStyle(text.clone()),
+                    attrs.0.clone(),
+                    &mut font_system.0,
+                );
+
+                cosmic_editor.0.set_select_opt(select_opt);
+                cosmic_editor.0.set_cursor(cursor);
+            }
+        }
+    }
+}