diff --git a/visions/ui/Taskfile.yml b/visions/ui/Taskfile.yml
index 88e9d26..90212f2 100644
--- a/visions/ui/Taskfile.yml
+++ b/visions/ui/Taskfile.yml
@@ -4,3 +4,4 @@ tasks:
   dev:
     cmds:
       - trunk serve --open
+
diff --git a/visions/ui/src/components/mod.rs b/visions/ui/src/components/mod.rs
new file mode 100644
index 0000000..e69de29
diff --git a/visions/ui/src/main.rs b/visions/ui/src/main.rs
index 1b09023..5044058 100644
--- a/visions/ui/src/main.rs
+++ b/visions/ui/src/main.rs
@@ -2,13 +2,14 @@ use std::rc::Rc;
 
 use gloo_console::log;
 use visions_types::{AuthResponse, SessionId, UserOverview};
-use wasm_bindgen::JsCast;
-use web_sys::HtmlInputElement;
 use yew::prelude::*;
 
 mod client;
 use client::*;
 
+mod views;
+use views::Login;
+
 struct AuthInfo {
     session_id: Option<SessionId>,
 }
@@ -39,59 +40,6 @@ impl Reducible for AuthInfo {
     }
 }
 
-#[derive(Properties, PartialEq)]
-struct LoginProps {
-    on_login: Callback<(String, String)>,
-}
-
-#[function_component]
-fn Login(LoginProps { on_login }: &LoginProps) -> Html {
-    let username = use_state(|| "".to_owned());
-    let password = use_state(|| "".to_owned());
-
-    let on_click = {
-        let on_login = on_login.clone();
-        let username = username.clone();
-        let password = password.clone();
-        Callback::from(move |_| on_login.emit((username.to_string(), password.to_string())))
-    };
-
-    let on_username_changed = {
-        let username = username.clone();
-        Callback::from(move |event: Event| {
-            let input = event
-                .target()
-                .and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
-            if let Some(input) = input {
-                username.set(input.value());
-            }
-        })
-    };
-
-    let on_password_changed = {
-        let password = password.clone();
-        Callback::from(move |event: Event| {
-            let input = event
-                .target()
-                .and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
-            if let Some(input) = input {
-                password.set(input.value());
-            }
-        })
-    };
-
-    html! {
-        <div class="login-form">
-            <div class="card">
-                <h1>{"Welcome to Visions VTT"}</h1>
-                <input type="text" name="username" placeholder="username" onchange={on_username_changed} />
-                <input type="password" name="password" placeholder="password" onchange={on_password_changed} />
-                <button onclick={on_click}>{"Login"}</button>
-            </div>
-        </div>
-    }
-}
-
 #[derive(Properties, PartialEq)]
 struct LandingProps {
     session_id: SessionId,
diff --git a/visions/ui/src/views/login.rs b/visions/ui/src/views/login.rs
new file mode 100644
index 0000000..93f0e1e
--- /dev/null
+++ b/visions/ui/src/views/login.rs
@@ -0,0 +1,58 @@
+use wasm_bindgen::JsCast;
+use web_sys::HtmlInputElement;
+use yew::{function_component, html, use_state, Callback, Event, Html, Properties};
+
+#[derive(Properties, PartialEq)]
+pub struct LoginProps {
+    pub on_login: Callback<(String, String)>,
+}
+
+#[function_component]
+pub fn Login(LoginProps { on_login }: &LoginProps) -> Html {
+    let username = use_state(|| "".to_owned());
+    let password = use_state(|| "".to_owned());
+
+    let on_click = {
+        let on_login = on_login.clone();
+        let username = username.clone();
+        let password = password.clone();
+        Callback::from(move |_| on_login.emit((username.to_string(), password.to_string())))
+    };
+
+    let on_username_changed = {
+        let username = username.clone();
+        Callback::from(move |event: Event| {
+            let input = event
+                .target()
+                .and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
+            if let Some(input) = input {
+                username.set(input.value());
+            }
+        })
+    };
+
+    let on_password_changed = {
+        let password = password.clone();
+        Callback::from(move |event: Event| {
+            let input = event
+                .target()
+                .and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
+            if let Some(input) = input {
+                password.set(input.value());
+            }
+        })
+    };
+
+    html! {
+        <div class="login-form">
+            <div class="card">
+                <h1>{"Welcome to Visions VTT"}</h1>
+                <input type="text" name="username" placeholder="username" onchange={on_username_changed} />
+                <input type="password" name="password" placeholder="password" onchange={on_password_changed} />
+                <button onclick={on_click}>{"Login"}</button>
+            </div>
+        </div>
+    }
+}
+
+
diff --git a/visions/ui/src/views/mod.rs b/visions/ui/src/views/mod.rs
new file mode 100644
index 0000000..bba0880
--- /dev/null
+++ b/visions/ui/src/views/mod.rs
@@ -0,0 +1,2 @@
+mod login;
+pub use login::Login;