diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07ce39173..d1a31fe58 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Added Lists view and two ways to navigate to it. [#133](https://github.com/verse-pbc/issues/issues/133)
 - Added view for editing a list's title and description. [#134](https://github.com/verse-pbc/issues/issues/134)
 - Added List detail view. [#155](https://github.com/verse-pbc/issues/issues/155)
+- Added view for managing users in a list. [#135](https://github.com/verse-pbc/issues/issues/135)
 
 ### Internal Changes
 - Added function for creating a new list and a test verifying list editing. [#112](https://github.com/verse-pbc/issues/issues/112)
diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj
index e71ba1114..fc5620c4f 100644
--- a/Nos.xcodeproj/project.pbxproj
+++ b/Nos.xcodeproj/project.pbxproj
@@ -220,6 +220,7 @@
 		509533002C62535400E0BACA /* zap_request.json in Resources */ = {isa = PBXBuildFile; fileRef = 509532FF2C62535400E0BACA /* zap_request.json */; };
 		5095330B2C625B5D00E0BACA /* zap_request_one_sat.json in Resources */ = {isa = PBXBuildFile; fileRef = 509533092C625B5D00E0BACA /* zap_request_one_sat.json */; };
 		5095330C2C625B5D00E0BACA /* zap_request_no_amount.json in Resources */ = {isa = PBXBuildFile; fileRef = 5095330A2C625B5D00E0BACA /* zap_request_no_amount.json */; };
+		50CBD79A2D37FAF400BF8A0B /* UserSelectionCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CBD7992D37FAF000BF8A0B /* UserSelectionCircle.swift */; };
 		50CBD7AB2D39341B00BF8A0B /* AuthorListDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CBD7AA2D39341700BF8A0B /* AuthorListDetailView.swift */; };
 		50CBD8152D3A8FED00BF8A0B /* ListCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CBD80F2D3A8B6D00BF8A0B /* ListCircle.swift */; };
 		50DE6B1B2C6B88FE0065665D /* View+StyledBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DE6B1A2C6B88FE0065665D /* View+StyledBorder.swift */; };
@@ -230,6 +231,7 @@
 		50EA885C2D2D523F001E62CC /* follow_set_with_unknown_tag.json in Resources */ = {isa = PBXBuildFile; fileRef = 50EA885B2D2D5235001E62CC /* follow_set_with_unknown_tag.json */; };
 		50EA886F2D2D5783001E62CC /* AuthorListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EA886E2D2D5780001E62CC /* AuthorListsView.swift */; };
 		50EA89A62D3010EA001E62CC /* EditAuthorListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EA89A52D3010EA001E62CC /* EditAuthorListView.swift */; };
+		50EA8A742D32CB38001E62CC /* AuthorListManageUsersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EA8A732D32CB33001E62CC /* AuthorListManageUsersView.swift */; };
 		50F695072C6392C4000E4C74 /* zap_receipt.json in Resources */ = {isa = PBXBuildFile; fileRef = 50F695062C6392C4000E4C74 /* zap_receipt.json */; };
 		5B098DBC2BDAF6CB00500A1B /* NoteParserTests+NIP08.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B098DBB2BDAF6CB00500A1B /* NoteParserTests+NIP08.swift */; };
 		5B098DC62BDAF73500500A1B /* AttributedString+Links.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B098DC52BDAF73500500A1B /* AttributedString+Links.swift */; };
@@ -279,7 +281,7 @@
 		5BFF66B62A58A8A000AA79DD /* MutesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF66B52A58A8A000AA79DD /* MutesView.swift */; };
 		659B27242BD9CB4500BEA6CC /* VerifiableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 659B27232BD9CB4500BEA6CC /* VerifiableEvent.swift */; };
 		659B27312BD9D6FE00BEA6CC /* VerifiableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 659B27232BD9CB4500BEA6CC /* VerifiableEvent.swift */; };
-		65BD8DB92BDAF28200802039 /* CircularFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BD8DB82BDAF28200802039 /* CircularFollowButton.swift */; };
+		65BD8DB92BDAF28200802039 /* CircularButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BD8DB82BDAF28200802039 /* CircularButton.swift */; };
 		65BD8DC22BDAF2C300802039 /* DiscoverTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BD8DBE2BDAF2C300802039 /* DiscoverTab.swift */; };
 		65BD8DC32BDAF2C300802039 /* FeaturedAuthorCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BD8DBF2BDAF2C300802039 /* FeaturedAuthorCategory.swift */; };
 		65BD8DC42BDAF2C300802039 /* DiscoverContentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BD8DC02BDAF2C300802039 /* DiscoverContentsView.swift */; };
