diff --git a/.idea/runConfigurations/Briar_Desktop.xml b/.idea/runConfigurations/Briar_Desktop.xml
index 2a3884ba8a55285a39f5910e0dd8787cce559eef..ddf5232dce5cc4d1aec65ea54a1bcb5eaf130412 100644
--- a/.idea/runConfigurations/Briar_Desktop.xml
+++ b/.idea/runConfigurations/Briar_Desktop.xml
@@ -1,7 +1,8 @@
 <component name="ProjectRunConfigurationManager">
   <configuration default="false" name="Briar Desktop" type="JetRunConfigurationType">
-    <option name="MAIN_CLASS_NAME" value="org.briarproject.briar.compose.MainKt" />
+    <option name="MAIN_CLASS_NAME" value="org.briarproject.briar.desktop.MainKt" />
     <module name="briar-desktop.main" />
+    <option name="PROGRAM_PARAMETERS" value="--debug" />
     <method v="2">
       <option name="Make" enabled="true" />
     </method>
diff --git a/build.gradle.kts b/build.gradle.kts
index 04454a24543f9cf83a0a6a1ed887a1699465f07b..7c576af444ffed2603f1d3b905e446384f972d7a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,9 +3,9 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
-    kotlin("jvm") version "1.4.30"
+    kotlin("jvm") version "1.5.10"
     kotlin("kapt") version "1.3.72"
-    id("org.jetbrains.compose") version "0.3.1"
+    id("org.jetbrains.compose") version "0.4.0"
     id("java")
     id("idea")
 }
