diff --git a/examples/basic_sprite.rs b/examples/basic_sprite.rs
index 29b8b93c39573cb4d4fcdfb0b9c96e1571c5a4d9..e2befd82470db50e402f85fc858a83134fbef4dd 100644
--- a/examples/basic_sprite.rs
+++ b/examples/basic_sprite.rs
@@ -20,26 +20,24 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
 
     let scale_factor = primary_window.scale_factor() as f32;
 
-    let cosmic_edit = (
-        CosmicEditBundle {
-            metrics: CosmicMetrics {
-                font_size: 14.,
-                line_height: 18.,
-                scale_factor,
-            },
-            text_position: CosmicTextPosition::Center,
-            attrs: CosmicAttrs(AttrsOwned::new(attrs)),
-            text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()),
-            ..default()
+    let cosmic_edit = (CosmicEditBundle {
+        metrics: CosmicMetrics {
+            font_size: 14.,
+            line_height: 18.,
+            scale_factor,
         },
-        SpriteBundle {
+        text_position: CosmicTextPosition::Center,
+        attrs: CosmicAttrs(AttrsOwned::new(attrs)),
+        text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()),
+        sprite_bundle: SpriteBundle {
             sprite: Sprite {
                 custom_size: Some(Vec2::new(primary_window.width(), primary_window.height())),
                 ..default()
             },
             ..default()
         },
-    );
+        ..default()
+    },);
 
     let cosmic_edit = commands.spawn(cosmic_edit).id();
 
diff --git a/examples/basic_ui.rs b/examples/basic_ui.rs
index 1688f79c194544722144347ce889810f07ba33fc..50c90082bf3502c6bd8990b0ee99fdcebc928831 100644
--- a/examples/basic_ui.rs
+++ b/examples/basic_ui.rs
@@ -1,7 +1,7 @@
 use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::PrimaryWindow};
 use bevy_cosmic_edit::{
     AttrsOwned, CosmicAttrs, CosmicEditBundle, CosmicEditPlugin, CosmicEditor, CosmicFontConfig,
-    CosmicMetrics, CosmicText, CosmicTextPosition, Focus,
+    CosmicMetrics, CosmicSource, CosmicText, CosmicTextPosition, Focus,
 };
 
 fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
@@ -20,8 +20,8 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
 
     let scale_factor = primary_window.scale_factor() as f32;
 
-    let cosmic_edit = (
-        CosmicEditBundle {
+    let cosmic_edit = commands
+        .spawn(CosmicEditBundle {
             metrics: CosmicMetrics {
                 font_size: 14.,
                 line_height: 18.,
@@ -31,21 +31,28 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             attrs: CosmicAttrs(AttrsOwned::new(attrs)),
             text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()),
             ..default()
-        },
-        // Use buttonbundle for layout
-        ButtonBundle {
-            style: Style {
-                width: Val::Percent(100.),
-                height: Val::Percent(100.),
+        })
+        .id();
+
+    commands
+        .spawn(
+            // Use buttonbundle for layout
+            // Includes Interaction and UiImage which are used by the plugin.
+            ButtonBundle {
+                style: Style {
+                    width: Val::Percent(100.),
+                    height: Val::Percent(100.),
+                    ..default()
+                },
+                // Needs to be set to prevent a bug where nothing is displayed
+                background_color: Color::WHITE.into(),
                 ..default()
             },
-            // Needs to be set to prevent a bug where nothing is displayed
-            background_color: Color::WHITE.into(),
-            ..default()
-        },
-    );
-
-    let cosmic_edit = commands.spawn(cosmic_edit).id();
+        )
+        // point editor at this entity.
+        // Plugin looks for UiImage and sets it's
+        // texture to the editor's rendered image
+        .insert(CosmicSource(cosmic_edit));
 
     commands.insert_resource(Focus(Some(cosmic_edit)));
 }
