diff --git a/bevy_kayak_ui/src/key.rs b/bevy_kayak_ui/src/key.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9da2ba4505ae5ab4b3c1ca16a52f7c33f28b2218
--- /dev/null
+++ b/bevy_kayak_ui/src/key.rs
@@ -0,0 +1,169 @@
+use kayak_core::KeyCode;
+
+pub fn convert_virtual_key_code(virtual_key_code: bevy::prelude::KeyCode) -> KeyCode {
+    match virtual_key_code {
+        bevy::prelude::KeyCode::Key1 => KeyCode::Key1,
+        bevy::prelude::KeyCode::Key2 => KeyCode::Key2,
+        bevy::prelude::KeyCode::Key3 => KeyCode::Key3,
+        bevy::prelude::KeyCode::Key4 => KeyCode::Key4,
+        bevy::prelude::KeyCode::Key5 => KeyCode::Key5,
+        bevy::prelude::KeyCode::Key6 => KeyCode::Key6,
+        bevy::prelude::KeyCode::Key7 => KeyCode::Key7,
+        bevy::prelude::KeyCode::Key8 => KeyCode::Key8,
+        bevy::prelude::KeyCode::Key9 => KeyCode::Key9,
+        bevy::prelude::KeyCode::Key0 => KeyCode::Key0,
+        bevy::prelude::KeyCode::A => KeyCode::A,
+        bevy::prelude::KeyCode::B => KeyCode::B,
+        bevy::prelude::KeyCode::C => KeyCode::C,
+        bevy::prelude::KeyCode::D => KeyCode::D,
+        bevy::prelude::KeyCode::E => KeyCode::E,
+        bevy::prelude::KeyCode::F => KeyCode::F,
+        bevy::prelude::KeyCode::G => KeyCode::G,
+        bevy::prelude::KeyCode::H => KeyCode::H,
+        bevy::prelude::KeyCode::I => KeyCode::I,
+        bevy::prelude::KeyCode::J => KeyCode::J,
+        bevy::prelude::KeyCode::K => KeyCode::K,
+        bevy::prelude::KeyCode::L => KeyCode::L,
+        bevy::prelude::KeyCode::M => KeyCode::M,
+        bevy::prelude::KeyCode::N => KeyCode::N,
+        bevy::prelude::KeyCode::O => KeyCode::O,
+        bevy::prelude::KeyCode::P => KeyCode::P,
+        bevy::prelude::KeyCode::Q => KeyCode::Q,
+        bevy::prelude::KeyCode::R => KeyCode::R,
+        bevy::prelude::KeyCode::S => KeyCode::S,
+        bevy::prelude::KeyCode::T => KeyCode::T,
+        bevy::prelude::KeyCode::U => KeyCode::U,
+        bevy::prelude::KeyCode::V => KeyCode::V,
+        bevy::prelude::KeyCode::W => KeyCode::W,
+        bevy::prelude::KeyCode::X => KeyCode::X,
+        bevy::prelude::KeyCode::Y => KeyCode::Y,
+        bevy::prelude::KeyCode::Z => KeyCode::Z,
+        bevy::prelude::KeyCode::Escape => KeyCode::Escape,
+        bevy::prelude::KeyCode::F1 => KeyCode::F1,
+        bevy::prelude::KeyCode::F2 => KeyCode::F2,
+        bevy::prelude::KeyCode::F3 => KeyCode::F3,
+        bevy::prelude::KeyCode::F4 => KeyCode::F4,
+        bevy::prelude::KeyCode::F5 => KeyCode::F5,
+        bevy::prelude::KeyCode::F6 => KeyCode::F6,
+        bevy::prelude::KeyCode::F7 => KeyCode::F7,
+        bevy::prelude::KeyCode::F8 => KeyCode::F8,
+        bevy::prelude::KeyCode::F9 => KeyCode::F9,
+        bevy::prelude::KeyCode::F10 => KeyCode::F10,
+        bevy::prelude::KeyCode::F11 => KeyCode::F11,
+        bevy::prelude::KeyCode::F12 => KeyCode::F12,
+        bevy::prelude::KeyCode::F13 => KeyCode::F13,
+        bevy::prelude::KeyCode::F14 => KeyCode::F14,
+        bevy::prelude::KeyCode::F15 => KeyCode::F15,
+        bevy::prelude::KeyCode::F16 => KeyCode::F16,
+        bevy::prelude::KeyCode::F17 => KeyCode::F17,
+        bevy::prelude::KeyCode::F18 => KeyCode::F18,
+        bevy::prelude::KeyCode::F19 => KeyCode::F19,
+        bevy::prelude::KeyCode::F20 => KeyCode::F20,
+        bevy::prelude::KeyCode::F21 => KeyCode::F21,
+        bevy::prelude::KeyCode::F22 => KeyCode::F22,
+        bevy::prelude::KeyCode::F23 => KeyCode::F23,
+        bevy::prelude::KeyCode::F24 => KeyCode::F24,
+        bevy::prelude::KeyCode::Snapshot => KeyCode::Snapshot,
+        bevy::prelude::KeyCode::Scroll => KeyCode::Scroll,
+        bevy::prelude::KeyCode::Pause => KeyCode::Pause,
+        bevy::prelude::KeyCode::Insert => KeyCode::Insert,
+        bevy::prelude::KeyCode::Home => KeyCode::Home,
+        bevy::prelude::KeyCode::Delete => KeyCode::Delete,
+        bevy::prelude::KeyCode::End => KeyCode::End,
+        bevy::prelude::KeyCode::PageDown => KeyCode::PageDown,
+        bevy::prelude::KeyCode::PageUp => KeyCode::PageUp,
+        bevy::prelude::KeyCode::Left => KeyCode::Left,
+        bevy::prelude::KeyCode::Up => KeyCode::Up,
+        bevy::prelude::KeyCode::Right => KeyCode::Right,
+        bevy::prelude::KeyCode::Down => KeyCode::Down,
+        bevy::prelude::KeyCode::Back => KeyCode::Back,
+        bevy::prelude::KeyCode::Return => KeyCode::Return,
+        bevy::prelude::KeyCode::Space => KeyCode::Space,
+        bevy::prelude::KeyCode::Compose => KeyCode::Compose,
+        bevy::prelude::KeyCode::Caret => KeyCode::Caret,
+        bevy::prelude::KeyCode::Numlock => KeyCode::Numlock,
+        bevy::prelude::KeyCode::Numpad0 => KeyCode::Numpad0,
+        bevy::prelude::KeyCode::Numpad1 => KeyCode::Numpad1,
+        bevy::prelude::KeyCode::Numpad2 => KeyCode::Numpad2,
+        bevy::prelude::KeyCode::Numpad3 => KeyCode::Numpad3,
+        bevy::prelude::KeyCode::Numpad4 => KeyCode::Numpad4,
+        bevy::prelude::KeyCode::Numpad5 => KeyCode::Numpad5,
+        bevy::prelude::KeyCode::Numpad6 => KeyCode::Numpad6,
+        bevy::prelude::KeyCode::Numpad7 => KeyCode::Numpad7,
+        bevy::prelude::KeyCode::Numpad8 => KeyCode::Numpad8,
+        bevy::prelude::KeyCode::Numpad9 => KeyCode::Numpad9,
+        bevy::prelude::KeyCode::AbntC1 => KeyCode::AbntC1,
+        bevy::prelude::KeyCode::AbntC2 => KeyCode::AbntC2,
+        bevy::prelude::KeyCode::NumpadAdd => KeyCode::NumpadAdd,
+        bevy::prelude::KeyCode::Apostrophe => KeyCode::Apostrophe,
+        bevy::prelude::KeyCode::Apps => KeyCode::Apps,
+        bevy::prelude::KeyCode::Asterisk => KeyCode::Asterisk,
+        bevy::prelude::KeyCode::Plus => KeyCode::Plus,
+        bevy::prelude::KeyCode::At => KeyCode::At,
+        bevy::prelude::KeyCode::Ax => KeyCode::Ax,
+        bevy::prelude::KeyCode::Backslash => KeyCode::Backslash,
+        bevy::prelude::KeyCode::Calculator => KeyCode::Calculator,
+        bevy::prelude::KeyCode::Capital => KeyCode::Capital,
+        bevy::prelude::KeyCode::Colon => KeyCode::Colon,
+        bevy::prelude::KeyCode::Comma => KeyCode::Comma,
+        bevy::prelude::KeyCode::Convert => KeyCode::Convert,
+        bevy::prelude::KeyCode::NumpadDecimal => KeyCode::NumpadDecimal,
+        bevy::prelude::KeyCode::NumpadDivide => KeyCode::NumpadDivide,
+        bevy::prelude::KeyCode::Equals => KeyCode::Equals,
+        bevy::prelude::KeyCode::Grave => KeyCode::Grave,
+        bevy::prelude::KeyCode::Kana => KeyCode::Kana,
+        bevy::prelude::KeyCode::Kanji => KeyCode::Kanji,
+        bevy::prelude::KeyCode::LAlt => KeyCode::LAlt,
+        bevy::prelude::KeyCode::LBracket => KeyCode::LBracket,
+        bevy::prelude::KeyCode::LControl => KeyCode::LControl,
+        bevy::prelude::KeyCode::LShift => KeyCode::LShift,
+        bevy::prelude::KeyCode::LWin => KeyCode::LWin,
+        bevy::prelude::KeyCode::Mail => KeyCode::Mail,
+        bevy::prelude::KeyCode::MediaSelect => KeyCode::MediaSelect,
+        bevy::prelude::KeyCode::MediaStop => KeyCode::MediaStop,
+        bevy::prelude::KeyCode::Minus => KeyCode::Minus,
+        bevy::prelude::KeyCode::NumpadMultiply => KeyCode::NumpadMultiply,
+        bevy::prelude::KeyCode::Mute => KeyCode::Mute,
+        bevy::prelude::KeyCode::MyComputer => KeyCode::MyComputer,
+        bevy::prelude::KeyCode::NavigateForward => KeyCode::NavigateForward,
+        bevy::prelude::KeyCode::NavigateBackward => KeyCode::NavigateBackward,
+        bevy::prelude::KeyCode::NextTrack => KeyCode::NextTrack,
+        bevy::prelude::KeyCode::NoConvert => KeyCode::NoConvert,
+        bevy::prelude::KeyCode::NumpadComma => KeyCode::NumpadComma,
+        bevy::prelude::KeyCode::NumpadEnter => KeyCode::NumpadEnter,
+        bevy::prelude::KeyCode::NumpadEquals => KeyCode::NumpadEquals,
+        bevy::prelude::KeyCode::Oem102 => KeyCode::Oem102,
+        bevy::prelude::KeyCode::Period => KeyCode::Period,
+        bevy::prelude::KeyCode::PlayPause => KeyCode::PlayPause,
+        bevy::prelude::KeyCode::Power => KeyCode::Power,
+        bevy::prelude::KeyCode::PrevTrack => KeyCode::PrevTrack,
+        bevy::prelude::KeyCode::RAlt => KeyCode::RAlt,
+        bevy::prelude::KeyCode::RBracket => KeyCode::RBracket,
+        bevy::prelude::KeyCode::RControl => KeyCode::RControl,
+        bevy::prelude::KeyCode::RShift => KeyCode::RShift,
+        bevy::prelude::KeyCode::RWin => KeyCode::RWin,
+        bevy::prelude::KeyCode::Semicolon => KeyCode::Semicolon,
+        bevy::prelude::KeyCode::Slash => KeyCode::Slash,
+        bevy::prelude::KeyCode::Sleep => KeyCode::Sleep,
+        bevy::prelude::KeyCode::Stop => KeyCode::Stop,
+        bevy::prelude::KeyCode::NumpadSubtract => KeyCode::NumpadSubtract,
+        bevy::prelude::KeyCode::Sysrq => KeyCode::Sysrq,
+        bevy::prelude::KeyCode::Tab => KeyCode::Tab,
+        bevy::prelude::KeyCode::Underline => KeyCode::Underline,
+        bevy::prelude::KeyCode::Unlabeled => KeyCode::Unlabeled,
+        bevy::prelude::KeyCode::VolumeDown => KeyCode::VolumeDown,
+        bevy::prelude::KeyCode::VolumeUp => KeyCode::VolumeUp,
+        bevy::prelude::KeyCode::Wake => KeyCode::Wake,
+        bevy::prelude::KeyCode::WebBack => KeyCode::WebBack,
+        bevy::prelude::KeyCode::WebFavorites => KeyCode::WebFavorites,
+        bevy::prelude::KeyCode::WebForward => KeyCode::WebForward,
+        bevy::prelude::KeyCode::WebHome => KeyCode::WebHome,
+        bevy::prelude::KeyCode::WebRefresh => KeyCode::WebRefresh,
+        bevy::prelude::KeyCode::WebSearch => KeyCode::WebSearch,
+        bevy::prelude::KeyCode::WebStop => KeyCode::WebStop,
+        bevy::prelude::KeyCode::Yen => KeyCode::Yen,
+        bevy::prelude::KeyCode::Copy => KeyCode::Copy,
+        bevy::prelude::KeyCode::Paste => KeyCode::Paste,
+        bevy::prelude::KeyCode::Cut => KeyCode::Cut,
+    }
+}
diff --git a/bevy_kayak_ui/src/lib.rs b/bevy_kayak_ui/src/lib.rs
index 16511b328c2cf152460c6891c9f4a853699f9403..44e37cb91462abaa3fb75b1e88a1de8cb5c40b4a 100644
--- a/bevy_kayak_ui/src/lib.rs
+++ b/bevy_kayak_ui/src/lib.rs
@@ -1,13 +1,14 @@
 use bevy::{
-    input::{mouse::MouseButtonInput, ElementState},
+    input::{keyboard::KeyboardInput, mouse::MouseButtonInput, ElementState},
     math::Vec2,
     prelude::{EventReader, IntoExclusiveSystem, MouseButton, Plugin, Res, World},
     render::color::Color,
-    window::{CursorMoved, WindowCreated, WindowResized, Windows},
+    window::{CursorMoved, ReceivedCharacter, WindowCreated, WindowResized, Windows},
 };
 
 mod bevy_context;
 mod camera;
