diff --git a/examples/bevy_api_testing.rs b/examples/bevy_api_testing.rs
index 6f1e9ae672cc5af9d5352daee3172c8301bced69..f51ff48c77107186d6925db5da1fa5e6cf9bc194 100644
--- a/examples/bevy_api_testing.rs
+++ b/examples/bevy_api_testing.rs
@@ -18,6 +18,7 @@ fn setup(mut commands: Commands) {
             Attrs::new().color(bevy_color_to_cosmic(Color::GREEN)),
         )),
         max_lines: CosmicMaxLines(1),
+        placeholder_setter: PlaceholderText(CosmicText::OneStyle("Place held :)".into())),
         ..default()
     });
 
diff --git a/examples/every_option.rs b/examples/every_option.rs
index 431906892ac73b7248cc820fdcf0bf830faa3c03..c2330f32a43203c6436fb5c6316965fb489339dd 100644
--- a/examples/every_option.rs
+++ b/examples/every_option.rs
@@ -47,6 +47,10 @@ 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,
+            placeholder_setter: PlaceholderText(CosmicText::OneStyle("Placeholder".into())),
+            placeholder_attrs: PlaceholderAttrs(AttrsOwned::new(
+                Attrs::new().color(CosmicColor::rgb(88, 88, 88)),
+            )),
         })
         .id();
 
diff --git a/examples/text_input.rs b/examples/text_input.rs
index 75d5ee3dc874e5f0c12e68b260fed7ab5e75463c..f8e6773072243914595ff35be3b4a0f00e33527c 100644
--- a/examples/text_input.rs
+++ b/examples/text_input.rs
@@ -4,6 +4,8 @@ use bevy_cosmic_edit::*;
 fn create_editable_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())));
