diff --git a/public/sw.js b/public/sw.js
index 14514039..b44a94b0 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -39,7 +39,7 @@ registerRoute(imageRoute);
 // - /api/v1/preferences
 // - /api/v1/lists/:id
 const apiExtendedRoute = new RegExpRoute(
-  /^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+)/,
+  /^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+)$/,
   new StaleWhileRevalidate({
     cacheName: 'api-extended',
     plugins: [
diff --git a/src/components/account-info.css b/src/components/account-info.css
index f14780bb..06bf133b 100644
--- a/src/components/account-info.css
+++ b/src/components/account-info.css
@@ -260,6 +260,28 @@
   animation: shine 1s ease-in-out 1s;
 }
 
+#list-add-remove-container .list-add-remove {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  margin: 0;
+  padding: 8px 0;
+  list-style: none;
+}
+#list-add-remove-container .list-add-remove button {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  width: 100%;
+}
+#list-add-remove-container .list-add-remove button .icon {
+  opacity: 0.15;
+}
+#list-add-remove-container .list-add-remove button.checked .icon {
+  opacity: 1;
+  color: var(--green-color);
+}
+
 @media (min-width: 40em) {
   .timeline-start .account-container {
     --item-radius: 16px;
diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx
index a7fa85c2..0acc64b4 100644
--- a/src/components/account-info.jsx
+++ b/src/components/account-info.jsx
@@ -1,13 +1,7 @@
 import './account-info.css';
 
-import {
-  Menu,
-  MenuDivider,
-  MenuHeader,
-  MenuItem,
-  SubMenu,
-} from '@szhsin/react-menu';
-import { useEffect, useRef, useState } from 'preact/hooks';
+import { Menu, MenuDivider, MenuItem, SubMenu } from '@szhsin/react-menu';
+import { useEffect, useReducer, useRef, useState } from 'preact/hooks';
 
 import { api } from '../utils/api';
 import emojifyText from '../utils/emojify-text';
@@ -24,6 +18,8 @@ import AccountBlock from './account-block';
 import Avatar from './avatar';
 import Icon from './icon';
 import Link from './link';
+import ListAddEdit from './list-add-edit';
+import Loader from './loader';
 import Modal from './modal';
 import TranslationBlock from './translation-block';
 
@@ -487,6 +483,7 @@ function RelatedActions({ info, instance, authenticated }) {
   const menuInstanceRef = useRef(null);
 
   const [showTranslatedBio, setShowTranslatedBio] = useState(false);
+  const [showAddRemoveLists, setShowAddRemoveLists] = useState(false);
 
   return (
     <>
@@ -583,6 +580,17 @@ function RelatedActions({ info, instance, authenticated }) {
                   <Icon icon="translate" />
                   <span>Translate bio</span>
                 </MenuItem>
+                {/* Add/remove from lists is only possible if following the account */}
+                {following && (
+                  <MenuItem
+                    onClick={() => {
+                      setShowAddRemoveLists(true);
+                    }}
+                  >
+                    <Icon icon="list" />
+                    <span>Add/remove from Lists</span>
+                  </MenuItem>
+                )}
                 <MenuDivider />
               </>
             )}
@@ -840,6 +848,18 @@ function RelatedActions({ info, instance, authenticated }) {
           <TranslatedBioSheet note={note} fields={fields} />
         </Modal>
       )}
+      {!!showAddRemoveLists && (
+        <Modal
+          class="light"
+          onClick={(e) => {
+            if (e.target === e.currentTarget) {
+              setShowAddRemoveLists(false);
+            }
+          }}
+        >
+          <AddRemoveListsSheet accountID={accountID.current} />
+        </Modal>
+      )}
     </>
   );
 }
@@ -900,4 +920,127 @@ function TranslatedBioSheet({ note, fields }) {
     </div>
   );
 }
