diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Extensions.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Extensions.kt
index 17cc21832a8c7b0197aca179b3ea3182c11c9c09..114c8c396472eee9dbbbb866403a19aeeeea9573 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Extensions.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/credentials/Extensions.kt
@@ -2,4 +2,4 @@ package net.novagamestudios.kaffeekasse.model.credentials
 
 val Login.isValid get() = username.isNotBlank() && password.isNotBlank()
 
-val DeviceCredentials.isValid get() = deviceId.isNotBlank() && apiKey.isNotBlank()
+val DeviceCredentials.isValid get() = deviceId.isNotBlank() && apiKey.isNotBlank() && deviceId.length == ((2 + 1) * 6 - 1)
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt
index e37cd50dd913eb3317271762884988fcf1e904ce..a5431dd3538a006ac705e2d39dba9008b3448cb3 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt
@@ -1,24 +1,35 @@
 package net.novagamestudios.kaffeekasse.ui.login
 
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Key
 import androidx.compose.material.icons.filled.PhonelinkErase
+import androidx.compose.material.icons.filled.Refresh
 import androidx.compose.material.icons.filled.ScreenLockLandscape
 import androidx.compose.material.icons.filled.SmartScreen
+import androidx.compose.material.icons.rounded.Warning
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
+import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -28,19 +39,28 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import net.novagamestudios.common_utils.compose.Toasts
+import net.novagamestudios.common_utils.compose.ToastsState
 import net.novagamestudios.common_utils.compose.components.CircularLoadingBox
+import net.novagamestudios.common_utils.compose.components.ColumnCenter
 import net.novagamestudios.common_utils.compose.state.collectAsStateIn
 import net.novagamestudios.common_utils.voyager.nearestScreen
 import net.novagamestudios.kaffeekasse.model.credentials.DeviceCredentials
 import net.novagamestudios.kaffeekasse.model.credentials.isValid
 import net.novagamestudios.kaffeekasse.repositories.LoginRepository
 import net.novagamestudios.kaffeekasse.ui.navigation.LoginNavigation
+import net.novagamestudios.kaffeekasse.ui.theme.disabled
+import net.novagamestudios.kaffeekasse.ui.util.MacAddress
 import net.novagamestudios.kaffeekasse.ui.util.rememberSerializableState
+import java.util.LinkedList
+import kotlin.math.absoluteValue
+import kotlin.random.Random
 
 
 class LoginDeviceState(
@@ -113,6 +133,9 @@ private fun LoginDeviceDialog(
                 Text("Abbrechen")
             }
         },
+        title = {
+            Text("Gerät einloggen")
+        },
         text = {
             CircularLoadingBox(loading = isLoading) {
                 LoginDeviceForm(
@@ -131,6 +154,7 @@ private fun LoginDeviceForm(
     modifier: Modifier = Modifier
 ) {
     var input by inputState
+
     Column(
         modifier.width(IntrinsicSize.Min),
         verticalArrangement = Arrangement.spacedBy(8.dp),
@@ -138,11 +162,13 @@ private fun LoginDeviceForm(
     ) {
         val focusManager = LocalFocusManager.current
         OutlinedTextField(
-            value = input.deviceId,
-            onValueChange = { input = input.copy(deviceId = it.uppercase()) },
+            value = with(MacAddress) { input.deviceId.compress() },
+            onValueChange = {
+                input = input.copy(deviceId = with(MacAddress) { it.decompress() })
+            },
             label = { Text("Geräte-Id") },
-            placeholder = { Text("XX:XX:XX:XX:XX:XX") },
             leadingIcon = { Icon(Icons.Default.SmartScreen, "Geräte-Id") },
+            visualTransformation = MacAddress.VisualTransformation(placeholderColor = LocalContentColor.current.disabled()),
             keyboardOptions = KeyboardOptions(
                 keyboardType = KeyboardType.Text,
                 imeAction = ImeAction.Next
@@ -150,6 +176,7 @@ private fun LoginDeviceForm(
             keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }),
             singleLine = true
         )
+
         OutlinedTextField(
             value = input.apiKey,
             onValueChange = { input = input.copy(apiKey = it) },
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/MacAddress.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/MacAddress.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8e4a1c78ff17c8f0af9d286bbb988eefc70b711f
--- /dev/null
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/MacAddress.kt
@@ -0,0 +1,70 @@
+package net.novagamestudios.kaffeekasse.ui.util
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.input.TransformedText
+
+object MacAddress {
+
+    private const val ChunkCount = 6
+    private const val ChunkLength = 2
+    private const val ChunkSeparator = ":"
+
+    fun String.compress() = this
+        .filter { it.isLetterOrDigit() }
+        .take(ChunkCount * ChunkLength)
+        .uppercase()
+
+    private fun String.chunks() = this
+        .compress()
+        .chunked(ChunkLength)
+
+    fun String.decompress(): String {
+        return chunks().joinToString(ChunkSeparator)
+    }
+
+    val String.isValid: Boolean get() {
+        return compress().length == ChunkCount * ChunkLength
+    }
+
+    class VisualTransformation(
+        private val placeholderColor: Color,
+        private val placeholder: Char = 'X'
+    ) : androidx.compose.ui.text.input.VisualTransformation {
+        override fun filter(original: AnnotatedString): TransformedText {
+            val chunks = original.text.chunks()
+            val transformed = AnnotatedString.Builder().apply {
+                for (i in 0 until ChunkCount) {
+                    val chunk = chunks.getOrElse(i) { "" }
+                    append(chunk)
+                    val missing = ChunkLength - chunk.length
+                    if (missing > 0) {
+                        pushStyle(SpanStyle(color = placeholderColor))
+                        append("$placeholder".repeat(missing))
+                        pop()
+                    }
+                    if (i < ChunkCount - 1) {
+                        append(ChunkSeparator)
+                    }
+                }
+            }.toAnnotatedString()
+
+            return TransformedText(
+                text = transformed,
+                offsetMapping = OffsetMapping(original.text)
+            )
+        }
+    }
+
+    private class OffsetMapping(
+        private val originalText: String
+    ) : androidx.compose.ui.text.input.OffsetMapping {
+        override fun originalToTransformed(offset: Int): Int {
+            return (offset + offset / ChunkLength).coerceIn(0, (ChunkLength + 1) * ChunkCount - 1)
+        }
+        override fun transformedToOriginal(offset: Int): Int {
+            return (offset - offset / (ChunkLength + 1)).coerceIn(0, originalText.length)
+        }
+    }
+}