+    let placeholder_attrs =
+        AttrsOwned::new(Attrs::new().color(bevy_color_to_cosmic(Color::hex("#e6e6e6").unwrap())));
     commands
         .spawn(CosmicEditUiBundle {
             border_color: Color::hex("#ededed").unwrap().into(),
@@ -25,6 +27,8 @@ fn create_editable_widget(commands: &mut Commands, scale_factor: f32, text: Stri
             text_setter: CosmicText::OneStyle(text),
             text_position: CosmicTextPosition::Left { padding: 20 },
             mode: CosmicMode::InfiniteLine,
+            placeholder_setter: PlaceholderText(CosmicText::OneStyle("Type something...".into())),
+            placeholder_attrs: PlaceholderAttrs(placeholder_attrs.clone()),
             ..default()
         })
         .id()
diff --git a/src/lib.rs b/src/lib.rs
index d86b6bbdeb40bafd5d01302edad5374ebc054285..cb9113f2ba8b10ad3c6ad1a44ef13b3f5b8653f0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -190,6 +190,20 @@ pub struct CosmicMaxChars(pub usize);
 #[derive(Component, Default)]
 pub struct FillColor(pub Color);
 
+#[derive(Component)]
+pub struct Placeholder(pub CosmicEditor);
+
+#[derive(Component, Default)]
+pub struct PlaceholderText(pub CosmicText);
+
+#[derive(Component)]
+pub struct PlaceholderAttrs(pub AttrsOwned);
+
+impl Default for PlaceholderAttrs {
+    fn default() -> Self {
+        Self(AttrsOwned::new(Attrs::new()))
+    }
+}
 #[derive(Bundle)]
 pub struct CosmicEditUiBundle {
     // Bevy UI bits
@@ -245,6 +259,9 @@ pub struct CosmicEditUiBundle {
     pub text_setter: CosmicText,
     /// Text input mode
     pub mode: CosmicMode,
+    /// Setting this will update the placeholder text
+    pub placeholder_setter: PlaceholderText,
+    pub placeholder_attrs: PlaceholderAttrs,
 }
 
 impl Default for CosmicEditUiBundle {
@@ -272,6 +289,8 @@ impl Default for CosmicEditUiBundle {
             text_setter: Default::default(),
             mode: Default::default(),
             background_color: BackgroundColor(Color::WHITE),
+            placeholder_setter: Default::default(),
+            placeholder_attrs: Default::default(),
         }
     }
 }
@@ -387,36 +406,43 @@ impl Plugin for CosmicEditPlugin {
     fn build(&self, app: &mut App) {
         let font_system = create_cosmic_font_system(self.font_config.clone());
 
-        app.add_systems(First, (cosmic_editor_builder, on_scale_factor_change))
-            .add_systems(PreUpdate, update_buffer_text)
-            .add_systems(
-                Update,
-                (
-                    input_kb,
-                    input_mouse,
-                    blink_cursor,
-                    freeze_cursor_blink,
-                    hide_inactive_or_readonly_cursor,
-                    clear_inactive_selection,
-                ),
-            )
-            .add_systems(
-                PostUpdate,
-                (cosmic_edit_redraw_buffer_ui, cosmic_edit_redraw_buffer)
-                    .after(TransformSystem::TransformPropagate),
-            )
-            .init_resource::<Focus>()
-            .insert_resource(CursorBlinkTimer(Timer::from_seconds(
-                0.53,
-                TimerMode::Repeating,
-            )))
-            .insert_resource(CursorVisibility(true))
-            .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>();
+        app.add_systems(
+            First,
+            (
+                cosmic_editor_builder,
+                placeholder_builder,
+                on_scale_factor_change,
+            ),
+        )
+        .add_systems(PreUpdate, (update_buffer_text, update_placeholder_text))
+        .add_systems(
+            Update,
+            (
+                input_kb,
+                input_mouse,
+                blink_cursor,
+                freeze_cursor_blink,
+                hide_inactive_or_readonly_cursor,
+                clear_inactive_selection,
+            ),
+        )
+        .add_systems(
+            PostUpdate,
+            (cosmic_edit_redraw_buffer_ui, cosmic_edit_redraw_buffer)
+                .after(TransformSystem::TransformPropagate),
+        )
+        .init_resource::<Focus>()
+        .insert_resource(CursorBlinkTimer(Timer::from_seconds(
+            0.53,
+            TimerMode::Repeating,
+        )))
+        .insert_resource(CursorVisibility(true))
+        .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 => {
@@ -479,6 +505,23 @@ fn cosmic_editor_builder(
     }
 }
 
+fn placeholder_builder(
+    mut added_editors: Query<(Entity, &CosmicMetrics), Added<PlaceholderText>>,
+    mut font_system: ResMut<CosmicFontSystem>,
+    mut commands: Commands,
+) {
+    for (entity, metrics) in added_editors.iter_mut() {
+        let buffer = Buffer::new(
+            &mut font_system.0,
+            Metrics::new(metrics.font_size, metrics.line_height).scale(metrics.scale_factor),
+        );
+
+        let editor = CosmicEditor(Editor::new(buffer));
+
+        commands.entity(entity).insert(Placeholder(editor));
+    }
+}
+
 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();
@@ -572,6 +615,21 @@ fn update_buffer_text(
     }
 }
 
+/// Updates editor buffer when text component changes
+fn update_placeholder_text(
+    mut editor_q: Query<
+        (&mut Placeholder, &mut PlaceholderText, &PlaceholderAttrs),
+        Changed<PlaceholderText>,
+    >,
+    mut font_system: ResMut<CosmicFontSystem>,
+) {
+    for (mut editor, text, attrs) in editor_q.iter_mut() {
+        editor
+            .0
+            .set_text(text.0.to_owned(), attrs.0.clone(), &mut font_system.0);
+    }
+}
+
 fn trim_text(text: CosmicText, max_chars: usize, max_lines: usize) -> CosmicText {
     if max_chars == 0 && max_lines == 0 {
         // no limits, no work to do
@@ -722,30 +780,41 @@ fn blink_cursor(
     time: Res<Time>,
     active_editor: ResMut<Focus>,
     mut cosmic_editor_q: Query<&mut CosmicEditor, Without<ReadOnly>>,
+    mut placeholder_editor_q: Query<&mut Placeholder, Without<ReadOnly>>,
 ) {
     if let Some(e) = active_editor.0 {
-        if let Ok(mut editor) = cosmic_editor_q.get_mut(e) {
-            timer.0.tick(time.delta());
-            if !timer.0.just_finished() && !active_editor.is_changed() {
-                return;
-            }
-            visibility.0 = !visibility.0;
+        timer.0.tick(time.delta());
+        if !timer.0.just_finished() && !active_editor.is_changed() {
+            return;
+        }
+        visibility.0 = !visibility.0;
 
-            // always start cursor visible on focus
-            if active_editor.is_changed() {
-                visibility.0 = true;
-                timer.0.set_elapsed(Duration::ZERO);
-            }
+        // always start cursor visible on focus
+        if active_editor.is_changed() {
+            visibility.0 = true;
+            timer.0.set_elapsed(Duration::ZERO);
+        }
 
-            let mut cursor = editor.0.cursor();
-            let new_color = if visibility.0 {
-                None
-            } else {
-                Some(cosmic_text::Color::rgba(0, 0, 0, 0))
-            };
+        let new_color = if visibility.0 {
+            None
+        } else {
+            Some(cosmic_text::Color::rgba(0, 0, 0, 0))
+        };
+
+        if let Ok(mut editor) = cosmic_editor_q.get_mut(e) {
+            let editor = &mut editor.0;
+            let mut cursor = editor.cursor();
             cursor.color = new_color;
-            editor.0.set_cursor(cursor);
-            editor.0.buffer_mut().set_redraw(true);
+            editor.set_cursor(cursor);
+            editor.buffer_mut().set_redraw(true);
+        }
+
+        if let Ok(mut placeholder) = placeholder_editor_q.get_mut(e) {
+            let placeholder = &mut placeholder.0 .0;
+            let mut cursor_p = placeholder.cursor();
+            cursor_p.color = new_color;
+            placeholder.set_cursor(cursor_p);
+            placeholder.buffer_mut().set_redraw(true);
         }
     }
 }
@@ -784,6 +853,7 @@ fn freeze_cursor_blink(
 
 fn hide_inactive_or_readonly_cursor(
     mut cosmic_editor_q_readonly: Query<&mut CosmicEditor, With<ReadOnly>>,
+    mut cosmic_editor_q_placeholder: Query<(Entity, &mut Placeholder, Option<&ReadOnly>)>,
     mut cosmic_editor_q_editable: Query<(Entity, &mut CosmicEditor), Without<ReadOnly>>,
     active_editor: Res<Focus>,
 ) {
@@ -798,6 +868,16 @@ fn hide_inactive_or_readonly_cursor(
         return;
     }
 
+    for (e, mut editor, readonly_opt) in &mut cosmic_editor_q_placeholder.iter_mut() {
+        if e != active_editor.0.unwrap() || readonly_opt.is_some() {
+            let editor = &mut editor.0;
+            let mut cursor = editor.0.cursor();
+            cursor.color = Some(cosmic_text::Color::rgba(0, 0, 0, 0));
+            editor.0.set_cursor(cursor);
+            editor.0.buffer_mut().set_redraw(true);
+        }
+    }
+
     for (e, mut editor) in &mut cosmic_editor_q_editable.iter_mut() {
         if e != active_editor.0.unwrap() {
             let mut cursor = editor.0.cursor();
@@ -829,6 +909,7 @@ fn cosmic_edit_redraw_buffer_ui(
     mut swash_cache_state: ResMut<SwashCacheState>,
     mut cosmic_edit_query: Query<(
         &mut CosmicEditor,
+        Option<&mut Placeholder>,
         &CosmicAttrs,
         &CosmicBackground,
         &FillColor,
@@ -846,6 +927,7 @@ fn cosmic_edit_redraw_buffer_ui(
 
     for (
         mut editor,
+        mut placeholder_opt,
         attrs,
         background_image,
         fill_color,
@@ -857,8 +939,20 @@ fn cosmic_edit_redraw_buffer_ui(
         mode,
     ) in &mut cosmic_edit_query.iter_mut()
     {
-        editor.0.shape_as_needed(&mut font_system.0);
-        if !editor.0.buffer().redraw() {
+        let editor = if editor.get_text().is_empty() && placeholder_opt.is_some() {
+            let placeholder = &mut placeholder_opt.as_mut().unwrap().0 .0;
+            let mut cursor = placeholder.cursor();
+            cursor.index = 0;
+            placeholder.set_cursor(cursor);
+            placeholder.buffer_mut().set_redraw(true);
+            *x_offset = XOffset(None);
+            placeholder
+        } else {
+            &mut editor.0
+        };
+
+        editor.shape_as_needed(&mut font_system.0);
+        if !editor.buffer().redraw() {
             continue;
         }
 
@@ -879,12 +973,11 @@ fn cosmic_edit_redraw_buffer_ui(
             CosmicMode::Wrap => (widget_width - padding_x, widget_height),
         };
         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.0.buffer());
+            let text_size = get_text_size(editor.buffer());
             let text_height = (text_size.1 + 30.) / primary_window.scale_factor() as f32;
             if text_height > height {
                 height = text_height.ceil();
@@ -897,7 +990,7 @@ fn cosmic_edit_redraw_buffer_ui(
             &mut x_offset,
             &mut images,
             &mut swash_cache_state,
-            &mut editor.0,
+            editor,
             attrs,
             background_image.0.clone(),
             fill_color.0,