+
+function AddRemoveListsSheet({ accountID }) {
+  const { masto } = api();
+  const [uiState, setUiState] = useState('default');
+  const [lists, setLists] = useState([]);
+  const [listsContainingAccount, setListsContainingAccount] = useState([]);
+  const [reloadCount, reload] = useReducer((c) => c + 1, 0);
+
+  useEffect(() => {
+    setUiState('loading');
+    (async () => {
+      try {
+        const lists = await masto.v1.lists.list();
+        const listsContainingAccount = await masto.v1.accounts.listLists(
+          accountID,
+        );
+        console.log({ lists, listsContainingAccount });
+        setLists(lists);
+        setListsContainingAccount(listsContainingAccount);
+        setUiState('default');
+      } catch (e) {
+        console.error(e);
+        setUiState('error');
+      }
+    })();
+  }, [reloadCount]);
+
+  const [showListAddEditModal, setShowListAddEditModal] = useState(false);
+
+  return (
+    <div class="sheet" id="list-add-remove-container">
+      <header>
+        <h2>Add/Remove from Lists</h2>
+      </header>
+      <main>
+        {lists.length > 0 ? (
+          <ul class="list-add-remove">
+            {lists.map((list) => {
+              const inList = listsContainingAccount.some(
+                (l) => l.id === list.id,
+              );
+              return (
+                <li>
+                  <button
+                    type="button"
+                    class={`light ${inList ? 'checked' : ''}`}
+                    disabled={uiState === 'loading'}
+                    onClick={() => {
+                      setUiState('loading');
+                      (async () => {
+                        try {
+                          if (inList) {
+                            await masto.v1.lists.removeAccount(list.id, {
+                              accountIds: [accountID],
+                            });
+                          } else {
+                            await masto.v1.lists.addAccount(list.id, {
+                              accountIds: [accountID],
+                            });
+                          }
+                          // setUiState('default');
+                          reload();
+                        } catch (e) {
+                          console.error(e);
+                          setUiState('error');
+                          alert(
+                            inList
+                              ? 'Unable to remove from list.'
+                              : 'Unable to add to list.',
+                          );
+                        }
+                      })();
+                    }}
+                  >
+                    <Icon icon="check-circle" />
+                    <span>{list.title}</span>
+                  </button>
+                </li>
+              );
+            })}
+          </ul>
+        ) : uiState === 'loading' ? (
+          <p class="ui-state">
+            <Loader abrupt />
+          </p>
+        ) : uiState === 'error' ? (
+          <p class="ui-state">Unable to load lists.</p>
+        ) : (
+          <p class="ui-state">No lists.</p>
+        )}
+        <button
+          type="button"
+          class="plain2"
+          onClick={() => setShowListAddEditModal(true)}
+          disabled={uiState !== 'default'}
+        >
+          <Icon icon="plus" size="l" /> <span>New list</span>
+        </button>
+      </main>
+      {showListAddEditModal && (
+        <Modal
+          class="light"
+          onClick={(e) => {
+            if (e.target === e.currentTarget) {
+              setShowListAddEditModal(false);
+            }
+          }}
+        >
+          <ListAddEdit
+            list={showListAddEditModal?.list}
+            onClose={(result) => {
+              if (result.state === 'success') {
+                reload();
+              }
+              setShowListAddEditModal(false);
+            }}
+          />
+        </Modal>
+      )}
+    </div>
+  );
+}
+
 export default AccountInfo;
