diff --git a/examples/text_box.rs b/examples/text_box.rs
index 5a51d0217d585c4d016e8281949d5926673e0f1b..26e13662e37850dda4db6eefb7577b302cee4335 100644
--- a/examples/text_box.rs
+++ b/examples/text_box.rs
@@ -18,7 +18,7 @@ fn TextBoxExample() {
     let (value, set_value, _) = use_state!("I started with a value!".to_string());
     let (empty_value, set_empty_value, _) = use_state!("".to_string());
     let (red_value, set_red_value, _) = use_state!("This text is red".to_string());
-    let (spin_value, set_spin_value, _) = use_state!("3".to_string());
+    let (spin_value, set_spin_value, _) = use_state!(3.0f32);
 
     let input_styles = Style {
         top: StyleProp::Value(Units::Pixels(10.0)),
@@ -43,7 +43,7 @@ fn TextBoxExample() {
     });
 
     let on_change_spin = OnChange::new(move |event| {
-        set_spin_value(event.value);
+        set_spin_value(32.0);
     });
 
     rsx! {
@@ -56,7 +56,7 @@ fn TextBoxExample() {
                 placeholder={Some("This is a placeholder".to_string())}
             />
             <TextBox styles={Some(red_text_styles)} value={red_value} on_change={Some(on_change_red)} />
-            <SpinBox styles={Some(input_styles)} value={spin_value} on_change={Some(on_change_spin)} />
+            <SpinBox<f32> styles={Some(input_styles)} value={spin_value} on_change={Some(on_change_spin)} />
         </Window>
     }
 }
diff --git a/src/widgets/spin_box.rs b/src/widgets/spin_box.rs
index fa160c1016fb440fb9b7305e4144e302375f6f84..b43aa4d157ad20fc5f239d0c5f62789f840a7f6a 100644
--- a/src/widgets/spin_box.rs
+++ b/src/widgets/spin_box.rs
@@ -1,3 +1,5 @@
+use std::str::FromStr;
+
 use crate::{
     core::{
         render_command::RenderCommand,
@@ -9,13 +11,19 @@ use crate::{
 };
 use kayak_core::{
     styles::{LayoutType, StyleProp},
-    CursorIcon, OnLayout,
+    CursorIcon, Index, OnLayout, Widget,
 };
 
 use crate::widgets::{Background, Clip, OnChange, Text};
 
+#[derive(Debug, PartialEq, Clone, Default)]
+pub struct SpinBox<T> {
+    pub id: Index,
+    pub props: SpinBoxProps<T>,
+}
+
 #[derive(Debug, PartialEq, Clone)]
-pub struct SpinBoxProps {
+pub struct SpinBoxProps<T> {
     /// If true, prevents the widget from being focused (and consequently edited)
     pub disabled: bool,
     /// A callback for when the text value was changed
@@ -26,7 +34,7 @@ pub struct SpinBoxProps {
     ///
     /// This is a controlled state. You _must_ set this to the value to you wish to be displayed.
     /// You can use the [`on_change`] callback to update this prop as the user types.
-    pub value: String,
+    pub value: Option<T>,
     pub styles: Option<Style>,
     /// Text on increment button defaults to `>`
     pub incr_str: String,
@@ -38,14 +46,13 @@ pub struct SpinBoxProps {
     pub focusable: Option<bool>,
 }
 
-
-impl Default for SpinBoxProps {
-    fn default() -> SpinBoxProps {
-        SpinBoxProps { 
+impl<T> Default for SpinBoxProps<T> {
+    fn default() -> SpinBoxProps<T> {
+        Self {
             incr_str: ">".into(),
             decr_str: "<".into(),
             disabled: Default::default(),
-            on_change:  Default::default(),
+            on_change: Default::default(),
             placeholder: Default::default(),
             value: Default::default(),
             styles: Default::default(),
@@ -57,7 +64,10 @@ impl Default for SpinBoxProps {
     }
 }
 
-impl WidgetProps for SpinBoxProps {
+impl<T> WidgetProps for SpinBoxProps<T>
+where
+    T: Widget,
+{
     fn get_children(&self) -> Option<Children> {
         self.children.clone()
     }
@@ -86,7 +96,6 @@ impl WidgetProps for SpinBoxProps {
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub struct FocusSpinbox(pub bool);
 
-#[widget]
 /// A widget that displays a spinnable text field
 ///
 /// # Props
@@ -101,121 +110,161 @@ pub struct FocusSpinbox(pub bool);
 /// | `on_layout` | ✅        |
 /// | `focusable` | ✅        |
 ///
-pub fn SpinBox(props: SpinBoxProps) {
-    let SpinBoxProps {
-        on_change,
-        placeholder,
-        value,
-        ..
-    } = props.clone();
-
-    props.styles = Some(
-        Style::default()
-            // Required styles
-            .with_style(Style {
-                render_command: RenderCommand::Layout.into(),
-                ..Default::default()
-            })
-            // Apply any prop-given styles
-            .with_style(&props.styles)
-            // If not set by props, apply these styles
-            .with_style(Style {
-                top: Units::Pixels(0.0).into(),
-                bottom: Units::Pixels(0.0).into(),
-                height: Units::Pixels(26.0).into(),
-                cursor: CursorIcon::Text.into(),
-                ..Default::default()
-            }),
-    );
-
-    let background_styles = Style {
-        background_color: Color::new(0.176, 0.196, 0.215, 1.0).into(),
-        border_radius: Corner::all(5.0).into(),
-        height: Units::Pixels(26.0).into(),
-        padding_left: Units::Pixels(5.0).into(),
-        padding_right: Units::Pixels(5.0).into(),
-        ..Default::default()
-    };
-
-    let has_focus = context.create_state(FocusSpinbox(false)).unwrap();
-
-    let mut current_value = value.clone();
-    let cloned_on_change = on_change.clone();
-    let cloned_has_focus = has_focus.clone();
-
-    props.on_event = Some(OnEvent::new(move |_, event| match event.event_type {
-        EventType::CharInput { c } => {
-            if !cloned_has_focus.get().0 {
-                return;
-            }
-            if is_backspace(c) {
-                if !current_value.is_empty() {
-                    current_value.truncate(current_value.len() - 1);
+impl<T> Widget for SpinBox<T>
+where
+    T: Widget + ToString + FromStr + Default,
+{
+    type Props = SpinBoxProps<T>;
+
+    fn constructor(props: Self::Props) -> Self
+    where
+        Self: Sized,
+    {
+        Self {
+            id: Index::default(),
+            props,
+        }
+    }
+
+    fn get_id(&self) -> Index {
+        self.id
+    }
+
+    fn set_id(&mut self, id: Index) {
+        self.id = id;
+    }
+
+    fn get_props(&self) -> &Self::Props {
+        &self.props
+    }
+
+    fn get_props_mut(&mut self) -> &mut Self::Props {
+        &mut self.props
+    }
+
+    fn render(&mut self, context: &mut kayak_core::KayakContextRef) {
+        let children = self.props.get_children();
+        let mut props = self.props.clone();
+        let SpinBoxProps {
+            on_change,
+            placeholder,
+            value,
+            ..
+        } = props.clone();
+
+        props.styles = Some(
+            Style::default()
+                // Required styles
+                .with_style(Style {
+                    render_command: RenderCommand::Layout.into(),
+                    ..Default::default()
+                })
+                // Apply any prop-given styles
+                .with_style(&props.styles)
+                // If not set by props, apply these styles
+                .with_style(Style {
+                    top: Units::Pixels(0.0).into(),
+                    bottom: Units::Pixels(0.0).into(),
+                    height: Units::Pixels(26.0).into(),
+                    cursor: CursorIcon::Text.into(),
+                    ..Default::default()
+                }),
+        );
+
+        let background_styles = Style {
+            background_color: Color::new(0.176, 0.196, 0.215, 1.0).into(),
+            border_radius: Corner::all(5.0).into(),
+            height: Units::Pixels(26.0).into(),
+            padding_left: Units::Pixels(5.0).into(),
+            padding_right: Units::Pixels(5.0).into(),
+            ..Default::default()
+        };
+
+        let has_focus = context.create_state(FocusSpinbox(false)).unwrap();
+        let mut current_value = value.clone().map_or(String::new(), |f| { f.to_string()});
+        let cloned_on_change = on_change.clone();
+        let cloned_has_focus = has_focus.clone();
+
+        props.on_event = Some(OnEvent::new(move |_, event| match event.event_type {
+            EventType::CharInput { c } => {
+                if !cloned_has_focus.get().0 {
+                    return;
                 }
-            } 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(),
-                    });
+                if is_backspace(c) {
+                    if !current_value.is_empty() {
+                        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(),
+                        });
+                    }
                 }
             }
-        }
-        EventType::Focus => cloned_has_focus.set(FocusSpinbox(true)),
-        EventType::Blur => cloned_has_focus.set(FocusSpinbox(false)),
-        _ => {}
-    }));
-
-    let text_styles = if value.is_empty() || (has_focus.get().0 && value.is_empty()) {
-        Style {
-            color: Color::new(0.5, 0.5, 0.5, 1.0).into(),
-            ..Style::default()
-        }
-    } else {
-        Style {
-            width: Units::Stretch(100.0).into(),
+            EventType::Focus => cloned_has_focus.set(FocusSpinbox(true)),
+            EventType::Blur => cloned_has_focus.set(FocusSpinbox(false)),
+            _ => {}
+        }));
+
+        let text_styles = if value.is_none() || (has_focus.get().0 && value.is_none()) {
+            Style {
+                color: Color::new(0.5, 0.5, 0.5, 1.0).into(),
+                ..Style::default()
+            }
+        } else {
+            Style {
+                width: Units::Stretch(100.0).into(),
+                ..Style::default()
+            }
+        };
+
+        let button_style = Some(Style {
+            height: Units::Pixels(24.0).into(),
+            width: Units::Pixels(24.0).into(),
+            ..Default::default()
+        });
+
+        // if current_value.is_empty() {
+        //     current_value = placeholder.unwrap_or(current_value);
+        // };
+
+        // let value = if current_value.is_empty() {
+        //     None
+        // } else {
+        //     T::from_str(&current_value).ok()
+        // };
+
+        let inline_style = Style {
+            layout_type: StyleProp::Value(LayoutType::Row),
             ..Style::default()
+        };
+
+        let incr_str = props.clone().incr_str;
+        let decr_str = props.clone().decr_str;
+        let content = value.clone().map_or(String::new(), |f| { f.to_string()});
+
+
+        rsx! {
+            <Background styles={Some(background_styles)}>
+                <Clip styles={Some(inline_style)}>
+                    <Button styles={button_style}>
+                        <Text content={decr_str} />
+                    </Button>
+                    <Text
+                        content={content}
+                        size={14.0}
+                        styles={Some(text_styles)}
+                    />
+                    <Button styles={button_style}>
+                        <Text content={incr_str} />
+                    </Button>
+                </Clip>
+            </Background>
         }
-    };
-
-    let button_style = Some(Style {
-        height: Units::Pixels(24.0).into(),
-        width: Units::Pixels(24.0).into(),
-        ..Default::default()
-    });
-
-    let value = if value.is_empty() {
-        placeholder.unwrap_or_else(|| value.clone())
-    } else {
-        value
-    };
-
-    let inline_style = Style {
-        layout_type: StyleProp::Value(LayoutType::Row),
-        ..Style::default()
-    };
-
-    let incr_str = props.clone().incr_str;
-    let decr_str = props.clone().decr_str;
-
-    rsx! {
-        <Background styles={Some(background_styles)}>
-            <Clip styles={Some(inline_style)}>
-                <Button styles={button_style}>
-                    <Text content={decr_str} />
-                </Button>
-                <Text
-                    content={value}
-                    size={14.0}
-                    styles={Some(text_styles)}
-                />
-                <Button styles={button_style}>
-                    <Text content={incr_str} />
-                </Button>
-            </Clip>
-        </Background>
     }
 }