+mod key;
 mod render;
 
 pub use bevy_context::BevyContext;
@@ -50,6 +51,8 @@ pub fn process_events(
     windows: Res<Windows>,
     mut cursor_moved_events: EventReader<CursorMoved>,
     mut mouse_button_input_events: EventReader<MouseButtonInput>,
+    mut char_input_events: EventReader<ReceivedCharacter>,
+    mut keyboard_input_events: EventReader<KeyboardInput>,
 ) {
     let window_size = if let Some(window) = windows.get_primary() {
         Vec2::new(window.width(), window.height())
@@ -78,6 +81,19 @@ pub fn process_events(
             }
         }
 
+        for event in char_input_events.iter() {
+            input_events.push(InputEvent::CharEvent { c: event.char });
+        }
+
+        for event in keyboard_input_events.iter() {
+            if let Some(key_code) = event.key_code {
+                let kayak_key_code = key::convert_virtual_key_code(key_code);
+                input_events.push(InputEvent::Keyboard {
+                    key: kayak_key_code,
+                });
+            }
+        }
+
         context.process_events(input_events);
     }
 }
diff --git a/examples/text_box.rs b/examples/text_box.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6cd17e64fdc91e8b7f5a33725ec34102fb1f6adc
--- /dev/null
+++ b/examples/text_box.rs
@@ -0,0 +1,78 @@
+use bevy::{
+    prelude::{App as BevyApp, AssetServer, Commands, Res, ResMut},
+    window::WindowDescriptor,
+    DefaultPlugins,
+};
+use kayak_ui::bevy::{BevyContext, BevyKayakUIPlugin, FontMapping, UICameraBundle};
+use kayak_ui::core::{
+    render, rsx,
+    styles::{Style, StyleProp, Units},
+    widget, Bound, Index, MutableBound,
+};
+use kayak_widgets::{App, OnChange, TextBox, Window};
+
+#[widget]
+fn TextBoxExample(context: &mut KayakContext) {
+    let value = context.create_state("".to_string()).unwrap();
+    let value2 = context.create_state("".to_string()).unwrap();
+
+    let input_styles = Style {
+        top: StyleProp::Value(Units::Pixels(10.0)),
+        ..Default::default()
+    };
+
+    let cloned_value = value.clone();
+    let on_change = OnChange::new(move |event| {
+        cloned_value.set(event.value);
+    });
+
+    let cloned_value2 = value2.clone();
+    let on_change2 = OnChange::new(move |event| {
+        cloned_value2.set(event.value);
+    });
+
+    let current_value = value.get();
+    let current_value2 = value2.get();
+    rsx! {
+        <>
+            <Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"TextBox Example".to_string()}>
+                <TextBox styles={Some(input_styles)} value={current_value} on_change={Some(on_change)} />
+                <TextBox styles={Some(input_styles)} value={current_value2} on_change={Some(on_change2)} />
+            </Window>
+        </>
+    }
+}
+
+fn startup(
+    mut commands: Commands,
+    mut font_mapping: ResMut<FontMapping>,
+    asset_server: Res<AssetServer>,
+) {
+    commands.spawn_bundle(UICameraBundle::new());
+
+    font_mapping.add(asset_server.load("roboto.kayak_font"));
+
+    let context = BevyContext::new(|context| {
+        render! {
+            <App>
+                <TextBoxExample />
+            </App>
+        }
+    });
+
+    commands.insert_resource(context);
+}
+
+fn main() {
+    BevyApp::new()
+        .insert_resource(WindowDescriptor {
+            width: 1270.0,
+            height: 720.0,
+            title: String::from("UI Example"),
+            ..Default::default()
+        })
+        .add_plugins(DefaultPlugins)
+        .add_plugin(BevyKayakUIPlugin)
+        .add_startup_system(startup)
+        .run();
+}
diff --git a/kayak_core/src/binding.rs b/kayak_core/src/binding.rs
index f1221a4063ad8cd6b9dbb60da25e1f54d1dba377..fbd4a31d61ed7487818052104d8811dc819f813b 100644
--- a/kayak_core/src/binding.rs
+++ b/kayak_core/src/binding.rs
@@ -1 +1,29 @@
+use std::time::Instant;
+
 pub use flo_binding::{bind, notify, Binding, Bound, Changeable, MutableBound, Releasable};
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Debouncer {
+    last_updated: Instant,
+    time: f32,
+}
+
+impl Debouncer {
+    pub fn new(time: f32) -> Self {
+        Self {
+            time,
+            last_updated: Instant::now(),
+        }
+    }
+
+    pub fn should_update(&mut self) -> bool {
+        let elapsed_time = self.last_updated.elapsed().as_secs_f32();
+        if elapsed_time > self.time {
+            self.last_updated = Instant::now();
+
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs
index 19300e25d7d55a3c27cdfea62b6ebb8a5f95e37c..60141262681be536e661e45676b039961d227548 100644
--- a/kayak_core/src/context.rs
+++ b/kayak_core/src/context.rs
@@ -287,6 +287,16 @@ impl KayakContext {
                                 events_stream.push(click_event);
                             }
                         }
+                        InputEvent::CharEvent { c } => events_stream.push(Event {
+                            target: index,
+                            event_type: EventType::CharInput { c: *c },
+                            ..Event::default()
+                        }),
+                        InputEvent::Keyboard { key } => events_stream.push(Event {
+                            target: index,
+                            event_type: EventType::KeyboardInput { key: *key },
+                            ..Event::default()
+                        }),
                     }
                 }
             }