diff --git a/src/components/list-add-edit.jsx b/src/components/list-add-edit.jsx
new file mode 100644
index 00000000..a3fd8f3f
--- /dev/null
+++ b/src/components/list-add-edit.jsx
@@ -0,0 +1,133 @@
+import { useEffect, useRef, useState } from 'preact/hooks';
+
+import { api } from '../utils/api';
+
+function ListAddEdit({ list, onClose = () => {} }) {
+  const { masto } = api();
+  const [uiState, setUiState] = useState('default');
+  const editMode = !!list;
+  const nameFieldRef = useRef();
+  const repliesPolicyFieldRef = useRef();
+  useEffect(() => {
+    if (editMode) {
+      nameFieldRef.current.value = list.title;
+      repliesPolicyFieldRef.current.value = list.repliesPolicy;
+    }
+  }, [editMode]);
+  return (
+    <div class="sheet">
+      <header>
+        <h2>{editMode ? 'Edit list' : 'New list'}</h2>
+      </header>
+      <main>
+        <form
+          class="list-form"
+          onSubmit={(e) => {
+            e.preventDefault(); // Get form values
+
+            const formData = new FormData(e.target);
+            const title = formData.get('title');
+            const repliesPolicy = formData.get('replies_policy');
+            console.log({
+              title,
+              repliesPolicy,
+            });
+            setUiState('loading');
+
+            (async () => {
+              try {
+                let listResult;
+
+                if (editMode) {
+                  listResult = await masto.v1.lists.update(list.id, {
+                    title,
+                    replies_policy: repliesPolicy,
+                  });
+                } else {
+                  listResult = await masto.v1.lists.create({
+                    title,
+                    replies_policy: repliesPolicy,
+                  });
+                }
+
+                console.log(listResult);
+                setUiState('default');
+                onClose({
+                  state: 'success',
+                  list: listResult,
+                });
+              } catch (e) {
+                console.error(e);
+                setUiState('error');
+                alert(
+                  editMode ? 'Unable to edit list.' : 'Unable to create list.',
+                );
+              }
+            })();
+          }}
+        >
+          <div class="list-form-row">
+            <label for="list-title">
+              Name{' '}
+              <input
+                ref={nameFieldRef}
+                type="text"
+                id="list-title"
+                name="title"
+                required
+                disabled={uiState === 'loading'}
+              />
+            </label>
+          </div>
+          <div class="list-form-row">
+            <select
+              ref={repliesPolicyFieldRef}
+              name="replies_policy"
+              required
+              disabled={uiState === 'loading'}
+            >
+              <option value="list">Show replies to list members</option>
+              <option value="followed">Show replies to people I follow</option>
+              <option value="none">Don't show replies</option>
+            </select>
+          </div>
+          <div class="list-form-footer">
+            <button type="submit" disabled={uiState === 'loading'}>
+              {editMode ? 'Save' : 'Create'}
+            </button>
+            {editMode && (
+              <button
+                type="button"
+                class="light danger"
+                disabled={uiState === 'loading'}
+                onClick={() => {
+                  const yes = confirm('Delete this list?');
+                  if (!yes) return;
+                  setUiState('loading');
+
+                  (async () => {
+                    try {
+                      await masto.v1.lists.remove(list.id);
+                      setUiState('default');
+                      onClose({
+                        state: 'deleted',
+                      });
+                    } catch (e) {
+                      console.error(e);
+                      setUiState('error');
+                      alert('Unable to delete list.');
+                    }
+                  })();
+                }}
+              >
+                Delete…
+              </button>
+            )}
+          </div>
+        </form>
+      </main>
+    </div>
+  );
+}
+
+export default ListAddEdit;
diff --git a/src/index.css b/src/index.css
index 339df71f..1a124fe5 100644
--- a/src/index.css
+++ b/src/index.css
@@ -194,6 +194,9 @@ button,
   color: var(--text-color);
   border: 1px solid var(--outline-color);
 }
+:is(button, .button).light:not(:disabled, .disabled):is(:hover, :focus) {
+  border-color: var(--outline-hover-color);
+}
 :is(button, .button).light.danger:not(:disabled, .disabled) {
   color: var(--red-color);
 }
diff --git a/src/pages/list.jsx b/src/pages/list.jsx
index 54c53c58..3b9155dc 100644
--- a/src/pages/list.jsx
+++ b/src/pages/list.jsx
@@ -1,8 +1,15 @@
-import { useEffect, useRef, useState } from 'preact/hooks';
-import { useParams } from 'react-router-dom';
+import './lists.css';
 
