Repository: inovex/scrumlr.io (ts)

Issue:
Add option to show/hide notifications upon usage of hotkeys
As a user I want to toggle the notifications for the usage of hotkeys in the "Appearance" tab of the settings dialog. By default it should be turned on.

Pull request description:
feat: toggle hotkey notifications
### Description
As a user I want to toggle the notifications for the usage of hotkeys in the "Appearance" tab of the settings dialog. By default it should be turned on.

Closes #2245 

### Changelog
- Add notifications toggle to the "Appearance" tab of the settings dialog
- Persist notifications enabled state in localStorage
- Add `dispatchNotification` function to HotkeyAnchor component that automatically dispatches a toast if notifications are enabled
- Improve accessibility of settings buttons / toggles 

### Visual Changes
![localhost_3000_board_526d2377-5239-4af9-854b-7f5c584a1039_settings_appearance(iPad Mini)](https://github.com/inovex/scrumlr.io/assets/36969812/f435a985-c261-44d4-b958-f534949dffa8)

![localhost_3000_board_526d2377-5239-4af9-854b-7f5c584a1039_settings_appearance(iPad Mini) (1)](https://github.com/inovex/scrumlr.io/assets/36969812/3b35c80b-8fbf-4cab-8942-dd47ee1bb19b)


Patch:
diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json
index d3cea6c0..b09b0a67 100644
--- a/public/locales/de/translation.json
+++ b/public/locales/de/translation.json
@@ -348,7 +348,8 @@
     "Language": "Sprache",
     "colorScheme": "Farbschema",
     "colorSchemeLight": "Hell",
-    "colorSchemeDark": "Dunkel"
+    "colorSchemeDark": "Dunkel",
+    "showHotkeyNotifications": "Zeige Hotkey-Notifikationen"
   },
   "Feedback": {
     "PraiseInputLabel": "Möchtest Du uns noch etwas mitteilen?",
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index 184b3b29..c594b735 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -348,7 +348,8 @@
     "Language": "Language",
     "colorScheme": "Color scheme",
     "colorSchemeLight": "Light",
-    "colorSchemeDark": "Dark"
+    "colorSchemeDark": "Dark",
+    "showHotkeyNotifications": "Show hotkey notifications"
   },
   "Feedback": {
     "PraiseInputLabel": "Anything else you want to tell us?",
diff --git a/src/components/HotkeyAnchor/HotkeyAnchor.tsx b/src/components/HotkeyAnchor/HotkeyAnchor.tsx
index c27c8a95..1ee53593 100644
--- a/src/components/HotkeyAnchor/HotkeyAnchor.tsx
+++ b/src/components/HotkeyAnchor/HotkeyAnchor.tsx
@@ -46,6 +46,7 @@ export const HotkeyAnchor = () => {
       showNotesOfOtherUsers: rootState.board.data?.showNotesOfOtherUsers,
       showHiddenColumns: rootState.participants!.self.showHiddenColumns,
       hotkeysAreActive: rootState.view.hotkeysAreActive,
+      hotkeyNotificationsEnabled: rootState.view.hotkeyNotificationsEnabled,
     }),
     _.isEqual
   );
@@ -61,13 +62,19 @@ export const HotkeyAnchor = () => {
     enabled: state.hotkeysAreActive && isAdmin,
   };
 