@@ -794,6 +796,7 @@
 		509532FF2C62535400E0BACA /* zap_request.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = zap_request.json; sourceTree = "<group>"; };
 		509533092C625B5D00E0BACA /* zap_request_one_sat.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = zap_request_one_sat.json; sourceTree = "<group>"; };
 		5095330A2C625B5D00E0BACA /* zap_request_no_amount.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = zap_request_no_amount.json; sourceTree = "<group>"; };
+		50CBD7992D37FAF000BF8A0B /* UserSelectionCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionCircle.swift; sourceTree = "<group>"; };
 		50CBD7AA2D39341700BF8A0B /* AuthorListDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorListDetailView.swift; sourceTree = "<group>"; };
 		50CBD80F2D3A8B6D00BF8A0B /* ListCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCircle.swift; sourceTree = "<group>"; };
 		50DE6B1A2C6B88FE0065665D /* View+StyledBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+StyledBorder.swift"; sourceTree = "<group>"; };
@@ -802,6 +805,7 @@
 		50EA885B2D2D5235001E62CC /* follow_set_with_unknown_tag.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = follow_set_with_unknown_tag.json; sourceTree = "<group>"; };
 		50EA886E2D2D5780001E62CC /* AuthorListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorListsView.swift; sourceTree = "<group>"; };
 		50EA89A52D3010EA001E62CC /* EditAuthorListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAuthorListView.swift; sourceTree = "<group>"; };
+		50EA8A732D32CB33001E62CC /* AuthorListManageUsersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorListManageUsersView.swift; sourceTree = "<group>"; };
 		50F695062C6392C4000E4C74 /* zap_receipt.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = zap_receipt.json; sourceTree = "<group>"; };
 		5B098DBB2BDAF6CB00500A1B /* NoteParserTests+NIP08.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NoteParserTests+NIP08.swift"; sourceTree = "<group>"; };
 		5B098DC52BDAF73500500A1B /* AttributedString+Links.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Links.swift"; sourceTree = "<group>"; };
@@ -852,7 +856,7 @@
 		5BFF66B32A58853D00AA79DD /* PublishedEventsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedEventsView.swift; sourceTree = "<group>"; };
 		5BFF66B52A58A8A000AA79DD /* MutesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutesView.swift; sourceTree = "<group>"; };
 		659B27232BD9CB4500BEA6CC /* VerifiableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifiableEvent.swift; sourceTree = "<group>"; };
-		65BD8DB82BDAF28200802039 /* CircularFollowButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularFollowButton.swift; sourceTree = "<group>"; };
+		65BD8DB82BDAF28200802039 /* CircularButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularButton.swift; sourceTree = "<group>"; };
 		65BD8DBE2BDAF2C300802039 /* DiscoverTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverTab.swift; sourceTree = "<group>"; };
 		65BD8DBF2BDAF2C300802039 /* FeaturedAuthorCategory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeaturedAuthorCategory.swift; sourceTree = "<group>"; };
 		65BD8DC02BDAF2C300802039 /* DiscoverContentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverContentsView.swift; sourceTree = "<group>"; };
@@ -1388,7 +1392,7 @@
 				C987F81929BA4D0E00B44E7A /* ActionButton.swift */,
 				C987F81629BA4C6900B44E7A /* BigActionButton.swift */,
 				CD2CF38D299E67F900332116 /* CardButtonStyle.swift */,
-				65BD8DB82BDAF28200802039 /* CircularFollowButton.swift */,
+				65BD8DB82BDAF28200802039 /* CircularButton.swift */,
 				A303AF8229A9153A005DC8FC /* FollowButton.swift */,
 				C960C57029F3236200929990 /* LikeButton.swift */,
 				030036AA2C5D872B002C71F5 /* NewNotesButton.swift */,
@@ -1398,6 +1402,7 @@
 				5BE281C62AE2CCD800880466 /* ReplyButton.swift */,
 				C960C57329F3251E00929990 /* RepostButton.swift */,
 				C9A0DAD929C685E500466635 /* SideMenuButton.swift */,
+				50CBD7992D37FAF000BF8A0B /* UserSelectionCircle.swift */,
 			);
 			path = Button;
 			sourceTree = "<group>";
@@ -1595,6 +1600,7 @@
 			children = (
 				50CBD7AA2D39341700BF8A0B /* AuthorListDetailView.swift */,
 				50EA886E2D2D5780001E62CC /* AuthorListsView.swift */,
+				50EA8A732D32CB33001E62CC /* AuthorListManageUsersView.swift */,
 				50EA89A52D3010EA001E62CC /* EditAuthorListView.swift */,
 				50CBD80F2D3A8B6D00BF8A0B /* ListCircle.swift */,
 			);
@@ -2565,6 +2571,7 @@
 				C98CA9042B14FA3D00929141 /* PagedRelaySubscription.swift in Sources */,
 				5B0D99032A94090A0039F0C5 /* DoubleTapToPopModifier.swift in Sources */,
 				030024192CC00DFC0073ED56 /* SplashScreenView.swift in Sources */,
+				50CBD8102D3A8B7000BF8A0B /* ListCircle.swift in Sources */,
 				0357299B2BE415E5005FEE85 /* ContentWarningController.swift in Sources */,
 				5BFF66B42A58853D00AA79DD /* PublishedEventsView.swift in Sources */,
 				03D1B42C2C3C1B0D001778CD /* TLVElement.swift in Sources */,