+import { Menu, MenuItem } from '@szhsin/react-menu';
+import { useEffect, useRef, useState } from 'preact/hooks';
+import { InView } from 'react-intersection-observer';
+import { useNavigate, useParams } from 'react-router-dom';
+
+import AccountBlock from '../components/account-block';
 import Icon from '../components/icon';
 import Link from '../components/link';
+import ListAddEdit from '../components/list-add-edit';
+import Modal from '../components/modal';
 import Timeline from '../components/timeline';
 import { api } from '../utils/api';
 import { filteredItems } from '../utils/filters';
@@ -14,7 +21,9 @@ const LIMIT = 20;
 function List(props) {
   const { masto, instance } = api();
   const id = props?.id || useParams()?.id;
+  const navigate = useNavigate();
   const latestItem = useRef();
+  // const [reloadCount, reload] = useReducer((c) => c + 1, 0);
 
   const listIterator = useRef();
   async function fetchList(firstLoad) {
@@ -55,37 +64,231 @@ function List(props) {
     }
   }
 
-  const [title, setTitle] = useState(`List`);
-  useTitle(title, `/l/:id`);
+  const [list, setList] = useState({ title: 'List' });
+  // const [title, setTitle] = useState(`List`);
+  useTitle(list.title, `/l/:id`);
   useEffect(() => {
     (async () => {
       try {
         const list = await masto.v1.lists.fetch(id);
-        setTitle(list.title);
+        setList(list);
+        // setTitle(list.title);
       } catch (e) {
         console.error(e);
       }
     })();
   }, [id]);
 
+  const [showListAddEditModal, setShowListAddEditModal] = useState(false);
+  const [showManageMembersModal, setShowManageMembersModal] = useState(false);
+
   return (
-    <Timeline
-      title={title}
-      id="list"
-      emptyText="Nothing yet."
-      errorText="Unable to load posts."
-      instance={instance}
-      fetchItems={fetchList}
-      checkForUpdates={checkForUpdates}
-      useItemID
-      boostsCarousel
-      allowFilters
-      headerStart={
-        <Link to="/l" class="button plain">
-          <Icon icon="list" size="l" />
-        </Link>
+    <>
+      <Timeline
+        title={list.title}
+        id="list"
+        emptyText="Nothing yet."
+        errorText="Unable to load posts."
+        instance={instance}
+        fetchItems={fetchList}
+        checkForUpdates={checkForUpdates}
+        useItemID
+        boostsCarousel
+        allowFilters
+        // refresh={reloadCount}
+        headerStart={
+          <Link to="/l" class="button plain">
+            <Icon icon="list" size="l" />
+          </Link>
+        }
+        headerEnd={
+          <Menu
+            portal={{
+              target: document.body,
+            }}
+            setDownOverflow
+            overflow="auto"
+            viewScroll="close"
+            position="anchor"
+            boundingBoxPadding="8 8 8 8"
+            menuButton={
+              <button type="button" class="plain">
+                <Icon icon="more" size="l" />
+              </button>
+            }
+          >
+            <MenuItem
+              onClick={() =>
+                setShowListAddEditModal({
+                  list,
+                })
+              }
+            >
+              <Icon icon="pencil" size="l" />
+              <span>Edit</span>
+            </MenuItem>
+            <MenuItem onClick={() => setShowManageMembersModal(true)}>
+              <Icon icon="group" size="l" />
+              <span>Manage members</span>
+            </MenuItem>
+          </Menu>
+        }
+      />
+      {showListAddEditModal && (
+        <Modal
+          class="light"
+          onClick={(e) => {
+            if (e.target === e.currentTarget) {
+              setShowListAddEditModal(false);
+            }
+          }}
+        >
+          <ListAddEdit
+            list={showListAddEditModal?.list}
+            onClose={(result) => {
+              if (result.state === 'success' && result.list) {
+                setList(result.list);
+                // reload();
+              } else if (result.state === 'deleted') {
+                navigate('/l');
+              }
+              setShowListAddEditModal(false);
+            }}
+          />
+        </Modal>
+      )}
+      {showManageMembersModal && (
+        <Modal
+          class="light"
+          onClick={(e) => {
+            if (e.target === e.currentTarget) {
+              setShowManageMembersModal(false);
+            }
+          }}
+        >
+          <ListManageMembers listID={id} />
+        </Modal>
+      )}
+    </>
+  );
+}
+
+const MEMBERS_LIMIT = 10;
+function ListManageMembers({ listID }) {
+  // Show list of members with [Remove] button
+  // API only returns 40 members at a time, so this need to be paginated with infinite scroll
+  // Show [Add] button after removing a member
+  const { masto, instance } = api();
+  const [members, setMembers] = useState([]);
+  const [uiState, setUIState] = useState('default');
+  const [showMore, setShowMore] = useState(false);
+
+  const membersIterator = useRef();
+
+  async function fetchMembers(firstLoad) {
+    setShowMore(false);
+    setUIState('loading');
+    (async () => {
+      try {
+        if (firstLoad || !membersIterator.current) {
+          membersIterator.current = masto.v1.lists.listAccounts(listID, {
+            limit: MEMBERS_LIMIT,
+          });
+        }
+        const results = await membersIterator.current.next();
+        let { done, value } = results;
+        if (value?.length) {
+          if (firstLoad) {
+            setMembers(value);
+          } else {
+            setMembers(members.concat(value));
+          }
+          setShowMore(!done);
+        } else {
+          setShowMore(false);
+        }
+        setUIState('default');
+      } catch (e) {
+        setUIState('error');
       }
-    />
+    })();
+  }
+
+  useEffect(() => {
+    fetchMembers(true);
+  }, []);
+
+  return (
+    <div class="sheet" id="list-manage-members-container">
+      <header>
+        <h2>Manage members</h2>
+      </header>
+      <main>
+        <ul>
+          {members.map((member) => (
+            <li key={member.id}>
+              <AccountBlock account={member} instance={instance} />
+              <RemoveAddButton account={member} listID={listID} />
+            </li>
+          ))}
+          {showMore && uiState === 'default' && (
+            <InView as="li" onChange={(inView) => inView && fetchMembers()}>
+              <button type="button" class="light block" onClick={fetchMembers}>
+                Show more&hellip;
+              </button>
+            </InView>
+          )}
+        </ul>
+      </main>
+    </div>
+  );
+}
+
+function RemoveAddButton({ account, listID }) {
+  const { masto } = api();
+  const [uiState, setUIState] = useState('default');
+  const [removed, setRemoved] = useState(false);
+
+  return (
+    <button
+      type="button"
+      class={`light ${removed ? '' : 'danger'}`}
+      disabled={uiState === 'loading'}
+      onClick={() => {
+        if (removed) {
+          setUIState('loading');
+          (async () => {
+            try {
+              await masto.v1.lists.addAccount(listID, {
+                accountIds: [account.id],
+              });
+              setUIState('default');
+              setRemoved(false);
+            } catch (e) {
+              setUIState('error');
+            }
+          })();
+        } else {
+          const yes = confirm(`Remove ${account.username} from this list?`);
+          if (!yes) return;
+          setUIState('loading');
+
+          (async () => {
+            try {
+              await masto.v1.lists.removeAccount(listID, {
+                accountIds: [account.id],
+              });
+              setUIState('default');
+              setRemoved(true);
+            } catch (e) {
+              setUIState('error');
+            }
+          })();
+        }
+      }}
+    >
+      {removed ? 'Add' : 'Remove…'}
+    </button>
   );
 }
 
diff --git a/src/pages/lists.css b/src/pages/lists.css
new file mode 100644
index 00000000..dcf5ffc6
--- /dev/null
+++ b/src/pages/lists.css
@@ -0,0 +1,33 @@
+.list-form {
+  padding: 8px 0;
+  display: flex;
+  gap: 8px;
+  flex-direction: column;
+}
+
+.list-form-row :is(input, select) {
+  width: 100%;
+}
+
+.list-form-footer {
+  display: flex;
+  gap: 8px;
+  justify-content: space-between;
+}
+.list-form-footer button[type='submit'] {
+  padding-inline: 24px;
+}
+
+#list-manage-members-container ul {
+  display: block;
+  list-style: none;
+  padding: 8px 0;
+  margin: 0;
+}
+#list-manage-members-container ul li {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 0;
+}
diff --git a/src/pages/lists.jsx b/src/pages/lists.jsx
index 48ba6899..5aa3ceea 100644
--- a/src/pages/lists.jsx
+++ b/src/pages/lists.jsx
@@ -1,9 +1,13 @@
-import { useEffect, useState } from 'preact/hooks';
+import './lists.css';
+
+import { useEffect, useReducer, useRef, useState } from 'preact/hooks';
 
 import Icon from '../components/icon';
 import Link from '../components/link';
