diff --git a/web/apps/app-listing.react.js b/web/apps/app-listing.react.js
new file mode 100644
--- /dev/null
+++ b/web/apps/app-listing.react.js
@@ -0,0 +1,83 @@
+// @flow
+
+import { faCheckCircle } from '@fortawesome/free-regular-svg-icons';
+import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import classnames from 'classnames';
+import * as React from 'react';
+import { useDispatch } from 'react-redux';
+
+import {
+  disableAppActionType,
+  enableAppActionType,
+} from 'lib/reducers/enabled-apps-reducer';
+import type { SupportedApps } from 'lib/types/enabled-apps';
+
+import SWMansionIcon from '../SWMansionIcon.react';
+import css from './apps.css';
+
+type Props = {
+  +id: SupportedApps | 'chat',
+  +readOnly: boolean,
+  +enabled: boolean,
+  +name: string,
+  +icon: 'message-square' | 'calendar',
+  +copy: string,
+};
+
+function AppListing(props: Props): React.Node {
+  const { id, readOnly, enabled, name, icon, copy } = props;
+  const dispatch = useDispatch();
+
+  const switchAppState = React.useCallback(
+    () =>
+      dispatch({
+        type: enabled ? disableAppActionType : enableAppActionType,
+        payload: id,
+      }),
+    [dispatch, enabled, id],
+  );
+
+  const actionButton = React.useMemo(() => {
+    const switchIcon = enabled ? faCheckCircle : faPlusCircle;
+    if (readOnly) {
+      const readOnlyIconClasses = classnames(
+        css.appListingIcon,
+        css.appListingIconState,
+        css.iconReadOnly,
+      );
+      return (
+        <div className={readOnlyIconClasses}>
+          <FontAwesomeIcon icon={switchIcon} />
+        </div>
+      );
+    }
+    const iconClasses = classnames(
+      css.appListingIcon,
+      css.appListingIconState,
+      {
+        [css.iconEnabled]: enabled,
+        [css.iconDisabled]: !enabled,
+      },
+    );
+    return (
+      <div className={iconClasses} onClick={switchAppState}>
+        <FontAwesomeIcon icon={enabled ? faCheckCircle : faPlusCircle} />
+      </div>
+    );
+  }, [enabled, readOnly, switchAppState]);
+  return (
+    <div className={css.appListingContainer}>
+      <div className={css.appListingIcon}>
+        <SWMansionIcon icon={icon} size={20} />
+      </div>
+      <div className={css.appListingTextContainer}>
+        <h5 className={css.appName}>{name}</h5>
+        <small className={css.appCopy}>{copy}</small>
+      </div>
+      {actionButton}
+    </div>
+  );
+}
+
+export default AppListing;
diff --git a/web/apps/apps-directory.react.js b/web/apps/apps-directory.react.js
--- a/web/apps/apps-directory.react.js
+++ b/web/apps/apps-directory.react.js
@@ -1,9 +1,56 @@
 // @flow
 
 import * as React from 'react';
+import { useSelector } from 'react-redux';
+
+import AppListing from './app-listing.react';
+import css from './apps.css';
+
+const APP_DIRECTORY_DATA = [
+  {
+    id: 'chat',
+    defaultEnabled: true,
+    readOnly: true,
+    name: 'Chat',
+    icon: 'message-square',
+    copy: 'Keep in touch with your community',
+  },
+  {
+    id: 'calendar',
+    defaultEnabled: true,
+    readOnly: false,
+    name: 'Calendar',
+    icon: 'calendar',
+    copy: 'Shared calendar for your community',
+  },
+];
 
 function AppsDirectory(): React.Node {
-  return <div style={{ color: 'white' }}>Apps directory</div>;
+  const enabledApps = useSelector(state => state.enabledApps);
+
+  const appData = React.useMemo(
+    () =>
+      APP_DIRECTORY_DATA.map(app => {
+        const { defaultEnabled, ...data } = app;
+        return {
+          ...data,
+          enabled: enabledApps[app.id] ?? defaultEnabled,
+        };
+      }),
+    [enabledApps],
+  );
+
+  const appItems = React.useMemo(
+    () => appData.map(item => <AppListing key={item.id} {...item} />),
+    [appData],
+  );
+
+  return (
+    <div className={css.appsDirectoryContainer}>
+      <h4 className={css.appsHeader}>Choose Apps</h4>
+      <div className={css.appsDirectoryList}>{appItems}</div>
+    </div>
+  );
 }
 
 export default AppsDirectory;
diff --git a/web/apps/apps.css b/web/apps/apps.css
new file mode 100644
--- /dev/null
+++ b/web/apps/apps.css
@@ -0,0 +1,63 @@
+div.appsDirectoryContainer {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+h4.appsHeader {
+  color: var(--fg);
+  padding: 20px 0px 40px 40px;
+  font-weight: var(--semi-bold);
+}
+
+div.appsDirectoryList {
+  margin-left: 20px;
+  display: flex;
+  flex-direction: column;
+  row-gap: 10px;
+}
+
+div.appListingContainer {
+  color: var(--fg);
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+div.appListingTextContainer {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+}
+
+h5.appName {
+  font-weight: var(--semi-bold);
+  margin-bottom: 4px;
+}
+
+small.appCopy {
+  font-size: var(--xs-font-12);
+}
+
+div.appListingIcon {
+  padding: 0 20px;
+  align-self: stretch;
+  display: flex;
+  align-items: center;
+}
+
+div.appListingIconState {
+  font-size: var(--xl-font-20);
+}
+
+div.iconReadOnly {
+  color: var(--app-list-icon-read-only-color);
+}
+
+div.iconEnabled {
+  color: var(--app-list-icon-enabled-color);
+}
+
+div.iconDisabled {
+  color: var(--app-list-icon-disabled-color);
+}
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -108,4 +108,7 @@
   --thread-menu-color-hover: var(--shades-white-100);
   --thread-menu-color-dangerous: var(--error-primary);
   --thread-menu-color-dangerous-hover: var(--error-light-50);
+  --app-list-icon-read-only-color: var(--shades-black-60);
+  --app-list-icon-enabled-color: var(--success-primary);
+  --app-list-icon-disabled-color: var(--shades-white-80);
 }