diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt
index e66b88535187b55b52547fe3f5332b820e7bf145..f380fea9d45859e40d2dd81c93e54759590e4aff 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt
@@ -31,7 +31,6 @@ import org.briarproject.briar.desktop.utils.TimeUtils.getFormattedTimestamp
 @Composable
 fun ContactCard(
     contact: Contact,
-    selContact: Contact,
     onSel: (Contact) -> Unit,
     selected: Boolean,
     drawNotifications: Boolean
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDrawerMakeIntro.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDrawerMakeIntro.kt
index f8885da16c092b851415d2af2303b01d05c1f36c..d0fc82fb2e26734d115c5ea87701893974ce0057 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDrawerMakeIntro.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDrawerMakeIntro.kt
@@ -57,7 +57,7 @@ fun ContactDrawerMakeIntro(contact: Contact, contacts: List<Contact>, setInfoDra
             Column(Modifier.verticalScroll(rememberScrollState())) {
                 for (c in contacts) {
                     if (c.id != contact.id) {
-                        ContactCard(c, contact, { onCancelSel(c); introNextPg = true }, false, false)
+                        ContactCard(c, { onCancelSel(c); introNextPg = true }, false, false)
                     }
                 }
             }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt
index ef1d1a5a112f1c8f3f7e048df4c300557fea29e7..42edcbf1a70fcb48875265490f5a92655992a354 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt
@@ -5,41 +5,22 @@ import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Scaffold
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
-import org.briarproject.bramble.api.contact.Contact
 import org.briarproject.briar.desktop.theme.surfaceVariant
 import org.briarproject.briar.desktop.ui.Constants.CONTACTLIST_WIDTH
 import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
 
 @Composable
 fun ContactList(
-    contact: Contact,
-    contacts: List<Contact>,
-    onContactSelect: (Contact) -> Unit,
-    onContactAdd: (Boolean) -> Unit
+    contacts: ContactsViewModel,
+    onContactAdd: () -> Unit,
 ) {
-    var searchValue by remember { mutableStateOf("") }
-    val filteredContacts = if (searchValue.isEmpty()) {
-        ArrayList(contacts)
-    } else {
-        val resultList = ArrayList<Contact>()
-        for (c in contacts) {
-            if (c.author.name.lowercase().contains(searchValue.lowercase())) {
-                resultList.add(c)
-            }
-        }
-        resultList
-    }
     Scaffold(
         modifier = Modifier.fillMaxHeight().width(CONTACTLIST_WIDTH),
         backgroundColor = MaterialTheme.colors.surfaceVariant,
@@ -47,13 +28,17 @@ fun ContactList(
             Column(
                 modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp),
             ) {
-                SearchTextField(searchValue, onValueChange = { searchValue = it }, onContactAdd)
+                SearchTextField(
+                    contacts.filterBy.value,
+                    onValueChange = contacts::setFilterBy,
+                    onContactAdd
+                )
             }
         },
         content = {
-            Column(Modifier.verticalScroll(rememberScrollState())) {
-                for (c in filteredContacts) {
-                    ContactCard(c, contact, onContactSelect, contact.id == c.id, true)
+            LazyColumn {
+                itemsIndexed(contacts.contactList) { index, contact ->
+                    ContactCard(contact, { contacts.selectContact(index) }, contacts.isSelected(index), true)
                 }
             }
         },
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
index 9e51de185321c436107891c7c371c911423967ed..9bc56f9778384b0006acbb55c087eb4194bf8141 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
@@ -1,6 +1,8 @@
 package org.briarproject.briar.desktop.contact
 
+import androidx.compose.runtime.State
 import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
 import org.briarproject.bramble.api.contact.Contact
 import org.briarproject.bramble.api.contact.ContactManager
 import java.util.logging.Logger
@@ -16,17 +18,43 @@ constructor(
         private val LOG = Logger.getLogger(ContactsViewModel::class.java.name)
     }
 
-    internal val contacts = mutableStateListOf<Contact>()
+    private val _contactList = mutableListOf<Contact>()
+    private val _filteredContactList = mutableStateListOf<Contact>()
+    private val _filterBy = mutableStateOf("")
+    private var _selectedContactIndex = -1;
+    private val _selectedContact = mutableStateOf<Contact?>(null)
+
+    val contactList: List<Contact> = _filteredContactList
+    val filterBy: State<String> = _filterBy
+    val selectedContact: State<Contact?> = _selectedContact
 
     internal fun loadContacts() {
-        val contacts = contactManager.contacts
-        for (contact in contacts) {
-            LOG.info("loaded contact: ${contact.author.name} (${contact.alias})")
-            this.contacts.add(contact)
+        _contactList.apply {
+            clear()
+            addAll(contactManager.contacts)
+        }
+        updateFilteredList()
+    }
+
+    fun selectContact(index: Int) {
+        _selectedContactIndex = index
+        _selectedContact.value = _filteredContactList[index]
+    }
+
+    fun isSelected(index: Int) = _selectedContactIndex == index
+
+    private fun updateFilteredList() {
+        _filteredContactList.apply {
+            clear()
+            addAll(_contactList.filter {
+                // todo: also filter on alias?
+                it.author.name.lowercase().contains(_filterBy.value)
+            })
         }
     }
 
-    fun getFirst(): Contact? {
-        return if (contacts.isEmpty()) null else contacts[0]
+    fun setFilterBy(filter: String) {
+        _filterBy.value = filter
+        updateFilteredList()
     }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt
index 29542dd7d07d47a172f63a06202775b3e9314a1b..61d13a2f4cd48fa352f4cc2b36cd6909b483624a 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt
@@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
 @Composable
-fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onContactAdd: (Boolean) -> Unit) {
+fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onContactAdd: () -> Unit) {
     TextField(
         value = searchValue,
         onValueChange = onValueChange,
@@ -36,7 +36,7 @@ fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onCont
         },
         trailingIcon = {
             IconButton(
-                onClick = { onContactAdd(true) },
+                onClick = onContactAdd,
                 modifier = Modifier.padding(end = 10.dp).size(32.dp)
                     .background(MaterialTheme.colors.primary, CircleShape)
             ) {
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt
index 929c3f076221676d3cdc5bf951174be5a5da055a..74b89783592ba6dec3e65c948e174c05d59ab001 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt
@@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import org.briarproject.bramble.api.contact.Contact
 import org.briarproject.briar.desktop.contact.AddContactDialog
 import org.briarproject.briar.desktop.contact.ContactInfoDrawerState.MakeIntro
 import org.briarproject.briar.desktop.contact.ContactList
@@ -17,9 +16,7 @@ import org.briarproject.briar.desktop.ui.VerticalDivider
 
 @Composable
 fun PrivateMessageView(
-    contact: Contact,
     contacts: ContactsViewModel,
-    onContactSelect: (Contact) -> Unit
 ) {
     val (isDialogVisible, setDialogVisibility) = remember { mutableStateOf(false) }
     val (dropdownExpanded, setExpanded) = remember { mutableStateOf(false) }
@@ -27,18 +24,20 @@ fun PrivateMessageView(
     val (contactDrawerState, setDrawerState) = remember { mutableStateOf(MakeIntro) }
     AddContactDialog(isDialogVisible, setDialogVisibility)
     Row(modifier = Modifier.fillMaxWidth()) {
-        ContactList(contact, contacts.contacts, onContactSelect, setDialogVisibility)
+        ContactList(contacts) { setDialogVisibility(true) }
         VerticalDivider()
         Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
-            Conversation(
-                contact,
-                contacts.contacts,
-                dropdownExpanded,
-                setExpanded,
-                infoDrawer,
-                setInfoDrawer,
-                contactDrawerState
-            )
+            contacts.selectedContact.value?.let { selectedContact ->
+                Conversation(
+                    selectedContact,
+                    contacts.contactList,
+                    dropdownExpanded,
+                    setExpanded,
+                    infoDrawer,
+                    setInfoDrawer,
+                    contactDrawerState
+                )
+            }
         }
     }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt
index 9a27637bbebd2921e4f4149938f851db1476ecd6..f0ac067ca8ecfd22423f4a39adae4385944c85c1 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt
@@ -28,19 +28,12 @@ fun MainScreen(
 ) {
     // current selected mode, changed using the sidebar buttons
     val (uiMode, setUiMode) = remember { mutableStateOf(UiMode.CONTACTS) }
-    // TODO Figure out how to handle accounts with 0 contacts
-    // current selected contact
-    val (contact, setContact) = remember { mutableStateOf(contactsViewModel.getFirst()) }
     // Other global state that we need to track should go here also
     Row {
         BriarSidebar(uiMode, setUiMode)
         VerticalDivider()
         when (uiMode) {
-            UiMode.CONTACTS -> if (contact != null) PrivateMessageView(
-                contact,
-                contactsViewModel,
-                setContact
-            )
+            UiMode.CONTACTS -> PrivateMessageView(contactsViewModel)
             UiMode.SETTINGS -> PlaceHolderSettingsView(isDark, setDark)
             else -> Surface(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
                 Text("TBD")