+import ListAddEdit from '../components/list-add-edit';
 import Loader from '../components/loader';
 import Menu from '../components/menu';
+import Modal from '../components/modal';
 import { api } from '../utils/api';
 import useTitle from '../utils/useTitle';
 
@@ -12,6 +16,7 @@ function Lists() {
   useTitle(`Lists`, `/l`);
   const [uiState, setUiState] = useState('default');
 
+  const [reloadCount, reload] = useReducer((c) => c + 1, 0);
   const [lists, setLists] = useState([]);
   useEffect(() => {
     setUiState('loading');
@@ -26,7 +31,9 @@ function Lists() {
         setUiState('error');
       }
     })();
-  }, []);
+  }, [reloadCount]);
+
+  const [showListAddEditModal, setShowListAddEditModal] = useState(false);
 
   return (
     <div id="lists-page" class="deck-container" tabIndex="-1">
@@ -40,7 +47,15 @@ function Lists() {
               </Link>
             </div>
             <h1>Lists</h1>
-            <div class="header-side" />
+            <div class="header-side">
+              <button
+                type="button"
+                class="plain"
+                onClick={() => setShowListAddEditModal(true)}
+              >
+                <Icon icon="plus" size="l" alt="New list" />
+              </button>
+            </div>
           </div>
         </header>
         <main>
@@ -49,7 +64,22 @@ function Lists() {
               {lists.map((list) => (
                 <li>
                   <Link to={`/l/${list.id}`}>
-                    <Icon icon="list" /> <span>{list.title}</span>
+                    <span>
+                      <Icon icon="list" /> <span>{list.title}</span>
+                    </span>
+                    {/* <button
+                      type="button"
+                      class="plain"
+                      onClick={(e) => {
+                        e.preventDefault();
+                        e.stopPropagation();
+                        setShowListAddEditModal({
+                          list,
+                        });
+                      }}
+                    >
+                      <Icon icon="pencil" />
+                    </button> */}
                   </Link>
                 </li>
               ))}
@@ -65,6 +95,26 @@ function Lists() {
           )}
         </main>
       </div>
+      {showListAddEditModal && (
+        <Modal
+          class="light"
+          onClick={(e) => {
+            if (e.target === e.currentTarget) {
+              setShowListAddEditModal(false);
+            }
+          }}
+        >
+          <ListAddEdit
+            list={showListAddEditModal?.list}
+            onClose={(result) => {
+              if (result.state === 'success') {
+                reload();
+              }
+              setShowListAddEditModal(false);
+            }}
+          />
+        </Modal>
+      )}
     </div>
   );
 }