diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt
new file mode 100644
index 000000000..ee4d61c4f
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt
@@ -0,0 +1,49 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderTextWithSupportLabel
+import com.x8bit.bitwarden.ui.vault.feature.vault.VaultEntryListItem
+
+/**
+ * Content view for the [VaultItemListingScreen].
+ */
+@Composable
+fun VaultItemListingContent(
+    state: VaultItemListingState.ViewState.Content,
+    vaultItemClick: (id: String) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    LazyColumn(
+        modifier = modifier,
+    ) {
+        item {
+            BitwardenListHeaderTextWithSupportLabel(
+                label = stringResource(id = R.string.items),
+                supportingLabel = state.displayItemList.size.toString(),
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 16.dp),
+            )
+        }
+        items(state.displayItemList) {
+            VaultEntryListItem(
+                startIcon = painterResource(id = it.iconRes),
+                label = it.title,
+                supportingLabel = it.subtitle,
+                onClick = { vaultItemClick(it.id) },
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 16.dp),
+            )
+        }
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingEmpty.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingEmpty.kt
new file mode 100644
index 000000000..7c3fc7aeb
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingEmpty.kt
@@ -0,0 +1,72 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.vault.feature.vault.VaultNoItems
+
+/**
+ * Empty view for the [VaultItemListingScreen].
+ */
+@Composable
+fun VaultItemListingEmpty(
+    paddingValues: PaddingValues,
+    itemListingType: VaultItemListingState.ItemListingType,
+    addItemClickAction: () -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    when (itemListingType) {
+        is VaultItemListingState.ItemListingType.Folder -> {
+            GenericNoItems(
+                modifier = modifier,
+                text = stringResource(id = R.string.no_items_folder),
+            )
+        }
+
+        is VaultItemListingState.ItemListingType.Trash -> {
+            GenericNoItems(
+                modifier = modifier,
+                text = stringResource(id = R.string.no_items_trash),
+            )
+        }
+
+        else -> {
+            VaultNoItems(
+                paddingValues = paddingValues,
+                addItemClickAction = addItemClickAction,
+            )
+        }
+    }
+}
+
+@Composable
+private fun GenericNoItems(
+    text: String,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier = modifier,
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        Text(
+            textAlign = TextAlign.Center,
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(horizontal = 16.dp),
+            text = text,
+            style = MaterialTheme.typography.bodyMedium,
+        )
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingError.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingError.kt
new file mode 100644
index 000000000..39bcb88b0
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingError.kt
@@ -0,0 +1,52 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
+
+/**
+ * The top level error UI state for the [VaultItemListingScreen].
+ */
+@Composable
+fun VaultItemListingError(
+    state: VaultItemListingState.ViewState.Error,
+    onRefreshClick: () -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier = modifier,
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        Text(
+            text = state.message(),
+            style = MaterialTheme.typography.bodyMedium,
+            textAlign = TextAlign.Center,
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(horizontal = 16.dp),
+        )
+        Spacer(modifier = Modifier.height(16.dp))
+        BitwardenTextButton(
+            label = stringResource(id = R.string.try_again),
+            onClick = onRefreshClick,
+            modifier = Modifier.padding(horizontal = 16.dp),
+        )
+        Spacer(modifier = Modifier.height(88.dp))
+        Spacer(modifier = Modifier.navigationBarsPadding())
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingLoading.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingLoading.kt
new file mode 100644
index 000000000..503c7e999
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingLoading.kt
@@ -0,0 +1,27 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+
+/**
+ * Loading view for the [VaultItemListingScreen].
+ */
+@Composable
+fun VaultItemListingLoading(
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier = modifier,
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        CircularProgressIndicator()
+        Spacer(modifier = Modifier.navigationBarsPadding())
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt
index 37847f711..d5d337ab7 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt
@@ -1,12 +1,29 @@
 package com.x8bit.bitwarden.ui.vault.feature.itemlisting
 
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
+import android.widget.Toast
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.hilt.navigation.compose.hiltViewModel
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
+import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
+import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
+import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem
+import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
 
 /**
  * Displays the vault item listing screen.
@@ -16,12 +33,133 @@ fun VaultItemListingScreen(
     onNavigateBack: () -> Unit,
     onNavigateToVaultItem: (id: String) -> Unit,
     onNavigateToVaultAddItemScreen: () -> Unit,
+    viewModel: VaultItemListingViewModel = hiltViewModel(),
 ) {
-    Column(
-        modifier = Modifier.fillMaxSize(),
-        verticalArrangement = Arrangement.Center,
-        horizontalAlignment = Alignment.CenterHorizontally,
-    ) {
-        Text(text = "Listing Screen")
+    val context = LocalContext.current
+    val resources = context.resources
+    EventsEffect(viewModel = viewModel) { event ->
+        when (event) {
+            is VaultItemListingEvent.NavigateBack -> onNavigateBack()
+
+            is VaultItemListingEvent.NavigateToVaultItem -> {
+                onNavigateToVaultItem(event.id)
+            }
+
+            is VaultItemListingEvent.ShowToast -> {
+                Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
+            }
+
+            is VaultItemListingEvent.NavigateToAddVaultItem -> {
+                onNavigateToVaultAddItemScreen()
+            }
+
+            is VaultItemListingEvent.NavigateToVaultSearchScreen -> {
+                // TODO Create vault search screen and navigation implementation BIT-213
+                Toast
+                    .makeText(context, "Navigate to the vault search screen.", Toast.LENGTH_SHORT)
+                    .show()
+            }
+        }
+    }
+    VaultItemListingScaffold(
+        state = viewModel.stateFlow.collectAsState().value,
+        backClick = remember(viewModel) {
+            { viewModel.trySendAction(VaultItemListingsAction.BackClick) }
+        },
+        searchIconClick = remember(viewModel) {
+            { viewModel.trySendAction(VaultItemListingsAction.SearchIconClick) }
+        },
+        addVaultItemClick = remember(viewModel) {
+            { viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick) }
+        },
+        vaultItemClick = remember(viewModel) {
+            { viewModel.trySendAction(VaultItemListingsAction.ItemClick(it)) }
+        },
+        refreshClick = remember(viewModel) {
+            { viewModel.trySendAction(VaultItemListingsAction.RefreshClick) }
+        },
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Suppress("LongMethod")
+@Composable
+private fun VaultItemListingScaffold(
+    state: VaultItemListingState,
+    backClick: () -> Unit,
+    searchIconClick: () -> Unit,
+    addVaultItemClick: () -> Unit,
+    vaultItemClick: (id: String) -> Unit,
+    refreshClick: () -> Unit,
+) {
+    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
+    BitwardenScaffold(
+        modifier = Modifier
+            .fillMaxSize()
+            .nestedScroll(scrollBehavior.nestedScrollConnection),
+        topBar = {
+            BitwardenTopAppBar(
+                title = state.itemListingType.titleText(),
+                scrollBehavior = scrollBehavior,
+                navigationIcon = painterResource(id = R.drawable.ic_back),
+                navigationIconContentDescription = stringResource(id = R.string.back),
+                onNavigationIconClick = backClick,
+                actions = {
+                    BitwardenSearchActionItem(
+                        contentDescription = stringResource(id = R.string.search_vault),
+                        onClick = searchIconClick,
+                    )
+                    BitwardenOverflowActionItem()
+                },
+            )
+        },
+        floatingActionButton = {
+            if (state.itemListingType.hasFab) {
+                FloatingActionButton(
+                    containerColor = MaterialTheme.colorScheme.primaryContainer,
+                    onClick = addVaultItemClick,
+                ) {
+                    Icon(
+                        painter = painterResource(id = R.drawable.ic_plus),
+                        contentDescription = stringResource(id = R.string.add_item),
+                    )
+                }
+            }
+        },
+    ) { paddingValues ->
+        val modifier = Modifier
+            .fillMaxSize()
+            .padding(paddingValues)
+
+        when (state.viewState) {
+            is VaultItemListingState.ViewState.Content -> {
+                VaultItemListingContent(
+                    state = state.viewState,
+                    vaultItemClick = vaultItemClick,
+                    modifier = modifier,
+                )
+            }
+
+            is VaultItemListingState.ViewState.NoItems -> {
+                VaultItemListingEmpty(
+                    paddingValues = paddingValues,
+                    itemListingType = state.itemListingType,
+                    addItemClickAction = addVaultItemClick,
+                    modifier = modifier,
+                )
+            }
+
+            is VaultItemListingState.ViewState.Error -> {
+                VaultItemListingError(
+                    state = state.viewState,
+                    onRefreshClick = refreshClick,
+                    modifier = modifier,
+                )
+            }
+
+            is VaultItemListingState.ViewState.Loading -> {
+                VaultItemListingLoading(modifier = modifier)
+            }
+        }
     }
 }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt
new file mode 100644
index 000000000..a72456e23
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt
@@ -0,0 +1,305 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting
+
+import androidx.annotation.DrawableRes
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
+import com.x8bit.bitwarden.ui.platform.base.util.Text
+import com.x8bit.bitwarden.ui.platform.base.util.asText
+import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+/**
+ * Manages [VaultItemListingState], handles [VaultItemListingsAction],
+ * and launches [VaultItemListingEvent] for the [VaultItemListingScreen].
+ */
+@HiltViewModel
+@Suppress("MagicNumber")
+class VaultItemListingViewModel @Inject constructor(
+    savedStateHandle: SavedStateHandle,
+) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
+    initialState = VaultItemListingState(
+        itemListingType = VaultItemListingArgs(savedStateHandle = savedStateHandle)
+            .vaultItemListingType
+            .toItemListingType(),
+        viewState = VaultItemListingState.ViewState.Loading,
+    ),
+) {
+
+    init {
+        // TODO fetch real listing data in BIT-1057
+        viewModelScope.launch {
+            delay(2000)
+            mutableStateFlow.update {
+                it.copy(
+                    viewState = VaultItemListingState.ViewState.NoItems,
+                )
+            }
+        }
+    }
+
+    override fun handleAction(action: VaultItemListingsAction) {
+        when (action) {
+            is VaultItemListingsAction.BackClick -> handleBackClick()
+            is VaultItemListingsAction.SearchIconClick -> handleSearchIconClick()
+            is VaultItemListingsAction.ItemClick -> handleItemClick(action)
+            is VaultItemListingsAction.AddVaultItemClick -> handleAddVaultItemClick()
+            is VaultItemListingsAction.RefreshClick -> handleRefreshClick()
+        }
+    }
+
+    //region VaultItemListing Handlers
+    private fun handleRefreshClick() {
+        // TODO implement refresh in BIT-1057
+        sendEvent(
+            event = VaultItemListingEvent.ShowToast(
+                text = "Not yet implemented".asText(),
+            ),
+        )
+    }
+
+    private fun handleAddVaultItemClick() {
+        sendEvent(
+            event = VaultItemListingEvent.NavigateToAddVaultItem,
+        )
+    }
+
+    private fun handleItemClick(action: VaultItemListingsAction.ItemClick) {
+        sendEvent(
+            event = VaultItemListingEvent.NavigateToVaultItem(
+                id = action.id,
+            ),
+        )
+    }
+
+    private fun handleBackClick() {
+        sendEvent(
+            event = VaultItemListingEvent.NavigateBack,
+        )
+    }
+
+    private fun handleSearchIconClick() {
+        sendEvent(
+            event = VaultItemListingEvent.NavigateToVaultSearchScreen,
+        )
+    }
+    //endregion VaultItemListing Handlers
+}
+
+/**
+ * Models state for the [VaultItemListingScreen].
+ */
+data class VaultItemListingState(
+    val itemListingType: ItemListingType,
+    val viewState: ViewState,
+) {
+
+    /**
+     * Represents the specific view states for the [VaultItemListingScreen].
+     */
+    sealed class ViewState {
+
+        /**
+         * Loading state for the [VaultItemListingScreen],
+         * signifying that the content is being processed.
+         */
+        data object Loading : ViewState()
+
+        /**
+         * Represents a state where the [VaultItemListingScreen] has no items to display.
+         */
+        data object NoItems : ViewState()
+
+        /**
+         * Content state for the [VaultItemListingScreen] showing the actual content or items.
+         *
+         * @property displayItemList List of items to display.
+         */
+        data class Content(
+            val displayItemList: List<DisplayItem>,
+        ) : ViewState()
+
+        /**
+         * Represents an error state for the [VaultItemListingScreen].
+         *
+         * @property message Error message to display.
+         */
+        data class Error(
+            val message: Text,
+        ) : ViewState()
+    }
+
+    /**
+     * An item to be displayed.
+     *
+     * @property id the id of the item.
+     * @property title title of the item.
+     * @property subtitle subtitle of the item.
+     * @property uri uri for the icon to be displayed (nullable).
+     * @property iconRes the icon to be displayed.
+     */
+    data class DisplayItem(
+        val id: String,
+        val title: String,
+        val subtitle: String,
+        val uri: String?,
+        @DrawableRes
+        val iconRes: Int,
+    )
+
+    /**
+     * Represents different types of item listing.
+     */
+    sealed class ItemListingType {
+
+        /**
+         * The title to display at the top of the screen.
+         */
+        abstract val titleText: Text
+
+        /**
+         * Whether or not the screen has a floating action button (FAB).
+         */
+        abstract val hasFab: Boolean
+
+        /**
+         * A Login item listing.
+         */
+        data object Login : ItemListingType() {
+            override val titleText: Text
+                get() = R.string.logins.asText()
+            override val hasFab: Boolean
+                get() = true
+        }
+
+        /**
+         * A Card item listing.
+         */
+        data object Card : ItemListingType() {
+            override val titleText: Text
+                get() = R.string.cards.asText()
+            override val hasFab: Boolean
+                get() = true
+        }
+
+        /**
+         * An Identity item listing.
+         */
+        data object Identity : ItemListingType() {
+            override val titleText: Text
+                get() = R.string.identities.asText()
+            override val hasFab: Boolean
+                get() = true
+        }
+
+        /**
+         * A Secure Note item listing.
+         */
+        data object SecureNote : ItemListingType() {
+            override val titleText: Text
+                get() = R.string.secure_notes.asText()
+            override val hasFab: Boolean
+                get() = true
+        }
+
+        /**
+         * A Secure Trash item listing.
+         */
+        data object Trash : ItemListingType() {
+            override val titleText: Text
+                get() = R.string.trash.asText()
+            override val hasFab: Boolean
+                get() = false
+        }
+
+        /**
+         * A Folder item listing.
+         *
+         * @property folderId the id of the folder.
+         * @property folderName the name of the folder.
+         */
+        data class Folder(
+            val folderId: String?,
+            // The folderName will always initially be an empty string
+            val folderName: String = "",
+        ) : ItemListingType() {
+            override val titleText: Text
+                get() = folderName.asText()
+            override val hasFab: Boolean
+                get() = false
+        }
+    }
+}
+
+/**
+ * Models events for the [VaultItemListingScreen].
+ */
+sealed class VaultItemListingEvent {
+
+    /**
+     * Navigates to the Create Account screen.
+     */
+    data object NavigateBack : VaultItemListingEvent()
+
+    /**
+     * Navigates to the VaultAddItemScreen.
+     */
+    data object NavigateToAddVaultItem : VaultItemListingEvent()
+
+    /**
+     * Navigates to the VaultItemScreen.
+     *
+     * @property id the id of the item to navigate to.
+     */
+    data class NavigateToVaultItem(val id: String) : VaultItemListingEvent()
+
+    /**
+     * Navigates to the VaultSearchScreen.
+     */
+    data object NavigateToVaultSearchScreen : VaultItemListingEvent()
+
+    /**
+     * Show a toast with the given message.
+     *
+     * @property text the text to display.
+     */
+    data class ShowToast(val text: Text) : VaultItemListingEvent()
+}
+
+/**
+ * Models actions for the [VaultItemListingScreen].
+ */
+sealed class VaultItemListingsAction {
+
+    /**
+     * Click the refresh button.
+     */
+    data object RefreshClick : VaultItemListingsAction()
+
+    /**
+     * Click the back button.
+     */
+    data object BackClick : VaultItemListingsAction()
+
+    /**
+     * Click the search icon.
+     */
+    data object SearchIconClick : VaultItemListingsAction()
+
+    /**
+     * Click the add item button.
+     */
+    data object AddVaultItemClick : VaultItemListingsAction()
+
+    /**
+     * Click on an item.
+     *
+     * @property id the id of the item that has been clicked.
+     */
+    data class ItemClick(val id: String) : VaultItemListingsAction()
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt
new file mode 100644
index 000000000..ab1b1037a
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt
@@ -0,0 +1,20 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
+
+import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
+import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
+
+/**
+ * Transforms a [VaultItemListingType] into a [VaultItemListingState.ItemListingType].
+ */
+fun VaultItemListingType.toItemListingType(): VaultItemListingState.ItemListingType =
+    when (this) {
+        is VaultItemListingType.Card -> VaultItemListingState.ItemListingType.Card
+        is VaultItemListingType.Folder -> {
+            VaultItemListingState.ItemListingType.Folder(folderId = folderId)
+        }
+
+        is VaultItemListingType.Identity -> VaultItemListingState.ItemListingType.Card
+        is VaultItemListingType.Login -> VaultItemListingState.ItemListingType.Login
+        is VaultItemListingType.SecureNote -> VaultItemListingState.ItemListingType.SecureNote
+        is VaultItemListingType.Trash -> VaultItemListingState.ItemListingType.Trash
+    }
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt
new file mode 100644
index 000000000..d650e9382
--- /dev/null
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt
@@ -0,0 +1,407 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasScrollToNodeAction
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToNode
+import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
+import com.x8bit.bitwarden.ui.platform.base.util.asText
+import com.x8bit.bitwarden.ui.util.isProgressBar
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import com.x8bit.bitwarden.R
+
+class VaultItemListingScreenTest : BaseComposeTest() {
+
+    private var onNavigateBackCalled = false
+    private var onNavigateToVaultAddItemScreenCalled = false
+    private var onNavigateToVaultItemId: String? = null
+
+    private val mutableEventFlow = MutableSharedFlow<VaultItemListingEvent>(
+        extraBufferCapacity = Int.MAX_VALUE,
+    )
+    private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
+    private val viewModel = mockk<VaultItemListingViewModel>(relaxed = true) {
+        every { eventFlow } returns mutableEventFlow
+        every { stateFlow } returns mutableStateFlow
+    }
+
+    @Before
+    fun setUp() {
+        composeTestRule.setContent {
+            VaultItemListingScreen(
+                viewModel = viewModel,
+                onNavigateBack = { onNavigateBackCalled = true },
+                onNavigateToVaultItem = { onNavigateToVaultItemId = it },
+                onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreenCalled = true },
+            )
+        }
+    }
+
+    @Test
+    fun `NavigateBack event should invoke NavigateBack`() {
+        mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateBack)
+        assertTrue(onNavigateBackCalled)
+    }
+
+    @Test
+    fun `clicking back button should send BackClick action`() {
+        composeTestRule
+            .onNodeWithContentDescription(label = "Back")
+            .performClick()
+        verify { viewModel.trySendAction(VaultItemListingsAction.BackClick) }
+    }
+
+    @Test
+    fun `search icon click should send SearchIconClick action`() {
+        composeTestRule
+            .onNodeWithContentDescription("Search vault")
+            .performClick()
+        verify { viewModel.trySendAction(VaultItemListingsAction.SearchIconClick) }
+    }
+
+    @Test
+    fun `floating action button click should send AddItemClick action`() {
+        composeTestRule
+            .onNodeWithContentDescription("Add item")
+            .performClick()
+        verify { viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick) }
+    }
+
+    @Test
+    fun `add an item button click should send AddItemClick action`() {
+        mutableStateFlow.update { it.copy(viewState = VaultItemListingState.ViewState.NoItems) }
+        composeTestRule
+            .onNodeWithText("Add an Item")
+            .performClick()
+        verify { viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick) }
+    }
+
+    @Test
+    fun `refresh button click should send RefreshClick action`() {
+        mutableStateFlow.update {
+            it.copy(viewState = VaultItemListingState.ViewState.Error(message = "".asText()))
+        }
+        composeTestRule
+            .onNodeWithText("Try again")
+            .performClick()
+        verify { viewModel.trySendAction(VaultItemListingsAction.RefreshClick) }
+    }
+
+    @Test
+    fun `NavigateToAdd VaultItem event should call NavigateToVaultAddItemScreen`() {
+        mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToAddVaultItem)
+        assertTrue(onNavigateToVaultAddItemScreenCalled)
+    }
+
+    @Test
+    fun `NavigateToVaultItem event should call NavigateToVaultItemScreen`() {
+        val id = "id4321"
+        mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToVaultItem(id = id))
+        assertEquals(id, onNavigateToVaultItemId)
+    }
+
+    @Test
+    fun `progressbar should be displayed according to state`() {
+        mutableStateFlow.update { DEFAULT_STATE }
+
+        composeTestRule
+            .onNode(isProgressBar)
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(viewState = VaultItemListingState.ViewState.NoItems)
+        }
+
+        composeTestRule
+            .onNode(isProgressBar)
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun `error text and retry should be displayed according to state`() {
+        val message = "error_message"
+        mutableStateFlow.update { DEFAULT_STATE }
+        composeTestRule
+            .onNodeWithText(message)
+            .assertIsNotDisplayed()
+
+        mutableStateFlow.update { it.copy(viewState = VaultItemListingState.ViewState.NoItems) }
+        composeTestRule
+            .onNodeWithText(message)
+            .assertIsNotDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(viewState = VaultItemListingState.ViewState.Error(message.asText()))
+        }
+        composeTestRule
+            .onNodeWithText(message)
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun `Add an item button should be displayed according to state`() {
+        mutableStateFlow.update { DEFAULT_STATE }
+        composeTestRule
+            .onNodeWithText(text = "Add an Item")
+            .assertDoesNotExist()
+
+        mutableStateFlow.update {
+            it.copy(viewState = VaultItemListingState.ViewState.NoItems)
+        }
+        composeTestRule
+            .onNodeWithText(text = "Add an Item")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
+        }
+        composeTestRule
+            .onNodeWithText(text = "Add an Item")
+            .assertDoesNotExist()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Folder(folderId = null))
+        }
+        composeTestRule
+            .onNodeWithText(text = "Add an Item")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun `empty text should be displayed according to state`() {
+        mutableStateFlow.update {
+            DEFAULT_STATE.copy(viewState = VaultItemListingState.ViewState.NoItems)
+        }
+        composeTestRule
+            .onNodeWithText(text = "There are no items in your vault.")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
+        }
+        composeTestRule
+            .onNodeWithText(text = "There are no items in the trash.")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Folder(folderId = null))
+        }
+        composeTestRule
+            .onNodeWithText(text = "There are no items in this folder.")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun `floating action button should be displayed according to state`() {
+        mutableStateFlow.update { DEFAULT_STATE }
+
+        composeTestRule
+            .onNodeWithContentDescription("Add item")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
+        }
+
+        composeTestRule
+            .onNodeWithContentDescription("Add item")
+            .assertDoesNotExist()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Folder(folderId = null))
+        }
+
+        composeTestRule
+            .onNodeWithContentDescription("Add item")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun `Items text should be displayed according to state`() {
+        val items = "Items"
+        mutableStateFlow.update { DEFAULT_STATE }
+        composeTestRule
+            .onNodeWithText(text = items)
+            .assertDoesNotExist()
+
+        mutableStateFlow.update {
+            it.copy(
+                viewState = VaultItemListingState.ViewState.Content(
+                    displayItemList = listOf(
+                        createDisplayItem(number = 1),
+                    ),
+                ),
+            )
+        }
+        composeTestRule
+            .onNode(hasScrollToNodeAction())
+            .performScrollToNode(hasText(items))
+        composeTestRule
+            .onNodeWithText(text = items)
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun `Items text count should be displayed according to state`() {
+        val items = "Items"
+        mutableStateFlow.update { DEFAULT_STATE }
+        composeTestRule
+            .onNodeWithText(text = items)
+            .assertDoesNotExist()
+
+        mutableStateFlow.update {
+            it.copy(
+                viewState = VaultItemListingState.ViewState.Content(
+                    displayItemList = listOf(
+                        createDisplayItem(number = 1),
+                    ),
+                ),
+            )
+        }
+        composeTestRule
+            .onNode(hasScrollToNodeAction())
+            .performScrollToNode(hasText(items))
+        composeTestRule
+            .onNodeWithText(text = items)
+            .assertIsDisplayed()
+            .assertTextEquals(items, 1.toString())
+
+        mutableStateFlow.update {
+            it.copy(
+                viewState = VaultItemListingState.ViewState.Content(
+                    displayItemList = listOf(
+                        createDisplayItem(number = 1),
+                        createDisplayItem(number = 2),
+                        createDisplayItem(number = 3),
+                        createDisplayItem(number = 4),
+                    ),
+                ),
+            )
+        }
+
+        composeTestRule
+            .onNode(hasScrollToNodeAction())
+            .performScrollToNode(hasText(items))
+        composeTestRule
+            .onNodeWithText(text = items)
+            .assertIsDisplayed()
+            .assertTextEquals(items, 4.toString())
+    }
+
+    @Test
+    fun `displayItems should be displayed according to state`() {
+        mutableStateFlow.update {
+            it.copy(
+                viewState = VaultItemListingState.ViewState.Content(
+                    displayItemList = listOf(
+                        createDisplayItem(number = 1),
+                    ),
+                ),
+            )
+        }
+
+        composeTestRule
+            .onNodeWithText(text = "mockTitle-1")
+            .assertIsDisplayed()
+        composeTestRule
+            .onNodeWithText(text = "mockSubtitle-1")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun `clicking on a display item should send ItemClick action`() {
+        mutableStateFlow.update {
+            it.copy(
+                viewState = VaultItemListingState.ViewState.Content(
+                    displayItemList = listOf(
+                        createDisplayItem(number = 1),
+                    ),
+                ),
+            )
+        }
+
+        composeTestRule
+            .onNodeWithText(text = "mockTitle-1")
+            .assertIsDisplayed()
+            .performClick()
+        verify {
+            viewModel.trySendAction(VaultItemListingsAction.ItemClick("mockId-1"))
+        }
+    }
+
+    @Test
+    fun `topBar title should be displayed according to state`() {
+        mutableStateFlow.update { DEFAULT_STATE }
+        composeTestRule
+            .onNodeWithText(text = "Logins")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.SecureNote)
+        }
+        composeTestRule
+            .onNodeWithText(text = "Secure notes")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Card)
+        }
+        composeTestRule
+            .onNodeWithText(text = "Cards")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Identity)
+        }
+        composeTestRule
+            .onNodeWithText(text = "Identities")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
+        }
+        composeTestRule
+            .onNodeWithText(text = "Trash")
+            .assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(
+                itemListingType = VaultItemListingState.ItemListingType.Folder(
+                    folderId = "mockId",
+                    folderName = "mockName",
+                ),
+            )
+        }
+        composeTestRule
+            .onNodeWithText(text = "mockName")
+            .assertIsDisplayed()
+    }
+}
+
+private val DEFAULT_STATE = VaultItemListingState(
+    itemListingType = VaultItemListingState.ItemListingType.Login,
+    viewState = VaultItemListingState.ViewState.Loading,
+)
+
+private fun createDisplayItem(number: Int): VaultItemListingState.DisplayItem =
+    VaultItemListingState.DisplayItem(
+        id = "mockId-$number",
+        title = "mockTitle-$number",
+        subtitle = "mockSubtitle-$number",
+        uri = "mockUri-$number",
+        iconRes = R.drawable.ic_card_item,
+    )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt
new file mode 100644
index 000000000..00616ad62
--- /dev/null
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt
@@ -0,0 +1,120 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting
+
+import androidx.lifecycle.SavedStateHandle
+import app.cash.turbine.test
+import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
+import com.x8bit.bitwarden.ui.platform.base.util.asText
+import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
+import kotlinx.coroutines.test.runTest
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class VaultItemListingViewModelTest : BaseViewModelTest() {
+
+    private val initialState = createVaultItemListingState()
+    private val initialSavedStateHandle = createSavedStateHandleWithVaultItemListingType(
+        vaultItemListingType = VaultItemListingType.Login,
+    )
+
+    @Test
+    fun `initial state should be correct`() = runTest {
+        val viewModel = createVaultItemListingViewModel()
+        viewModel.stateFlow.test {
+            assertEquals(
+                initialState, awaitItem(),
+            )
+        }
+    }
+
+    @Test
+    fun `BackClick should emit NavigateBack`() = runTest {
+        val viewModel = createVaultItemListingViewModel()
+        viewModel.eventFlow.test {
+            viewModel.actionChannel.trySend(VaultItemListingsAction.BackClick)
+            assertEquals(VaultItemListingEvent.NavigateBack, awaitItem())
+        }
+    }
+
+    @Test
+    fun `SearchIconClick should emit NavigateToVaultSearchScreen`() = runTest {
+        val viewModel = createVaultItemListingViewModel()
+        viewModel.eventFlow.test {
+            viewModel.actionChannel.trySend(VaultItemListingsAction.SearchIconClick)
+            assertEquals(VaultItemListingEvent.NavigateToVaultSearchScreen, awaitItem())
+        }
+    }
+
+    @Test
+    fun `ItemClick should emit NavigateToVaultItem`() = runTest {
+        val viewModel = createVaultItemListingViewModel()
+        viewModel.eventFlow.test {
+            viewModel.actionChannel.trySend(VaultItemListingsAction.ItemClick(id = "mock"))
+            assertEquals(VaultItemListingEvent.NavigateToVaultItem(id = "mock"), awaitItem())
+        }
+    }
+
+    @Test
+    fun `AddVaultItemClick should emit NavigateToAddVaultItem`() = runTest {
+        val viewModel = createVaultItemListingViewModel()
+        viewModel.eventFlow.test {
+            viewModel.actionChannel.trySend(VaultItemListingsAction.AddVaultItemClick)
+            assertEquals(VaultItemListingEvent.NavigateToAddVaultItem, awaitItem())
+        }
+    }
+
+    @Test
+    fun `RefreshClick should emit ShowToast`() = runTest {
+        val viewModel = createVaultItemListingViewModel()
+        viewModel.eventFlow.test {
+            viewModel.actionChannel.trySend(VaultItemListingsAction.RefreshClick)
+            assertEquals(
+                VaultItemListingEvent.ShowToast("Not yet implemented".asText()),
+                awaitItem(),
+            )
+        }
+    }
+
+    private fun createSavedStateHandleWithVaultItemListingType(
+        vaultItemListingType: VaultItemListingType,
+    ) = SavedStateHandle().apply {
+        set(
+            "vault_item_listing_type",
+            when (vaultItemListingType) {
+                is VaultItemListingType.Card -> "card"
+                is VaultItemListingType.Folder -> "folder"
+                is VaultItemListingType.Identity -> "identity"
+                is VaultItemListingType.Login -> "login"
+                is VaultItemListingType.SecureNote -> "secure_note"
+                is VaultItemListingType.Trash -> "trash"
+            },
+        )
+        set(
+            "id",
+            when (vaultItemListingType) {
+                is VaultItemListingType.Card -> null
+                is VaultItemListingType.Folder -> vaultItemListingType.folderId
+                is VaultItemListingType.Identity -> null
+                is VaultItemListingType.Login -> null
+                is VaultItemListingType.SecureNote -> null
+                is VaultItemListingType.Trash -> null
+            },
+        )
+    }
+
+    private fun createVaultItemListingViewModel(
+        savedStateHandle: SavedStateHandle = initialSavedStateHandle,
+    ): VaultItemListingViewModel =
+        VaultItemListingViewModel(
+            savedStateHandle = savedStateHandle,
+        )
+
+    @Suppress("MaxLineLength")
+    private fun createVaultItemListingState(
+        itemListingType: VaultItemListingState.ItemListingType = VaultItemListingState.ItemListingType.Login,
+        viewState: VaultItemListingState.ViewState = VaultItemListingState.ViewState.Loading,
+    ): VaultItemListingState =
+        VaultItemListingState(
+            itemListingType = itemListingType,
+            viewState = viewState,
+        )
+}
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt
new file mode 100644
index 000000000..8da4362ca
--- /dev/null
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt
@@ -0,0 +1,28 @@
+package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
+
+import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
+import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class VaultItemListingTypeExtensionsTest {
+
+    @Suppress("MaxLineLength")
+    @Test
+    fun `toItemListingType should transform a VaultItemListingType into a VaultItemListingState ItemListingType`() {
+        val itemListingTypeList = listOf(
+            VaultItemListingType.Folder(folderId = "mock"),
+            VaultItemListingType.Trash,
+        )
+
+        val result = itemListingTypeList.map { it.toItemListingType() }
+
+        assertEquals(
+            listOf(
+                VaultItemListingState.ItemListingType.Folder(folderId = "mock"),
+                VaultItemListingState.ItemListingType.Trash,
+            ),
+            result,
+        )
+    }
+}