diff --git a/bevy_kayak_ui/src/render/unified/font/extract.rs b/bevy_kayak_ui/src/render/unified/font/extract.rs
index 5948efc52b5af61e915b904c7be8bf431dfcf9ca..43bce7d403757fe05d37f183525f19f4003290b2 100644
--- a/bevy_kayak_ui/src/render/unified/font/extract.rs
+++ b/bevy_kayak_ui/src/render/unified/font/extract.rs
@@ -138,6 +138,8 @@ pub fn extract_texts(
                         type_index: 0,
                         border_radius: (0.0, 0.0, 0.0, 0.0),
                         image: None,
+                        uv_max: None,
+                        uv_min: None,
                     },
                 });
 
diff --git a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs
index 572627267a4a4011ecd6518ec43fb8bb0906daa7..f9d526c3a8bacd28907f885bcaff608bb1831ae6 100644
--- a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs
+++ b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs
@@ -305,7 +305,6 @@ impl FontTextureCache {
         atlas_texture: &GpuImage,
         size: Vec2,
     ) {
-        dbg!(size);
         Self::create_texture(
             images,
             font_handle.clone_weak(),
diff --git a/bevy_kayak_ui/src/render/unified/font/mod.rs b/bevy_kayak_ui/src/render/unified/font/mod.rs
index 1ae275c690267860df26b5dc60f641c700a4a052..8b2a9fff050a30a686c6dd3d4c92c376f2159046 100644
--- a/bevy_kayak_ui/src/render/unified/font/mod.rs
+++ b/bevy_kayak_ui/src/render/unified/font/mod.rs
@@ -90,14 +90,11 @@ pub fn set_font_texture(
 ) {
     // quick and dirty, run this for all textures anytime a texture is created.
     for event in texture_events.iter() {
-        dbg!(&event);
         match event {
             AssetEvent::Created { handle } => {
                 let handle_path = asset_server.get_handle_path(handle).unwrap();
-                dbg!(&handle_path);
                 if handle_path.path().to_str().unwrap().contains("roboto") {
                     if let Some(mut texture) = textures.get_mut(handle) {
-                        dbg!("Setting font texture!");
                         texture.texture_descriptor.format = TextureFormat::Rgba8Unorm;
                         texture.sampler_descriptor.min_filter = FilterMode::Linear;
                         texture.sampler_descriptor.mipmap_filter = FilterMode::Linear;
diff --git a/bevy_kayak_ui/src/render/unified/image/extract.rs b/bevy_kayak_ui/src/render/unified/image/extract.rs
index fee53b688ede10e56af934611176b295eb0c97c5..dfee468af1f4e1c74d456fcae9c05923d5b9f68f 100644
--- a/bevy_kayak_ui/src/render/unified/image/extract.rs
+++ b/bevy_kayak_ui/src/render/unified/image/extract.rs
@@ -51,6 +51,8 @@ pub fn extract_images(
                 image: image_manager
                     .get_handle(handle)
                     .and_then(|a| Some(a.clone_weak())),
+                uv_max: None,
+                uv_min: None,
             },
         });
     }
diff --git a/bevy_kayak_ui/src/render/unified/image/image_manager.rs b/bevy_kayak_ui/src/render/unified/image/image_manager.rs
index b92b08e5ce9a0db06b16b2399cbd93aefce1c4db..0493f8dbc26fe3322fc17b0b36dc4e379383675f 100644
--- a/bevy_kayak_ui/src/render/unified/image/image_manager.rs
+++ b/bevy_kayak_ui/src/render/unified/image/image_manager.rs
@@ -22,7 +22,7 @@ impl ImageManager {
         } else {
             let id = self.count;
             self.count += 1;
-            self.mapping.insert(id, image_handle.clone_weak());
+            self.mapping.insert(id, image_handle.clone());
             self.reverse_mapping.insert(image_handle.clone_weak(), id);
             return id;
         }
diff --git a/bevy_kayak_ui/src/render/unified/mod.rs b/bevy_kayak_ui/src/render/unified/mod.rs
index c6ce51ce23c0d449835082a6f5eab7ec51138405..152bdd7eb1a26c1f0fb6ac31e5fa56be8b51fdb9 100644
--- a/bevy_kayak_ui/src/render/unified/mod.rs
+++ b/bevy_kayak_ui/src/render/unified/mod.rs
@@ -13,6 +13,7 @@ use self::pipeline::ImageBindGroups;
 
 pub mod font;
 pub mod image;
+mod nine_patch;
 mod pipeline;
 mod quad;
 
@@ -46,6 +47,7 @@ impl Plugin for UnifiedRenderPlugin {
 
         app.add_plugin(font::TextRendererPlugin)
             .add_plugin(quad::QuadRendererPlugin)
-            .add_plugin(image::ImageRendererPlugin);
+            .add_plugin(image::ImageRendererPlugin)
+            .add_plugin(nine_patch::NinePatchRendererPlugin);
     }
 }
diff --git a/bevy_kayak_ui/src/render/unified/nine_patch/extract.rs b/bevy_kayak_ui/src/render/unified/nine_patch/extract.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c2d75f93c997255de0d2027eecf8a171ee6df4a1
--- /dev/null
+++ b/bevy_kayak_ui/src/render/unified/nine_patch/extract.rs
@@ -0,0 +1,260 @@
+use bevy::{
+    math::Vec2,
+    prelude::{Assets, Commands, Res},
+    render2::{color::Color, texture::Image},
+    sprite2::Rect,
+};
+use kayak_core::render_primitive::RenderPrimitive;
+
+use crate::{
+    render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType},
+    BevyContext, ImageManager,
+};
+
+pub fn extract_nine_patch(
+    mut commands: Commands,
+    context: Res<BevyContext>,
+    image_manager: Res<ImageManager>,
+    images: Res<Assets<Image>>,
+) {
+    let render_commands = if let Ok(context) = context.kayak_context.read() {
+        context.widget_manager.build_render_primitives()
+    } else {
+        vec![]
+    };
+
+    let image_commands: Vec<&RenderPrimitive> = render_commands
+        .iter()
+        .filter(|command| matches!(command, RenderPrimitive::NinePatch { .. }))
+        .collect::<Vec<_>>();
+
+    let mut extracted_quads = Vec::new();
+    for render_primitive in image_commands {
+        let (layout, handle, border) = match render_primitive {
+            RenderPrimitive::NinePatch {
+                layout,
+                handle,
+                border,
+            } => (layout, handle, border),
+            _ => panic!(""),
+        };
+
+        let image_handle = image_manager
+            .get_handle(handle)
+            .and_then(|a| Some(a.clone_weak()));
+
+        let image = images.get(image_handle.as_ref().unwrap());
+
+        if image.is_none() {
+            dbg!("Uh oh no image! :(");
+            continue;
+        }
+
+        let image_size = image
+            .and_then(|i| {
+                Some(Vec2::new(
+                    i.texture_descriptor.size.width as f32,
+                    i.texture_descriptor.size.height as f32,
+                ))
+            })
+            .unwrap();
+
+        let extracted_quad_template = ExtractedQuad {
+            rect: Rect {
+                min: Vec2::ZERO,
+                max: Vec2::ZERO,
+            },
+            color: Color::WHITE,
+            vertex_index: 0,
+            char_id: 0,
+            z_index: layout.z_index,
+            font_handle: None,
+            quad_type: UIQuadType::Image,
+            type_index: 0,
+            border_radius: (0.0, 0.0, 0.0, 0.0),
+            image: image_handle,
+            uv_max: None,
+            uv_min: None,
+        };
+
+        // TOP
+        let top_left_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(layout.posx, layout.posy),
+                    max: Vec2::new(layout.posx + border.left, layout.posy + border.top),
+                },
+                uv_min: Some(Vec2::new(0.0, border.top / image_size.y)),
+                uv_max: Some(Vec2::new(border.left / image_size.x, 0.0)),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(top_left_quad);
+
+        let top_right_pos_x = (layout.posx + layout.width) - border.left;
+        let top_right_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(top_right_pos_x, layout.posy),
+                    max: Vec2::new(top_right_pos_x + border.left, layout.posy + border.top),
+                },
+                uv_min: Some(Vec2::new(
+                    (image_size.x - border.left) / image_size.x,
+                    border.top / image_size.y,
+                )),
+                uv_max: Some(Vec2::new(1.0, 0.0)),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(top_right_quad);
+
+        let top_middle_pos_x = layout.posx + border.left;
+        let top_middle_size_x = layout.width - (border.left + border.right);
+        let top_middle_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(top_middle_pos_x, layout.posy),
+                    max: Vec2::new(
+                        top_middle_pos_x + top_middle_size_x,
+                        layout.posy + border.top,
+                    ),
+                },
+                uv_min: Some(Vec2::new(
+                    border.left / image_size.x,
+                    border.top / image_size.y,
+                )),
+                uv_max: Some(Vec2::new((image_size.x - border.left) / image_size.x, 0.0)),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(top_middle_quad);
+
+        // Bottom
+        let bottom_y_pos = layout.posy + (layout.width - border.bottom);
+        let bottom_left_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(layout.posx, bottom_y_pos),
+                    max: Vec2::new(layout.posx + border.left, bottom_y_pos + border.bottom),
+                },
+                uv_min: Some(Vec2::new(0.0, 1.0)),
+                uv_max: Some(Vec2::new(
+                    border.left / image_size.x,
+                    (image_size.y - border.bottom) / image_size.y,
+                )),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(bottom_left_quad);
+
+        let bottom_right_pos_x = (layout.posx + layout.width) - border.left;
+        let bottom_right_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(bottom_right_pos_x, bottom_y_pos),
+                    max: Vec2::new(bottom_right_pos_x + border.left, bottom_y_pos + border.top),
+                },
+                uv_min: Some(Vec2::new((image_size.x - border.left) / image_size.x, 1.0)),
+                uv_max: Some(Vec2::new(
+                    1.0,
+                    (image_size.y - border.bottom) / image_size.y,
+                )),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(bottom_right_quad);
+
+        let bottom_middle_pos_x = layout.posx + border.left;
+        let bottom_middle_size_x = layout.width - (border.left + border.right);
+        let bottom_middle_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(bottom_middle_pos_x, bottom_y_pos),
+                    max: Vec2::new(
+                        bottom_middle_pos_x + bottom_middle_size_x,
+                        bottom_y_pos + border.top,
+                    ),
+                },
+                uv_min: Some(Vec2::new(border.left / image_size.x, 1.0)),
+                uv_max: Some(Vec2::new(
+                    (image_size.x - border.left) / image_size.x,
+                    (image_size.y - border.bottom) / image_size.y,
+                )),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(bottom_middle_quad);
+
+        // Left + Right center
+        let left_middle_pos_y = layout.posy + border.top;
+        let left_middle_size_y = layout.height - (border.top + border.bottom);
+        let left_middle_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(layout.posx, left_middle_pos_y),
+                    max: Vec2::new(
+                        layout.posx + border.left,
+                        left_middle_pos_y + left_middle_size_y,
+                    ),
+                },
+                uv_min: Some(Vec2::new(
+                    0.0,
+                    (image_size.y - border.bottom) / image_size.y,
+                )),
+                uv_max: Some(Vec2::new(
+                    border.left / image_size.x,
+                    border.top / image_size.y,
+                )),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(left_middle_quad);
+
+        let right_middle_pos_x = layout.posx + (layout.width - border.right);
+        let right_middle_pos_y = layout.posy + border.top;
+        let right_middle_size_y = layout.height - (border.top + border.bottom);
+        let right_middle_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(right_middle_pos_x, right_middle_pos_y),
+                    max: Vec2::new(
+                        right_middle_pos_x + border.left,
+                        right_middle_pos_y + right_middle_size_y,
+                    ),
+                },
+                uv_min: Some(Vec2::new(
+                    (image_size.x - border.left) / image_size.x,
+                    (image_size.y - border.bottom) / image_size.y,
+                )),
+                uv_max: Some(Vec2::new(1.0, border.top / image_size.y)),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(right_middle_quad);
+
+        // Last quad in middle.
+        let middle_pos_x = layout.posx + border.left;
+        let middle_pos_y = layout.posy + border.top;
+        let middle_size_x = layout.width - (border.left + border.right);
+        let middle_size_y = layout.height - (border.top + border.bottom);
+        let middle_quad = ExtractQuadBundle {
+            extracted_quad: ExtractedQuad {
+                rect: Rect {
+                    min: Vec2::new(middle_pos_x, middle_pos_y),
+                    max: Vec2::new(middle_pos_x + middle_size_x, middle_pos_y + middle_size_y),
+                },
+                uv_min: Some(Vec2::new(
+                    border.left / image_size.x,
+                    border.bottom / image_size.y,
+                )),
+                uv_max: Some(Vec2::new(
+                    (image_size.x - border.right) / image_size.x,
+                    (image_size.y - border.bottom) / image_size.y,
+                )),
+                ..extracted_quad_template.clone()
+            },
+        };
+        extracted_quads.push(middle_quad);
+    }
+    commands.spawn_batch(extracted_quads);
+}
diff --git a/bevy_kayak_ui/src/render/unified/nine_patch/mod.rs b/bevy_kayak_ui/src/render/unified/nine_patch/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dfaec008a1ea982b0d6781f75d977a639ea2fc6b
--- /dev/null
+++ b/bevy_kayak_ui/src/render/unified/nine_patch/mod.rs
@@ -0,0 +1,15 @@
+use bevy::{
+    prelude::Plugin,
+    render2::{RenderApp, RenderStage},
+};
+
+mod extract;
+
+pub struct NinePatchRendererPlugin;
+
+impl Plugin for NinePatchRendererPlugin {
+    fn build(&self, app: &mut bevy::prelude::App) {
+        let render_app = app.sub_app(RenderApp);
+        render_app.add_system_to_stage(RenderStage::Extract, extract::extract_nine_patch);
+    }
+}
diff --git a/bevy_kayak_ui/src/render/unified/pipeline.rs b/bevy_kayak_ui/src/render/unified/pipeline.rs
index 483983c32551ca37914d26473d9c1e3b63fede5b..a787e7152ef7f32786639027c9b2d475acdfc320 100644
--- a/bevy_kayak_ui/src/render/unified/pipeline.rs
+++ b/bevy_kayak_ui/src/render/unified/pipeline.rs
@@ -4,7 +4,7 @@ use bevy::{
         lifetimeless::{Read, SQuery, SRes},
         SystemState,
     },