@@ -20,13 +20,18 @@ repositories {
 }
 
 dependencies {
-    testImplementation(kotlin("test-testng"))
     implementation(compose.desktop.currentOs)
+
     implementation("com.fasterxml.jackson.core:jackson-databind:2.10.0")
+    implementation("com.github.ajalt:clikt:2.2.0")
+
     implementation(project(path = ":briar:briar-core", configuration = "default"))
     implementation(project(path = ":briar:bramble-java", configuration = "default"))
+
     val daggerVersion = "2.24"
     kapt("com.google.dagger:dagger-compiler:$daggerVersion")
+
+    testImplementation(kotlin("test-testng"))
 }
 
 tasks.test {
diff --git a/src/main/kotlin/org/briarproject/briar/compose/BriarService.kt b/src/main/kotlin/org/briarproject/briar/compose/BriarService.kt
deleted file mode 100644
index 5a017de5a52445b191e110682931e3f9b13dc25f..0000000000000000000000000000000000000000
--- a/src/main/kotlin/org/briarproject/briar/compose/BriarService.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.briarproject.briar.compose
-
-import org.briarproject.bramble.api.account.AccountManager
-import org.briarproject.bramble.api.crypto.DecryptionException
-import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator
-import org.briarproject.bramble.api.lifecycle.LifecycleManager
-import javax.annotation.concurrent.Immutable
-import javax.inject.Inject
-import javax.inject.Singleton
-import javax.swing.JOptionPane
-
-interface BriarService {
-    fun start()
-    fun stop()
-}
-
-@Immutable
-@Singleton
-internal class BriarServiceImpl
-@Inject
-constructor(
-    private val accountManager: AccountManager,
-    private val lifecycleManager: LifecycleManager,
-    private val passwordStrengthEstimator: PasswordStrengthEstimator
-) : BriarService {
-
-    override fun start() {
-        if (!accountManager.accountExists()) {
-            createAccount();
-        } else {
-            while (true) {
-//                val password = PasswordPrompt.promptForPassword();
-//                if (!password.isValid) {
-//                    // this happens when dismissing the dialog or clicking 'cancel'
-//                    exitProcess(1)
-//                }
-                val password = "sdifjasdjhfksjadf"
-                try {
-                    accountManager.signIn(password)
-                    break
-                } catch (e: DecryptionException) {
-                    JOptionPane.showMessageDialog(
-                        null, "Wrong password",
-                        "Error", JOptionPane.ERROR_MESSAGE
-                    )
-                }
-            }
-        }
-        val dbKey = accountManager.databaseKey ?: throw AssertionError()
-        lifecycleManager.startServices(dbKey)
-        lifecycleManager.waitForStartup()
-    }
-
-    override fun stop() {
-        lifecycleManager.stopServices()
-        lifecycleManager.waitForShutdown()
-    }
-
-    private fun createAccount() {
-//        echo("No account found. Let's create one!\n\n")
-//        val result = NewAccountPrompt.promptForDetails();
-//        if (!result.isValid) {
-//            echo("Error: Please enter a username and password")
-//            exitProcess(1)
-//        }
-//        try {
-//            check(passwordStrengthEstimator, result)
-//        } catch (e: UsageError) {
-//            return;
-//        }
-
-        accountManager.createAccount("Nico", "sdifjasdjhfksjadf")
-    }
-
-}
diff --git a/src/main/kotlin/org/briarproject/briar/compose/main.kt b/src/main/kotlin/org/briarproject/briar/compose/main.kt
deleted file mode 100644
index 773691a753453ab72b8a99805e8379e7fc00873a..0000000000000000000000000000000000000000
--- a/src/main/kotlin/org/briarproject/briar/compose/main.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.briarproject.briar.compose
-
-import org.briarproject.bramble.BrambleCoreEagerSingletons
-import org.briarproject.briar.BriarCoreEagerSingletons
-import java.io.File
-import java.io.File.separator
-import java.io.IOException
-import java.lang.System.getProperty
-import java.nio.file.Files.setPosixFilePermissions
-import java.nio.file.attribute.PosixFilePermission
-import java.util.logging.Level
-import java.util.logging.LogManager
-
-fun main() {
-    LogManager.getLogManager().getLogger("").level = Level.INFO
-
-    val dataDir = getDataDir()
-    val app =
-        DaggerBriarSwingApp.builder().swingModule(
-            SwingModule(
-                dataDir
-            )
-        ).build()
-    // We need to load the eager singletons directly after making the
-    // dependency graphs
-    BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app)
-    BriarCoreEagerSingletons.Helper.injectEagerSingletons(app)
-
-    app.getUI().startBriar()
-    app.getUI().startUI()
-}
-
-private fun getDataDir(): File {
-    val file = File(getProperty("user.home") + separator + ".briar")
-    if (!file.exists() && !file.mkdirs()) {
-        throw IOException("Could not create directory: ${file.absolutePath}")
-    } else if (!file.isDirectory) {
-        throw IOException("Data dir is not a directory: ${file.absolutePath}")
-    }
-    val perms = HashSet<PosixFilePermission>()
-    perms.add(PosixFilePermission.OWNER_READ)
-    perms.add(PosixFilePermission.OWNER_WRITE)
-    perms.add(PosixFilePermission.OWNER_EXECUTE)
-    setPosixFilePermissions(file.toPath(), perms)
-    return file
-}
\ No newline at end of file
diff --git a/src/main/kotlin/org/briarproject/briar/compose/BriarSwingApp.kt b/src/main/kotlin/org/briarproject/briar/desktop/BriarDesktopApp.kt
similarity index 74%
rename from src/main/kotlin/org/briarproject/briar/compose/BriarSwingApp.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/BriarDesktopApp.kt
index 74737afbc7c2dea9efb8d94cf437e3309ae5fa00..dfc7a8889d33c255143f6e7546bb364c05c87a63 100644
--- a/src/main/kotlin/org/briarproject/briar/compose/BriarSwingApp.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/BriarDesktopApp.kt
@@ -1,4 +1,4 @@
-package org.briarproject.briar.compose
+package org.briarproject.briar.desktop
 
 import dagger.Component
 import org.briarproject.bramble.BrambleCoreEagerSingletons
@@ -12,11 +12,11 @@ import javax.inject.Singleton
     modules = [
         BrambleCoreModule::class,
         BriarCoreModule::class,
-        SwingModule::class
+        DesktopModule::class
     ]
 )
 @Singleton
