diff --git a/web/app-list/app-list.css b/web/app-list/app-list.css
new file mode 100644
--- /dev/null
+++ b/web/app-list/app-list.css
@@ -0,0 +1,15 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ min-width: 160px;
+}
+
+.appList {
+ display: flex;
+ flex-direction: column;
+ row-gap: 16px;
+ background-color: var(--card-background-primary-default);
+ padding: 16px;
+ flex: 1;
+ border-radius: 0 0 8px 8px;
+}
diff --git a/web/app-list/app-list.react.js b/web/app-list/app-list.react.js
new file mode 100644
--- /dev/null
+++ b/web/app-list/app-list.react.js
@@ -0,0 +1,96 @@
+// @flow
+
+import * as React from 'react';
+
+import { mostRecentlyReadThreadSelector } from 'lib/selectors/thread-selectors.js';
+import { useDispatch } from 'lib/utils/redux-utils.js';
+
+import AppListHeader from './app-list-header.react.js';
+import AppListItem from './app-list-item.react.js';
+import css from './app-list.css';
+import { updateNavInfoActionType } from '../redux/action-types.js';
+import { useSelector } from '../redux/redux-utils.js';
+
+function AppList(): React.Node {
+ const dispatch = useDispatch();
+
+ const onClickCalendar = React.useCallback(() => {
+ dispatch({
+ type: updateNavInfoActionType,
+ payload: { tab: 'calendar' },
+ });
+ }, [dispatch]);
+
+ const isCalendarEnabled = useSelector(state => state.enabledApps.calendar);
+
+ const calendarAppListItem = React.useMemo(() => {
+ if (!isCalendarEnabled) {
+ return null;
+ }
+
+ return (
+