diff --git a/examples/bevy_api_testing.rs b/examples/bevy_api_testing.rs
index 892ccb08c48dc9833d7e089c5232b2a77c6456e8..742f5f2e2b302286b7bc796c68a565e8e7fa514e 100644
--- a/examples/bevy_api_testing.rs
+++ b/examples/bevy_api_testing.rs
@@ -4,7 +4,7 @@ use bevy_cosmic_edit::*;
 fn setup(mut commands: Commands) {
     commands.spawn(Camera2dBundle::default());
 
-    // spawn a new CosmicEditBundle
+    // UI editor
     let ui_editor = commands
         .spawn(CosmicEditBundle {
             attrs: CosmicAttrs(AttrsOwned::new(
@@ -13,30 +13,33 @@ fn setup(mut commands: Commands) {
             max_lines: CosmicMaxLines(1),
             ..default()
         })
-        .insert(ButtonBundle {
+        .insert(CosmicEditPlaceholderBundle {
+            text_setter: PlaceholderText(CosmicText::OneStyle("Placeholder".into())),
+            attrs: PlaceholderAttrs(AttrsOwned::new(
+                Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
+            )),
+        })
+        .id();
+
+    commands
+        .spawn(ButtonBundle {
             style: Style {
                 // Size and position of text box
                 width: Val::Px(300.),
                 height: Val::Px(50.),
                 left: Val::Px(100.),
                 top: Val::Px(100.),
-                // needs to be set to prevent a bug where nothing is displayed
                 ..default()
             },
+            // needs to be set to prevent a bug where nothing is displayed
             background_color: BackgroundColor(Color::WHITE),
             ..default()
         })
-        .insert(CosmicEditPlaceholderBundle {
-            text_setter: PlaceholderText(CosmicText::OneStyle("Placeholder".into())),
-            attrs: PlaceholderAttrs(AttrsOwned::new(
-                Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
-            )),
-        })
-        .id();
+        .insert(CosmicSource(ui_editor));
 
-    commands.spawn((
-        CosmicEditBundle { ..default() },
-        SpriteBundle {
+    // Sprite editor
+    commands.spawn((CosmicEditBundle {
+        sprite_bundle: SpriteBundle {
             // Sets size of text box
             sprite: Sprite {
                 custom_size: Some(Vec2::new(300., 100.)),
@@ -46,7 +49,8 @@ fn setup(mut commands: Commands) {
             transform: Transform::from_xyz(0., 100., 0.),
             ..default()
         },
-    ));
+        ..default()
+    },));
 
     commands.insert_resource(Focus(Some(ui_editor)));
 }
@@ -63,16 +67,13 @@ fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> CosmicColor {
 fn change_active_editor_ui(
     mut commands: Commands,
     mut interaction_query: Query<
-        (&Interaction, Entity),
-        (
-            Changed<Interaction>,
-            (With<CosmicEditor>, Without<ReadOnly>),
-        ),
+        (&Interaction, &CosmicSource),
+        (Changed<Interaction>, Without<ReadOnly>),
     >,
 ) {
-    for (interaction, entity) in interaction_query.iter_mut() {
+    for (interaction, source) in interaction_query.iter_mut() {
         if let Interaction::Pressed = interaction {
-            commands.insert_resource(Focus(Some(entity)));
+            commands.insert_resource(Focus(Some(source.0)));
         }
     }
 }
@@ -82,7 +83,7 @@ fn change_active_editor_sprite(
     windows: Query<&Window, With<PrimaryWindow>>,
     buttons: Res<Input<MouseButton>>,
     mut cosmic_edit_query: Query<
-        (&mut Sprite, &GlobalTransform, Entity),
+        (&mut Sprite, &GlobalTransform, &Visibility, Entity),
         (With<CosmicEditor>, Without<ReadOnly>),
     >,
     camera_q: Query<(&Camera, &GlobalTransform)>,
@@ -90,8 +91,11 @@ fn change_active_editor_sprite(
     let window = windows.single();
     let (camera, camera_transform) = camera_q.single();
     if buttons.just_pressed(MouseButton::Left) {
-        for (sprite, node_transform, entity) in &mut cosmic_edit_query.iter_mut() {
-            let size = sprite.custom_size.unwrap_or(Vec2::new(1., 1.));
+        for (sprite, node_transform, visibility, entity) in &mut cosmic_edit_query.iter_mut() {
+            if visibility == Visibility::Hidden {
+                continue;
+            }
+            let size = sprite.custom_size.unwrap_or(Vec2::ONE);
             let x_min = node_transform.affine().translation.x - size.x / 2.;
             let y_min = node_transform.affine().translation.y - size.y / 2.;
             let x_max = node_transform.affine().translation.x + size.x / 2.;
diff --git a/examples/every_option.rs b/examples/every_option.rs
index 28dbbae701d38926fb70e5683938f9a979c997e7..048879f61ef544c8a9b69a49dc3faa64460948ac 100644
--- a/examples/every_option.rs
+++ b/examples/every_option.rs
@@ -26,9 +26,33 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             max_lines: CosmicMaxLines(1),
             text_setter: CosmicText::OneStyle("BANANA IS THE CODEWORD!".into()),
             mode: CosmicMode::Wrap,
-            canvas: Default::default(),
+            // CosmicEdit draws to this spritebundle
+            sprite_bundle: SpriteBundle {
+                sprite: Sprite {
+                    // when using another target like a UI element, this is overridden
+                    custom_size: Some(Vec2::ONE * 128.0),
+                    ..default()
+                },
+                // this is the default behaviour for targeting UI elements.
+                // If wanting a sprite, define your own SpriteBundle and
+                // leave the visibility on. See examples/basic_sprite.rs
+                visibility: Visibility::Hidden,
+                ..default()
+            },
+            // Computed fields
+            padding: Default::default(),
+            widget_size: Default::default(),
         })
-        .insert(ButtonBundle {
+        .insert(CosmicEditPlaceholderBundle {
+            text_setter: PlaceholderText(CosmicText::OneStyle("Placeholder".into())),
+            attrs: PlaceholderAttrs(AttrsOwned::new(
+                Attrs::new().color(CosmicColor::rgb(88, 88, 88)),
+            )),
+        })
+        .id();
+
+    commands
+        .spawn(ButtonBundle {
             border_color: Color::LIME_GREEN.into(),
             style: Style {
                 // Size and position of text box
@@ -42,13 +66,7 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             background_color: Color::WHITE.into(),
             ..default()
         })
-        .insert(CosmicEditPlaceholderBundle {
-            text_setter: PlaceholderText(CosmicText::OneStyle("Placeholder".into())),
-            attrs: PlaceholderAttrs(AttrsOwned::new(
-                Attrs::new().color(CosmicColor::rgb(88, 88, 88)),
-            )),
-        })
-        .id();
+        .insert(CosmicSource(editor));
 
     commands.insert_resource(Focus(Some(editor)));
 
diff --git a/examples/font_per_widget.rs b/examples/font_per_widget.rs
index a5cc79f70a11134e9992e8a7144345d30cb52268..0f513566c21d93a42f3402c296ee895145f4d090 100644
--- a/examples/font_per_widget.rs
+++ b/examples/font_per_widget.rs
@@ -207,8 +207,8 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
         )],
     ];
 
-    let cosmic_edit_1 = (
-        CosmicEditBundle {
+    let cosmic_edit_1 = commands
+        .spawn(CosmicEditBundle {
             text_position: bevy_cosmic_edit::CosmicTextPosition::Center,
             attrs: CosmicAttrs(AttrsOwned::new(attrs)),
             metrics: CosmicMetrics {
@@ -218,24 +218,15 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             },
             text_setter: CosmicText::MultiStyle(lines),
             ..default()
-        },
-        ButtonBundle {
-            style: Style {
-                width: Val::Percent(50.),
-                height: Val::Percent(100.),
-                ..default()
-            },
-            background_color: BackgroundColor(Color::WHITE),
-            ..default()
-        },
-    );
+        })
+        .id();
 
     let mut attrs_2 = Attrs::new();
     attrs_2 = attrs_2.family(Family::Name("Times New Roman"));
     attrs_2.color_opt = Some(bevy_color_to_cosmic(Color::PURPLE));
 
-    let cosmic_edit_2 = (
-        CosmicEditBundle {
+    let cosmic_edit_2 = commands
+        .spawn(CosmicEditBundle {
             attrs: CosmicAttrs(AttrsOwned::new(attrs_2)),
             metrics: CosmicMetrics {
                 font_size: 28.,
@@ -245,27 +236,38 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             text_position: CosmicTextPosition::Center,
             text_setter: CosmicText::OneStyle("Widget 2.\nClick on me =>".to_string()),
             ..default()
-        },
-        ButtonBundle {
-            background_color: BackgroundColor(Color::WHITE.with_a(0.8)),
-            style: Style {
-                width: Val::Percent(50.),
-                height: Val::Percent(100.),
-                ..default()
-            },
-            ..default()
-        },
-    );
+        })
+        .id();
 
-    let mut id = None;
     // Spawn the CosmicEditUiBundles as children of root
     commands.entity(root).with_children(|parent| {
-        id = Some(parent.spawn(cosmic_edit_1).id());
-        parent.spawn(cosmic_edit_2);
+        parent
+            .spawn(ButtonBundle {
+                style: Style {
+                    width: Val::Percent(50.),
+                    height: Val::Percent(100.),
+                    ..default()
+                },
+                background_color: BackgroundColor(Color::WHITE),
+                ..default()
+            })
+            .insert(CosmicSource(cosmic_edit_1));
+
+        parent
+            .spawn(ButtonBundle {
+                background_color: BackgroundColor(Color::WHITE.with_a(0.8)),
+                style: Style {
+                    width: Val::Percent(50.),
+                    height: Val::Percent(100.),
+                    ..default()
+                },
+                ..default()
+            })
+            .insert(CosmicSource(cosmic_edit_2));
     });
 
     // Set active editor
-    commands.insert_resource(Focus(id));
+    commands.insert_resource(Focus(Some(cosmic_edit_1)));
 }
 
 fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> CosmicColor {
@@ -280,16 +282,13 @@ fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> CosmicColor {
 fn change_active_editor_ui(
     mut commands: Commands,
     mut interaction_query: Query<
-        (&Interaction, Entity),
-        (
-            Changed<Interaction>,
-            (With<CosmicEditor>, Without<ReadOnly>),
-        ),
+        (&Interaction, &CosmicSource),
+        (Changed<Interaction>, Without<ReadOnly>),
     >,
 ) {
-    for (interaction, entity) in interaction_query.iter_mut() {
+    for (interaction, source) in interaction_query.iter_mut() {
         if let Interaction::Pressed = interaction {
-            commands.insert_resource(Focus(Some(entity)));
+            commands.insert_resource(Focus(Some(source.0)));
         }
     }
 }
diff --git a/examples/image_background.rs b/examples/image_background.rs
index 8d700ae94948aff58409939d025b369b1c34e6a1..1b9ed6db508a34a998dbf8a73b32ddcc2b99c18f 100644
--- a/examples/image_background.rs
+++ b/examples/image_background.rs
@@ -14,7 +14,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
             background_image: CosmicBackground(Some(bg_image_handle)),
             ..default()
         })
-        .insert(ButtonBundle {
+        .id();
+
+    commands
+        .spawn(ButtonBundle {
             style: Style {
                 // Size and position of text box
                 width: Val::Px(300.),
@@ -26,7 +29,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
             background_color: Color::WHITE.into(),
             ..default()
         })
-        .id();
+        .insert(CosmicSource(editor));
+
     commands.insert_resource(Focus(Some(editor)));
 }
 
diff --git a/examples/login.rs b/examples/login.rs
index 8670a50c70fde696b80a8aac8a45791b0f89fcda..1dbee608853aac9285c008fb9abb4856f8a031ae 100644
--- a/examples/login.rs
+++ b/examples/login.rs
@@ -21,6 +21,90 @@ fn setup(mut commands: Commands, window: Query<&Window, With<PrimaryWindow>>) {
 
     commands.spawn(Camera2dBundle::default());
 
+    let login_editor = commands
+        .spawn(CosmicEditBundle {
+            max_lines: CosmicMaxLines(1),
+            metrics: CosmicMetrics {
+                scale_factor: window.scale_factor() as f32,
+                ..default()
+            },
+            sprite_bundle: SpriteBundle {
+                sprite: Sprite {
+                    custom_size: Some(Vec2::new(300.0, 50.0)),
+                    ..default()
+                },
+                visibility: Visibility::Hidden,
+                ..default()
+            },
+            ..default()
+        })
+        .insert(CosmicEditPlaceholderBundle {
+            text_setter: PlaceholderText(CosmicText::OneStyle("Username".into())),
+            attrs: PlaceholderAttrs(AttrsOwned::new(
+                Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
+            )),
+        })
+        .insert(UsernameTag)
+        .id();
+
+    let password_editor = commands
+        .spawn(CosmicEditBundle {
+            max_lines: CosmicMaxLines(1),
+            metrics: CosmicMetrics {
+                scale_factor: window.scale_factor() as f32,
+                ..default()
+            },
+            ..default()
+        })
+        .insert(CosmicEditPlaceholderBundle {
+            text_setter: PlaceholderText(CosmicText::OneStyle("Password".into())),
+            attrs: PlaceholderAttrs(AttrsOwned::new(
+                Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
+            )),
+        })
+        .insert(PasswordTag)
+        .insert(PasswordInput::default())
+        .id();
+
+    let submit_editor = commands
+        .spawn(CosmicEditBundle {
+            max_lines: CosmicMaxLines(1),
+            metrics: CosmicMetrics {
+                font_size: 25.0,
+                line_height: 25.0,
+                scale_factor: window.scale_factor() as f32,
+                ..default()
+            },
+            attrs: CosmicAttrs(AttrsOwned::new(
+                Attrs::new().color(bevy_color_to_cosmic(Color::WHITE)),
+            )),
+            text_setter: CosmicText::OneStyle("Submit".into()),
+            fill_color: FillColor(Color::GREEN),
+            ..default()
+        })
+        .insert(ReadOnly)
+        .id();
+
+    let display_editor = commands
+        .spawn(CosmicEditBundle {
+            metrics: CosmicMetrics {
+                scale_factor: window.scale_factor() as f32,
+                ..default()
+            },
+            ..default()
+        })
+        .insert(CosmicEditPlaceholderBundle {
+            text_setter: PlaceholderText(CosmicText::OneStyle("Output".into())),
+            attrs: PlaceholderAttrs(AttrsOwned::new(
+                Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
+            )),
+        })
+        .insert((ReadOnly, DisplayTag))
+        .id();
+
+    commands.insert_resource(Focus(Some(login_editor)));
+
+    // Spawn UI
     commands
         .spawn(NodeBundle {
             style: Style {
@@ -34,15 +118,7 @@ fn setup(mut commands: Commands, window: Query<&Window, With<PrimaryWindow>>) {
             ..default()
         })
         .with_children(|root| {
-            root.spawn(CosmicEditBundle {
-                max_lines: CosmicMaxLines(1),
-                metrics: CosmicMetrics {
-                    scale_factor: window.scale_factor() as f32,
-                    ..default()
-                },
-                ..default()
-            })
-            .insert(ButtonBundle {
+            root.spawn(ButtonBundle {
                 style: Style {
                     // Size and position of text box
                     width: Val::Px(300.),
@@ -53,23 +129,9 @@ fn setup(mut commands: Commands, window: Query<&Window, With<PrimaryWindow>>) {
                 background_color: BackgroundColor(Color::WHITE),
                 ..default()
             })
-            .insert(CosmicEditPlaceholderBundle {
-                text_setter: PlaceholderText(CosmicText::OneStyle("Username".into())),
-                attrs: PlaceholderAttrs(AttrsOwned::new(
-                    Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
-                )),
-            })
-            .insert(UsernameTag);
+            .insert(CosmicSource(login_editor));
 
-            root.spawn(CosmicEditBundle {
-                max_lines: CosmicMaxLines(1),
-                metrics: CosmicMetrics {
-                    scale_factor: window.scale_factor() as f32,
-                    ..default()
-                },
-                ..default()
-            })
-            .insert(ButtonBundle {
+            root.spawn(ButtonBundle {
                 style: Style {
                     // Size and position of text box
                     width: Val::Px(300.),
@@ -80,31 +142,9 @@ fn setup(mut commands: Commands, window: Query<&Window, With<PrimaryWindow>>) {
                 background_color: BackgroundColor(Color::WHITE),
                 ..default()
             })
-            .insert(CosmicEditPlaceholderBundle {
-                text_setter: PlaceholderText(CosmicText::OneStyle("Password".into())),
-                attrs: PlaceholderAttrs(AttrsOwned::new(
-                    Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
-                )),
-            })
-            .insert(PasswordTag)
-            .insert(PasswordInput::default());
-
-            root.spawn(CosmicEditBundle {
-                max_lines: CosmicMaxLines(1),
-                metrics: CosmicMetrics {
-                    font_size: 25.0,
-                    line_height: 25.0,
-                    scale_factor: window.scale_factor() as f32,
-                    ..default()
-                },
-                attrs: CosmicAttrs(AttrsOwned::new(
-                    Attrs::new().color(bevy_color_to_cosmic(Color::WHITE)),
-                )),
-                text_setter: CosmicText::OneStyle("Submit".into()),
-                fill_color: FillColor(Color::GREEN),
-                ..default()
-            })
-            .insert(ButtonBundle {
+            .insert(CosmicSource(password_editor));
+
+            root.spawn(ButtonBundle {
                 style: Style {
                     // Size and position of text box
                     width: Val::Px(150.),
@@ -119,16 +159,9 @@ fn setup(mut commands: Commands, window: Query<&Window, With<PrimaryWindow>>) {
                 ..default()
             })
             .insert(SubmitButton)
-            .insert(ReadOnly);
+            .insert(CosmicSource(submit_editor));
 
-            root.spawn(CosmicEditBundle {
-                metrics: CosmicMetrics {
-                    scale_factor: window.scale_factor() as f32,
-                    ..default()
-                },
-                ..default()
-            })
-            .insert(ButtonBundle {
+            root.spawn(ButtonBundle {
                 style: Style {
                     // Size and position of text box
                     width: Val::Px(300.),
@@ -139,13 +172,7 @@ fn setup(mut commands: Commands, window: Query<&Window, With<PrimaryWindow>>) {
                 background_color: BackgroundColor(Color::WHITE),
                 ..default()
             })
-            .insert(CosmicEditPlaceholderBundle {
-                text_setter: PlaceholderText(CosmicText::OneStyle("Output".into())),
-                attrs: PlaceholderAttrs(AttrsOwned::new(
-                    Attrs::new().color(bevy_color_to_cosmic(Color::rgb_u8(128, 128, 128))),
-                )),
-            })
-            .insert((ReadOnly, DisplayTag));
+            .insert(CosmicSource(display_editor));
         });
 }
 
@@ -161,16 +188,13 @@ fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> CosmicColor {
 fn change_active_editor_ui(
     mut commands: Commands,
     mut interaction_query: Query<
-        (&Interaction, Entity),
-        (
-            Changed<Interaction>,
-            (With<CosmicEditor>, Without<ReadOnly>),
-        ),
+        (&Interaction, &CosmicSource),
+        (Changed<Interaction>, Without<ReadOnly>),
     >,
 ) {
-    for (interaction, entity) in interaction_query.iter_mut() {
+    for (interaction, source) in interaction_query.iter_mut() {
         if let Interaction::Pressed = interaction {
-            commands.insert_resource(Focus(Some(entity)));
+            commands.insert_resource(Focus(Some(source.0)));
         }
     }
 }
diff --git a/examples/multiple_sprites.rs b/examples/multiple_sprites.rs
index dc2f06fce6b10f7554659058ba95541eb3957824..0366731bb53f409ab1731d92adaa02fea4678476 100644
--- a/examples/multiple_sprites.rs
+++ b/examples/multiple_sprites.rs
@@ -20,16 +20,13 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
         scale_factor: primary_window.scale_factor() as f32,
     };
 
-    let cosmic_edit_1 = (
-        CosmicEditBundle {
-            attrs: CosmicAttrs(AttrsOwned::new(attrs)),
-            metrics: metrics.clone(),
-            text_position: CosmicTextPosition::Center,
-            fill_color: FillColor(Color::ALICE_BLUE),
-            text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()),
-            ..default()
-        },
-        SpriteBundle {
+    let cosmic_edit_1 = (CosmicEditBundle {
+        attrs: CosmicAttrs(AttrsOwned::new(attrs)),
+        metrics: metrics.clone(),
+        text_position: CosmicTextPosition::Center,
+        fill_color: FillColor(Color::ALICE_BLUE),
+        text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y".to_string()),
+        sprite_bundle: SpriteBundle {
             sprite: Sprite {
                 custom_size: Some(Vec2 {
                     x: primary_window.width() / 2.,
@@ -40,18 +37,16 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             transform: Transform::from_translation(Vec3::new(-primary_window.width() / 4., 0., 1.)),
             ..default()
         },
-    );
+        ..default()
+    },);
 
-    let cosmic_edit_2 = (
-        CosmicEditBundle {
-            attrs: CosmicAttrs(AttrsOwned::new(attrs)),
-            metrics,
-            text_position: CosmicTextPosition::Center,
-            fill_color: FillColor(Color::GRAY.with_a(0.5)),
-            text_setter: CosmicText::OneStyle("Widget_2. Click on me".to_string()),
-            ..default()
-        },
-        SpriteBundle {
+    let cosmic_edit_2 = (CosmicEditBundle {
+        attrs: CosmicAttrs(AttrsOwned::new(attrs)),
+        metrics,
+        text_position: CosmicTextPosition::Center,
+        fill_color: FillColor(Color::GRAY.with_a(0.5)),
+        text_setter: CosmicText::OneStyle("Widget_2. Click on me".to_string()),
+        sprite_bundle: SpriteBundle {
             sprite: Sprite {
                 custom_size: Some(Vec2 {
                     x: primary_window.width() / 2.,
@@ -66,7 +61,8 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             )),
             ..default()
         },
-    );
+        ..default()
+    },);
 
     let id = commands.spawn(cosmic_edit_1).id();
 
diff --git a/examples/readonly.rs b/examples/readonly.rs
index e218b099058122cb8bf36eb563f9532d55fe0a27..0d7aa8c053fa7aa40d2d66ab526539dd0a34d8a6 100644
--- a/examples/readonly.rs
+++ b/examples/readonly.rs
@@ -20,8 +20,9 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
     attrs = attrs.family(Family::Name("Victor Mono"));
     attrs = attrs.color(bevy_color_to_cosmic(Color::PURPLE));
 
-    let cosmic_edit = (
-        CosmicEditBundle {
+    // spawn editor
+    let cosmic_edit = commands
+        .spawn(CosmicEditBundle {
             attrs: CosmicAttrs(AttrsOwned::new(attrs)),
             text_position: CosmicTextPosition::Center,
             metrics: CosmicMetrics {
@@ -31,25 +32,27 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
             },
             text_setter: CosmicText::OneStyle("馃榾馃榾馃榾 x => y\nRead only widget".to_string()),
             ..default()
-        },
-        ButtonBundle {
-            style: Style {
-                width: Val::Percent(100.),
-                height: Val::Percent(100.),
-                ..default()
-            },
-            background_color: BackgroundColor(Color::WHITE),
-            ..default()
-        },
-    );
+        })
+        .insert(ReadOnly)
+        .id();
 
-    let mut id = None;
-    // Spawn the CosmicEditUiBundle as a child of root
+    // Spawn the ButtonBundle as a child of root
     commands.entity(root).with_children(|parent| {
-        id = Some(parent.spawn(cosmic_edit).insert(ReadOnly).id());
+        parent
+            .spawn(ButtonBundle {
+                style: Style {
+                    width: Val::Percent(100.),
+                    height: Val::Percent(100.),
+                    ..default()
+                },
+                background_color: BackgroundColor(Color::WHITE),
+                ..default()
+            })
+            // add cosmic source
+            .insert(CosmicSource(cosmic_edit));
     });
 
-    commands.insert_resource(Focus(id));
+    commands.insert_resource(Focus(Some(cosmic_edit)));
 }
 
 pub fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> CosmicColor {
diff --git a/examples/text_input.rs b/examples/text_input.rs
index 7ba06f178d7668a5c9f19cc742d9724296306973..f65e4dad0e2fc4150cf09dad2a366872629dd03c 100644
--- a/examples/text_input.rs
+++ b/examples/text_input.rs
@@ -9,7 +9,7 @@ fn create_editable_widget(commands: &mut Commands, scale_factor: f32, text: Stri
         AttrsOwned::new(Attrs::new().color(bevy_color_to_cosmic(Color::hex("4d4d4d").unwrap())));
     let placeholder_attrs =
         AttrsOwned::new(Attrs::new().color(bevy_color_to_cosmic(Color::hex("#e6e6e6").unwrap())));
-    commands
+    let editor = commands
         .spawn((
             CosmicEditBundle {
                 attrs: CosmicAttrs(attrs.clone()),
@@ -24,31 +24,36 @@ fn create_editable_widget(commands: &mut Commands, scale_factor: f32, text: Stri
                 mode: CosmicMode::InfiniteLine,
                 ..default()
             },
-            ButtonBundle {
-                border_color: Color::hex("#ededed").unwrap().into(),
-                style: Style {
-                    border: UiRect::all(Val::Px(3.)),
-                    width: Val::Percent(20.),
-                    height: Val::Px(50.),
-                    left: Val::Percent(40.),
-                    top: Val::Px(100.),
-                    ..default()
-                },
-                background_color: Color::WHITE.into(),
-                ..default()
-            },
             CosmicEditPlaceholderBundle {
                 text_setter: PlaceholderText(CosmicText::OneStyle("Type something...".into())),
                 attrs: PlaceholderAttrs(placeholder_attrs.clone()),
             },
         ))
-        .id()
+        .id();
+    commands
+        .spawn(ButtonBundle {
+            border_color: Color::hex("#ededed").unwrap().into(),
+            style: Style {
+                border: UiRect::all(Val::Px(3.)),
+                width: Val::Percent(20.),
+                height: Val::Px(50.),
+                left: Val::Percent(40.),
+                top: Val::Px(100.),
+                ..default()
+            },
+            background_color: Color::WHITE.into(),
+            ..default()
+        })
+        .insert(CosmicSource(editor));
+
+    editor
 }
 
 fn create_readonly_widget(commands: &mut Commands, scale_factor: f32, text: String) -> Entity {
     let attrs =
         AttrsOwned::new(Attrs::new().color(bevy_color_to_cosmic(Color::hex("4d4d4d").unwrap())));
-    commands
+
+    let editor = commands
         .spawn((
             CosmicEditBundle {
                 attrs: CosmicAttrs(attrs.clone()),
@@ -62,22 +67,27 @@ fn create_readonly_widget(commands: &mut Commands, scale_factor: f32, text: Stri
                 mode: CosmicMode::AutoHeight,
                 ..default()
             },
-            ButtonBundle {
-                border_color: Color::hex("#ededed").unwrap().into(),
-                style: Style {
-                    border: UiRect::all(Val::Px(3.)),
-                    width: Val::Percent(20.),
-                    height: Val::Px(50.),
-                    left: Val::Percent(40.),
-                    top: Val::Px(100.),
-                    ..default()
-                },
-                background_color: Color::WHITE.into(),
-                ..default()
-            },
             ReadOnly,
         ))
-        .id()
+        .id();
+
+    commands
+        .spawn(ButtonBundle {
+            border_color: Color::hex("#ededed").unwrap().into(),
+            style: Style {
+                border: UiRect::all(Val::Px(3.)),
+                width: Val::Percent(20.),
+                height: Val::Px(50.),
+                left: Val::Percent(40.),
+                top: Val::Px(100.),
+                ..default()
+            },
+            background_color: Color::WHITE.into(),
+            ..default()
+        })
+        .insert(CosmicSource(editor));
+
+    editor
 }
 
 fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
@@ -94,12 +104,20 @@ fn setup(mut commands: Commands, windows: Query<&Window, With<PrimaryWindow>>) {
 fn handle_enter(
     mut commands: Commands,
     keys: Res<Input<KeyCode>>,
-    mut mode: Query<(Entity, &CosmicEditor, &mut CosmicMode)>,
+    mut query_dest: Query<(Entity, &CosmicSource)>,
+    mut query_source: Query<(Entity, &CosmicEditor, &CosmicMode)>,
     windows: Query<&Window, With<PrimaryWindow>>,
 ) {
     if keys.just_pressed(KeyCode::Return) {
         let scale_factor = windows.single().scale_factor() as f32;
-        for (entity, editor, mode) in mode.iter_mut() {
+        for (entity, editor, mode) in query_source.iter_mut() {
+            // Remove UI elements
+            for (dest_entity, source) in query_dest.iter_mut() {
+                if source.0 == entity {
+                    commands.entity(dest_entity).despawn_recursive();
+                }
+            }
+
             let text = editor.get_text();
             commands.entity(entity).despawn_recursive();
             if *mode == CosmicMode::AutoHeight {
diff --git a/src/cursor.rs b/src/cursor.rs
index e0485b48e3c43edb3f805a4e00837c17044fbf5f..7c09799a0d3a17e4028d78830c5dfa082e5b0b85 100644
--- a/src/cursor.rs
+++ b/src/cursor.rs
@@ -1,6 +1,6 @@
 use bevy::{input::mouse::MouseMotion, prelude::*, window::PrimaryWindow};
 
-use crate::{CosmicEditor, CosmicTextChanged};
+use crate::{CosmicEditor, CosmicSource, CosmicTextChanged};
 
 #[cfg(feature = "multicam")]
 use crate::CosmicPrimaryCamera;
@@ -45,7 +45,7 @@ type CameraQuery<'a, 'b, 'c, 'd> = Query<'a, 'b, (&'c Camera, &'d GlobalTransfor
 
 pub fn hover_sprites(
     windows: Query<&Window, With<PrimaryWindow>>,
-    mut cosmic_edit_query: Query<(&mut Sprite, &GlobalTransform), With<CosmicEditor>>,
+    mut cosmic_edit_query: Query<(&mut Sprite, &Visibility, &GlobalTransform), With<CosmicEditor>>,
     camera_q: CameraQuery,
     mut hovered: Local<bool>,
     mut last_hovered: Local<bool>,
@@ -55,8 +55,11 @@ pub fn hover_sprites(
     *hovered = false;
     let window = windows.single();
     let (camera, camera_transform) = camera_q.single();
-    for (sprite, node_transform) in &mut cosmic_edit_query.iter_mut() {
-        let size = sprite.custom_size.unwrap_or(Vec2::new(1., 1.));
+    for (sprite, visibility, node_transform) in &mut cosmic_edit_query.iter_mut() {
+        if visibility == Visibility::Hidden {
+            continue;
+        }
+        let size = sprite.custom_size.unwrap_or(Vec2::ONE);
         let x_min = node_transform.affine().translation.x - size.x / 2.;
         let y_min = node_transform.affine().translation.y - size.y / 2.;
         let x_max = node_transform.affine().translation.x + size.x / 2.;
@@ -82,7 +85,7 @@ pub fn hover_sprites(
 }
 
 pub fn hover_ui(
-    mut interaction_query: Query<&Interaction, (Changed<Interaction>, With<CosmicEditor>)>,
+    mut interaction_query: Query<&Interaction, (Changed<Interaction>, With<CosmicSource>)>,
     mut evw_hover_in: EventWriter<TextHoverIn>,
     mut evw_hover_out: EventWriter<TextHoverOut>,
 ) {
diff --git a/src/input.rs b/src/input.rs
index 3ef16bab07b7ba42ca44e038644033725edead36..b5486f4223f9b3d8c7132f5d6f6df093279470e1 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, CosmicTextChanged, CosmicTextPosition, Focus, PasswordInput,
-    ReadOnly, XOffset,
+    CosmicMaxChars, CosmicMaxLines, CosmicSource, CosmicTextChanged, CosmicTextPosition, Focus,
+    PasswordInput, ReadOnly, XOffset,
 };
 
 #[derive(Resource)]
@@ -47,15 +47,15 @@ pub(crate) fn input_mouse(
     active_editor: Res<Focus>,
     keys: Res<Input<KeyCode>>,
     buttons: Res<Input<MouseButton>>,
-    mut cosmic_edit_query: Query<(
+    mut editor_q: Query<(
         &mut CosmicEditor,
         &GlobalTransform,
         &CosmicTextPosition,
         Entity,
         &XOffset,
-        Option<&mut Node>,
-        Option<&mut Sprite>,
+        &mut Sprite,
     )>,
+    node_q: Query<(&Node, &GlobalTransform, &CosmicSource)>,
     mut font_system: ResMut<CosmicFontSystem>,
     mut scroll_evr: EventReader<MouseWheel>,
     camera_q: Query<(&Camera, &GlobalTransform)>,
@@ -86,21 +86,29 @@ 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, node_transform, text_position, entity, x_offset, node_opt, sprite_opt) in
-        &mut cosmic_edit_query.iter_mut()
+
+    for (mut editor, sprite_transform, text_position, entity, x_offset, sprite) in
+        &mut editor_q.iter_mut()
     {
         if active_editor.0 != Some(entity) {
             continue;
         }
 
-        let (width, height, is_ui_node) = match node_opt {
-            Some(node) => (node.size().x, node.size().y, true),
-            None => {
-                let sprite = sprite_opt.unwrap();
-                let size = sprite.custom_size.unwrap();
-                (size.x, size.y, false)
+        let mut is_ui_node = false;
+        let mut transform = sprite_transform;
+        let (mut width, mut height) =
+            (sprite.custom_size.unwrap().x, sprite.custom_size.unwrap().y);
+
+        // TODO: this is bad loop nesting, rethink system with relationships in mind
+        for (node, node_transform, source) in node_q.iter() {
+            if source.0 != entity {
+                continue;
             }
-        };
+            is_ui_node = true;
+            transform = node_transform;
+            width = node.size().x;
+            height = node.size().y;
+        }
 
         let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
 
@@ -132,7 +140,7 @@ pub(crate) fn input_mouse(
         if buttons.just_pressed(MouseButton::Left) {
             if let Some(node_cursor_pos) = get_node_cursor_pos(
                 primary_window,
-                node_transform,
+                transform,
                 (width, height),
                 is_ui_node,
                 camera,
@@ -171,7 +179,7 @@ pub(crate) fn input_mouse(
         if buttons.pressed(MouseButton::Left) && *click_count == 0 {
             if let Some(node_cursor_pos) = get_node_cursor_pos(
                 primary_window,
-                node_transform,
+                transform,
                 (width, height),
                 is_ui_node,
                 camera,
@@ -213,8 +221,6 @@ pub(crate) fn input_mouse(
 }
 
 // TODO: split copy/paste into own fn, separate fn for wasm
-// Maybe split undo/redo too, just drop inputs from this fn when pressed
-/// Handles undo/redo, copy/paste and char input
 pub(crate) fn input_kb(
     active_editor: Res<Focus>,
     keys: Res<Input<KeyCode>>,
diff --git a/src/lib.rs b/src/lib.rs
index 59e8013d9d9ead303415122a883fba44220e15bf..0b3d479fe29dd539c443d1ef96c717e1e1837fe1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,7 +6,7 @@ mod render;
 
 use std::{collections::VecDeque, path::PathBuf};
 
-use bevy::{prelude::*, render::texture::DEFAULT_IMAGE_HANDLE, transform::TransformSystem};
+use bevy::{prelude::*, transform::TransformSystem};
 pub use cosmic_text::{
     Action, Attrs, AttrsOwned, Color as CosmicColor, Cursor, Edit, Family, Style as FontStyle,
     Weight as FontWeight,
@@ -20,10 +20,10 @@ use input::{input_kb, input_mouse, undo_redo, ClickTimer};
 #[cfg(target_arch = "wasm32")]
 use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel};
 use render::{
-    blink_cursor, cosmic_edit_redraw_buffer, freeze_cursor_blink, hide_inactive_or_readonly_cursor,
-    hide_password_text, on_scale_factor_change, restore_password_text, restore_placeholder_text,
-    set_initial_scale, show_placeholder, CursorBlinkTimer, CursorVisibility, PasswordValues,
-    SwashCacheState,
+    blink_cursor, freeze_cursor_blink, hide_inactive_or_readonly_cursor, hide_password_text,
+    on_scale_factor_change, restore_password_text, restore_placeholder_text, set_initial_scale,
+    show_placeholder, CosmicPadding, CosmicRenderSet, CosmicWidgetSize, CursorBlinkTimer,
+    CursorVisibility, PasswordValues, SwashCacheState,
 };
 
 #[cfg(feature = "multicam")]
@@ -221,15 +221,9 @@ impl Default for PasswordInput {
 }
 
 #[derive(Component)]
-pub struct CosmicCanvas(pub Handle<Image>);
+pub struct CosmicSource(pub Entity);
 
-impl Default for CosmicCanvas {
-    fn default() -> Self {
-        CosmicCanvas(DEFAULT_IMAGE_HANDLE.typed())
-    }
-}
-
-#[derive(Bundle, Default)]
+#[derive(Bundle)]
 pub struct CosmicEditBundle {
     // cosmic bits
     pub fill_color: FillColor,
@@ -241,7 +235,36 @@ pub struct CosmicEditBundle {
     pub max_chars: CosmicMaxChars,
     pub text_setter: CosmicText,
     pub mode: CosmicMode,
-    pub canvas: CosmicCanvas,
+    pub sprite_bundle: SpriteBundle,
+    // render bits
+    pub padding: CosmicPadding,
+    pub widget_size: CosmicWidgetSize,
+}
+
+impl Default for CosmicEditBundle {
+    fn default() -> Self {
+        CosmicEditBundle {
+            fill_color: Default::default(),
+            text_position: Default::default(),
+            metrics: Default::default(),
+            attrs: Default::default(),
+            background_image: Default::default(),
+            max_lines: Default::default(),
+            max_chars: Default::default(),
+            text_setter: Default::default(),
+            mode: Default::default(),
+            sprite_bundle: SpriteBundle {
+                sprite: Sprite {
+                    custom_size: Some(Vec2::ONE * 128.0),
+                    ..default()
+                },
+                visibility: Visibility::Hidden,
+                ..default()
+            },
+            padding: Default::default(),
+            widget_size: Default::default(),
+        }
+    }
 }
 
 #[derive(Bundle)]
@@ -305,8 +328,20 @@ impl Plugin for CosmicEditPlugin {
             freeze_cursor_blink,
             hide_inactive_or_readonly_cursor,
             clear_inactive_selection,
-            render::update_handle_ui,
-            render::update_handle_sprite,
+        );
+
+        let render_systems = (
+            render::new_image_from_default.in_set(CosmicRenderSet::Setup),
+            render::set_size_from_ui.in_set(CosmicRenderSet::Setup),
+            render::cosmic_reshape.in_set(CosmicRenderSet::Shaping),
+            render::cosmic_widget_size.in_set(CosmicRenderSet::Sizing),
+            render::cosmic_buffer_size.in_set(CosmicRenderSet::Sizing),
+            render::auto_height
+                .after(CosmicRenderSet::Sizing)
+                .before(CosmicRenderSet::Draw),
+            render::cosmic_padding.in_set(CosmicRenderSet::Padding),
+            render::set_cursor.in_set(CosmicRenderSet::Cursor),
+            render::render_texture.in_set(CosmicRenderSet::Draw),
         );
 
         app.add_systems(
@@ -314,8 +349,7 @@ impl Plugin for CosmicEditPlugin {
             (
                 set_initial_scale,
                 (cosmic_editor_builder, on_scale_factor_change).after(set_initial_scale),
-                render::cosmic_ui_to_canvas,
-                render::cosmic_sprite_to_canvas,
+                render::swap_target_handle,
             ),
         )
         .add_systems(
@@ -329,12 +363,25 @@ impl Plugin for CosmicEditPlugin {
             )
                 .chain(),
         )
+        .configure_sets(
+            PostUpdate,
+            (
+                CosmicRenderSet::Setup,
+                CosmicRenderSet::Shaping,
+                CosmicRenderSet::Sizing,
+                CosmicRenderSet::Cursor,
+                CosmicRenderSet::Padding,
+                CosmicRenderSet::Draw,
+            )
+                .chain()
+                .after(TransformSystem::TransformPropagate),
+        )
         .add_systems(
             PostUpdate,
             (
                 hide_password_text,
                 show_placeholder,
-                cosmic_edit_redraw_buffer.after(TransformSystem::TransformPropagate),
+                render_systems,
                 apply_deferred, // Prevents one-frame inputs adding placeholder to editor
                 restore_password_text,
                 restore_placeholder_text,
diff --git a/src/render.rs b/src/render.rs
index baffb0f8e6b10a920d7524cc981cf4abc0e6f258..39921911a73662289fe16a05b105ce80d7ec869b 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -3,7 +3,7 @@ use std::time::Duration;
 use bevy::{
     asset::HandleId,
     prelude::*,
-    render::render_resource::Extent3d,
+    render::{render_resource::Extent3d, texture::DEFAULT_IMAGE_HANDLE},
     utils::HashMap,
     window::{PrimaryWindow, WindowScaleFactorChanged},
 };
@@ -12,11 +12,21 @@ use image::{imageops::FilterType, GenericImageView};
 
 use crate::{
     get_text_size, get_x_offset_center, get_y_offset_center, CosmicAttrs, CosmicBackground,
-    CosmicCanvas, CosmicEditor, CosmicFontSystem, CosmicMetrics, CosmicMode, CosmicText,
+    CosmicEditor, CosmicFontSystem, CosmicMetrics, CosmicMode, CosmicSource, CosmicText,
     CosmicTextPosition, FillColor, Focus, PasswordInput, PlaceholderAttrs, PlaceholderText,
     ReadOnly, XOffset, DEFAULT_SCALE_PLACEHOLDER,
 };
 
+#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
+pub enum CosmicRenderSet {
+    Setup,
+    Shaping,
+    Sizing,
+    Cursor,
+    Padding,
+    Draw,
+}
+
 #[derive(Resource)]
 pub(crate) struct SwashCacheState {
     pub swash_cache: SwashCache,
@@ -34,137 +44,115 @@ pub(crate) struct PasswordValues(pub HashMap<Entity, (String, usize)>);
 #[derive(Component)]
 pub(crate) struct Placeholder;
 
-pub(crate) fn cosmic_edit_redraw_buffer(
-    windows: Query<&Window, With<PrimaryWindow>>,
-    mut images: ResMut<Assets<Image>>,
-    mut swash_cache_state: ResMut<SwashCacheState>,
-    mut cosmic_edit_query: Query<(
-        &mut CosmicEditor,
-        &CosmicAttrs,
-        &CosmicBackground,
-        &FillColor,
-        &mut CosmicCanvas,
+#[derive(Component, Default)]
+pub struct CosmicPadding(pub Vec2);
+
+#[derive(Component, Default)]
+pub struct CosmicWidgetSize(pub Vec2);
+
+pub(crate) fn cosmic_padding(
+    mut query: Query<(
+        &mut CosmicPadding,
         &CosmicTextPosition,
-        Option<&Node>,
-        Option<&mut Style>,
-        Option<&mut Sprite>,
-        &mut XOffset,
-        &CosmicMode,
+        &CosmicEditor,
+        &CosmicWidgetSize,
     )>,
-    mut font_system: ResMut<CosmicFontSystem>,
 ) {
-    let primary_window = windows.single();
-    let scale = primary_window.scale_factor() as f32;
-
-    for (
-        mut cosmic_editor,
-        attrs,
-        background_image,
-        fill_color,
-        mut canvas,
-        text_position,
-        node_opt,
-        style_opt,
-        sprite_opt,
-        mut x_offset,
-        mode,
-    ) in &mut cosmic_edit_query.iter_mut()
-    {
-        if !cosmic_editor.0.buffer().redraw() {
-            continue;
-        }
-
-        let editor = &mut cosmic_editor.0;
-
-        editor.shape_as_needed(&mut font_system.0);
-
-        // Get numbers, do maths to find and set cursor
-        //
-        let (base_width, mut base_height) = match node_opt {
-            Some(node) => (node.size().x.ceil(), node.size().y.ceil()),
-            None => (
-                sprite_opt.as_ref().unwrap().custom_size.unwrap().x.ceil(),
-                sprite_opt.as_ref().unwrap().custom_size.unwrap().y.ceil(),
+    for (mut padding, position, editor, size) in query.iter_mut() {
+        padding.0 = match position {
+            CosmicTextPosition::Center => Vec2::new(
+                get_x_offset_center(size.0.x, editor.0.buffer()) as f32,
+                get_y_offset_center(size.0.y, editor.0.buffer()) as f32,
             ),
-        };
+            CosmicTextPosition::TopLeft { padding } => Vec2::new(*padding as f32, *padding as f32),
+            CosmicTextPosition::Left { padding } => Vec2::new(
+                *padding as f32,
+                get_y_offset_center(size.0.y, editor.0.buffer()) as f32,
+            ),
+        }
+    }
+}
 
-        let widget_width = base_width * scale;
-        let widget_height = base_height * scale;
+pub(crate) fn cosmic_widget_size(
+    mut query: Query<(&mut CosmicWidgetSize, &Sprite), Changed<Sprite>>,
+    windows: Query<&Window, With<PrimaryWindow>>,
+) {
+    let scale = windows.single().scale_factor() as f32;
+    for (mut size, sprite) in query.iter_mut() {
+        size.0 = sprite.custom_size.unwrap().ceil() * scale;
+    }
+}
 
-        let padding_x = match text_position {
+pub(crate) fn cosmic_buffer_size(
+    mut query: Query<(
+        &mut CosmicEditor,
+        &CosmicMode,
+        &CosmicWidgetSize,
+        &CosmicTextPosition,
+    )>,
+    mut font_system: ResMut<CosmicFontSystem>,
+) {
+    for (mut editor, mode, size, position) in query.iter_mut() {
+        let padding_x = match position {
             CosmicTextPosition::Center => 0.,
             CosmicTextPosition::TopLeft { padding } => *padding as f32,
             CosmicTextPosition::Left { padding } => *padding as f32,
         };
 
         let (buffer_width, buffer_height) = match mode {
-            CosmicMode::InfiniteLine => (f32::MAX, widget_height),
-            CosmicMode::AutoHeight => (widget_width - padding_x, (i32::MAX / 2) as f32),
-            CosmicMode::Wrap => (widget_width - padding_x, widget_height),
+            CosmicMode::InfiniteLine => (f32::MAX, size.0.y),
+            CosmicMode::AutoHeight => (size.0.x - padding_x, (i32::MAX / 2) as f32),
+            CosmicMode::Wrap => (size.0.x - padding_x, size.0.y),
         };
 
         editor
+            .0
             .buffer_mut()
             .set_size(&mut font_system.0, buffer_width, buffer_height);
+    }
+}
 
-        if mode == &CosmicMode::AutoHeight {
-            let text_size = get_text_size(editor.buffer());
-            let text_height = (text_size.1 + 30.) / primary_window.scale_factor() as f32;
-            if text_height > base_height {
-                base_height = text_height.ceil();
-                match style_opt {
-                    Some(mut style) => style.height = Val::Px(base_height),
-                    None => sprite_opt.unwrap().custom_size.unwrap().y = base_height,
-                }
-            }
-        }
-
-        let mut cursor_x = 0.;
-        if mode == &CosmicMode::InfiniteLine {
-            if let Some(line) = editor.buffer().layout_runs().next() {
-                for (idx, glyph) in line.glyphs.iter().enumerate() {
-                    if editor.cursor().affinity == Affinity::Before {
-                        if idx <= editor.cursor().index {
-                            cursor_x += glyph.w;
-                        }
-                    } else if idx < editor.cursor().index {
-                        cursor_x += glyph.w;
-                    } else {
-                        break;
-                    }
-                }
-            }
-        }
-
-        if mode == &CosmicMode::InfiniteLine && x_offset.0.is_none() {
-            let padding_x = match text_position {
-                CosmicTextPosition::Center => get_x_offset_center(widget_width, editor.buffer()),
-                CosmicTextPosition::TopLeft { padding } => *padding,
-                CosmicTextPosition::Left { padding } => *padding,
-            };
-            *x_offset = XOffset(Some((0., widget_width - 2. * padding_x as f32)));
-        }
+pub(crate) fn cosmic_reshape(
+    mut query: Query<&mut CosmicEditor>,
+    mut font_system: ResMut<CosmicFontSystem>,
+) {
+    for mut cosmic_editor in query.iter_mut() {
+        cosmic_editor.0.shape_as_needed(&mut font_system.0);
+    }
+}
 
-        if let Some((x_min, x_max)) = x_offset.0 {
-            if cursor_x > x_max {
-                let diff = cursor_x - x_max;
-                *x_offset = XOffset(Some((x_min + diff, cursor_x)));
-            }
-            if cursor_x < x_min {
-                let diff = x_min - cursor_x;
-                *x_offset = XOffset(Some((cursor_x, x_max - diff)));
-            }
+pub(crate) fn render_texture(
+    mut query: Query<(
+        &mut CosmicEditor,
+        &CosmicAttrs,
+        &CosmicBackground,
+        &FillColor,
+        &Handle<Image>,
+        &CosmicWidgetSize,
+        &CosmicPadding,
+        &XOffset,
+    )>,
+    mut font_system: ResMut<CosmicFontSystem>,
+    mut images: ResMut<Assets<Image>>,
+    mut swash_cache_state: ResMut<SwashCacheState>,
+) {
+    for (mut cosmic_editor, attrs, background_image, fill_color, canvas, size, padding, x_offset) in
+        query.iter_mut()
+    {
+        // TODO: redraw tag component
+        if !cosmic_editor.0.buffer().redraw() {
+            continue;
         }
 
         // Draw background
-        let mut pixels = vec![0; widget_width as usize * widget_height as usize * 4];
+        let mut pixels = vec![0; size.0.x as usize * size.0.y as usize * 4];
         if let Some(bg_image) = background_image.0.clone() {
             if let Some(image) = images.get(&bg_image) {
                 let mut dynamic_image = image.clone().try_into_dynamic().unwrap();
-                if image.size().x != widget_width || image.size().y != widget_height {
+                if image.size().x != size.0.x || image.size().y != size.0.y {
                     dynamic_image = dynamic_image.resize_to_fill(
-                        widget_width as u32,
-                        widget_height as u32,
+                        size.0.x as u32,
+                        size.0.y as u32,
                         FilterType::Triangle,
                     );
                 }
@@ -187,26 +175,13 @@ pub(crate) fn cosmic_edit_redraw_buffer(
             }
         }
 
-        // Get values for glyph draw step
-        let (padding_x, padding_y) = match text_position {
-            CosmicTextPosition::Center => (
-                get_x_offset_center(widget_width, editor.buffer()),
-                get_y_offset_center(widget_height, editor.buffer()),
-            ),
-            CosmicTextPosition::TopLeft { padding } => (*padding, *padding),
-            CosmicTextPosition::Left { padding } => (
-                *padding,
-                get_y_offset_center(widget_height, editor.buffer()),
-            ),
-        };
-
         let font_color = attrs
             .0
             .color_opt
             .unwrap_or(cosmic_text::Color::rgb(0, 0, 0));
 
         // Draw glyphs
-        editor.draw(
+        cosmic_editor.0.draw(
             &mut font_system.0,
             &mut swash_cache_state.swash_cache,
             font_color,
@@ -215,10 +190,10 @@ pub(crate) fn cosmic_edit_redraw_buffer(
                     for col in 0..w as i32 {
                         draw_pixel(
                             &mut pixels,
-                            widget_width as i32,
-                            widget_height as i32,
-                            x + col + padding_x - x_offset.0.unwrap_or((0., 0.)).0 as i32,
-                            y + row + padding_y,
+                            size.0.x as i32,
+                            size.0.y as i32,
+                            x + col + padding.0.x as i32 - x_offset.0.unwrap_or((0., 0.)).0 as i32,
+                            y + row + padding.0.y as i32,
                             color,
                         );
                     }
@@ -226,37 +201,134 @@ pub(crate) fn cosmic_edit_redraw_buffer(
             },
         );
 
-        let canvas = &mut canvas.0;
-
         if let Some(prev_image) = images.get_mut(canvas) {
-            if *canvas == bevy::render::texture::DEFAULT_IMAGE_HANDLE.typed() {
-                let mut prev_image = prev_image.clone();
-                prev_image.data.clear();
-                prev_image.data.extend_from_slice(pixels.as_slice());
-                prev_image.resize(Extent3d {
-                    width: widget_width as u32,
-                    height: widget_height as u32,
-                    depth_or_array_layers: 1,
-                });
+            prev_image.data.clear();
+            prev_image.data.extend_from_slice(pixels.as_slice());
+            prev_image.resize(Extent3d {
+                width: size.0.x as u32,
+                height: size.0.y as u32,
+                depth_or_array_layers: 1,
+            });
+        }
+
+        cosmic_editor.0.buffer_mut().set_redraw(false);
+    }
+}
+
+pub(crate) fn new_image_from_default(
+    mut query: Query<&mut Handle<Image>, Added<CosmicEditor>>,
+    mut images: ResMut<Assets<Image>>,
+) {
+    for mut canvas in query.iter_mut() {
+        if let Some(prev_image) = images.get_mut(&canvas) {
+            if *canvas == DEFAULT_IMAGE_HANDLE.typed() {
+                let prev_image = prev_image.clone();
                 let handle_id: HandleId = HandleId::random::<Image>();
                 let new_handle: Handle<Image> = Handle::weak(handle_id);
                 let new_handle = images.set(new_handle, prev_image);
                 *canvas = new_handle;
-            } else {
-                prev_image.data.clear();
-                prev_image.data.extend_from_slice(pixels.as_slice());
-                prev_image.resize(Extent3d {
-                    width: widget_width as u32,
-                    height: widget_height as u32,
-                    depth_or_array_layers: 1,
-                });
+            }
+        }
+    }
+}
+
+pub(crate) fn set_cursor(
+    mut query: Query<(
+        &mut XOffset,
+        &CosmicMode,
+        &CosmicEditor,
+        &CosmicWidgetSize,
+        &CosmicPadding,
+    )>,
+) {
+    for (mut x_offset, mode, cosmic_editor, size, padding) in query.iter_mut() {
+        let mut cursor_x = 0.;
+        if mode == &CosmicMode::InfiniteLine {
+            if let Some(line) = cosmic_editor.0.buffer().layout_runs().next() {
+                for (idx, glyph) in line.glyphs.iter().enumerate() {
+                    if cosmic_editor.0.cursor().affinity == Affinity::Before {
+                        if idx <= cosmic_editor.0.cursor().index {
+                            cursor_x += glyph.w;
+                        }
+                    } else if idx < cosmic_editor.0.cursor().index {
+                        cursor_x += glyph.w;
+                    } else {
+                        break;
+                    }
+                }
             }
         }
 
-        editor.buffer_mut().set_redraw(false);
+        if mode == &CosmicMode::InfiniteLine && x_offset.0.is_none() {
+            *x_offset = XOffset(Some((0., size.0.x - 2. * padding.0.x)));
+        }
+
+        if let Some((x_min, x_max)) = x_offset.0 {
+            if cursor_x > x_max {
+                let diff = cursor_x - x_max;
+                *x_offset = XOffset(Some((x_min + diff, cursor_x)));
+            }
+            if cursor_x < x_min {
+                let diff = x_min - cursor_x;
+                *x_offset = XOffset(Some((cursor_x, x_max - diff)));
+            }
+        }
     }
 }
 
+pub(crate) fn auto_height(
+    mut query: Query<(
+        Entity,
+        &mut Sprite,
+        &CosmicMode,
+        &mut CosmicEditor,
+        &CosmicWidgetSize,
+    )>,
+    mut style_q: Query<(&mut Style, &CosmicSource)>,
+    windows: Query<&Window, With<PrimaryWindow>>,
+) {
+    let scale = windows.single().scale_factor() as f32;
+
+    for (entity, mut sprite, mode, mut cosmic_editor, size) in query.iter_mut() {
+        if mode == &CosmicMode::AutoHeight {
+            let text_size = get_text_size(cosmic_editor.0.buffer());
+            let text_height = (text_size.1 + 30.) / scale;
+            if text_height > size.0.y / scale {
+                let mut new_size = sprite.custom_size.unwrap();
+                new_size.y = text_height.ceil();
+                // TODO this gets set automatically in UI cases but needs to be done for all other cases.
+                // redundant work but easier to just set on all sprites
+                sprite.custom_size = Some(new_size);
+
+                cosmic_editor.0.buffer_mut().set_redraw(true);
+
+                // TODO: bad loop nesting
+                for (mut style, source) in style_q.iter_mut() {
+                    if source.0 != entity {
+                        continue;
+                    }
+                    style.height = Val::Px(text_height.ceil());
+                }
+            }
+        }
+    }
+}
+
+pub(crate) fn set_size_from_ui(
+    mut source_q: Query<&mut Sprite, With<CosmicEditor>>,
+    dest_q: Query<(&Node, &CosmicSource)>,
+) {
+    for (node, source) in dest_q.iter() {
+        if let Ok(mut sprite) = source_q.get_mut(source.0) {
+            sprite.custom_size = Some(node.size().ceil().max(Vec2::ONE));
+        }
+    }
+}
+
+pub(crate) fn _set_size_from_mesh() {
+    // TODO
+}
+
 fn draw_pixel(
     buffer: &mut [u8],
     width: i32,
@@ -431,35 +503,27 @@ pub(crate) fn on_scale_factor_change(
     }
 }
 
-pub(crate) fn cosmic_ui_to_canvas(
-    mut added_ui_images: Query<(&mut UiImage, &CosmicCanvas), Added<UiImage>>,
-) {
-    for (mut ui_image, canvas) in added_ui_images.iter_mut() {
-        ui_image.texture = canvas.0.clone_weak();
-    }
-}
-
-pub(crate) fn update_handle_ui(
-    mut changed_handles: Query<(&mut UiImage, &CosmicCanvas), Changed<CosmicCanvas>>,
-) {
-    for (mut ui_image, canvas) in changed_handles.iter_mut() {
-        ui_image.texture = canvas.0.clone_weak();
-    }
-}
-
-pub(crate) fn cosmic_sprite_to_canvas(
-    mut added_sprite_textures: Query<(&mut Handle<Image>, &CosmicCanvas), Added<Handle<Image>>>,
-) {
-    for (mut handle, canvas) in added_sprite_textures.iter_mut() {
-        *handle = canvas.0.clone_weak();
-    }
-}
-
-pub(crate) fn update_handle_sprite(
-    mut changed_handles: Query<(&mut Handle<Image>, &CosmicCanvas), Changed<CosmicCanvas>>,
+pub(crate) fn swap_target_handle(
+    source_q: Query<&Handle<Image>, (Changed<Handle<Image>>, With<CosmicEditor>)>,
+    mut dest_q: Query<
+        (
+            Option<&mut Handle<Image>>,
+            Option<&mut UiImage>,
+            &CosmicSource,
+        ),
+        Without<CosmicEditor>,
+    >,
 ) {
-    for (mut handle, canvas) in changed_handles.iter_mut() {
-        *handle = canvas.0.clone_weak();
+    // TODO: do this once
+    for (dest_handle_opt, dest_ui_opt, source_entity) in dest_q.iter_mut() {
+        if let Ok(source_handle) = source_q.get(source_entity.0) {
+            if let Some(mut dest_handle) = dest_handle_opt {
+                *dest_handle = source_handle.clone_weak();
+            }
+            if let Some(mut dest_ui) = dest_ui_opt {
+                dest_ui.texture = source_handle.clone_weak();
+            }
+        }
     }
 }