-internal interface BriarSwingApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons {
+internal interface BriarDesktopApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons {
 
     fun getUI(): UI
 
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..aa277f516012bf86ff48aa06a690f7e91a95dabe
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt
@@ -0,0 +1,48 @@
+package org.briarproject.briar.desktop
+
+import org.briarproject.bramble.api.account.AccountManager
+import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator
+import org.briarproject.bramble.api.lifecycle.LifecycleManager
+import org.briarproject.briar.desktop.dialogs.Login
+import org.briarproject.briar.desktop.dialogs.Registration
+import javax.annotation.concurrent.Immutable
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface BriarService {
+    fun start()
+    fun stop()
+}
+
+@Immutable
+@Singleton
+internal class BriarServiceImpl
+@Inject
+constructor(
+    private val accountManager: AccountManager,
+    private val lifecycleManager: LifecycleManager,
+    private val passwordStrengthEstimator: PasswordStrengthEstimator
+) : BriarService {
+
+    override fun start() {
+        if (!accountManager.accountExists()) {
+            createAccount()
+        } else {
+            login()
+        }
+    }
+
+    override fun stop() {
+        lifecycleManager.stopServices()
+        lifecycleManager.waitForShutdown()
+    }
+
+    private fun createAccount() {
+        print("No account found. Let's create one!\n\n")
+        Registration("Briar", accountManager, lifecycleManager)
+    }
+
+    private fun login() {
+        Login("Briar", accountManager, lifecycleManager)
+    }
+}
diff --git a/src/main/kotlin/org/briarproject/briar/compose/SwingDatabaseConfig.kt b/src/main/kotlin/org/briarproject/briar/desktop/DesktopDatabaseConfig.kt
similarity index 71%
rename from src/main/kotlin/org/briarproject/briar/compose/SwingDatabaseConfig.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/DesktopDatabaseConfig.kt
index 34154addc0b93817d4a0663b5eaddb1d668cbe31..8b936f047530c77102c45c9e7440292c603ddc0d 100644
--- a/src/main/kotlin/org/briarproject/briar/compose/SwingDatabaseConfig.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/DesktopDatabaseConfig.kt
@@ -1,10 +1,10 @@
-package org.briarproject.briar.compose
+package org.briarproject.briar.desktop
 
 import org.briarproject.bramble.api.crypto.KeyStrengthener
 import org.briarproject.bramble.api.db.DatabaseConfig
 import java.io.File
 
-internal class SwingDatabaseConfig(private val dbDir: File, private val keyDir: File) :
+internal class DesktopDatabaseConfig(private val dbDir: File, private val keyDir: File) :
     DatabaseConfig {
 
     override fun getDatabaseDirectory() = dbDir
diff --git a/src/main/kotlin/org/briarproject/briar/compose/SwingModule.kt b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt
similarity index 95%
rename from src/main/kotlin/org/briarproject/briar/compose/SwingModule.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt
index cfa67a1e1293c5d816bab8a3b74a30876c19d876..a196e6e1dd1357abf5ea7f4e63059c803a597ef5 100644
--- a/src/main/kotlin/org/briarproject/briar/compose/SwingModule.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt
@@ -1,4 +1,4 @@
-package org.briarproject.briar.compose
+package org.briarproject.briar.desktop
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import dagger.Module
@@ -41,7 +41,7 @@ import javax.inject.Singleton
         SocksModule::class
     ]
 )
-internal class SwingModule(private val appDir: File) {
+internal class DesktopModule(private val appDir: File) {
 
     @Provides
     @Singleton
@@ -52,7 +52,7 @@ internal class SwingModule(private val appDir: File) {
     internal fun provideDatabaseConfig(): DatabaseConfig {
         val dbDir = File(appDir, "db")
         val keyDir = File(appDir, "key")
-        return SwingDatabaseConfig(dbDir, keyDir)
+        return DesktopDatabaseConfig(dbDir, keyDir)
     }
 
     @Provides
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/Main.kt b/src/main/kotlin/org/briarproject/briar/desktop/Main.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a7006b69f91abe67c0470fb6e070d1628bd57f16
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/Main.kt
@@ -0,0 +1,80 @@
+package org.briarproject.briar.desktop
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.options.counted
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import org.briarproject.bramble.BrambleCoreEagerSingletons
+import org.briarproject.briar.BriarCoreEagerSingletons
+import java.io.File
+import java.io.File.separator
+import java.io.IOException
+import java.lang.System.getProperty
+import java.nio.file.Files.setPosixFilePermissions
+import java.nio.file.attribute.PosixFilePermission
+import java.nio.file.attribute.PosixFilePermission.*
+import java.util.logging.Level.*
+import java.util.logging.LogManager
+
+private val DEFAULT_DATA_DIR = getProperty("user.home") + separator + ".briar"
+
+private class Main : CliktCommand(
+    name = "briar-desktop",
+    help = "Briar Desktop Client"
+) {
+    private val debug by option("--debug", "-d", help = "Enable printing of debug messages").flag(
+        default = false
+    )
+    private val verbosity by option(
+        "--verbose",
+        "-v",
+        help = "Print verbose log messages"
+    ).counted()
+    private val dataDir by option(
+        "--data-dir",
+        help = "The directory where Briar will store its files. Default: $DEFAULT_DATA_DIR",
+        metavar = "PATH",
+        envvar = "BRIAR_DATA_DIR"
+    ).default(DEFAULT_DATA_DIR)
+
+    override fun run() {
+        val level = if (debug) ALL else when (verbosity) {
+            0 -> WARNING
+            1 -> INFO
+            else -> ALL
+        }
+
+        LogManager.getLogManager().getLogger("").level = level
+
+        val dataDir = getDataDir()
+        val app =
+            DaggerBriarDesktopApp.builder().desktopModule(
+                DesktopModule(dataDir)
+            ).build()
+        // We need to load the eager singletons directly after making the
+        // dependency graphs
+        BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app)
+        BriarCoreEagerSingletons.Helper.injectEagerSingletons(app)
+
+        app.getUI().startBriar()
+        app.getUI().startUI()
+    }
+
+    private fun getDataDir(): File {
+        val file = File(dataDir)
+        if (!file.exists() && !file.mkdirs()) {
+            throw IOException("Could not create directory: ${file.absolutePath}")
+        } else if (!file.isDirectory) {
+            throw IOException("Data dir is not a directory: ${file.absolutePath}")
+        }
+        val perms = HashSet<PosixFilePermission>()
+        perms.add(OWNER_READ)
+        perms.add(OWNER_WRITE)
+        perms.add(OWNER_EXECUTE)
+        setPosixFilePermissions(file.toPath(), perms)
+        return file
+    }
+}
+
+fun main(args: Array<String>) = Main().main(args)
\ No newline at end of file
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/MainUI.kt b/src/main/kotlin/org/briarproject/briar/desktop/MainUI.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1c4e79540233f55da682bbf752d06511a088ca2a
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/MainUI.kt
@@ -0,0 +1,37 @@
+package org.briarproject.briar.desktop
+
+import org.briarproject.bramble.api.account.AccountManager
+import org.briarproject.bramble.api.contact.ContactManager
+import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator
+import org.briarproject.bramble.api.event.EventBus
+import org.briarproject.briar.api.conversation.ConversationManager
+import org.briarproject.briar.api.introduction.IntroductionManager
+import org.briarproject.briar.api.messaging.MessagingManager
+import org.briarproject.briar.api.messaging.PrivateMessageFactory
+
+class MainUI(
+    private val briarService: BriarService,
+    val accountManager: AccountManager,
+    val contactManager: ContactManager,
+    private val conversationManager: ConversationManager,
+    private val messagingManager: MessagingManager,
+    private val introductionManager: IntroductionManager,
+    private val privateMessageFactory: PrivateMessageFactory,
+    private val eventBus: EventBus,
+    val passwordStrengthEstimator: PasswordStrengthEstimator
+) {
+
+    init {
+        // Should be shown only when logged in
+//        val title = "Briar Desktop"
+//        Window (title = title) {
+//            Column(
+//                modifier = Modifier.padding(16.dp).fillMaxSize(),
+//                verticalArrangement = Arrangement.Center,
+//                horizontalAlignment = Alignment.CenterHorizontally
+//            ) {
+//                Text("Welcome to Briar")
+//            }
+//        }
+    }
+}
diff --git a/src/main/kotlin/org/briarproject/briar/compose/UI.kt b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt
similarity index 51%
rename from src/main/kotlin/org/briarproject/briar/compose/UI.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/UI.kt
index 791e128bb66d6b5dc8a1220fde2fd3ed5c9f2cb5..957e2d004a6100a36fb83800b95f5d6705ff9e04 100644
--- a/src/main/kotlin/org/briarproject/briar/compose/UI.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt
@@ -1,16 +1,5 @@
-package org.briarproject.briar.compose
+package org.briarproject.briar.desktop
 
-import androidx.compose.desktop.Window
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Button
-import androidx.compose.material.Text
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.res.svgResource
-import androidx.compose.ui.unit.dp
 import org.briarproject.bramble.api.account.AccountManager
 import org.briarproject.bramble.api.contact.ContactManager
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator
@@ -41,54 +30,27 @@ constructor(
 ) {
 
     private val logger = getLogger(UI::javaClass.name)
-//    private val configuration = Configuration()
 
     internal fun startBriar() {
         briarService.start();
     }
 
     internal fun startUI() {
-        Window {
-            Column(
-                modifier = Modifier.padding(16.dp)
-            ) {
-                TheImage()
-                Spacer(Modifier.height(32.dp))
-                TheText()
-                TheButton()
-            }
-        }
+        MainUI(
+            briarService,
+            accountManager,
+            contactManager,
+            conversationManager,
+            messagingManager,
+            introductionManager,
+            privateMessageFactory,
+            eventBus,
+            passwordStrengthEstimator
+        )
     }
 
     internal fun getContactManager(): ContactManager {
         return contactManager
     }
 
-}
-
-
-@Composable
-private fun TheButton() {
-    var text by remember { mutableStateOf("Start chatting") }
-    Button(onClick = {
-        text = "Sorry, not yet available"
-    }) {
-        Text(text)
-    }
-}
-
-@Composable
-private fun TheImage() {
-    Image(
-        painter = svgResource("images/logo_circle.svg"),
-        contentDescription = "Briar logo",
-        modifier = Modifier
-            .fillMaxWidth()
-            .clip(shape = RoundedCornerShape(400.dp))
-    )
-}
-
-@Composable
-private fun TheText() {
-    Text("Welcome to Briar")
-}
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/dialogs/Login.kt b/src/main/kotlin/org/briarproject/briar/desktop/dialogs/Login.kt
new file mode 100644
index 0000000000000000000000000000000000000000..24c8546caa630a47f9bd27b6d2ad99b49ec6373c
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/dialogs/Login.kt
@@ -0,0 +1,58 @@
+package org.briarproject.briar.desktop.dialogs
+
+import androidx.compose.desktop.Window
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.svgResource
+import androidx.compose.ui.unit.dp
+import org.briarproject.bramble.api.account.AccountManager
+import org.briarproject.bramble.api.lifecycle.LifecycleManager
+
+// TODO: Error handling
+fun Login(title: String, accountManager: AccountManager, lifecycleManager: LifecycleManager) =
+    Window(title = title) {
+        Column(
+            modifier = Modifier.padding(16.dp).fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            TheImage()
+            Spacer(Modifier.height(32.dp))
+            TheTextField(accountManager, lifecycleManager)
+        }
+    }
+
+@Composable
+private fun TheImage() {
+    Image(
+        painter = svgResource("images/logo_circle.svg"),
+        contentDescription = "Briar logo",
+        modifier = Modifier
+            .fillMaxWidth()
+            .clip(shape = RoundedCornerShape(400.dp))
+    )
+}
+
+@Composable
+private fun TheTextField(accountManager: AccountManager, lifecycleManager: LifecycleManager) {
+    var password by remember { mutableStateOf("") }
+    OutlinedTextField(password, { password = it }, label = { Text("Password") })
+    Spacer(Modifier.height(16.dp))
+    Button(onClick = {
+        accountManager.signIn(password)
+
+        val dbKey = accountManager.databaseKey ?: throw AssertionError()
+        lifecycleManager.startServices(dbKey)
+        lifecycleManager.waitForStartup()
+    }) {
+        Text("Login")
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/dialogs/Registration.kt b/src/main/kotlin/org/briarproject/briar/desktop/dialogs/Registration.kt
new file mode 100644
index 0000000000000000000000000000000000000000..aba87a41f5e28cd8a609a6d413de927c89e0a528
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/dialogs/Registration.kt
@@ -0,0 +1,59 @@
+package org.briarproject.briar.desktop.dialogs
+
+import androidx.compose.desktop.Window
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.svgResource
+import androidx.compose.ui.unit.dp
+import org.briarproject.bramble.api.account.AccountManager
+import org.briarproject.bramble.api.lifecycle.LifecycleManager
+
+// TODO: Error handling and password strength
+fun Registration(title: String, accountManager: AccountManager, lifecycleManager: LifecycleManager) =
+    Window(title = title) {
+        Column(
+            modifier = Modifier.padding(16.dp).fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            TheImage()
+            Spacer(Modifier.height(32.dp))
+            TheTextFields(accountManager, lifecycleManager)
+        }
+    }
+
+@Composable
+private fun TheImage() {
+    Image(
+        painter = svgResource("images/logo_circle.svg"),
+        contentDescription = "Briar logo",
+        modifier = Modifier
+            .fillMaxWidth()
+            .clip(shape = RoundedCornerShape(400.dp))
+    )
+}
+
+@Composable
+private fun TheTextFields(accountManager: AccountManager, lifecycleManager: LifecycleManager) {
+    var username by remember { mutableStateOf("") }
+    var password by remember { mutableStateOf("") }
+    OutlinedTextField(username, { username = it }, label = { Text("Username") })
+    OutlinedTextField(password, { password = it }, label = { Text("Password") })
+    Spacer(Modifier.height(16.dp))
+    Button(onClick = {
+        accountManager.createAccount(username, password)
+        val dbKey = accountManager.databaseKey ?: throw AssertionError()
+        lifecycleManager.startServices(dbKey)
+        lifecycleManager.waitForStartup()
+    }) {
+        Text("Register")
+    }
+}
\ No newline at end of file