diff --git a/Cargo.lock b/Cargo.lock
index e70356486b16ce06a2f46cda493253c2637b18fb..c282a360f85a35be7fb6aafb94a2d2c0b669e779 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -417,7 +417,6 @@ dependencies = [
  "js-sys",
  "sys-locale",
  "unicode-segmentation",
- "util",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
@@ -3182,14 +3181,6 @@ version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
 
-[[package]]
-name = "util"
-version = "0.1.0"
-dependencies = [
- "bevy",
- "bevy_cosmic_edit",
-]
-
 [[package]]
 name = "uuid"
 version = "1.7.0"
diff --git a/Cargo.toml b/Cargo.toml
index 2c425e21aa9fe12f9ddf1d57732b6403f91dc97c..a3d03582168743a3f35581eba5d1d64a7e84bcb4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,3 @@
-workspace = { members = ["util"] }
 [package]
 name = "bevy_cosmic_edit"
 version = "0.19.0"
@@ -52,4 +51,3 @@ web-sys = { version = "0.3.67", features = [
 
 [dev-dependencies]
 insta = "1.29.0"
-util = { path = "./util/" }
diff --git a/examples/basic_sprite.rs b/examples/basic_sprite.rs
index 09073d0dd2ed969a571185079afa9414ad7c1b80..8f498f28ee11895d6a4940271bac95916b1652d2 100644
--- a/examples/basic_sprite.rs
+++ b/examples/basic_sprite.rs
@@ -1,6 +1,5 @@
 use bevy::{prelude::*, window::PrimaryWindow};
 use bevy_cosmic_edit::*;
-use util::{change_active_editor_sprite, deselect_editor_on_esc, print_editor_text};
 
 fn setup(
     mut commands: Commands,
diff --git a/examples/basic_ui.rs b/examples/basic_ui.rs
index 94eb841d7697f97b9589aa2f45bd7359f863794f..1c44bf012477487eac65e8d3cc0ed98fd1807244 100644
--- a/examples/basic_ui.rs
+++ b/examples/basic_ui.rs
@@ -1,6 +1,5 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::*;
-use util::{change_active_editor_ui, deselect_editor_on_esc, print_editor_text};
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
     let camera_bundle = Camera2dBundle {
diff --git a/examples/every_option.rs b/examples/every_option.rs
index b6e3ba90beb6c7e9ed13ca60c355aff9dde211ae..1e1d6aa4e1428ba2673cf560e7322cb6b12b0a23 100644
--- a/examples/every_option.rs
+++ b/examples/every_option.rs
@@ -1,6 +1,5 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::*;
-use util::{bevy_color_to_cosmic, change_active_editor_ui, deselect_editor_on_esc};
 
 #[derive(Resource)]
 struct TextChangeTimer(pub Timer);
diff --git a/examples/font_per_widget.rs b/examples/font_per_widget.rs
index 17282745303a8cd5cc5de4f2249dae72f73ab705..a599c689134616c4a823ae38f127465e54af8639 100644
--- a/examples/font_per_widget.rs
+++ b/examples/font_per_widget.rs
@@ -2,7 +2,6 @@
 
 use bevy::prelude::*;
 use bevy_cosmic_edit::*;
-use util::{bevy_color_to_cosmic, change_active_editor_ui, deselect_editor_on_esc};
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
     commands.spawn(Camera2dBundle::default());
diff --git a/examples/image_background.rs b/examples/image_background.rs
index 3d9b404260569b5f6c9194f76e8fc3d786a2b30f..48152788956371873d2c1501b42c343e26ce9259 100644
--- a/examples/image_background.rs
+++ b/examples/image_background.rs
@@ -1,6 +1,5 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::*;
-use util::{bevy_color_to_cosmic, change_active_editor_ui, deselect_editor_on_esc};
 
 fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
     commands.spawn(Camera2dBundle::default());
diff --git a/examples/multiple_sprites.rs b/examples/multiple_sprites.rs
index e8623fcaf16552907c23699ac2a987aa010fd2c3..e5cdb5098ff9937382ec04625e808f01f10c0eb3 100644
--- a/examples/multiple_sprites.rs
+++ b/examples/multiple_sprites.rs
@@ -1,6 +1,5 @@
 use bevy::{prelude::*, window::PrimaryWindow};
 use bevy_cosmic_edit::*;
-use util::{bevy_color_to_cosmic, change_active_editor_sprite};
 
 fn setup(
     mut commands: Commands,
diff --git a/examples/password.rs b/examples/password.rs
index 0539f39557b5f438a56a570ade71930164928b4f..4314c464ae098b9d823f36738c6f139f95b5a2a8 100644
--- a/examples/password.rs
+++ b/examples/password.rs
@@ -1,6 +1,5 @@
 use bevy::prelude::*;
-use bevy_cosmic_edit::{password::Password, placeholder::Placeholder, *};
-use util::{change_active_editor_sprite, deselect_editor_on_esc, print_editor_text};
+use bevy_cosmic_edit::*;
 
 fn setup(mut commands: Commands) {
     commands.spawn(Camera2dBundle::default());
@@ -40,7 +39,7 @@ fn main() {
             (
                 change_active_editor_sprite,
                 deselect_editor_on_esc,
-                print_editor_text.after(KbInput),
+                print_editor_text.after(InputSet),
             ),
         )
         .run();
diff --git a/examples/placeholder.rs b/examples/placeholder.rs
index 82d459515548428c7a89a781ad60f275a872f56b..64192ad6f92d693aaf3747d8856b9d1924a68816 100644
--- a/examples/placeholder.rs
+++ b/examples/placeholder.rs
@@ -1,8 +1,5 @@
 use bevy::prelude::*;
-use bevy_cosmic_edit::{placeholder::Placeholder, *};
-use util::{
-    bevy_color_to_cosmic, change_active_editor_ui, deselect_editor_on_esc, print_editor_text,
-};
+use bevy_cosmic_edit::*;
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
     let camera_bundle = Camera2dBundle {
diff --git a/examples/readonly.rs b/examples/readonly.rs
index 0a76bd89f69ca32326a3256605b251a42ac6d92f..b339fa9c6b8bd76561a00d5803eca2587ce1472d 100644
--- a/examples/readonly.rs
+++ b/examples/readonly.rs
@@ -1,6 +1,5 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::*;
-use util::{bevy_color_to_cosmic, change_active_editor_ui};
 
 fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) {
     commands.spawn(Camera2dBundle::default());
diff --git a/examples/sprite_and_ui_clickable.rs b/examples/sprite_and_ui_clickable.rs
index 0afb584c4e46d19013cf82c0fe977aeea85d1ed4..68d87e407c25fc7b6ec89f4d8ddd7ee5871da6be 100644
--- a/examples/sprite_and_ui_clickable.rs
+++ b/examples/sprite_and_ui_clickable.rs
@@ -1,9 +1,5 @@
 use bevy::prelude::*;
 use bevy_cosmic_edit::*;
-use util::{
-    bevy_color_to_cosmic, change_active_editor_sprite, change_active_editor_ui,
-    deselect_editor_on_esc,
-};
 
 fn setup(mut commands: Commands) {
     commands.spawn(Camera2dBundle::default());
diff --git a/src/buffer.rs b/src/buffer.rs
index 1244e35b633c7ac39d5acee4ab042c7f576e2d05..422c6050d047575da35068474728f86916350c7a 100644
--- a/src/buffer.rs
+++ b/src/buffer.rs
@@ -1,6 +1,27 @@
 use crate::*;
 use bevy::{prelude::*, window::PrimaryWindow};
 
+#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct BufferSet;
+
+pub struct BufferPlugin;
+
+impl Plugin for BufferPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(
+            First,
+            (
+                add_font_system,
+                set_initial_scale,
+                set_redraw,
+                set_editor_redraw,
+                swap_target_handle,
+            )
+                .chain(),
+        );
+    }
+}
+
 pub trait BufferExtras {
     fn get_text(&self) -> String;
 }
diff --git a/src/cosmic_edit.rs b/src/cosmic_edit.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ab13543341355ba1e778e7277f7509e68be6a6d1
--- /dev/null
+++ b/src/cosmic_edit.rs
@@ -0,0 +1,133 @@
+use crate::*;
+use bevy::prelude::*;
+
+#[derive(Clone, Component, PartialEq, Default)]
+pub enum CosmicMode {
+    InfiniteLine,
+    #[default]
+    Wrap,
+}
+
+/// Enum representing the position of the cosmic text.
+#[derive(Clone, Component)]
+pub enum CosmicTextPosition {
+    Center { padding: i32 },
+    TopLeft { padding: i32 },
+    Left { padding: i32 },
+}
+
+impl Default for CosmicTextPosition {
+    fn default() -> Self {
+        CosmicTextPosition::Center { padding: 5 }
+    }
+}
+
+#[derive(Component)]
+pub struct ReadOnly; // tag component
+
+#[derive(Component, Debug, Default)]
+pub struct XOffset {
+    pub left: f32,
+    pub width: f32,
+}
+
+#[derive(Component, Deref, DerefMut)]
+pub struct DefaultAttrs(pub AttrsOwned);
+
+impl Default for DefaultAttrs {
+    fn default() -> Self {
+        DefaultAttrs(AttrsOwned::new(Attrs::new()))
+    }
+}
+
+#[derive(Component, Default)]
+pub struct CosmicBackground(pub Option<Handle<Image>>);
+
+#[derive(Component, Default, Deref)]
+pub struct FillColor(pub Color);
+
+#[derive(Component, Default, Deref)]
+pub struct CursorColor(pub Color);
+
+#[derive(Component, Default, Deref)]
+pub struct SelectionColor(pub Color);
+
+#[derive(Component, Default)]
+pub struct CosmicMaxLines(pub usize);
+
+#[derive(Component, Default)]
+pub struct CosmicMaxChars(pub usize);
+
+#[derive(Component)]
+pub struct CosmicSource(pub Entity);
+
+#[derive(Bundle)]
+pub struct CosmicEditBundle {
+    // cosmic bits
+    pub buffer: CosmicBuffer,
+    // render bits
+    pub fill_color: FillColor,
+    pub cursor_color: CursorColor,
+    pub selection_color: SelectionColor,
+    pub default_attrs: DefaultAttrs,
+    pub background_image: CosmicBackground,
+    pub sprite_bundle: SpriteBundle,
+    // restriction bits
+    pub max_lines: CosmicMaxLines,
+    pub max_chars: CosmicMaxChars,
+    // layout bits
+    pub x_offset: XOffset,
+    pub mode: CosmicMode,
+    pub text_position: CosmicTextPosition,
+    pub padding: CosmicPadding,
+    pub widget_size: CosmicWidgetSize,
+}
+
+impl Default for CosmicEditBundle {
+    fn default() -> Self {
+        CosmicEditBundle {
+            buffer: Default::default(),
+            fill_color: Default::default(),
+            cursor_color: CursorColor(Color::BLACK),
+            selection_color: SelectionColor(Color::GRAY),
+            text_position: Default::default(),
+            default_attrs: Default::default(),
+            background_image: Default::default(),
+            max_lines: Default::default(),
+            max_chars: Default::default(),
+            mode: Default::default(),
+            sprite_bundle: SpriteBundle {
+                sprite: Sprite {
+                    custom_size: Some(Vec2::ONE * 128.0),
+                    ..default()
+                },
+                visibility: Visibility::Hidden,
+                ..default()
+            },
+            x_offset: Default::default(),
+            padding: Default::default(),
+            widget_size: Default::default(),
+        }
+    }
+}
+
+#[derive(Resource, Deref, DerefMut)]
+pub struct CosmicFontSystem(pub FontSystem);
+
+#[derive(Component, Deref, DerefMut)]
+pub struct CosmicEditor {
+    #[deref]
+    pub editor: Editor<'static>,
+    pub cursor_visible: bool,
+    pub cursor_timer: Timer,
+}
+
+impl CosmicEditor {
+    pub fn new(editor: Editor<'static>) -> Self {
+        Self {
+            editor,
+            cursor_visible: true,
+            cursor_timer: Timer::new(Duration::from_millis(530), TimerMode::Repeating),
+        }
+    }
+}
diff --git a/src/cursor.rs b/src/cursor.rs
index a044bcfb7d63a78b2c415dbc684e19f0ab9f42ff..e14720948a932036f3b4c9a720b50386070e89e0 100644
--- a/src/cursor.rs
+++ b/src/cursor.rs
@@ -1,9 +1,30 @@
+use crate::*;
 use bevy::{input::mouse::MouseMotion, prelude::*, window::PrimaryWindow};
 
-use crate::{CosmicBuffer, CosmicSource, CosmicTextChanged};
+#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct CursorSet;
 
-#[cfg(feature = "multicam")]
-use crate::CosmicPrimaryCamera;
+pub struct CursorPlugin {
+    pub change_cursor: CursorConfig,
+}
+
+impl Plugin for CursorPlugin {
+    fn build(&self, app: &mut App) {
+        match self.change_cursor {
+            CursorConfig::Default => {
+                app.add_systems(Update, (hover_sprites, hover_ui, change_cursor))
+                    .add_event::<TextHoverIn>()
+                    .add_event::<TextHoverOut>();
+            }
+            CursorConfig::Events => {
+                app.add_systems(Update, (hover_sprites, hover_ui))
+                    .add_event::<TextHoverIn>()
+                    .add_event::<TextHoverOut>();
+            }
+            CursorConfig::None => {}
+        }
+    }
+}
 
 /// For use with custom cursor control; Event is emitted when cursor enters a text widget
 #[derive(Event)]
diff --git a/src/events.rs b/src/events.rs
new file mode 100644
index 0000000000000000000000000000000000000000..57aa2008ee256b13ccd5312dd1f337b206dfe296
--- /dev/null
+++ b/src/events.rs
@@ -0,0 +1,14 @@
+// File for all events, meant for easy documentation
+
+use bevy::prelude::*;
+
+pub struct EventsPlugin;
+
+impl Plugin for EventsPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_event::<CosmicTextChanged>();
+    }
+}
+
+#[derive(Event, Debug)]
+pub struct CosmicTextChanged(pub (Entity, String));
diff --git a/src/focus.rs b/src/focus.rs
index 6520f0dd77491b2d01848d6c85b3869149067f99..4f8d5f85347070a6a49b352f7cb984d33c84eb1e 100644
--- a/src/focus.rs
+++ b/src/focus.rs
@@ -1,7 +1,23 @@
+use crate::*;
 use bevy::prelude::*;
 use cosmic_text::{Edit, Editor};
 
-use crate::{CosmicBuffer, CosmicEditor};
+#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct FocusSet;
+
+pub struct FocusPlugin;
+
+impl Plugin for FocusPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(
+            PostUpdate,
+            (drop_editor_unfocused, add_editor_to_focused)
+                .chain()
+                .in_set(FocusSet),
+        )
+        .init_resource::<FocusedWidget>();
+    }
+}
 
 /// Resource struct that keeps track of the currently active editor entity.
 #[derive(Resource, Default, Deref, DerefMut)]
diff --git a/src/input.rs b/src/input.rs
index be89f1fe277b9f8583e8b88bb60838d7a75c2d51..6e7ad8f7307c24b65190af510f2b97e74722be9a 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,8 +1,6 @@
 #![allow(clippy::too_many_arguments, clippy::type_complexity)]
 
-#[cfg(target_arch = "wasm32")]
-use bevy::tasks::AsyncComputeTaskPool;
-
+use crate::*;
 use bevy::{
     input::mouse::{MouseMotion, MouseScrollUnit, MouseWheel},
     prelude::*,
@@ -13,18 +11,31 @@ use cosmic_text::{Action, Cursor, Edit, Motion, Selection};
 #[cfg(target_arch = "wasm32")]
 use crate::DefaultAttrs;
 #[cfg(target_arch = "wasm32")]
+use bevy::tasks::AsyncComputeTaskPool;
+#[cfg(target_arch = "wasm32")]
 use js_sys::Promise;
 #[cfg(target_arch = "wasm32")]
 use wasm_bindgen::prelude::*;
 #[cfg(target_arch = "wasm32")]
 use wasm_bindgen_futures::JsFuture;
 
-use crate::{
-    buffer::{get_x_offset_center, get_y_offset_center, BufferExtras},
-    get_node_cursor_pos, CosmicBuffer, CosmicEditor, CosmicFontSystem, CosmicMaxChars,
-    CosmicMaxLines, CosmicSource, CosmicTextChanged, CosmicTextPosition, FocusedWidget, ReadOnly,
-    XOffset,
-};
+#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct InputSet;
+
+pub struct InputPlugin;
+
+impl Plugin for InputPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(PreUpdate, input_mouse.in_set(InputSet))
+            .add_systems(
+                Update,
+                (kb_move_cursor, kb_input_text, kb_clipboard)
+                    .chain()
+                    .in_set(InputSet),
+            )
+            .insert_resource(ClickTimer(Timer::from_seconds(0.5, TimerMode::Once)));
+    }
+}
 
 #[derive(Resource)]
 pub struct ClickTimer(pub Timer);
diff --git a/src/lib.rs b/src/lib.rs
index 1cc13e040cc458929113ac5fb4116b6ef813a7af..49348cea4d473c48be945fee1d42e6e88045c4cb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,184 +1,82 @@
 #![allow(clippy::type_complexity)]
 
 mod buffer;
+mod cosmic_edit;
 mod cursor;
-pub mod focus;
+mod events;
+mod focus;
 mod input;
-mod layout;
+mod password;
+mod placeholder;
 mod render;
-
-mod plugins;
-pub use plugins::*;
+mod util;
+mod widget;
 
 use std::{path::PathBuf, time::Duration};
 
 use bevy::{prelude::*, transform::TransformSystem};
 
-use buffer::{
-    add_font_system, set_editor_redraw, set_initial_scale, set_redraw, swap_target_handle,
-};
-pub use buffer::{get_x_offset_center, get_y_offset_center, CosmicBuffer};
+pub use buffer::*;
+pub use cosmic_edit::*;
 pub use cosmic_text::{
-    Action, Attrs, AttrsOwned, Color as CosmicColor, Cursor, Edit, Family, Metrics, Shaping,
-    Style as FontStyle, Weight as FontWeight,
+    Action, Attrs, AttrsOwned, Buffer, Color as CosmicColor, Cursor, Edit, Editor, Family,
+    FontSystem, Metrics, Shaping, Style as FontStyle, Weight as FontWeight,
 };
-use cosmic_text::{Buffer, Editor, FontSystem, SwashCache};
-use cursor::{change_cursor, hover_sprites, hover_ui};
-pub use cursor::{TextHoverIn, TextHoverOut};
+pub use cursor::*;
+pub use events::*;
 pub use focus::*;
-use input::{input_mouse, kb_clipboard, kb_input_text, kb_move_cursor, ClickTimer};
-#[cfg(target_arch = "wasm32")]
-use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel};
-use layout::{
-    new_image_from_default, reshape, set_buffer_size, set_padding, set_sprite_size_from_ui,
-    set_widget_size, set_x_offset, CosmicPadding, CosmicWidgetSize,
-};
-use render::{blink_cursor, render_texture, SwashCacheState};
-
-#[cfg(feature = "multicam")]
-#[derive(Component)]
-pub struct CosmicPrimaryCamera;
-
-#[derive(Clone, Component, PartialEq, Default)]
-pub enum CosmicMode {
-    InfiniteLine,
-    #[default]
-    Wrap,
-}
+pub use input::*;
+pub use password::*;
+pub use placeholder::*;
+pub use render::*;
+pub use util::*;
+pub use widget::*;
 
+/// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
 #[derive(Default)]
-pub enum CursorConfig {
-    #[default]
-    Default,
-    Events,
-    None,
-}
-
-/// Enum representing the position of the cosmic text.
-#[derive(Clone, Component)]
-pub enum CosmicTextPosition {
-    Center { padding: i32 },
-    TopLeft { padding: i32 },
-    Left { padding: i32 },
-}
-
-impl Default for CosmicTextPosition {
-    fn default() -> Self {
-        CosmicTextPosition::Center { padding: 5 }
-    }
+pub struct CosmicEditPlugin {
+    pub font_config: CosmicFontConfig,
+    pub change_cursor: CursorConfig,
 }
 
-#[derive(Event, Debug)]
-pub struct CosmicTextChanged(pub (Entity, String));
-
-#[derive(Resource, Deref, DerefMut)]
-pub struct CosmicFontSystem(pub FontSystem);
-
-#[derive(Component)]
-pub struct ReadOnly; // tag component
-
-#[derive(Component, Debug, Default)]
-pub struct XOffset {
-    pub left: f32,
-    pub width: f32,
-}
+impl Plugin for CosmicEditPlugin {
+    fn build(&self, app: &mut App) {
+        let font_system = create_cosmic_font_system(self.font_config.clone());
 
-#[derive(Component, Deref, DerefMut)]
-pub struct CosmicEditor {
-    #[deref]
-    pub editor: Editor<'static>,
-    pub cursor_visible: bool,
-    pub cursor_timer: Timer,
-}
+        app.add_plugins((
+            BufferPlugin,
+            RenderPlugin,
+            WidgetPlugin,
+            InputPlugin,
+            FocusPlugin,
+            CursorPlugin {
+                change_cursor: self.change_cursor.clone(),
+            },
+            PlaceholderPlugin,
+            PasswordPlugin,
+            EventsPlugin,
+        ))
+        .insert_resource(CosmicFontSystem(font_system));
 
-impl CosmicEditor {
-    fn new(editor: Editor<'static>) -> Self {
-        Self {
-            editor,
-            cursor_visible: true,
-            cursor_timer: Timer::new(Duration::from_millis(530), TimerMode::Repeating),
+        #[cfg(target_arch = "wasm32")]
+        {
+            let (tx, rx) = crossbeam_channel::bounded::<WasmPaste>(1);
+            app.insert_resource(WasmPasteAsyncChannel { tx, rx })
+                .add_systems(Update, poll_wasm_paste);
         }
     }
 }
 
-#[derive(Component, Deref, DerefMut)]
-pub struct DefaultAttrs(pub AttrsOwned);
-
-impl Default for DefaultAttrs {
-    fn default() -> Self {
-        DefaultAttrs(AttrsOwned::new(Attrs::new()))
-    }
-}
-
-#[derive(Component, Default)]
-pub struct CosmicBackground(pub Option<Handle<Image>>);
-
-#[derive(Component, Default, Deref)]
-pub struct FillColor(pub Color);
-
-#[derive(Component, Default, Deref)]
-pub struct CursorColor(pub Color);
-
-#[derive(Component, Default, Deref)]
-pub struct SelectionColor(pub Color);
-
-#[derive(Component, Default)]
-pub struct CosmicMaxLines(pub usize);
-
-#[derive(Component, Default)]
-pub struct CosmicMaxChars(pub usize);
-
+#[cfg(feature = "multicam")]
 #[derive(Component)]
-pub struct CosmicSource(pub Entity);
-
-#[derive(Bundle)]
-pub struct CosmicEditBundle {
-    // cosmic bits
-    pub buffer: CosmicBuffer,
-    // render bits
-    pub fill_color: FillColor,
-    pub cursor_color: CursorColor,
-    pub selection_color: SelectionColor,
-    pub default_attrs: DefaultAttrs,
-    pub background_image: CosmicBackground,
-    pub sprite_bundle: SpriteBundle,
-    // restriction bits
-    pub max_lines: CosmicMaxLines,
-    pub max_chars: CosmicMaxChars,
-    // layout bits
-    pub x_offset: XOffset,
-    pub mode: CosmicMode,
-    pub text_position: CosmicTextPosition,
-    pub padding: CosmicPadding,
-    pub widget_size: CosmicWidgetSize,
-}
+pub struct CosmicPrimaryCamera;
 
-impl Default for CosmicEditBundle {
-    fn default() -> Self {
-        CosmicEditBundle {
-            buffer: Default::default(),
-            fill_color: Default::default(),
-            cursor_color: CursorColor(Color::BLACK),
-            selection_color: SelectionColor(Color::GRAY),
-            text_position: Default::default(),
-            default_attrs: Default::default(),
-            background_image: Default::default(),
-            max_lines: Default::default(),
-            max_chars: Default::default(),
-            mode: Default::default(),
-            sprite_bundle: SpriteBundle {
-                sprite: Sprite {
-                    custom_size: Some(Vec2::ONE * 128.0),
-                    ..default()
-                },
-                visibility: Visibility::Hidden,
-                ..default()
-            },
-            x_offset: Default::default(),
-            padding: Default::default(),
-            widget_size: Default::default(),
-        }
-    }
+#[derive(Default, Clone)]
+pub enum CursorConfig {
+    #[default]
+    Default,
+    Events,
+    None,
 }
 
 /// Resource struct that holds configuration options for cosmic fonts.
@@ -200,105 +98,6 @@ impl Default for CosmicFontConfig {
     }
 }
 
-#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct KbInput;
-
-#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Render;
-
-/// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
-#[derive(Default)]
-pub struct CosmicEditPlugin {
-    pub font_config: CosmicFontConfig,
-    pub change_cursor: CursorConfig,
-}
-
-impl Plugin for CosmicEditPlugin {
-    fn build(&self, app: &mut App) {
-        let font_system = create_cosmic_font_system(self.font_config.clone());
-
-        let layout_systems = (
-            (new_image_from_default, set_sprite_size_from_ui),
-            set_widget_size,
-            set_buffer_size,
-            set_padding,
-            set_x_offset,
-        )
-            .chain();
-
-        app.add_systems(
-            First,
-            (
-                add_font_system,
-                set_initial_scale,
-                set_redraw,
-                set_editor_redraw,
-                swap_target_handle,
-            )
-                .chain(),
-        )
-        .add_systems(PreUpdate, (input_mouse,).chain())
-        .add_systems(
-            Update,
-            (
-                (kb_move_cursor, kb_input_text, kb_clipboard, reshape)
-                    .chain()
-                    .in_set(KbInput),
-                blink_cursor,
-            ),
-        )
-        .add_systems(
-            PostUpdate,
-            (
-                layout_systems,
-                drop_editor_unfocused,
-                add_editor_to_focused,
-                render_texture,
-            )
-                .chain()
-                .in_set(Render)
-                .after(TransformSystem::TransformPropagate),
-        )
-        .init_resource::<FocusedWidget>()
-        .insert_resource(SwashCacheState {
-            swash_cache: SwashCache::new(),
-        })
-        .insert_resource(CosmicFontSystem(font_system))
-        .insert_resource(ClickTimer(Timer::from_seconds(0.5, TimerMode::Once)))
-        .add_event::<CosmicTextChanged>();
-
-        match self.change_cursor {
-            CursorConfig::Default => {
-                app.add_systems(Update, (hover_sprites, hover_ui, change_cursor))
-                    .add_event::<TextHoverIn>()
-                    .add_event::<TextHoverOut>();
-            }
-            CursorConfig::Events => {
-                app.add_systems(Update, (hover_sprites, hover_ui))
-                    .add_event::<TextHoverIn>()
-                    .add_event::<TextHoverOut>();
-            }
-            CursorConfig::None => {}
-        }
-
-        #[cfg(target_arch = "wasm32")]
-        {
-            let (tx, rx) = crossbeam_channel::bounded::<WasmPaste>(1);
-            app.insert_resource(WasmPasteAsyncChannel { tx, rx })
-                .add_systems(Update, poll_wasm_paste);
-        }
-
-        add_feature_plugins(app);
-    }
-}
-
-fn add_feature_plugins(app: &mut App) -> &mut App {
-    app.add_plugins(plugins::placeholder::PlaceholderPlugin);
-    app.add_plugins(plugins::password::PasswordPlugin);
-
-    app
-}
-
 fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> FontSystem {
     let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
     let mut db = cosmic_text::fontdb::Database::new();
@@ -316,55 +115,6 @@ fn create_cosmic_font_system(cosmic_font_config: CosmicFontConfig) -> FontSystem
     cosmic_text::FontSystem::new_with_locale_and_db(locale, db)
 }
 
-pub fn get_node_cursor_pos(
-    window: &Window,
-    node_transform: &GlobalTransform,
-    size: (f32, f32),
-    is_ui_node: bool,
-    camera: &Camera,
-    camera_transform: &GlobalTransform,
-) -> Option<(f32, f32)> {
-    let (x_min, y_min, x_max, y_max) = (
-        node_transform.affine().translation.x - size.0 / 2.,
-        node_transform.affine().translation.y - size.1 / 2.,
-        node_transform.affine().translation.x + size.0 / 2.,
-        node_transform.affine().translation.y + size.1 / 2.,
-    );
-
-    window.cursor_position().and_then(|pos| {
-        if is_ui_node {
-            if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max {
-                Some((pos.x - x_min, pos.y - y_min))
-            } else {
-                None
-            }
-        } else {
-            camera
-                .viewport_to_world_2d(camera_transform, pos)
-                .and_then(|pos| {
-                    if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max {
-                        Some((pos.x - x_min, y_max - pos.y))
-                    } else {
-                        None
-                    }
-                })
-        }
-    })
-}
-
-#[cfg(target_arch = "wasm32")]
-pub fn get_timestamp() -> f64 {
-    js_sys::Date::now()
-}
-
-#[cfg(not(target_arch = "wasm32"))]
-pub fn get_timestamp() -> f64 {
-    use std::time::SystemTime;
-    use std::time::UNIX_EPOCH;
-    let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-    duration.as_millis() as f64
-}
-
 #[cfg(test)]
 mod tests {
     use crate::*;
diff --git a/src/plugins/password/mod.rs b/src/password.rs
similarity index 94%
rename from src/plugins/password/mod.rs
rename to src/password.rs
index a36109c8e5bfc303172a59f6a326a95ce4863983..f84819e1504de9eba27eb24a7655cc978a59c124 100644
--- a/src/plugins/password/mod.rs
+++ b/src/password.rs
@@ -1,13 +1,8 @@
-use crate::{buffer::BufferExtras, placeholder::Placeholder};
+use crate::*;
 use bevy::prelude::*;
 use cosmic_text::{Cursor, Edit, Selection, Shaping};
 use unicode_segmentation::UnicodeSegmentation;
 
-use crate::{
-    input::{input_mouse, kb_input_text, kb_move_cursor},
-    CosmicBuffer, CosmicEditor, CosmicFontSystem, DefaultAttrs, Render,
-};
-
 pub struct PasswordPlugin;
 
 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
@@ -25,8 +20,8 @@ impl Plugin for PasswordPlugin {
             .add_systems(
                 PostUpdate,
                 (
-                    hide_password_text.before(Render).in_set(PasswordSet),
-                    restore_password_text.after(Render),
+                    hide_password_text.before(RenderSet).in_set(PasswordSet),
+                    restore_password_text.after(RenderSet),
                 ),
             );
     }
diff --git a/src/plugins/placeholder/mod.rs b/src/placeholder.rs
similarity index 96%
rename from src/plugins/placeholder/mod.rs
rename to src/placeholder.rs
index a1807795af188ba9753dd358b753d691880e791c..d956760bb5ac783dd360b0fe899d804901dc7ad8 100644
--- a/src/plugins/placeholder/mod.rs
+++ b/src/placeholder.rs
@@ -1,11 +1,7 @@
-use crate::{buffer::BufferExtras, Render};
+use crate::*;
 use bevy::prelude::*;
 use cosmic_text::{Attrs, Edit};
 
-use crate::{
-    CosmicBuffer, CosmicEditor, CosmicFontSystem, CosmicTextChanged, DefaultAttrs, KbInput,
-};
-
 #[derive(Component)]
 pub struct Placeholder {
     pub text: &'static str,
@@ -40,8 +36,8 @@ impl Plugin for PlaceholderPlugin {
                 remove_placeholder_on_input,
             )
                 .chain()
-                .after(KbInput)
-                .before(Render),
+                .after(InputSet)
+                .before(RenderSet),
         );
     }
 }
diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs
deleted file mode 100644
index 08a7f609753d4e9fc21c3d47d18b1c76649df80d..0000000000000000000000000000000000000000
--- a/src/plugins/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod password;
-pub mod placeholder;
diff --git a/src/render.rs b/src/render.rs
index 78e376ed4d9c069ce0bbc773944d780dd49b0bfc..ef9d04d729f65caa90e34c754ae3a8637358200f 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -1,12 +1,25 @@
+use crate::*;
 use bevy::{prelude::*, render::render_resource::Extent3d};
 use cosmic_text::{Color, Edit, SwashCache};
 use image::{imageops::FilterType, GenericImageView};
 
-use crate::{
-    layout::{CosmicPadding, CosmicWidgetSize},
-    CosmicBackground, CosmicBuffer, CosmicEditor, CosmicFontSystem, CosmicTextPosition,
-    CursorColor, DefaultAttrs, FillColor, ReadOnly, SelectionColor, XOffset,
-};
+#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct RenderSet;
+
+pub struct RenderPlugin;
+
+impl Plugin for RenderPlugin {
+    fn build(&self, app: &mut App) {
+        app.insert_resource(SwashCacheState {
+            swash_cache: SwashCache::new(),
+        })
+        .add_systems(Update, blink_cursor)
+        .add_systems(
+            PostUpdate,
+            (render_texture,).in_set(RenderSet).after(WidgetSet),
+        );
+    }
+}
 
 #[derive(Resource)]
 pub(crate) struct SwashCacheState {
@@ -63,7 +76,7 @@ fn draw_pixel(buffer: &mut [u8], width: i32, height: i32, x: i32, y: i32, color:
     buffer[offset + 3] = (out.a() * 255.0) as u8;
 }
 
-pub(crate) fn render_texture(
+fn render_texture(
     mut query: Query<(
         Option<&mut CosmicEditor>,
         &mut CosmicBuffer,
diff --git a/util/src/lib.rs b/src/util.rs
similarity index 65%
rename from util/src/lib.rs
rename to src/util.rs
index 405152cd2ac7aaa293d14eb85d459cc88788238a..6175e52a7b5cdf74b3bce6e5af9454af830bbde6 100644
--- a/util/src/lib.rs
+++ b/src/util.rs
@@ -1,6 +1,6 @@
 // Common functions for examples
+use crate::*;
 use bevy::{prelude::*, window::PrimaryWindow};
-use bevy_cosmic_edit::*;
 
 pub fn deselect_editor_on_esc(i: Res<ButtonInput<KeyCode>>, mut focus: ResMut<FocusedWidget>) {
     if i.just_pressed(KeyCode::Escape) {
@@ -8,6 +8,42 @@ pub fn deselect_editor_on_esc(i: Res<ButtonInput<KeyCode>>, mut focus: ResMut<Fo
     }
 }
 
+pub fn get_node_cursor_pos(
+    window: &Window,
+    node_transform: &GlobalTransform,
+    size: (f32, f32),
+    is_ui_node: bool,
+    camera: &Camera,
+    camera_transform: &GlobalTransform,
+) -> Option<(f32, f32)> {
+    let (x_min, y_min, x_max, y_max) = (
+        node_transform.affine().translation.x - size.0 / 2.,
+        node_transform.affine().translation.y - size.1 / 2.,
+        node_transform.affine().translation.x + size.0 / 2.,
+        node_transform.affine().translation.y + size.1 / 2.,
+    );
+
+    window.cursor_position().and_then(|pos| {
+        if is_ui_node {
+            if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max {
+                Some((pos.x - x_min, pos.y - y_min))
+            } else {
+                None
+            }
+        } else {
+            camera
+                .viewport_to_world_2d(camera_transform, pos)
+                .and_then(|pos| {
+                    if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max {
+                        Some((pos.x - x_min, y_max - pos.y))
+                    } else {
+                        None
+                    }
+                })
+        }
+    })
+}
+
 pub fn change_active_editor_sprite(
     mut commands: Commands,
     windows: Query<&Window, With<PrimaryWindow>>,
@@ -82,3 +118,16 @@ pub fn bevy_color_to_cosmic(color: bevy::prelude::Color) -> CosmicColor {
         (color.a() * 255.) as u8,
     )
 }
+
+#[cfg(target_arch = "wasm32")]
+pub fn get_timestamp() -> f64 {
+    js_sys::Date::now()
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+pub fn get_timestamp() -> f64 {
+    use std::time::SystemTime;
+    use std::time::UNIX_EPOCH;
+    let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+    duration.as_millis() as f64
+}
diff --git a/src/layout.rs b/src/widget.rs
similarity index 88%
rename from src/layout.rs
rename to src/widget.rs
index f5d0bce834a9809cf2fbd15f35922136e5655fd3..e79ac22f3ff7a01b9bc923592dfcf25899b1b400 100644
--- a/src/layout.rs
+++ b/src/widget.rs
@@ -2,7 +2,29 @@ use crate::*;
 use bevy::{prelude::*, window::PrimaryWindow};
 use cosmic_text::Affinity;
 
-use self::buffer::{get_x_offset_center, get_y_offset_center};
+#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct WidgetSet;
+
+pub struct WidgetPlugin;
+
+impl Plugin for WidgetPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(Update, reshape.in_set(WidgetSet).after(InputSet))
+            .add_systems(
+                PostUpdate,
+                (
+                    (new_image_from_default, set_sprite_size_from_ui),
+                    set_widget_size,
+                    set_buffer_size,
+                    set_padding,
+                    set_x_offset,
+                )
+                    .chain()
+                    .in_set(WidgetSet)
+                    .after(TransformSystem::TransformPropagate),
+            );
+    }
+}
 
 #[derive(Component, Default, Deref, DerefMut, Debug)]
 pub struct CosmicPadding(pub Vec2);
diff --git a/util/Cargo.toml b/util/Cargo.toml
deleted file mode 100644
index 51de83365dfe6af657fa3258f3c808e5a1af1b7c..0000000000000000000000000000000000000000
--- a/util/Cargo.toml
+++ /dev/null
@@ -1,22 +0,0 @@
-[package]
-name = "util"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-bevy = { version = "0.13", default-features = false, features = [
-  "bevy_asset",
-  "bevy_core_pipeline",
-  "bevy_render",
-  "bevy_scene",
-  "bevy_sprite",
-  "bevy_text",
-  "bevy_ui",
-  "bevy_winit",
-  "png",
-  "x11",
-  "webgpu",
-] }
-bevy_cosmic_edit = { version = "*", path = "../" }