@@ -2576,6 +2583,7 @@
 				A3B943CF299AE00100A15A08 /* Keychain.swift in Sources */,
 				C9671D73298DB94C00EE7E12 /* Data+Encoding.swift in Sources */,
 				03C7E7922CB9C0B30054624C /* WelcomeToFeedTip.swift in Sources */,
+				50EA8A742D32CB38001E62CC /* AuthorListManageUsersView.swift in Sources */,
 				C9646EA129B7A22C007239A4 /* Analytics.swift in Sources */,
 				03A743452CC048C700893CAE /* GoToFeedTip.swift in Sources */,
 				5045540D2C81E10C0044ECAE /* EditableAvatarView.swift in Sources */,
@@ -2656,7 +2664,7 @@
 				5B29B5842BEAA0D7008F6008 /* BioSheet.swift in Sources */,
 				C93CA0C329AE3A1E00921183 /* JSONEvent.swift in Sources */,
 				3FFB1D89299FF37C002A755D /* AvatarView.swift in Sources */,
-				65BD8DB92BDAF28200802039 /* CircularFollowButton.swift in Sources */,
+				65BD8DB92BDAF28200802039 /* CircularButton.swift in Sources */,
 				C97A1C8829E45B3C009D9E8D /* RawEventView.swift in Sources */,
 				C9DEC04529894BED0078B43A /* Event+CoreDataClass.swift in Sources */,
 				CD76865029B6503500085358 /* NoteOptionsButton.swift in Sources */,
@@ -2713,6 +2721,7 @@
 				04C9D7272CBF09C200EAAD4D /* TextField+PlaceHolderStyle.swift in Sources */,
 				C9DEC04D29894BED0078B43A /* Author+CoreDataClass.swift in Sources */,
 				C905B0772A619E99009B8A78 /* LPLinkViewRepresentable.swift in Sources */,
+				50CBD79A2D37FAF400BF8A0B /* UserSelectionCircle.swift in Sources */,
 				C95D68A7299E6FF000429F86 /* KeyFixture.swift in Sources */,
 				0304D0B22C9B731F001D16C7 /* MockOpenGraphService.swift in Sources */,
 				030E570D2CC2A05B00A4A51E /* DisplayNameView.swift in Sources */,
diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings
index 660d1a4ac..5ba8714ff 100644
--- a/Nos/Assets/Localization/Localizable.xcstrings
+++ b/Nos/Assets/Localization/Localizable.xcstrings
@@ -10489,6 +10489,17 @@
         }
       }
     },