@@ -302,13 +312,14 @@ impl KayakContext {
             target_widget.on_event(self, event);
             self.widget_manager.repossess(target_widget);
 
-            for parent in parents {
-                if event.should_propagate {
-                    let mut parent_widget = self.widget_manager.take(parent);
-                    parent_widget.on_event(self, event);
-                    self.widget_manager.repossess(parent_widget);
-                }
-            }
+            // TODO: Restore propagation.
+            // for parent in parents {
+            //     if event.should_propagate {
+            //         let mut parent_widget = self.widget_manager.take(parent);
+            //         parent_widget.on_event(self, event);
+            //         self.widget_manager.repossess(parent_widget);
+            //     }
+            // }
         }
     }
 
diff --git a/kayak_core/src/event.rs b/kayak_core/src/event.rs
index 1b1f3ecf4ef06cf217794cb54896a133a132e0bf..7b56d25ef8687c5fc27ad2eeab4dd15bbbc8edd7 100644
--- a/kayak_core/src/event.rs
+++ b/kayak_core/src/event.rs
@@ -1,4 +1,4 @@
-use crate::Index;
+use crate::{Index, KeyCode};
 
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub struct Event {
@@ -23,4 +23,6 @@ pub enum EventType {
     Hover,
     MouseIn,
     MouseOut,
+    CharInput { c: char },
+    KeyboardInput { key: KeyCode },
 }
diff --git a/kayak_core/src/input_event.rs b/kayak_core/src/input_event.rs
index 18856f738152c4db03c6bb6edfa2805e2edb69a6..547270285bb36a2ccbb82da6c89cbc7538e07836 100644
--- a/kayak_core/src/input_event.rs
+++ b/kayak_core/src/input_event.rs
@@ -1,4 +1,8 @@
+use crate::KeyCode;
+
 pub enum InputEvent {
     MouseMoved((f32, f32)),
     MouseLeftClick,
+    CharEvent { c: char },
+    Keyboard { key: KeyCode },
 }
diff --git a/kayak_core/src/keys.rs b/kayak_core/src/keys.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1510a6513be53a11a1f3b0a3ef5258eeb217f445
--- /dev/null
+++ b/kayak_core/src/keys.rs
@@ -0,0 +1,203 @@
+/// The key code of a keyboard input.
+#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[repr(u32)]
+pub enum KeyCode {
+    /// The '1' key over the letters.
+    Key1,
+    /// The '2' key over the letters.
+    Key2,
+    /// The '3' key over the letters.
+    Key3,
+    /// The '4' key over the letters.
+    Key4,
+    /// The '5' key over the letters.
+    Key5,
+    /// The '6' key over the letters.
+    Key6,
+    /// The '7' key over the letters.
+    Key7,
+    /// The '8' key over the letters.
+    Key8,
+    /// The '9' key over the letters.
+    Key9,
+    /// The '0' key over the 'O' and 'P' keys.
+    Key0,
+
+    A,
+    B,
+    C,
+    D,
+    E,
+    F,
+    G,
+    H,
+    I,
+    J,
+    K,
+    L,
+    M,
+    N,
+    O,
+    P,
+    Q,
+    R,
+    S,
+    T,
+    U,
+    V,
+    W,
+    X,
+    Y,
+    Z,
+
+    /// The Escape key, next to F1.
+    Escape,
+
+    F1,
+    F2,
+    F3,
+    F4,
+    F5,
+    F6,
+    F7,
+    F8,
+    F9,
+    F10,
+    F11,
+    F12,
+    F13,
+    F14,
+    F15,
+    F16,
+    F17,
+    F18,
+    F19,
+    F20,
+    F21,
+    F22,
+    F23,
+    F24,
+
+    /// Print Screen/SysRq.
+    Snapshot,
+    /// Scroll Lock.
+    Scroll,
+    /// Pause/Break key, next to Scroll lock.
+    Pause,
+
+    /// `Insert`, next to Backspace.
+    Insert,
+    Home,
+    Delete,
+    End,
+    PageDown,
+    PageUp,
+
+    Left,
+    Up,
+    Right,
+    Down,
+
+    /// The Backspace key, right over Enter.
+    Back,
+    /// The Enter key.
+    Return,
+    /// The space bar.
+    Space,
+
+    /// The "Compose" key on Linux.
+    Compose,
+
+    Caret,
+
+    Numlock,
+    Numpad0,
+    Numpad1,
+    Numpad2,
+    Numpad3,
+    Numpad4,
+    Numpad5,
+    Numpad6,
+    Numpad7,
+    Numpad8,
+    Numpad9,
+
+    AbntC1,
+    AbntC2,
+    NumpadAdd,
+    Apostrophe,
+    Apps,
+    Asterisk,
+    Plus,
+    At,
+    Ax,
+    Backslash,
+    Calculator,
+    Capital,
+    Colon,
+    Comma,
+    Convert,
+    NumpadDecimal,
+    NumpadDivide,
+    Equals,
+    Grave,
+    Kana,
+    Kanji,
+    /// The left alt key. Maps to left option on Mac.
+    LAlt,
+    LBracket,
+    LControl,
+    LShift,
+    /// The left Windows key. Maps to left Command on Mac.
+    LWin,
+    Mail,
+    MediaSelect,
+    MediaStop,
+    Minus,
+    NumpadMultiply,
+    Mute,
+    MyComputer,
+    NavigateForward,  // also called "Prior"
+    NavigateBackward, // also called "Next"
+    NextTrack,
+    NoConvert,
+    NumpadComma,
+    NumpadEnter,
+    NumpadEquals,
+    Oem102,
+    Period,
+    PlayPause,
+    Power,
+    PrevTrack,
+    /// The right alt key. Maps to right option on Mac.
+    RAlt,
+    RBracket,
+    RControl,
+    RShift,
+    /// The right Windows key. Maps to right Command on Mac.
+    RWin,
+    Semicolon,
+    Slash,
+    Sleep,
+    Stop,
+    NumpadSubtract,
+    Sysrq,
+    Tab,
+    Underline,
+    Unlabeled,
+    VolumeDown,
+    VolumeUp,
+    Wake,
+    WebBack,
+    WebFavorites,
+    WebForward,
+    WebHome,
+    WebRefresh,
+    WebSearch,
+    WebStop,
+    Yen,
+    Copy,
+    Paste,
+    Cut,
+}
diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs
index 7b2ee323f09ac1d73b2a61a54b8ec844e3271778..54edfeef3b7f6d4bb1e30704627265666b906503 100644
--- a/kayak_core/src/lib.rs
+++ b/kayak_core/src/lib.rs
@@ -5,6 +5,7 @@ pub mod event;
 pub mod fragment;
 pub(crate) mod generational_arena;
 mod input_event;
+mod keys;
 pub mod layout_cache;
 pub mod node;
 pub mod render_command;
@@ -18,12 +19,14 @@ pub mod widget_manager;
 use std::sync::{Arc, RwLock};
 
 pub use binding::*;
+pub use color::Color;
 pub use context::*;
 pub use event::*;
 pub use fragment::Fragment;
 pub use generational_arena::{Arena, Index};
 pub use input_event::*;
 pub use kayak_render_macros::{constructor, render, rsx, widget};
+pub use keys::KeyCode;
 pub use resources::Resources;
 pub use tree::{Tree, WidgetTree};
 pub use vec::VecTracker;
diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs
index 3d9e1c321443f8fe0d61ea7f726370682faeae4d..1f78f7fdb7d991095b6954da83f398d64a663967 100644
--- a/kayak_core/src/widget_manager.rs
+++ b/kayak_core/src/widget_manager.rs
@@ -202,6 +202,7 @@ impl WidgetManager {
             if matches!(styles.render_command.resolve(), RenderCommand::Empty) {
                 continue;
             }
+
             let mut node = NodeBuilder::empty()
                 .with_id(dirty_node_index)
                 .with_styles(styles)
diff --git a/kayak_render_macros/src/function_component.rs b/kayak_render_macros/src/function_component.rs
index bc543e668ac8afffe506cc92d1f36cdc2c05c4c6..fbcdaa0a043e9ef9dd2594aac68ce5d54ff7591e 100644
--- a/kayak_render_macros/src/function_component.rs
+++ b/kayak_render_macros/src/function_component.rs
@@ -53,8 +53,13 @@ pub fn create_function_widget(f: syn::ItemFn) -> TokenStream {
         let input_string = (quote! { #input }).to_string();
         if input_string.contains("children : Children") {
             *input = quote! {
-                 #[derivative(Debug = "ignore", PartialEq = "ignore")]
-                 pub children: Children
+                #[derivative(Debug = "ignore", PartialEq = "ignore")]
+                pub children: Children
+            };
+        } else if input_string.contains("on_event : Option < OnEvent >") {
+            *input = quote! {
+                #[derivative(Debug = "ignore", PartialEq = "ignore")]
+                pub on_event: Option<#kayak_core::OnEvent>
             };
         } else {
             *input = quote! {
@@ -67,7 +72,7 @@ pub fn create_function_widget(f: syn::ItemFn) -> TokenStream {
         (
             vec![
                 "styles : Option < Style >",
-                "styles : Option< kayak_core :: styles :: Style >",
+                "styles : Option< kayak_ui :: core :: styles :: Style >",
             ],
             quote! {
                 pub styles: Option<#kayak_core::styles::Style>
@@ -82,8 +87,9 @@ pub fn create_function_widget(f: syn::ItemFn) -> TokenStream {
         ),
         (
             vec![
-                "on_event: Option<OnEvent>",
-                "on_event : Option<kayak_core::OnEvent>",
+                "on_event : Option < OnEvent >",
+                "on_event : Option < kayak_ui :: core :: OnEvent >",
+                "on_event : Option <\nkayak_ui :: core :: OnEvent >",
             ],
             quote! {
                 #[derivative(Debug = "ignore", PartialEq = "ignore")]
diff --git a/kayak_widgets/src/lib.rs b/kayak_widgets/src/lib.rs
index 9a0f309107d5a696155abf938b931d6287bc2c82..fb09fe65f500d37b2c00886f7661c586ea6c8a65 100644
--- a/kayak_widgets/src/lib.rs
+++ b/kayak_widgets/src/lib.rs
@@ -5,6 +5,7 @@ mod clip;
 mod element;
 mod if_element;
 mod image;
+mod text_box;
 mod nine_patch;
 mod text;
 mod window;
@@ -16,6 +17,7 @@ pub use clip::*;
 pub use element::*;
 pub use if_element::*;
 pub use image::*;
+pub use text_box::*;
 pub use nine_patch::*;
 pub use text::*;
 pub use window::*;
diff --git a/kayak_widgets/src/text_box.rs b/kayak_widgets/src/text_box.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1f0121f16e67e09f75534eca62c48202dffd30ca
--- /dev/null
+++ b/kayak_widgets/src/text_box.rs
@@ -0,0 +1,80 @@
+use kayak_ui::core::{
+    rsx,
+    styles::{Style, StyleProp, Units},
+    widget, Bound, Color, EventType, MutableBound, OnEvent,
+};
+use std::sync::{Arc, RwLock};
+
+use crate::{Background, Clip, Text};
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct ChangeEvent {
+    pub value: String,
+}
+
+#[derive(Clone)]
+pub struct OnChange(pub Arc<RwLock<dyn FnMut(ChangeEvent) + Send + Sync + 'static>>);
+
+impl OnChange {
+    pub fn new<F: FnMut(ChangeEvent) + Send + Sync + 'static>(f: F) -> OnChange {
+        OnChange(Arc::new(RwLock::new(f)))
+    }
+}
+
+impl PartialEq for OnChange {
+    fn eq(&self, _other: &Self) -> bool {
+        true
+    }
+}
+
+impl std::fmt::Debug for OnChange {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_tuple("OnChange").finish()
+    }
+}
+
+#[widget]
+pub fn TextBox(value: String, on_change: Option<OnChange>) {
+    let background_styles = Style {
+        background_color: StyleProp::Value(Color::new(0.176, 0.196, 0.215, 1.0)),
+        border_radius: StyleProp::Value((5.0, 5.0, 5.0, 5.0)),
+        height: StyleProp::Value(Units::Pixels(26.0)),
+        padding_left: StyleProp::Value(Units::Pixels(5.0)),
+        padding_right: StyleProp::Value(Units::Pixels(5.0)),
+        ..styles.clone().unwrap_or_default()
+    };
+
+    let internal_value = context.create_state("".to_string()).unwrap();
+
+    let cloned_on_change = on_change.clone();
+    self.on_event = Some(OnEvent::new(move |_, event| match event.event_type {
+        EventType::CharInput { c } => {
+            let mut current_value = internal_value.get();
+            if c == '\u{8}' {
+                if current_value.len() > 0 {
+                    current_value.truncate(current_value.len() - 1);
+                }
+            } else if !c.is_control() {
+                current_value.push(c);
+            }
+            if let Some(on_change) = cloned_on_change.as_ref() {
+                if let Ok(mut on_change) = on_change.0.write() {
+                    on_change(ChangeEvent {
+                        value: current_value.clone(),
+                    });
+                }
+            }
+            internal_value.set(current_value);
+        }
+        _ => {}
+    }));
+
+    let value = value.clone();
+    rsx! {
+        <Background styles={Some(background_styles)}>
+            <Clip>
+                <Text content={value} size={14.0} />
+            </Clip>
+        </Background>
+    }
+}