-    math::{const_vec3, Mat4, Quat, Vec3, Vec4},
+    math::{const_vec3, Mat4, Quat, Vec2, Vec3, Vec4},
     prelude::{Bundle, Component, Entity, FromWorld, Handle, Query, Res, ResMut, World},
     render2::{
         color::Color,
@@ -289,7 +289,7 @@ impl FromWorld for UnifiedPipeline {
     }
 }
 
-#[derive(Bundle)]
+#[derive(Debug, Bundle)]
 pub struct ExtractQuadBundle {
     pub(crate) extracted_quad: ExtractedQuad,
 }
@@ -301,7 +301,7 @@ pub enum UIQuadType {
     Image,
 }
 
-#[derive(Component)]
+#[derive(Debug, Component, Clone)]
 pub struct ExtractedQuad {
     pub rect: Rect,
     pub color: Color,
@@ -313,6 +313,8 @@ pub struct ExtractedQuad {
     pub type_index: u32,
     pub border_radius: (f32, f32, f32, f32),
     pub image: Option<Handle<Image>>,
+    pub uv_min: Option<Vec2>,
+    pub uv_max: Option<Vec2>,
 }
 
 #[repr(C)]
@@ -390,27 +392,30 @@ pub fn prepare_quads(
             UIQuadType::Image => extracted_sprite.type_index = image_type_offset,
         };
 
+        let uv_min = extracted_sprite.uv_min.unwrap_or(Vec2::ZERO);
+        let uv_max = extracted_sprite.uv_max.unwrap_or(Vec2::ONE);
+
         let bottom_left = Vec4::new(
-            0.0,
-            1.0,
+            uv_min.x,
+            uv_max.y,
             extracted_sprite.char_id as f32,
             extracted_sprite.border_radius.0,
         );
         let top_left = Vec4::new(
-            0.0,
-            0.0,
+            uv_min.x,
+            uv_min.y,
             extracted_sprite.char_id as f32,
             extracted_sprite.border_radius.1,
         );
         let top_right = Vec4::new(
-            1.0,
-            0.0,
+            uv_max.x,
+            uv_min.y,
             extracted_sprite.char_id as f32,
             extracted_sprite.border_radius.2,
         );
         let bottom_right = Vec4::new(
-            1.0,
-            1.0,
+            uv_max.x,
+            uv_max.y,
             extracted_sprite.char_id as f32,
             extracted_sprite.border_radius.3,
         );
diff --git a/bevy_kayak_ui/src/render/unified/quad/extract.rs b/bevy_kayak_ui/src/render/unified/quad/extract.rs
index b81a7d0d9e18bec63f715dc0d4b210e7b61487b9..1a9fcf4ebb4a6e9aec7dd775cb8e8ea78c412002 100644
--- a/bevy_kayak_ui/src/render/unified/quad/extract.rs
+++ b/bevy_kayak_ui/src/render/unified/quad/extract.rs
@@ -48,6 +48,8 @@ pub fn extract_quads(mut commands: Commands, context: Res<BevyContext>) {
                 type_index: 0,
                 border_radius: *border_radius,
                 image: None,
+                uv_max: None,
+                uv_min: None,
             },
         });
     }