+  const dispatchHotkeyNotification = (title: string) => {
+    if (state.hotkeyNotificationsEnabled) {
+      Toast.info({title, autoClose: TOAST_TIMER_SHORT});
+    }
+  };
+
   const toggleHotkeys = () => {
     if (state.hotkeysAreActive) {
       dispatch(Actions.setHotkeyState(false));
-      Toast.info({title: t("Hotkeys.hotkeysDisabled"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.hotkeysDisabled"));
     } else {
       dispatch(Actions.setHotkeyState(true));
-      Toast.info({title: t("Hotkeys.hotkeysEnabled"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.hotkeysEnabled"));
     }
   };
 
@@ -76,32 +83,32 @@ export const HotkeyAnchor = () => {
       dispatch(Actions.stopSharing());
       dispatch(Actions.clearFocusInitiator());
       dispatch(Actions.setModerating(false));
-      Toast.info({title: t("Hotkeys.togglePresentationMode.endPresenting"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.togglePresentationMode.endPresenting"));
     } else {
       dispatch(Actions.setFocusInitiator(state.currentUser));
       if (note.current) dispatch(Actions.shareNote(note.current));
       dispatch(Actions.setModerating(true));
-      Toast.info({title: t("Hotkeys.togglePresentationMode.startPresenting"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.togglePresentationMode.startPresenting"));
     }
   };
 
   const toggleReadyState = () => {
     if (isReady) {
       dispatch(Actions.setUserReadyStatus(state.currentUser.user.id, false));
-      Toast.info({title: t("Hotkeys.toggleReadyState.notReady"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.toggleReadyState.notReady"));
     } else {
       dispatch(Actions.setUserReadyStatus(state.currentUser.user.id, true));
-      Toast.info({title: t("Hotkeys.toggleReadyState.ready"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.toggleReadyState.ready"));
     }
   };
 
   const toggleRaiseHand = () => {
     if (raisedHand) {
       dispatch(Actions.setRaisedHand(state.currentUser.user.id, false));
-      Toast.info({title: t("Hotkeys.toggleRaisedHand.lower"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.toggleRaisedHand.lower"));
     } else {
       dispatch(Actions.setRaisedHand(state.currentUser.user.id, true));
-      Toast.info({title: t("Hotkeys.toggleRaisedHand.raise"), autoClose: TOAST_TIMER_SHORT});
+      dispatchHotkeyNotification(t("Hotkeys.toggleRaisedHand.raise"));
     }
   };
 
@@ -112,10 +119,10 @@ export const HotkeyAnchor = () => {
 
   const showSettings = () => navigate("settings");
 
-  useHotkeys(TOGGLE_HOTKEYS, toggleHotkeys, [state.hotkeysAreActive]);
-  useHotkeys(TOGGLE_MODERATION, toggleModeration, hotkeyOptions, [state.moderation]);
-  useHotkeys(TOGGLE_READY_STATE, toggleReadyState, hotkeyOptions, [isReady]);
-  useHotkeys(TOGGLE_RAISED_HAND, toggleRaiseHand, hotkeyOptions, [raisedHand]);
+  useHotkeys(TOGGLE_HOTKEYS, toggleHotkeys, [state.hotkeysAreActive, state.hotkeyNotificationsEnabled]);
+  useHotkeys(TOGGLE_MODERATION, toggleModeration, hotkeyOptions, [state.moderation, state.hotkeyNotificationsEnabled]);
+  useHotkeys(TOGGLE_READY_STATE, toggleReadyState, hotkeyOptions, [isReady, state.hotkeyNotificationsEnabled]);
+  useHotkeys(TOGGLE_RAISED_HAND, toggleRaiseHand, hotkeyOptions, [raisedHand, state.hotkeyNotificationsEnabled]);
   useHotkeys(SHOW_SETTINGS, showSettings, hotkeyOptions);
   useHotkeys(
     TOGGLE_SHOW_AUTHORS,
@@ -123,14 +130,14 @@ export const HotkeyAnchor = () => {
       e.preventDefault();
       if (state.showAuthors) {
         dispatch(Actions.editBoard({showAuthors: false}));
-        Toast.info({title: t("Hotkeys.toggleShowAuthors.hide")});
+        dispatchHotkeyNotification(t("Hotkeys.toggleShowAuthors.hide"));
       } else {
         dispatch(Actions.editBoard({showAuthors: true}));
-        Toast.info({title: t("Hotkeys.toggleShowAuthors.show")});
+        dispatchHotkeyNotification(t("Hotkeys.toggleShowAuthors.show"));
       }
     },
     hotkeyOptionsAdmin,
-    [state.showAuthors]
+    [state.showAuthors, state.hotkeyNotificationsEnabled]
   );
   useHotkeys(
     TOGGLE_SHOW_OTHER_USERS_NOTES,
@@ -138,14 +145,14 @@ export const HotkeyAnchor = () => {
       e.preventDefault();
       if (state.showNotesOfOtherUsers) {
         dispatch(Actions.editBoard({showNotesOfOtherUsers: false}));
-        Toast.info({title: t("Hotkeys.toggleShowOtherUsersNotes.hide")});
+        dispatchHotkeyNotification(t("Hotkeys.toggleShowOtherUsersNotes.hide"));
       } else {
         dispatch(Actions.editBoard({showNotesOfOtherUsers: true}));
-        Toast.info({title: t("Hotkeys.toggleShowOtherUsersNotes.show")});
+        dispatchHotkeyNotification(t("Hotkeys.toggleShowOtherUsersNotes.show"));
       }
     },
     hotkeyOptionsAdmin,
-    [state.showNotesOfOtherUsers]
+    [state.showNotesOfOtherUsers, state.hotkeyNotificationsEnabled]
   );
   useHotkeys(
     TOGGLE_COLUMN_VISIBILITY,
@@ -153,14 +160,14 @@ export const HotkeyAnchor = () => {
       e.preventDefault();
       if (state.showHiddenColumns) {
         dispatch(Actions.setShowHiddenColumns(false));
-        Toast.info({title: t("Hotkeys.toggleColumnVisibility.hide"), autoClose: TOAST_TIMER_SHORT});
+        dispatchHotkeyNotification(t("Hotkeys.toggleColumnVisibility.hide"));
       } else {
         dispatch(Actions.setShowHiddenColumns(true));
-        Toast.info({title: t("Hotkeys.toggleColumnVisibility.show"), autoClose: TOAST_TIMER_SHORT});
+        dispatchHotkeyNotification(t("Hotkeys.toggleColumnVisibility.show"));
       }
     },
     hotkeyOptionsAdmin,
-    [state.showHiddenColumns]
+    [state.showHiddenColumns, state.hotkeyNotificationsEnabled]
   );
   const hotkeyTimerCombo = SET_TIMER_FIRST_KEY.map((firstKey) => _.range(1, 10).map((minute) => `${firstKey}+${minute}`)).join(",");
   useHotkeys(hotkeyTimerCombo, (e) => {
diff --git a/src/components/SettingsDialog/Appearance/Appearance.tsx b/src/components/SettingsDialog/Appearance/Appearance.tsx
index 13979dc7..84c01c43 100644
--- a/src/components/SettingsDialog/Appearance/Appearance.tsx
+++ b/src/components/SettingsDialog/Appearance/Appearance.tsx
@@ -4,6 +4,7 @@ import {LanguageSettingsDropdown} from "../Components/LanguageSettingsDropdown";
 import "../SettingsDialog.scss";
 import "./Appearance.scss";
 import {ThemeSettings} from "../Components/ThemeSettings";
+import {NotificationSettings} from "../Components/NotificationSettings";
 
 export const Appearance = () => {
   const {t} = useTranslation();
@@ -15,6 +16,7 @@ export const Appearance = () => {
       </header>
       <div className="appearance-container">
         <ThemeSettings />
+        <NotificationSettings />
         <LanguageSettingsDropdown />
       </div>
     </div>
diff --git a/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx b/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
index 58bc1a97..03dd9b0b 100644
--- a/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
+++ b/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
@@ -170,6 +170,8 @@ export const BoardSettings = () => {
                   onClick={() => {
                     store.dispatch(Actions.editBoard({showAuthors: !state.board.showAuthors}));
                   }}
+                  role="switch"
+                  aria-checked={state.board.showAuthors}
                 >
                   <div className="board-settings__show-author-value">
                     <Toggle active={state.board.showAuthors} />
@@ -181,6 +183,8 @@ export const BoardSettings = () => {
                   className="board-settings__show-notes-button"
                   label={t("BoardSettings.ShowOtherUsersNotesOption")}
                   onClick={() => store.dispatch(Actions.editBoard({showNotesOfOtherUsers: !state.board.showNotesOfOtherUsers}))}
+                  role="switch"
+                  aria-checked={state.board.showNotesOfOtherUsers}
                 >
                   <div className="board-settings__show-notes-value">
                     <Toggle active={state.board.showNotesOfOtherUsers} />
@@ -192,9 +196,11 @@ export const BoardSettings = () => {
                   className="board-settings__show-columns-button"
                   label={t("BoardSettings.ShowHiddenColumnsOption")}
                   onClick={() => store.dispatch(Actions.setShowHiddenColumns(!state.me?.showHiddenColumns))}
+                  role="switch"
+                  aria-checked={!!state.me?.showHiddenColumns}
                 >
                   <div className="board-settings__show-columns-value">
-                    <Toggle active={state.me?.showHiddenColumns ?? false} />
+                    <Toggle active={!!state.me?.showHiddenColumns} />
                   </div>
                 </SettingsButton>
                 <hr className="settings-dialog__separator" />
@@ -203,6 +209,8 @@ export const BoardSettings = () => {
                   className="board-settings__allow-note-repositioning-button"
                   label={t("BoardSettings.AllowNoteRepositioningOption")}
                   onClick={() => store.dispatch(Actions.editBoard({allowStacking: !state.board.allowStacking}))}
+                  role="switch"
+                  aria-checked={state.board.allowStacking}
                 >
                   <div className="board-settings__allow-note-repositioning-value">
                     <Toggle active={state.board.allowStacking} />
diff --git a/src/components/SettingsDialog/Components/NotificationSettings.tsx b/src/components/SettingsDialog/Components/NotificationSettings.tsx
new file mode 100644
index 00000000..718afdf8
--- /dev/null
+++ b/src/components/SettingsDialog/Components/NotificationSettings.tsx
@@ -0,0 +1,24 @@
+import {FC} from "react";
+import {Toggle} from "components/Toggle";
+import store, {useAppSelector} from "store";
+import {Actions} from "store/action";
+import {useTranslation} from "react-i18next";
+import {SettingsButton} from "./SettingsButton";
+
+export const NotificationSettings: FC = () => {
+  const {t} = useTranslation();
+  const hotkeyNotificationsEnabled = useAppSelector((state) => state.view.hotkeyNotificationsEnabled);
+
+  return (
+    <section>
+      <SettingsButton
+        aria-checked={hotkeyNotificationsEnabled}
+        label={t("Appearance.showHotkeyNotifications")}
+        onClick={() => (hotkeyNotificationsEnabled ? store.dispatch(Actions.disableHotkeyNotifications()) : store.dispatch(Actions.enableHotkeyNotifications()))}
+        role="switch"
+      >
+        <Toggle active={hotkeyNotificationsEnabled} />
+      </SettingsButton>
+    </section>
+  );
+};
diff --git a/src/components/SettingsDialog/Components/SettingsButton.tsx b/src/components/SettingsDialog/Components/SettingsButton.tsx
index da80d83c..6e07af3f 100644
--- a/src/components/SettingsDialog/Components/SettingsButton.tsx
+++ b/src/components/SettingsDialog/Components/SettingsButton.tsx
@@ -18,11 +18,12 @@ export const SettingsButton: FC<PropsWithChildren<SettingsButtonProps>> = ({labe
 
   return (
     <button
-      type="button"
-      disabled={disabled}
+      aria-label={label}
       className={classNames("settings-option-button", {"settings-option-button--disabled": disabled}, className)}
+      disabled={disabled}
       onClick={onClick}
       onBlur={onBlur}
+      type="button"
       {...other}
     >
       {label && <span className="settings-option-button__label">{label}</span>}
diff --git a/src/components/SettingsDialog/Components/ThemeSettings.tsx b/src/components/SettingsDialog/Components/ThemeSettings.tsx
index 993b39d0..8f9f3b60 100644
--- a/src/components/SettingsDialog/Components/ThemeSettings.tsx
+++ b/src/components/SettingsDialog/Components/ThemeSettings.tsx
@@ -1,10 +1,10 @@
+import {useEffect, useState} from "react";
+import {t} from "i18next";
 import {ReactComponent as DarkIcon} from "assets/icon-darkmode.svg";
 import {ReactComponent as LightIcon} from "assets/icon-lightmode.svg";
 import {ReactComponent as AutoIcon} from "assets/icon-settings.svg";
 import ThemePreviewDark from "assets/themes/theme-preview-dark.svg";
 import ThemePreviewLight from "assets/themes/theme-preview-light.svg";
-import {t} from "i18next";
-import {useEffect, useState} from "react";
 import "./ThemeSettings.scss";
 
 export const ThemeSettings = () => {
diff --git a/src/constants/storage.ts b/src/constants/storage.ts
index 4b111c0f..7b86ce05 100644
--- a/src/constants/storage.ts
+++ b/src/constants/storage.ts
@@ -5,3 +5,4 @@ export const LOCALE_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}locale`;
 export const CUSTOM_TIMER_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}custom_timer`;
 export const CUSTOM_NUMBER_OF_VOTES_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}custom_number_of_votes`;
 export const CUMULATIVE_VOTING_DEFAULT_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}cumulative_voting_default`;
+export const HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}hotkeyNotificationsEnable`;
diff --git a/src/store/action/view.ts b/src/store/action/view.ts
index 54b46006..a04621a1 100644
--- a/src/store/action/view.ts
+++ b/src/store/action/view.ts
@@ -5,6 +5,8 @@ export const ViewAction = {
   SetServerInfo: "scrumlr.io/setServerInfo" as const,
   SetRoute: "scrumlr.io/setRoute" as const,
   SetHotkeyState: "scrumlr.io/setHotkeyState" as const,
+  EnableHotkeyNotifications: "scrumlr.io/enableHotkeyNotifications" as const,
+  DisableHotkeyNotifications: "scrumlr.io/disableHotkeyNotifications" as const,
 };
 
 export const ViewActionFactory = {
@@ -38,6 +40,14 @@ export const ViewActionFactory = {
     type: ViewAction.SetHotkeyState,
     active,
   }),
+
+  enableHotkeyNotifications: () => ({
+    type: ViewAction.EnableHotkeyNotifications,
+  }),
+
+  disableHotkeyNotifications: () => ({
+    type: ViewAction.DisableHotkeyNotifications,
+  }),
 };
 
 export type ViewReduxAction =
@@ -46,4 +56,6 @@ export type ViewReduxAction =
   | ReturnType<typeof ViewActionFactory.setLanguage>
   | ReturnType<typeof ViewActionFactory.setRoute>
   | ReturnType<typeof ViewActionFactory.setServerInfo>
-  | ReturnType<typeof ViewActionFactory.setHotkeyState>;
+  | ReturnType<typeof ViewActionFactory.setHotkeyState>
+  | ReturnType<typeof ViewActionFactory.enableHotkeyNotifications>
+  | ReturnType<typeof ViewActionFactory.disableHotkeyNotifications>;
diff --git a/src/store/middleware/view.tsx b/src/store/middleware/view.tsx
index 539ae085..375c334a 100644
--- a/src/store/middleware/view.tsx
+++ b/src/store/middleware/view.tsx
@@ -3,7 +3,9 @@ import {ApplicationState} from "types";
 import {Action, Actions, ReduxAction} from "store/action";
 import {API} from "api";
 import i18n from "i18n";
-import {Toast} from "../../utils/Toast";
+import {Toast} from "utils/Toast";
+import {saveToStorage} from "utils/storage";
+import {HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY} from "constants/storage";
 import store from "../index";
 
 export const passViewMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
@@ -23,4 +25,16 @@ export const passViewMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
         });
       });
   }
+
+  if (action.type === Action.EnableHotkeyNotifications) {
+    if (typeof window !== undefined) {
+      saveToStorage(HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY, JSON.stringify(true));
+    }
+  }
+
+  if (action.type === Action.DisableHotkeyNotifications) {
+    if (typeof window !== undefined) {
+      saveToStorage(HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY, JSON.stringify(false));
+    }
+  }
 };
diff --git a/src/store/reducer/view.ts b/src/store/reducer/view.ts
index 8856bb90..8a1a9ecf 100644
--- a/src/store/reducer/view.ts
+++ b/src/store/reducer/view.ts
@@ -1,5 +1,7 @@
 import {ViewState} from "types/view";
 import {Action, ReduxAction} from "store/action";
+import {getFromStorage} from "utils/storage";
+import {HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY} from "constants/storage";
 
 const INITIAL_VIEW_STATE: ViewState = {
   moderating: false,
@@ -8,6 +10,7 @@ const INITIAL_VIEW_STATE: ViewState = {
   feedbackEnabled: false,
   hotkeysAreActive: true,
   noteFocused: false,
+  hotkeyNotificationsEnabled: typeof window !== "undefined" && getFromStorage(HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY) !== "false",
 };
 
 // eslint-disable-next-line @typescript-eslint/default-param-last
@@ -81,6 +84,20 @@ export const viewReducer = (state: ViewState = INITIAL_VIEW_STATE, action: Redux
       };
     }
 
+    case Action.EnableHotkeyNotifications: {
+      return {
+        ...state,
+        hotkeyNotificationsEnabled: true,
+      };
+    }
+
+    case Action.DisableHotkeyNotifications: {
+      return {
+        ...state,
+        hotkeyNotificationsEnabled: false,
+      };
+    }
+
     default:
       return state;
   }
diff --git a/src/types/view.ts b/src/types/view.ts
index 48aebabb..c3ec72ca 100644
--- a/src/types/view.ts
+++ b/src/types/view.ts
@@ -18,6 +18,8 @@ export interface View {
   readonly noteFocused: boolean;
 
   readonly hotkeysAreActive: boolean;
+
+  readonly hotkeyNotificationsEnabled: boolean;
 }
 
 export type ViewState = View;


Test patch:
diff --git a/src/components/SettingsDialog/Appearance/__tests__/Appearance.test.tsx b/src/components/SettingsDialog/Appearance/__tests__/Appearance.test.tsx
index 783a7cf9..a1fbadbe 100644
--- a/src/components/SettingsDialog/Appearance/__tests__/Appearance.test.tsx
+++ b/src/components/SettingsDialog/Appearance/__tests__/Appearance.test.tsx
@@ -3,6 +3,7 @@ import {Provider} from "react-redux";
 import {render} from "testUtils";
 import getTestStore from "utils/test/getTestStore";
 import {Appearance} from "../Appearance";
+import {getByLabelText} from "@testing-library/dom";
 
 const createAppearance = () => (
   <Provider store={getTestStore()}>
@@ -15,4 +16,9 @@ describe("Appearance", () => {
     const {container} = render(createAppearance());
     expect(container.firstChild).toMatchSnapshot();
   });
+
+  test("should have a notifications toggle", () => {
+    const {container} = render(createAppearance());
+    expect(getByLabelText(container, "Appearance.showHotkeyNotifications")).toBeInTheDocument();
+  });
 });
diff --git a/src/components/SettingsDialog/Appearance/__tests__/__snapshots__/Appearance.test.tsx.snap b/src/components/SettingsDialog/Appearance/__tests__/__snapshots__/Appearance.test.tsx.snap
index f8ac35b3..c998c9d8 100644
--- a/src/components/SettingsDialog/Appearance/__tests__/__snapshots__/Appearance.test.tsx.snap
+++ b/src/components/SettingsDialog/Appearance/__tests__/__snapshots__/Appearance.test.tsx.snap
@@ -110,6 +110,23 @@ exports[`Appearance should render all Settings correctly 1`] = `
         </label>
       </form>
     </div>
+    <section>
+      <button
+        aria-label="Appearance.showHotkeyNotifications"
+        class="settings-option-button"
+        role="switch"
+        type="button"
+      >
+        <span
+          class="settings-option-button__label"
+        >
+          Appearance.showHotkeyNotifications
+        </span>
+        <div
+          class="toggle"
+        />
+      </button>
+    </section>
     <div
       class="settings-dropdown"
     >
diff --git a/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap b/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap
index d6286eb1..bcb86c41 100644
--- a/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap
+++ b/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap
@@ -43,6 +43,7 @@ exports[`BoardSettings should match snapshot 1`] = `
           class="settings-dialog__group"
         >
           <button
+            aria-label="BoardSettings.AccessPolicy"
             class="settings-option-button settings-option-button--disabled board-settings__policy-button"
             disabled=""
             type="button"
@@ -98,8 +99,11 @@ exports[`BoardSettings should match snapshot 1`] = `
         class="settings-dialog__group"
       >
         <button
+          aria-checked="true"
+          aria-label="BoardSettings.ShowAuthorOption"
           class="settings-option-button board-settings__show-author-button"
           data-testid="author"
+          role="switch"
           type="button"
         >
           <span
@@ -119,8 +123,11 @@ exports[`BoardSettings should match snapshot 1`] = `
           class="settings-dialog__separator"
         />
         <button
+          aria-checked="true"
+          aria-label="BoardSettings.ShowOtherUsersNotesOption"
           class="settings-option-button board-settings__show-notes-button"
           data-testid="notes"
+          role="switch"
           type="button"
         >
           <span
@@ -140,8 +147,11 @@ exports[`BoardSettings should match snapshot 1`] = `
           class="settings-dialog__separator"
         />
         <button
+          aria-checked="false"
+          aria-label="BoardSettings.ShowHiddenColumnsOption"
           class="settings-option-button board-settings__show-columns-button"
           data-testid="columns"
+          role="switch"
           type="button"
         >
           <span
@@ -161,8 +171,11 @@ exports[`BoardSettings should match snapshot 1`] = `
           class="settings-dialog__separator"
         />
         <button
+          aria-checked="true"
+          aria-label="BoardSettings.AllowNoteRepositioningOption"
           class="settings-option-button board-settings__allow-note-repositioning-button"
           data-testid="note-repositioning"
+          role="switch"
           type="button"
         >
           <span
@@ -180,6 +193,7 @@ exports[`BoardSettings should match snapshot 1`] = `
         </button>
       </div>
       <button
+        aria-label="BoardSettings.DeleteBoard"
         class="settings-option-button board-settings__delete-button"
         type="button"
       >
diff --git a/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap b/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap
index 7d3b0067..b43588f9 100644
--- a/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap
+++ b/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap
@@ -23,6 +23,7 @@ Object {
           class="settings-dialog__group accent-color__backlog-blue"
         >
           <button
+            aria-label="Export as JSON"
             class="settings-option-button export-board__button-reverse-order"
             data-testid="export-json"
             type="button"
@@ -42,6 +43,7 @@ Object {
             class="settings-dialog__separator"
           />
           <button
+            aria-label="Export as CSV"
             class="settings-option-button export-board__button-reverse-order"
             data-testid="export-csv"
             type="button"
@@ -61,6 +63,7 @@ Object {
             class="settings-dialog__separator"
           />
           <button
+            aria-label="Open print view"
             class="settings-option-button export-board__button-reverse-order export-board__button-print-view"
             type="button"
           >
@@ -79,6 +82,7 @@ Object {
             class="settings-dialog__separator"
           />
           <button
+            aria-label="Copy Markdown to Clipboard"
             class="settings-option-button export-board__button-reverse-order"
             data-testid="export-markdown"
             type="button"
@@ -117,6 +121,7 @@ Object {
         class="settings-dialog__group accent-color__backlog-blue"
       >
         <button
+          aria-label="Export as JSON"
           class="settings-option-button export-board__button-reverse-order"
           data-testid="export-json"
           type="button"
@@ -136,6 +141,7 @@ Object {
           class="settings-dialog__separator"
         />
         <button
+          aria-label="Export as CSV"
           class="settings-option-button export-board__button-reverse-order"
           data-testid="export-csv"
           type="button"
@@ -155,6 +161,7 @@ Object {
           class="settings-dialog__separator"
         />
         <button
+          aria-label="Open print view"
           class="settings-option-button export-board__button-reverse-order export-board__button-print-view"
           type="button"
         >
@@ -173,6 +180,7 @@ Object {
           class="settings-dialog__separator"
         />
         <button
+          aria-label="Copy Markdown to Clipboard"
           class="settings-option-button export-board__button-reverse-order"
           data-testid="export-markdown"
           type="button"
diff --git a/src/components/SettingsDialog/Feedback/__tests__/__snapshots__/Feedback.test.tsx.snap b/src/components/SettingsDialog/Feedback/__tests__/__snapshots__/Feedback.test.tsx.snap
index d3a115f7..b320ebc9 100644
--- a/src/components/SettingsDialog/Feedback/__tests__/__snapshots__/Feedback.test.tsx.snap
+++ b/src/components/SettingsDialog/Feedback/__tests__/__snapshots__/Feedback.test.tsx.snap
@@ -88,6 +88,7 @@ exports[`Feedback should match snapshot 1`] = `
       </div>
     </div>
     <button
+      aria-label="Feedback.PraiseInputLabel"
       class="settings-option-button feedback-form__settings-button"
       type="button"
     >
@@ -103,6 +104,7 @@ exports[`Feedback should match snapshot 1`] = `
       />
     </button>
     <button
+      aria-label="Feedback.ContactInputLabel"
       class="settings-option-button feedback-form__settings-button"
       type="button"
     >