+    "listStep2" : {
+      "extractionState" : "manual",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Step 2: add users"
+          }
+        }
+      }
+    },
     "loading" : {
       "extractionState" : "manual",
       "localizations" : {
diff --git a/Nos/Controller/FeedController.swift b/Nos/Controller/FeedController.swift
index a4637778f..f6316b361 100644
--- a/Nos/Controller/FeedController.swift
+++ b/Nos/Controller/FeedController.swift
@@ -82,7 +82,11 @@ import SwiftUI
             .publisher
             .receive(on: DispatchQueue.main)
             .sink(receiveValue: { [weak self] lists in
-                self?.lists = lists
+                // ensure that we only publish the most recent list for each replaceable identifier
+                let grouped = Dictionary(grouping: lists, by: { $0.replaceableIdentifier ?? "" })
+                self?.lists = grouped.compactMap { _, events in
+                    events.max(by: { $0.createdAt ?? Date.distantPast < $1.createdAt ?? Date.distantPast })
+                }
             })
             .store(in: &cancellables)
     }
diff --git a/Nos/Controller/SearchController.swift b/Nos/Controller/SearchController.swift
index 6f712bb0e..c67aa4d6a 100644
--- a/Nos/Controller/SearchController.swift
+++ b/Nos/Controller/SearchController.swift
@@ -26,6 +26,9 @@ enum SearchState {
 enum SearchOrigin {
     /// Search initiated from the Discover tab
     case discover
+    
+    /// Search initiated from ``AuthorListManageUsersView``
+    case lists
 
     /// Search initiated from the mentions `AuthorSearchView`
     case mentions
@@ -91,6 +94,8 @@ enum SearchOrigin {
                     switch searchOrigin {
                     case .discover:
                         analytics.searchedDiscover()
+                    case .lists:
+                        break // TODO: Analytics
                     case .mentions:
                         analytics.mentionsAutocompleteCharactersEntered()
                     }
diff --git a/Nos/Models/CoreData/AuthorList+CoreDataClass.swift b/Nos/Models/CoreData/AuthorList+CoreDataClass.swift
index 08723c115..b5958f756 100644
--- a/Nos/Models/CoreData/AuthorList+CoreDataClass.swift
+++ b/Nos/Models/CoreData/AuthorList+CoreDataClass.swift
@@ -88,7 +88,7 @@ public class AuthorList: Event {
             owner,
             kind
         )
-        fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \AuthorList.identifier, ascending: true)]
+        fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
         fetchRequest.fetchLimit = 1
         return fetchRequest
     }
@@ -101,7 +101,7 @@ public class AuthorList: Event {
         let request = NSFetchRequest<AuthorList>(entityName: "AuthorList")
         request.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
         request.predicate = NSPredicate(
-            format: "kind = %i AND author = %@ AND title != nil AND deletedOn.@count = 0",
+            format: "kind = %i AND author = %@ AND title != nil AND title != '' AND deletedOn.@count = 0",
             EventKind.followSet.rawValue,
             owner
         )
diff --git a/Nos/Views/Components/Author/AuthorCard.swift b/Nos/Views/Components/Author/AuthorCard.swift
index 22c1c94ae..18ad9009c 100644
--- a/Nos/Views/Components/Author/AuthorCard.swift
+++ b/Nos/Views/Components/Author/AuthorCard.swift
@@ -1,23 +1,38 @@
 import SwiftUI
 
+/// Modes for determining the state of the ``UserSelectionCircle`` on the ``AuthorCard``
+enum AvatarOverlayMode {
+    /// Uses following state by the current user
+    case follows
+    
+    /// Uses inclusion in the given set of ``Author``s
+    case inSet(authors: Set<Author>)
+    
+    /// Always displays the selected state
+    case alwaysSelected
+}
+
 /// This view displays the information we have for an author suitable for being used in a list.
-struct AuthorCard: View {
+struct AuthorCard<AvatarOverlay: View>: View {
+    
     @ObservedObject var author: Author
     @Environment(CurrentUser.self) var currentUser
 
-    /// Whether the follow button should be displayed or not.
-    let showsFollowButton: Bool
-
-    var tapAction: (() -> Void)?
+    let avatarOverlayView: () -> AvatarOverlay?
+    let tapAction: (() -> Void)?
     
     /// Initializes an `AuthorCard` with the given parameters.
     /// - Parameters:
     ///   - author: The author to show in the card.
-    ///   - showsFollowButton: Whether the follow button should be displayed or not. Defaults to `true`.
+    ///   - avatarOverlayView: The view to show as an overlay at the bottom right of the avatar.
     ///   - onTap: The action to take when this card is tapped, if any. Defaults to `nil`.
-    init(author: Author, showsFollowButton: Bool = true, onTap: (() -> Void)? = nil) {
+    init(
+        author: Author,
+        @ViewBuilder avatarOverlayView: @escaping () -> AvatarOverlay? = { nil as AnyView? },
+        onTap: (() -> Void)? = nil
+    ) {
         self.author = author
-        self.showsFollowButton = showsFollowButton
+        self.avatarOverlayView = avatarOverlayView
         self.tapAction = onTap
     }
 
@@ -30,7 +45,10 @@ struct AuthorCard: View {
                     ZStack(alignment: .bottomTrailing) {
                         AvatarView(imageUrl: author.profilePhotoURL, size: 80)
                             .padding(.trailing, 12)
-                        if showsFollowButton {
+                        
+                        if let overlay = avatarOverlayView() {
+                            overlay
+                        } else {
                             CircularFollowButton(author: author)
                         }
                     }
diff --git a/Nos/Views/Components/Author/AuthorSearchView.swift b/Nos/Views/Components/Author/AuthorSearchView.swift
index 02c2f2871..11b84e538 100644
--- a/Nos/Views/Components/Author/AuthorSearchView.swift
+++ b/Nos/Views/Components/Author/AuthorSearchView.swift
@@ -1,22 +1,47 @@
 import Foundation
 import SwiftUI
 
-struct AuthorSearchView: View {
+struct AuthorSearchView<EmptyPlaceholder: View>: View {
     
-    @Binding var isPresented: Bool
+    @Environment(\.dismiss) private var dismiss
     
     @Environment(\.managedObjectContext) private var viewContext
 
-    @State private var searchController = SearchController(searchOrigin: .mentions)
+    @State private var searchController: SearchController
 
     @FocusState private var isSearching: Bool
     @State private var filteredAuthors: [Author] = []
-
+    
+    let title: LocalizedStringKey?
+    let isModal: Bool
+    let avatarOverlayMode: AvatarOverlayMode
+    
+    /// The view to show when the search bar is empty.
+    let emptyPlaceholder: () -> EmptyPlaceholder?
+    
     /// The authors are referenced in a note / who replied under the note the user is replying if any.
     var relatedAuthors: [Author]?
 
     var didSelectGesture: ((Author) -> Void)?
 
+    init(
+        searchOrigin: SearchOrigin,
+        title: LocalizedStringKey? = nil,
+        isModal: Bool,
+        avatarOverlayMode: AvatarOverlayMode = .follows,
+        relatedAuthors: [Author]? = nil,
+        @ViewBuilder emptyPlaceholder: @escaping () -> EmptyPlaceholder? = { nil },
+        didSelectGesture: ((Author) -> Void)? = nil
+    ) {
+        self.title = title
+        self.isModal = isModal
+        self.avatarOverlayMode = avatarOverlayMode
+        self.relatedAuthors = relatedAuthors
+        self.didSelectGesture = didSelectGesture
+        self.emptyPlaceholder = emptyPlaceholder
+        _searchController = State(initialValue: SearchController(searchOrigin: searchOrigin))
+    }
+    
     var body: some View {
         ScrollView(.vertical) {
             SearchBar(text: $searchController.query, isSearching: $isSearching)
@@ -25,19 +50,19 @@ struct AuthorSearchView: View {
                 .onSubmit {
                     searchController.submitSearch(query: searchController.query)
                 }
-            LazyVStack {
-                ForEach(filteredAuthors) { author in
-                    AuthorCard(author: author, showsFollowButton: false) {
-                        didSelectGesture?(author)
+            
+            if filteredAuthors.isEmpty {
+                emptyPlaceholder()
+            } else {
+                LazyVStack {
+                    ForEach(filteredAuthors) { author in
+                        row(forAuthor: author)
                     }
-                    .padding(.horizontal, 13)
-                    .padding(.top, 5)
-                    .readabilityPadding()
                 }
             }
         }
         .background(Color.appBg)
-        .nosNavigationBar("mention")
+        .nosNavigationBar(title ?? "")
         .onAppear {
             isSearching = true
 
@@ -55,14 +80,38 @@ struct AuthorSearchView: View {
         }
         .disableAutocorrection(true)
         .toolbar {
-            ToolbarItem(placement: .cancellationAction) {
-                Button(action: {
-                    isPresented = false
-                }, label: {
-                    Text("cancel")
-                        .foregroundColor(.primaryTxt)
-                })
+            if isModal {
+                ToolbarItem(placement: .cancellationAction) {
+                    Button(action: {
+                        dismiss()
+                    }, label: {
+                        Text("cancel")
+                            .foregroundColor(.primaryTxt)
+                    })
+                }
             }
         }
     }
+    
+    private func row(forAuthor author: Author) -> some View {
+        AuthorCard(
+            author: author,
+            avatarOverlayView: {
+                switch avatarOverlayMode {
+                case .follows:
+                    AnyView(CircularFollowButton(author: author))
+                case .alwaysSelected:
+                    AnyView(UserSelectionCircle(diameter: 30, selected: true))
+                case .inSet(let authors):
+                    AnyView(UserSelectionCircle(diameter: 30, selected: authors.contains(author)))
+                }
+            },
+            onTap: {
+                didSelectGesture?(author)
+            }
+        )
+        .padding(.horizontal, 13)
+        .padding(.top, 5)
+        .readabilityPadding()
+    }
 }
diff --git a/Nos/Views/Components/Button/CircularFollowButton.swift b/Nos/Views/Components/Button/CircularButton.swift
similarity index 60%
rename from Nos/Views/Components/Button/CircularFollowButton.swift
rename to Nos/Views/Components/Button/CircularButton.swift
index b3f249bda..55b4610dc 100644
--- a/Nos/Views/Components/Button/CircularFollowButton.swift
+++ b/Nos/Views/Components/Button/CircularButton.swift
@@ -39,27 +39,10 @@ struct CircularFollowButton: View {
                 }
             }
         } label: {
-            ZStack {
-                Circle()
-                    .frame(width: diameter)
-                    .foregroundStyle(
-                        following ? LinearGradient.verticalAccentSecondary : LinearGradient.verticalAccentPrimary
-                    )
-                    .background(
-                        Circle()
-                            .frame(width: diameter)
-                            .offset(y: 1)
-                            .foregroundStyle(
-                                following ? Color.actionSecondaryBackground : Color.actionPrimaryBackground
-                            )
-                    )
-                if following {
-                    Image.followingIcon
-                        .padding(.top, 3) // the icon file isn't square so we need to shift it down
-                } else {
-                    Image.followIcon
-                }
-            }
+            UserSelectionCircle(
+                diameter: diameter,
+                selected: following
+            )
         }
         .disabled(disabled)
     }
diff --git a/Nos/Views/Components/Button/UserSelectionCircle.swift b/Nos/Views/Components/Button/UserSelectionCircle.swift
new file mode 100644
index 000000000..430ccfaa5
--- /dev/null
+++ b/Nos/Views/Components/Button/UserSelectionCircle.swift
@@ -0,0 +1,36 @@
+import SwiftUI
+
+/// A circular image view that is either a purple checkmark or an orange user-plus based
+/// on the `selected` property.
+///
+/// This is useful to indicate "selected" or "included" state of the contextual item, such
+/// as a user being followed or not or being in an ``AuthorList`` or not.
+struct UserSelectionCircle: View {
+    let diameter: CGFloat
+    let selected: Bool
+    
+    var body: some View {
+        ZStack {
+            Circle()
+                .frame(width: diameter)
+                .foregroundStyle(
+                    selected ? LinearGradient.verticalAccentSecondary : LinearGradient.verticalAccentPrimary
+                )
+                .background(
+                    Circle()
+                        .frame(width: diameter)
+                        .offset(y: 1)
+                        .foregroundStyle(
+                            selected ? Color.actionSecondaryBackground : Color.actionPrimaryBackground
+                        )
+                )
+            
+            if selected {
+                Image.followingIcon
+                    .padding(.top, 3) // the icon file isn't square so we need to shift it down
+            } else {
+                Image.followIcon
+            }
+        }
+    }
+}
diff --git a/Nos/Views/Components/NoteTextEditor.swift b/Nos/Views/Components/NoteTextEditor.swift
index b43986cb0..bb49af062 100644
--- a/Nos/Views/Components/NoteTextEditor.swift
+++ b/Nos/Views/Components/NoteTextEditor.swift
@@ -35,15 +35,19 @@ struct NoteTextEditor: View {
         .sheet(isPresented: $controller.showMentionsAutocomplete) {
             NavigationStack {
                 AuthorSearchView(
-                    isPresented: $controller.showMentionsAutocomplete,
-                    relatedAuthors: relatedAuthors
-                ) { [weak controller] author in
-                    /// Guard against double presses
-                    guard let controller, controller.showMentionsAutocomplete else { return }
-
-                    controller.insertMention(of: author)
-                    controller.showMentionsAutocomplete = false
-                }
+                    searchOrigin: .mentions,
+                    title: "mention",
+                    isModal: true,
+                    relatedAuthors: relatedAuthors,
+                    emptyPlaceholder: { EmptyView() },
+                    didSelectGesture: { [weak controller] author in
+                        /// Guard against double presses
+                        guard let controller, controller.showMentionsAutocomplete else { return }
+                        
+                        controller.insertMention(of: author)
+                        controller.showMentionsAutocomplete = false
+                    }
+                )
             }
         }
     }
diff --git a/Nos/Views/Discover/DiscoverContentsView.swift b/Nos/Views/Discover/DiscoverContentsView.swift
index ff17274ac..691b181ee 100644
--- a/Nos/Views/Discover/DiscoverContentsView.swift
+++ b/Nos/Views/Discover/DiscoverContentsView.swift
@@ -50,13 +50,13 @@ struct DiscoverContentsView: View {
                         ScrollView {
                             LazyVStack {
                                 ForEach(searchController.authorResults) { author in
-                                    AuthorCard(author: author) {
+                                    AuthorCard(author: author, onTap: {
                                         let resultsCount = searchController.authorResults.count
                                         analytics.displayedAuthorFromDiscoverSearch(
                                             resultsCount: resultsCount
                                         )
                                         router.push(author)
-                                    }
+                                    })
                                     .padding(.horizontal, 15)
                                     .padding(.top, 10)
                                     .readabilityPadding()
@@ -86,9 +86,9 @@ struct DiscoverContentsView: View {
                         AuthorObservationView(authorID: authorID) { author in
                             VStack {
                                 if author.lastUpdatedMetadata != nil {
-                                    AuthorCard(author: author) {
+                                    AuthorCard(author: author, onTap: {
                                         router.push(author)
-                                    }
+                                    })
                                     .padding(.horizontal, 13)
                                     .padding(.top, 5)
                                     .readabilityPadding()
diff --git a/Nos/Views/Lists/AuthorListDetailView.swift b/Nos/Views/Lists/AuthorListDetailView.swift
index 06a4e8d17..a12644e4a 100644
--- a/Nos/Views/Lists/AuthorListDetailView.swift
+++ b/Nos/Views/Lists/AuthorListDetailView.swift
@@ -3,7 +3,9 @@ import SwiftUI
 
 struct AuthorListDetailView: View {
     
+    @Environment(\.dismiss) private var dismiss
     @Dependency(\.relayService) private var relayService
+    @Environment(CurrentUser.self) private var currentUser
     @EnvironmentObject private var router: Router
     
     @ObservedObject var list: AuthorList
@@ -12,6 +14,7 @@ struct AuthorListDetailView: View {
     @State private var subscriptions = [ObjectIdentifier: SubscriptionCancellable]()
     
     @State private var showingEditListInfo = false
+    @State private var showingManageUsers = false
 
     var body: some View {
         ScrollView {
@@ -43,7 +46,7 @@ struct AuthorListDetailView: View {
                         ProfileView(author: author)
                     } label: {
                         AuthorObservationView(authorID: author.hexadecimalPublicKey) { author in
-                            AuthorCard(author: author)
+                            AuthorCard(author: author, avatarOverlayView: { EmptyView() })
                                 .padding(.horizontal, 13)
                                 .padding(.top, 5)
                                 .readabilityPadding()
@@ -70,7 +73,7 @@ struct AuthorListDetailView: View {
                         showingEditListInfo = true
                     }
                     Button("manageUsers") {
-                        // TODO: Manage Users
+                        showingManageUsers = true
                     }
                     Button("deleteList", role: .destructive) {
                         // TODO: Delete List
@@ -88,5 +91,10 @@ struct AuthorListDetailView: View {
                 EditAuthorListView(list: list)
             }
         }
+        .sheet(isPresented: $showingManageUsers) {
+            NavigationStack {
+                AuthorListManageUsersView(list: list)
+            }
+        }
     }
 }
diff --git a/Nos/Views/Lists/AuthorListManageUsersView.swift b/Nos/Views/Lists/AuthorListManageUsersView.swift
new file mode 100644
index 000000000..6c40f1c6d
--- /dev/null
+++ b/Nos/Views/Lists/AuthorListManageUsersView.swift
@@ -0,0 +1,161 @@
+import Logger
+import SwiftUI
+
+/// Displays a search bar and user results. Allows the user to add or remove users
+/// from an existing ``AuthorList`` and while creating a new one.
+struct AuthorListManageUsersView: View {
+    
+    private enum Mode {
+        case create(title: String, description: String?)
+        case update(list: AuthorList)
+        
+        var buttonTitleKey: LocalizedStringKey {
+            switch self {
+            case .create:
+                "save"
+            case .update:
+                "done"
+            }
+        }
+    }
+    
+    @Environment(\.dismiss) private var dismiss
+    @Environment(RelayService.self) private var relayService
+    @Environment(CurrentUser.self) private var currentUser
+    @Environment(\.managedObjectContext) private var viewContext
+    @State private var authors: Set<Author>
+    
+    private var mode: Mode
+    
+    /// An action that runs after successfully saving an ``AuthorList``.
+    ///
+    /// Leaving the value nil will cause the Environment's `dismiss()` to be called, which may appear
+    /// as a modal dismissal or a navigational pop depending on the presentation context.
+    private let onSave: (() -> Void)?
+    
+    init(list: AuthorList) {
+        mode = .update(list: list)
+        _authors = State(initialValue: list.allAuthors)
+        onSave = nil
+    }
+    
+    init(title: String, description: String?, onSave: (() -> Void)?) {
+        mode = .create(title: title, description: description)
+        _authors = State(initialValue: [])
+        self.onSave = onSave
+    }
+    
+    var body: some View {
+        ZStack {
+            Color.appBg
+                .ignoresSafeArea()
+            
+            VStack(alignment: .leading, spacing: 0) {
+                if case .create = mode {
+                    Text("listStep2")
+                        .font(.clarity(.medium, textStyle: .subheadline))
+                        .foregroundColor(Color.secondaryTxt)
+                        .padding(EdgeInsets(top: 28, leading: 20, bottom: 0, trailing: 16))
+                }
+                
+                AuthorSearchView(
+                    searchOrigin: .lists,
+                    isModal: false,
+                    avatarOverlayMode: .inSet(authors: authors),
+                    emptyPlaceholder: {
+                        AuthorsView(
+                            authors: Array(authors),
+                            avatarOverlayMode: .alwaysSelected,
+                            onTapGesture: toggleAuthor
+                        )
+                    },
+                    didSelectGesture: toggleAuthor
+                )
+            }
+        }
+        .nosNavigationBar(title: AttributedString(viewTitle))
+        .toolbar {
+            if case .update = mode {
+                ToolbarItem(placement: .cancellationAction) {
+                    Button("cancel") {
+                        dismiss()
+                    }
+                }
+            }
+            ToolbarItem(placement: .primaryAction) {
+                ActionButton(mode.buttonTitleKey, action: saveButtonPressed)
+                    .frame(height: 22)
+                    .padding(.bottom, 3)
+            }
+        }
+    }
+    
+    private var viewTitle: String {
+        switch mode {
+        case .create(let title, _):
+            title
+        case .update(let list):
+            list.title ?? ""
+        }
+    }
+    
+    private var buttonTitleKey: LocalizedStringKey {
+        switch mode {
+        case .create:
+            "save"
+        case .update:
+            "done"
+        }
+    }
+    
+    private func toggleAuthor(_ author: Author) {
+        if authors.contains(author) {
+            authors.remove(author)
+        } else {
+            authors.insert(author)
+        }
+    }
+    
+    private func saveButtonPressed() {
+        guard let keyPair = currentUser.keyPair else {
+            return
+        }
+        
+        let title: String
+        let description: String?
+        let replaceableID: String?
+        
+        switch mode {
+        case .create(let newTitle, let newDescription):
+            title = newTitle
+            description = newDescription
+            replaceableID = nil
+        case .update(let list):
+            title = list.title ?? ""
+            description = list.listDescription
+            replaceableID = list.replaceableIdentifier
+        }
+        
+        let event = JSONEvent.followSet(
+            pubKey: keyPair.publicKeyHex,
+            title: title,
+            description: description,
+            replaceableID: replaceableID,
+            authorIDs: authors.compactMap { $0.hexadecimalPublicKey }
+        )
+        
+        Task {
+            do {
+                try await relayService.publishToAll(event: event, signingKey: keyPair, context: viewContext)
+                
+                if let onSave {
+                    onSave()
+                } else {
+                    dismiss()
+                }
+            } catch {
+                Log.error("Error when creating list: \(error.localizedDescription)")
+            }
+        }
+    }
+}
diff --git a/Nos/Views/Lists/AuthorListsView.swift b/Nos/Views/Lists/AuthorListsView.swift
index 2d53e00cf..796df2b2b 100644
--- a/Nos/Views/Lists/AuthorListsView.swift
+++ b/Nos/Views/Lists/AuthorListsView.swift
@@ -7,7 +7,6 @@ struct ListsDestination: Hashable {
 
 /// A view that displays a list of an ``Author``'s ``AuthorList``s.
 struct AuthorListsView: View {
-    @Environment(\.managedObjectContext) private var viewContext
     
     let author: Author
     
@@ -53,6 +52,7 @@ struct AuthorListsView: View {
                                             Text(list.rowDescription)
                                                 .foregroundStyle(Color.secondaryTxt)
                                                 .font(.footnote)
+                                                .lineLimit(1)
                                         }
                                         
                                         Spacer()
diff --git a/Nos/Views/Lists/EditAuthorListView.swift b/Nos/Views/Lists/EditAuthorListView.swift
index f31a89529..8d08d15f3 100644
--- a/Nos/Views/Lists/EditAuthorListView.swift
+++ b/Nos/Views/Lists/EditAuthorListView.swift
@@ -23,6 +23,8 @@ struct EditAuthorListView: View {
     @FocusState private var focusedField: Field?
     private let mode: Mode
     
+    @State private var showingManageUsers = false
+    
     init(list: AuthorList? = nil) {
         self.list = list
         mode = list == nil ? .create : .update
@@ -75,6 +77,11 @@ struct EditAuthorListView: View {
                     .padding(.bottom, 3)
             }
         }
+        .navigationDestination(isPresented: $showingManageUsers) {
+            AuthorListManageUsersView(title: title, description: description) {
+                dismiss()
+            }
+        }
         .onAppear {
             title = list?.title ?? ""
             description = list?.listDescription ?? ""
@@ -99,7 +106,7 @@ struct EditAuthorListView: View {
                 title: title,
                 description: description,
                 replaceableID: list?.replaceableIdentifier,
-                authorIDs: []
+                authorIDs: list?.authors.compactMap { $0.hexadecimalPublicKey } ?? []
             )
             
             Task {
@@ -111,7 +118,7 @@ struct EditAuthorListView: View {
                 }
             }
         } else {
-            // TODO: Manage Users
+            showingManageUsers = true
         }
     }
 }
diff --git a/Nos/Views/Profile/AuthorsView.swift b/Nos/Views/Profile/AuthorsView.swift
index 4744143c1..5d20dba35 100644
--- a/Nos/Views/Profile/AuthorsView.swift
+++ b/Nos/Views/Profile/AuthorsView.swift
@@ -15,10 +15,14 @@ struct FollowersDestination: Hashable {
 /// Displays a list of authors.
 struct AuthorsView: View {
     /// Screen title
-    let title: LocalizedStringKey
+    let title: LocalizedStringKey?
 
     /// Sorted list of authors to display in the list
     let authors: [Author]
+    
+    let avatarOverlayMode: AvatarOverlayMode
+    
+    let onTapGesture: ((Author) -> Void)?
 
     /// Subscriptions for metadata requests from the relay service, keyed by author ID.
     @State private var subscriptions = [ObjectIdentifier: SubscriptionCancellable]()
@@ -27,11 +31,15 @@ struct AuthorsView: View {
     @EnvironmentObject private var router: Router
 
     init(
-        _ title: LocalizedStringKey,
-        authors: [Author]
+        _ title: LocalizedStringKey? = nil,
+        authors: [Author],
+        avatarOverlayMode: AvatarOverlayMode = .follows,
+        onTapGesture: ((Author) -> Void)? = nil
     ) {
         self.title = title
         self.authors = authors
+        self.avatarOverlayMode = avatarOverlayMode
+        self.onTapGesture = onTapGesture
     }
     
     var body: some View {
@@ -39,9 +47,26 @@ struct AuthorsView: View {
             LazyVStack {
                 ForEach(authors) { author in
                     AuthorObservationView(authorID: author.hexadecimalPublicKey) { author in
-                        AuthorCard(author: author) {
-                            router.push(author)
-                        }
+                        AuthorCard(
+                            author: author,
+                            avatarOverlayView: {
+                                switch avatarOverlayMode {
+                                case .follows:
+                                    AnyView(CircularFollowButton(author: author))
+                                case .alwaysSelected:
+                                    AnyView(UserSelectionCircle(diameter: 30, selected: true))
+                                case .inSet(let authors):
+                                    AnyView(UserSelectionCircle(diameter: 30, selected: authors.contains(author)))
+                                }
+                            },
+                            onTap: {
+                                if let onTapGesture {
+                                    onTapGesture(author)
+                                } else {
+                                    router.push(author)
+                                }
+                            }
+                        )
                         .padding(.horizontal, 13)
                         .padding(.top, 5)
                         .readabilityPadding()
@@ -58,6 +83,6 @@ struct AuthorsView: View {
             .padding(.vertical, 12)
         }
         .background(Color.appBg)
-        .nosNavigationBar(title)
+        .nosNavigationBar(title ?? "")
     }
 }