diff --git a/bevy_kayak_ui/src/render/unified/shader.wgsl b/bevy_kayak_ui/src/render/unified/shader.wgsl
index 92b5463abf2909538a8d7ec6f1278d0961aa7f6d..c32bbb8bba67c9980d61303ece5c64507e576933 100644
--- a/bevy_kayak_ui/src/render/unified/shader.wgsl
+++ b/bevy_kayak_ui/src/render/unified/shader.wgsl
@@ -38,7 +38,6 @@ fn vertex(
     out.uv = vertex_uv.xyz;
     out.size = vertex_pos_size.zw;
     out.border_radius = vertex_uv.w;
-
     return out;
 }
 
diff --git a/examples/nine_patch.rs b/examples/nine_patch.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0a14f1a27c83ee6cbbd0787d724d51b7e6216c88
--- /dev/null
+++ b/examples/nine_patch.rs
@@ -0,0 +1,98 @@
+use bevy::{
+    math::Vec2,
+    prelude::{App as BevyApp, AssetServer, Commands, Handle, Res, ResMut},
+    window::{WindowDescriptor, Windows},
+    PipelinedDefaultPlugins,
+};
+use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, ImageManager, UICameraBundle};
+use kayak_components::NinePatch;
+use kayak_core::{
+    layout_cache::Space,
+    styles::{Style, StyleProp, Units},
+    Index,
+};
+use kayak_ui::components::App;
+use kayak_ui::core::rsx;
+
+fn startup(
+    mut commands: Commands,
+    windows: Res<Windows>,
+    asset_server: Res<AssetServer>,
+    mut image_manager: ResMut<ImageManager>,
+) {
+    commands.spawn_bundle(UICameraBundle::new());
+
+    let window_size = if let Some(window) = windows.get_primary() {
+        Vec2::new(window.width(), window.height())
+    } else {
+        panic!("Couldn't find primary window!");
+    };
+
+    let handle: Handle<bevy::render2::texture::Image> = asset_server.load("panel.png");
+    let ui_image_handle = image_manager.get(&handle);
+
+    let context = BevyContext::new(window_size.x, window_size.y, |styles, context| {
+        // Hack to trick the proc macro for right now..
+        let parent_id: Option<Index> = None;
+
+        // The border prop splits up the image into 9 quadrants like so:
+        // 1----2----3
+        // |         |
+        // 4    9    5
+        // |         |
+        // 6----7----8
+        // The sizes of sprites for a 15 pixel border are as follows:
+        // TopLeft = (15, 15)
+        // TopRight = (15, 15)
+        // LeftCenter = (15, image_height)
+        // RightCenter = (15, image_height)
+        // TopCenter = (image_width, 15)
+        // BottomCenter = (image_width, 15)
+        // BottomRight = (15, 15)
+        // BottomLeft = (15, 15)
+        // Middle = (
+        // 30 being left border + right border
+        //   image_width - 30
+        // 30 being top border + bottom border
+        //   image_height - 30
+        // )
+        //
+
+        let nine_patch_styles = Style {
+            width: StyleProp::Value(Units::Pixels(512.0)),
+            height: StyleProp::Value(Units::Pixels(512.0)),
+            ..Style::default()
+        };
+
+        rsx! {
+            <App styles={Some(styles.clone())}>
+                <NinePatch
+                    styles={Some(nine_patch_styles)}
+                    border={Space {
+                        left: 15.0,
+                        right: 15.0,
+                        top: 15.0,
+                        bottom: 15.0,
+                    }}
+                    handle={ui_image_handle}
+                />
+            </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(PipelinedDefaultPlugins)
+        .add_plugin(BevyKayakUIPlugin)
+        .add_startup_system(startup)
+        .run();
+}
diff --git a/kayak_components/src/lib.rs b/kayak_components/src/lib.rs
index 47472bd1936959d912f6b79d91d9e5cf40feb7c6..d59d930cb5444d0dce0afb700c63c784fa066f6e 100644
--- a/kayak_components/src/lib.rs
+++ b/kayak_components/src/lib.rs
@@ -3,6 +3,7 @@ mod background;
 mod button;
 mod clip;
 mod image;
+mod nine_patch;
 mod text;
 mod window;
 
@@ -11,5 +12,6 @@ pub use background::*;
 pub use button::*;
 pub use clip::*;
 pub use image::*;
+pub use nine_patch::*;
 pub use text::*;
 pub use window::*;
diff --git a/kayak_components/src/nine_patch.rs b/kayak_components/src/nine_patch.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8d652ab6ebd018b6f4de826fa0b99e491e2318b5
--- /dev/null
+++ b/kayak_components/src/nine_patch.rs
@@ -0,0 +1,24 @@
+use kayak_core::{
+    layout_cache::Space,
+    render_command::RenderCommand,
+    rsx,
+    styles::{Style, StyleProp},
+    widget, Children,
+};
+
+#[widget]
+pub fn NinePatch(handle: u16, border: Space, children: Children) {
+    *styles = Some(Style {
+        render_command: StyleProp::Value(RenderCommand::NinePatch {
+            handle: *handle,
+            border: *border,
+        }),
+        ..styles.clone().unwrap_or_default()
+    });
+
+    rsx! {
+        <>
+            {children}
+        </>
+    }
+}
diff --git a/kayak_core/src/layout_cache.rs b/kayak_core/src/layout_cache.rs
index c843be7332f35973d4dd37bd0a0d705ab5925962..9b31c08c8b4bfdedb474ceec4c6843787e504380 100644
--- a/kayak_core/src/layout_cache.rs
+++ b/kayak_core/src/layout_cache.rs
@@ -20,7 +20,7 @@ impl Rect {
     }
 }
 
-#[derive(Debug, Default, Clone, Copy)]
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
 pub struct Space {
     pub left: f32,
     pub right: f32,
diff --git a/kayak_core/src/render_command.rs b/kayak_core/src/render_command.rs
index cb6839f829e54b80adcb4910028ed9ed6084ef12..c717a2f23aa8706a9bb8a17c163f1fdb668c59f4 100644
--- a/kayak_core/src/render_command.rs
+++ b/kayak_core/src/render_command.rs
@@ -1,3 +1,5 @@
+use crate::layout_cache::Space;
+
 #[derive(Debug, Clone, PartialEq)]
 pub enum RenderCommand {
     Empty,
@@ -12,6 +14,10 @@ pub enum RenderCommand {
     Image {
         handle: u16,
     },
+    NinePatch {
+        border: Space,
+        handle: u16,
+    },
 }
 
 impl Default for RenderCommand {
diff --git a/kayak_core/src/render_primitive.rs b/kayak_core/src/render_primitive.rs
index 457c75e4cfb5791651a653b2847281255fe519fc..54b3224809697e9b06401be84786874482bed734 100644
--- a/kayak_core/src/render_primitive.rs
+++ b/kayak_core/src/render_primitive.rs
@@ -1,6 +1,6 @@
 use crate::{
     color::Color,
-    layout_cache::Rect,
+    layout_cache::{Rect, Space},
     render_command::RenderCommand,
     styles::{Style, StyleProp},
 };
@@ -27,6 +27,11 @@ pub enum RenderPrimitive {
         layout: Rect,
         handle: u16,
     },
+    NinePatch {
+        border: Space,
+        layout: Rect,
+        handle: u16,
+    },
 }
 
 impl RenderPrimitive {
@@ -36,6 +41,7 @@ impl RenderPrimitive {
             RenderPrimitive::Quad { layout, .. } => *layout = new_layout,
             RenderPrimitive::Text { layout, .. } => *layout = new_layout,
             RenderPrimitive::Image { layout, .. } => *layout = new_layout,
+            RenderPrimitive::NinePatch { layout, .. } => *layout = new_layout,
             _ => (),
         }
     }
@@ -77,6 +83,11 @@ impl From<&Style> for RenderPrimitive {
                 layout: Rect::default(),
                 handle,
             },
+            RenderCommand::NinePatch { handle, border } => Self::NinePatch {
+                border,
+                layout: Rect::default(),
+                handle,
+            },
         }
     }
 }