Repository: inovex/scrumlr.io
Pull request #2887 (2023-04-20 14:23:28)

feat: add new notifications
## Description

Implemented the new notifications based on this design: [https://www.figma.com/file/8m2V6OJTImuOdQoxruWurP/Scrumlr?node-id=409-14916&t=IaQ3uPMTeYoRS7JA-0](url)
 
Added a customToast component, that is rendering "singleToasts" and "multiToasts".
They differ in the size, layout, behaviour, etc. depending on the passed configurations.

**For the review:**
In particular, the files utils/Toast and customToast are important. 
The other changed files are just the appropriate adjustments and the testing.

**Optional follow-up ticket:**
Rework the content of the toasts:
- self-explanatory titles
- add messages (explaining in further detail)
- some toast-types don't fit well
- ...

## Checklist

- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] The light- and dark-theme are both supported and tested
- [x] The design was implemented and is responsive for all devices and screen sizes
- [x] The application was tested in the most commonly used browsers (e.g. Chrome, Firefox, Safari)



Patch:
diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json
index 56bdccbc..fd0610c1 100644
--- a/public/locales/de/translation.json
+++ b/public/locales/de/translation.json
@@ -255,7 +255,7 @@
     "allParticipantsDone": "Alle Teilnehmer sind fertig!",
     "moderatorResetReadyStates": "Markierung der Teilnehmer als fertig zurücksetzen?",
     "moderatorResetReadyStatesButton": "Zurücksetzen!",
-    "noteToHiddenColumn": "Du hast gerade ein Karte einer ausgeblendeten Spalte hinzugefügt!",
+    "noteToHiddenColumn": "Du hast gerade eine Karte einer ausgeblendeten Spalte hinzugefügt!",
     "noteToHiddenColumnButton": "Spalte für alle anzeigen",
     "noteMovedToStack": "Die Karte wurde in einen Stapel verschoben!",
     "noteDeleted": "Die Karte wurde gelöscht!"
diff --git a/src/api/feedback.ts b/src/api/feedback.ts
index 923849e9..c3da7c76 100644
--- a/src/api/feedback.ts
+++ b/src/api/feedback.ts
@@ -15,7 +15,7 @@ export const FeedbackAPI = {
         }),
       });
       if (response.ok) {
-        Toast.success(i18n.t("Feedback.SubmitNotification"));
+        Toast.success({title: i18n.t("Feedback.SubmitNotification")});
       }
     } catch (error) {
       throw new Error(`unable to send feedback: ${error}`);
diff --git a/src/assets/icon-info.svg b/src/assets/icon-info.svg
index 55575aa7..b991afab 100644
--- a/src/assets/icon-info.svg
+++ b/src/assets/icon-info.svg
@@ -1,10 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21">
-  <g transform="translate(-1878 -303)">
-    <g id="info-icon" transform="translate(1878 303)" fill="none" stroke-width="1.5">
-      <circle cx="10.5" cy="10.5" r="10.5" stroke="none" />
-      <circle cx="10.5" cy="10.5" r="9.75" fill="none" />
-    </g>
-  </g>
-  <line y2="6" transform="translate(10.5 9.5)" fill="none" stroke-linecap="round" stroke-width="2" />
-  <line transform="translate(10.5 5.5)" fill="none" stroke-linecap="round" stroke-width="2" />
-</svg>
\ No newline at end of file
+
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.34425 2.59413C4.63081 3.46571 3.26078 4.8889 2.45501 6.63426C1.64924 8.37963 1.45468 10.3455 1.90269 12.2149C2.35071 14.0844 3.41519 15.7485 4.92455 16.9391C6.43391 18.1296 8.3002 18.7772 10.2226 18.7775C12.477 18.7552 14.6315 17.844 16.2178 16.242C17.8041 14.64 18.694 12.4766 18.694 10.222C18.694 7.96752 17.8041 5.80412 16.2178 4.20208C14.6315 2.60005 12.477 1.68888 10.2226 1.66663" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.2218 9.6875V13.965" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.2218 5.40991V6.47908" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/src/assets/icon-success.svg b/src/assets/icon-success.svg
new file mode 100644
index 00000000..263aea9e
--- /dev/null
+++ b/src/assets/icon-success.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="19" viewBox="0 0 24 19" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2.38635 10.9224L7.90582 16.3201L21.6364 2.57007" stroke="white" stroke-width="2.5" stroke-linecap="square"/>
+</svg>
diff --git a/src/components/Column/ColumnSettings.tsx b/src/components/Column/ColumnSettings.tsx
index 59dcb443..457e2ca3 100644
--- a/src/components/Column/ColumnSettings.tsx
+++ b/src/components/Column/ColumnSettings.tsx
@@ -11,10 +11,9 @@ import {useTranslation} from "react-i18next";
 import {useDispatch} from "react-redux";
 import "./ColumnSettings.scss";
 import {useAppSelector} from "store";
-import i18n from "i18next";
 import classNames from "classnames";
 import {Toast} from "../../utils/Toast";
-import {TEMPORARY_COLUMN_ID} from "../../constants/misc";
+import {TEMPORARY_COLUMN_ID, TOAST_TIMER_SHORT} from "../../constants/misc";
 
 type ColumnSettingsProps = {
   id: string;
@@ -45,12 +44,7 @@ export const ColumnSettings: VFC<ColumnSettingsProps> = ({id, name, color, visib
   const handleAddColumn = (columnIndex: number) => {
     if (!showHiddenColumns) {
       dispatch(Actions.setShowHiddenColumns(true));
-      Toast.success(
-        <div>
-          <div>{i18n.t("Toast.hiddenColumnsVisible")}</div>
-        </div>,
-        3000
-      );
+      Toast.success({title: t("Toast.hiddenColumnsVisible"), autoClose: TOAST_TIMER_SHORT});
     }
     const randomColor = getColorForIndex(Math.floor(Math.random() * 100));
     dispatch(Actions.createColumnOptimistically({id: TEMPORARY_COLUMN_ID, name: "", color: randomColor, visible: false, index: columnIndex}));
diff --git a/src/components/CustomToast/CustomToast.scss b/src/components/CustomToast/CustomToast.scss
new file mode 100644
index 00000000..e12ed922
--- /dev/null
+++ b/src/components/CustomToast/CustomToast.scss
@@ -0,0 +1,472 @@
+@import "constants/style";
+
+// The namings "single" and "mutli" refer to the two types of toasts.
+// The single line toast or a toast that spans multiple lines of content
+
+// These and other darkmode collors from Figma need to be added/moved to style.css
+$color-info-darkmode: #6b9cff;
+$color-error: #ff0069;
+$color-success: #17bf71;
+
+$color-close-icon: #bfc1c5;
+
+// Toastify
+.Toastify__toast {
+  min-height: 56px;
+  width: 460px;
+  margin-left: -160px;
+  padding: 0px;
+  box-shadow: $box-shadow--light;
+  overflow: inherit;
+  cursor: default;
+  background-color: $color-white;
+}
+.Toastify__toast:has(div.toast-single) {
+  height: 60px;
+  border-radius: 28px;
+}
+.Toastify__toast:has(div.toast-multi) {
+  min-height: 172px;
+  border-radius: 16px;
+}
+.Toastify__toast-icon {
+  display: none;
+}
+.Toastify__toast-body {
+  margin: 0;
+  padding: 0;
+  min-height: 100%;
+  > div {
+    height: 100%;
+  }
+}
+.Toastify__close-button {
+  display: none;
+}
+
+// Toast styles
+.toast-single {
+  display: flex;
+  padding: 7px 8px 7px 8px;
+  align-items: center;
+  height: 100%;
+}
+.toast-multi {
+  padding: 30px 54px 24px 54px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+// Toast icons
+.toast__icon {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+  background-color: $menu-icon-background-color--dark;
+  svg {
+    height: 46px;
+    width: 46px;
+    color: $color-white;
+  }
+}
+.toast__icon-single {
+  height: 48px;
+  min-width: 48px;
+  svg {
+    height: 38px;
+    width: 38px;
+  }
+}
+.toast__icon-single-info {
+  svg {
+    height: 30px;
+    width: 30px;
+    path {
+      stroke: rgba($color-white, 0.7);
+    }
+  }
+}
+.toast__icon-single-success {
+  background-color: $color-success;
+  svg {
+    height: 22px;
+    width: 22px;
+  }
+}
+.toast__icon-single-error {
+  background-color: $color-error;
+  svg {
+    height: 32px;
+    width: 32px;
+  }
+}
+.toast__icon-multi {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  transform: translate(-38%, -31%);
+  width: 64px;
+  height: 64px;
+}
+.toast__icon-multi-info {
+  svg {
+    height: 38px;
+    width: 38px;
+    path {
+      stroke: rgba($color-white, 0.7);
+    }
+  }
+}
+.toast__icon-multi-success {
+  background-color: $color-success;
+  svg {
+    height: 32px;
+    width: 32px;
+  }
+}
+.toast__icon-multi-error {
+  background-color: $color-error;
+  svg {
+    height: 42px;
+    width: 42px;
+  }
+}
+
+// Toast close icon
+.toast__close-icon {
+  min-width: 32px;
+  color: $color-close-icon;
+  transition: all 0.1s ease-in-out;
+}
+.toast__close-icon-single {
+  margin: 0px 6px;
+  display: flex;
+  height: 100%;
+  justify-content: center;
+  align-items: center;
+  svg {
+    height: 100%;
+  }
+}
+.toast__close-icon-multi {
+  position: absolute;
+  top: 12px;
+  right: 13px;
+}
+.toast__close-icon-single:hover,
+.toast__close-icon-multi:hover {
+  cursor: pointer;
+  transform: scale(1.08);
+}
+
+// Toast title
+.toast__title {
+  font-size: 16px;
+  line-height: 19px;
+  font-weight: 700;
+  color: $menu-icon-background-color--dark;
+}
+
+// Toast message
+.toast__message {
+  font-size: 14px;
+  line-height: 18px;
+  font-weight: 500;
+  color: $menu-icon-background-color--dark;
+  margin-top: 5px;
+}
+
+//Toast buttons
+.toast__button {
+  font-size: 14px;
+  line-height: 16px;
+  padding: 12px 24px;
+  border-radius: 28px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  transition: all 0.1s ease-in-out;
+}
+.toast__button:hover {
+  transform: scale(1.03);
+}
+.toast__title-button-single-wrapper {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  width: 100%;
+  margin-left: 18px;
+  gap: 5px;
+}
+.toast__button-single {
+  border: none;
+  background: $menu-icon-background-color--dark;
+  color: $color-white;
+  margin-left: auto;
+  white-space: nowrap;
+  min-width: auto;
+}
+.toast__buttons-multi {
+  display: flex;
+  gap: 12px;
+  margin-top: auto;
+  padding-top: 17px;
+  min-height: 42px;
+  max-height: 59px;
+}
+.toast__button-multi-primary {
+  padding: 13px 32px;
+  color: $color-white;
+  background-color: $menu-icon-background-color--dark;
+  border: 2px solid $menu-icon-background-color--dark;
+}
+.toast__button-multi-secondary {
+  padding: 13px 32px;
+  background: $color-white;
+  border: 2px solid $menu-icon-background-color--dark;
+}
+.toast-single:hover .toast__button-single.toast__button-info,
+.toast__button-multi-primary.toast__button-info:hover {
+  border-color: $color-backlog-blue;
+  background-color: $color-backlog-blue;
+  color: $color-white;
+}
+.toast__button-multi-secondary.toast__button-info:hover {
+  color: $color-backlog-blue;
+  border-color: $color-backlog-blue;
+}
+.toast-single:hover .toast__button-single.toast__button-error,
+.toast__button-multi-primary.toast__button-error:hover {
+  border-color: $color-error;
+  background-color: $color-error;
+  color: $color-white;
+}
+.toast__button-multi-secondary.toast__button-error:hover {
+  color: $color-error;
+  border-color: $color-error;
+}
+.toast-single:hover .toast__button-single.toast__button-success,
+.toast__button-multi-primary.toast__button-success:hover {
+  border-color: $color-success;
+  background-color: $color-success;
+  color: $color-white;
+}
+.toast__button-multi-secondary.toast__button-success:hover {
+  color: $color-success;
+  border-color: $color-success;
+}
+
+// Toast hint
+.toast__hint {
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 16px;
+  display: grid;
+  grid-template-columns: 16px auto;
+  gap: 10px;
+  margin-top: 13px;
+  color: $color-middle-gray;
+  width: fit-content;
+}
+input[type="checkbox"] {
+  -webkit-appearance: none;
+  appearance: none;
+  background-color: $color-white;
+  margin: 0;
+  font: inherit;
+  color: $color-light-gray;
+  width: 1.125em;
+  height: 1.125em;
+  border: 0.15em solid currentColor;
+  border-radius: 0.2em;
+  display: grid;
+  place-content: center;
+}
+input[type="checkbox"]:hover {
+  cursor: pointer;
+  box-shadow: 0 0 0 3px rgba($color-light-gray, 0.3);
+}
+input[type="checkbox"]::before {
+  content: "";
+  width: 0.625em;
+  height: 0.625em;
+  transform: scale(0);
+  transition: 120ms transform ease-in-out;
+  background-color: $color-light-gray;
+}
+input[type="checkbox"]:checked::before {
+  transform: scale(1);
+}
+
+// Toast Dark-Theme (Desktop)
+[theme="dark"] {
+  .Toastify__toast.Toastify__toast:has(div.toast-single),
+  .Toastify__toast.Toastify__toast:has(div.toast-multi) {
+    background-color: $menu-icon-background-color--dark;
+  }
+
+  .toast__icon {
+    background-color: $color-white;
+    svg {
+      color: $menu-icon-background-color--dark;
+    }
+  }
+  .toast__icon-single-info,
+  .toast__icon-multi-info {
+    svg path {
+      stroke: $menu-icon-background-color--dark;
+    }
+  }
+  .toast__icon-single-success,
+  .toast__icon-multi-success {
+    background-color: $color-success;
+    svg {
+      color: $color-white;
+    }
+  }
+  .toast__icon-single-error,
+  .toast__icon-multi-error {
+    background-color: $color-error;
+    svg {
+      color: $color-white;
+    }
+  }
+  .toast__title,
+  .toast__message {
+    color: $color-white;
+  }
+  .toast__button-single,
+  .toast__button-multi-primary {
+    background-color: $color-white;
+    color: $menu-icon-background-color--dark;
+  }
+  .toast__button-multi-secondary {
+    background-color: $menu-icon-background-color--dark;
+    border-color: $color-white;
+    color: $color-white;
+  }
+  input[type="checkbox"] {
+    background-color: $menu-icon-background-color--dark;
+    color: inherit;
+  }
+  input[type="checkbox"]::before {
+    background-color: $menu-icon-background-color--dark;
+  }
+  input[type="checkbox"]:checked::before {
+    background-color: $color-middle-gray;
+  }
+}
+
+// Toast (mobile)
+@media #{$mini-smartphone} {
+  .Toastify__toast {
+    width: calc(100vw - 38px);
+    margin: 0px auto 19px auto;
+  }
+  .Toastify__toast:has(div.toast-single) {
+    height: 60px;
+  }
+  .Toastify__toast:has(div.toast-multi) {
+    min-height: 172px;
+  }
+  .toast-multi {
+    padding: 39px 34px 17px 31px;
+  }
+  .toast__icon-multi {
+    height: 55px;
+    width: 55px;
+    transform: translate(-28%, -32%);
+    svg {
+      height: 42px;
+      width: 42px;
+    }
+  }
+  .toast__icon-multi-info {
+    background-color: $menu-icon-background-color--dark;
+    svg {
+      height: 29px;
+      width: 29px;
+    }
+  }
+  .toast__icon-multi-error {
+    svg {
+      height: 42px;
+      width: 42px;
+    }
+  }
+  .toast__icon-multi-success {
+    svg {
+      height: 28px;
+      width: 28px;
+    }
+  }
+  .toast__button {
+    padding: 12px 30px;
+  }
+  .toast__button-multi {
+    padding: 12px 20px;
+  }
+  .toast__title-button-single-wrapper {
+    display: block;
+  }
+  .toast__button-single {
+    padding: 0px;
+    background-color: $color-white;
+    color: $color-backlog-blue;
+    margin-left: 0px;
+    line-height: 16px;
+    font-weight: 600;
+    margin-top: 3px;
+  }
+  .toast__button-single.toast__button-success {
+    color: $color-success;
+  }
+  .toast__button-single.toast__button-error {
+    color: $color-error;
+  }
+  .toast-single:hover .toast__button-single.toast__button-info {
+    border-color: $color-white;
+    background-color: $color-white;
+    color: $color-backlog-blue;
+  }
+  .toast-single:hover .toast__button-single.toast__button-error {
+    border-color: $color-white;
+    background-color: $color-white;
+    color: $color-error;
+  }
+  .toast-single:hover .toast__button-single.toast__button-success {
+    border-color: $color-white;
+    background-color: $color-white;
+    color: $color-success;
+  }
+
+  [theme="dark"] {
+    .toast-single:hover .toast__button-single.toast__button-info {
+      background: $menu-icon-background-color--dark;
+      color: $color-info-darkmode;
+    }
+    .toast-single:hover .toast__button-single.toast__button-error {
+      background: $menu-icon-background-color--dark;
+      color: $color-error;
+    }
+    .toast-single:hover .toast__button-single.toast__button-success {
+      background: $menu-icon-background-color--dark;
+      color: $color-success;
+    }
+    .toast__button-single.toast__button-info {
+      background: $menu-icon-background-color--dark;
+      color: $color-info-darkmode;
+    }
+    .toast__button-single.toast__button-error {
+      background: $menu-icon-background-color--dark;
+      color: $color-error;
+    }
+    .toast__button-single.toast__button-success {
+      background: $menu-icon-background-color--dark;
+      color: $color-success;
+    }
+  }
+}
diff --git a/src/components/CustomToast/CustomToast.tsx b/src/components/CustomToast/CustomToast.tsx
new file mode 100644
index 00000000..e9ed129e
--- /dev/null
+++ b/src/components/CustomToast/CustomToast.tsx
@@ -0,0 +1,143 @@
+import "./CustomToast.scss";
+import {FC, useEffect, useRef, useState} from "react";
+import {ReactComponent as CloseIcon} from "assets/icon-close.svg";
+import {ToastTypes} from "utils/Toast";
+import classNames from "classnames";
+
+export interface CustomToastProps {
+  title: string;
+  message?: string;
+  hintMessage?: string;
+  hintOnClick?: () => void;
+  buttons?: string[];
+  firstButtonOnClick?: () => void;
+  secondButtonOnClick?: () => void;
+  icon?: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
+  iconName?: string;
+  type?: ToastTypes;
+}
+
+export const CustomToast: FC<CustomToastProps> = ({title, message, buttons, hintMessage, hintOnClick, firstButtonOnClick, secondButtonOnClick, icon, iconName, type}) => {
+  const [isSingleToastTitle, setIsSingleToastTitle] = useState<boolean>(true);
+  const [isMobile, setIsMobile] = useState<boolean>(false);
+  const titleRef = useRef<HTMLDivElement>(null);
+  const standardIcon = ["info", "success", "error"].includes(iconName!);
+  const Icon = icon;
+
+  const TITLE_MAX_HEIGHT_DESKTOP = 38; // two lines of text
+  const TITLE_MAX_HEIGHT_MOBILE = 19; // one line of text
+  const MAX_WIDTH_MOBILE = 767; // $smartphone: "screen and (max-width: 767px)"
+
+  // check whether screensize is mobile/desktop
+  useEffect(() => {
+    const handleResize = () => {
+      setIsMobile(window.innerWidth <= MAX_WIDTH_MOBILE);
+    };
+    window.addEventListener("resize", handleResize);
+    handleResize();
+    return () => window.removeEventListener("resize", handleResize);
+  }, []);
+
+  // detects whether the title spans one or two lines
+  useEffect(() => {
+    if (!titleRef.current) {
+      return;
+    }
+    if (!isMobile) {
+      if (titleRef.current.offsetHeight > TITLE_MAX_HEIGHT_DESKTOP) {
+        setIsSingleToastTitle(false);
+      }
+    }
+    if (isMobile) {
+      if (buttons) {
+        if (buttons.length < 2) {
+          if (titleRef.current.offsetHeight > TITLE_MAX_HEIGHT_MOBILE) {
+            setIsSingleToastTitle(false);
+          }
+        } else {
+          setIsSingleToastTitle(false);
+        }
+      } else if (titleRef.current.offsetHeight > TITLE_MAX_HEIGHT_DESKTOP) {
+        setIsSingleToastTitle(false);
+      }
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [title, isMobile]);
+
+  const isSingleToast = (!buttons || buttons?.length <= 1) && isSingleToastTitle && !message && !hintMessage;
+
+  return (
+    <div className={`${isSingleToast ? "toast-single" : "toast-multi"}`}>
+      <div
+        className={classNames(
+          "toast__icon",
+          {"toast__icon-single": isSingleToast, "toast__icon-multi": !isSingleToast},
+          standardIcon && `toast__icon-${isSingleToast ? "single" : "multi"}-${type}`
+        )}
+      >
+        {Icon && <Icon />}
+      </div>
+      {isSingleToast ? (
+        <div className="toast__title-button-single-wrapper">
+          <div className="toast__title" ref={titleRef}>
+            {title}
+          </div>
+          {buttons?.length === 1 && (
+            <button className={`toast__button toast__button-single toast__button-${type}`} onClick={firstButtonOnClick}>
+              {buttons[0]}
+            </button>
+          )}
+        </div>
+      ) : (
+        <div className="toast__title" ref={titleRef}>
+          {title}
+        </div>
+      )}
+      {message && <div className="toast__message">{message}</div>}
+      {hintMessage && (
+        <label
+          className="toast__hint"
+          onClick={(e) => {
+            e.stopPropagation();
+          }}
+        >
+          <input
+            type="checkbox"
+            name="checkbox"
+            onClick={(e) => {
+              e.stopPropagation();
+              // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+              hintOnClick && hintOnClick();
+            }}
+          />
+          {hintMessage}
+        </label>
+      )}
+      {!isSingleToast && buttons && buttons.length > 0 && (
+        <div className="toast__buttons-multi">
+          {buttons &&
+            buttons.map((button, index) => {
+              if (index > 1) return false;
+              return (
+                <button
+                  // eslint-disable-next-line react/no-array-index-key
+                  key={index}
+                  className={
+                    index === 0
+                      ? `toast__button toast__button-multi toast__button-multi-primary toast__button-${type}`
+                      : `toast__button toast__button-multi toast__button-multi-secondary toast__button-${type}`
+                  }
+                  onClick={index === 0 ? firstButtonOnClick : secondButtonOnClick}
+                >
+                  {button}
+                </button>
+              );
+            })}
+        </div>
+      )}
+      <div className={`toast__close-icon ${isSingleToast ? "toast__close-icon-single" : "toast__close-icon-multi"}`}>
+        <CloseIcon />
+      </div>
+    </div>
+  );
+};
diff --git a/src/components/HotkeyAnchor/HotkeyAnchor.tsx b/src/components/HotkeyAnchor/HotkeyAnchor.tsx
index 1cd8022a..c27c8a95 100644
--- a/src/components/HotkeyAnchor/HotkeyAnchor.tsx
+++ b/src/components/HotkeyAnchor/HotkeyAnchor.tsx
@@ -9,6 +9,7 @@ import {hotkeyMap} from "constants/hotkeys";
 import "./HotkeyAnchor.scss";
 import {useNavigate, useParams} from "react-router";
 import {useEffect, useRef} from "react";
+import {TOAST_TIMER_SHORT} from "constants/misc";
 
 /**
  * Anchor for general hotkeys
@@ -63,10 +64,10 @@ export const HotkeyAnchor = () => {
   const toggleHotkeys = () => {
     if (state.hotkeysAreActive) {
       dispatch(Actions.setHotkeyState(false));
-      Toast.info(t("Hotkeys.hotkeysDisabled"), 1500);
+      Toast.info({title: t("Hotkeys.hotkeysDisabled"), autoClose: TOAST_TIMER_SHORT});
     } else {
       dispatch(Actions.setHotkeyState(true));
-      Toast.info(t("Hotkeys.hotkeysEnabled"), 1500);
+      Toast.info({title: t("Hotkeys.hotkeysEnabled"), autoClose: TOAST_TIMER_SHORT});
     }
   };
 
@@ -75,38 +76,38 @@ export const HotkeyAnchor = () => {
       dispatch(Actions.stopSharing());
       dispatch(Actions.clearFocusInitiator());
       dispatch(Actions.setModerating(false));
-      Toast.info(t("Hotkeys.togglePresentationMode.endPresenting"), 1500);
+      Toast.info({title: t("Hotkeys.togglePresentationMode.endPresenting"), autoClose: TOAST_TIMER_SHORT});
     } else {
       dispatch(Actions.setFocusInitiator(state.currentUser));
       if (note.current) dispatch(Actions.shareNote(note.current));
       dispatch(Actions.setModerating(true));
-      Toast.info(t("Hotkeys.togglePresentationMode.startPresenting"), 1500);
+      Toast.info({title: t("Hotkeys.togglePresentationMode.startPresenting"), autoClose: TOAST_TIMER_SHORT});
     }
   };
 
   const toggleReadyState = () => {
     if (isReady) {
       dispatch(Actions.setUserReadyStatus(state.currentUser.user.id, false));
-      Toast.info(t("Hotkeys.toggleReadyState.notReady"), 1500);
+      Toast.info({title: t("Hotkeys.toggleReadyState.notReady"), autoClose: TOAST_TIMER_SHORT});
     } else {
       dispatch(Actions.setUserReadyStatus(state.currentUser.user.id, true));
-      Toast.info(t("Hotkeys.toggleReadyState.ready"), 1500);
+      Toast.info({title: t("Hotkeys.toggleReadyState.ready"), autoClose: TOAST_TIMER_SHORT});
     }
   };
 
   const toggleRaiseHand = () => {
     if (raisedHand) {
       dispatch(Actions.setRaisedHand(state.currentUser.user.id, false));
-      Toast.info(t("Hotkeys.toggleRaisedHand.lower"), 1500);
+      Toast.info({title: t("Hotkeys.toggleRaisedHand.lower"), autoClose: TOAST_TIMER_SHORT});
     } else {
       dispatch(Actions.setRaisedHand(state.currentUser.user.id, true));
-      Toast.info(t("Hotkeys.toggleRaisedHand.raise"), 1500);
+      Toast.info({title: t("Hotkeys.toggleRaisedHand.raise"), autoClose: TOAST_TIMER_SHORT});
     }
   };
 
   const startTimer = (minutes: number) => {
     dispatch(Actions.setTimer(minutes));
-    Toast.info(`${t("TimerToggleButton.customTime")}: ${minutes} ${t("TimerToggleButton.min")}`);
+    Toast.info({title: `${t("TimerToggleButton.customTime")}: ${minutes} ${t("TimerToggleButton.min")}`});
   };
 
   const showSettings = () => navigate("settings");
@@ -122,10 +123,10 @@ export const HotkeyAnchor = () => {
       e.preventDefault();
       if (state.showAuthors) {
         dispatch(Actions.editBoard({showAuthors: false}));
-        Toast.info(t("Hotkeys.toggleShowAuthors.hide"));
+        Toast.info({title: t("Hotkeys.toggleShowAuthors.hide")});
       } else {
         dispatch(Actions.editBoard({showAuthors: true}));
-        Toast.info(t("Hotkeys.toggleShowAuthors.show"));
+        Toast.info({title: t("Hotkeys.toggleShowAuthors.show")});
       }
     },
     hotkeyOptionsAdmin,
@@ -137,10 +138,10 @@ export const HotkeyAnchor = () => {
       e.preventDefault();
       if (state.showNotesOfOtherUsers) {
         dispatch(Actions.editBoard({showNotesOfOtherUsers: false}));
-        Toast.info(t("Hotkeys.toggleShowOtherUsersNotes.hide"));
+        Toast.info({title: t("Hotkeys.toggleShowOtherUsersNotes.hide")});
       } else {
         dispatch(Actions.editBoard({showNotesOfOtherUsers: true}));
-        Toast.info(t("Hotkeys.toggleShowOtherUsersNotes.show"));
+        Toast.info({title: t("Hotkeys.toggleShowOtherUsersNotes.show")});
       }
     },
     hotkeyOptionsAdmin,
@@ -152,10 +153,10 @@ export const HotkeyAnchor = () => {
       e.preventDefault();
       if (state.showHiddenColumns) {
         dispatch(Actions.setShowHiddenColumns(false));
-        Toast.info(t("Hotkeys.toggleColumnVisibility.hide"), 1500);
+        Toast.info({title: t("Hotkeys.toggleColumnVisibility.hide"), autoClose: TOAST_TIMER_SHORT});
       } else {
         dispatch(Actions.setShowHiddenColumns(true));
-        Toast.info(t("Hotkeys.toggleColumnVisibility.show"), 1500);
+        Toast.info({title: t("Hotkeys.toggleColumnVisibility.show"), autoClose: TOAST_TIMER_SHORT});
       }
     },
     hotkeyOptionsAdmin,
diff --git a/src/components/NoteInput/NoteInput.scss b/src/components/NoteInput/NoteInput.scss
index 63490045..d4459d68 100644
--- a/src/components/NoteInput/NoteInput.scss
+++ b/src/components/NoteInput/NoteInput.scss
@@ -115,16 +115,6 @@ $note-input__input-right: 40px;
   }
 }
 
-.toast-button {
-  background-color: $color-backlog-blue;
-  border-style: none;
-  border-radius: 8px;
-  height: 40px;
-  width: 100%;
-  margin-top: $margin--default;
-  color: $color-white;
-}
-
 [theme="dark"] {
   .note-input {
     background-color: $color-dark-mode-note;
@@ -138,10 +128,6 @@ $note-input__input-right: 40px;
   .note-input__input {
     color: $color-white;
   }
-
-  .toast-button {
-    background-color: $color-planning-pink;
-  }
 }
 
 @keyframes fade-in {
diff --git a/src/components/NoteInput/NoteInput.tsx b/src/components/NoteInput/NoteInput.tsx
index cce7030d..2eab7c6b 100644
--- a/src/components/NoteInput/NoteInput.tsx
+++ b/src/components/NoteInput/NoteInput.tsx
@@ -49,18 +49,16 @@ export const NoteInput = ({columnIndex, columnId, maxNoteLength, columnIsVisible
       setValue(e.target.value);
     }
   };
+
   const onAddNote = () => {
     if (value) {
       dispatch(Actions.addNote(columnId!, value));
       if (!columnIsVisible && !toastDisplayed) {
-        Toast.info(
-          <div>
-            <div>{t("Toast.noteToHiddenColumn")}</div>
-            <button className="toast-button" onClick={toggleColumnVisibility}>
-              {t("Toast.noteToHiddenColumnButton")}
-            </button>
-          </div>
-        );
+        Toast.info({
+          title: t("Toast.noteToHiddenColumn"),
+          buttons: [t("Toast.noteToHiddenColumnButton")],
+          firstButtonOnClick: toggleColumnVisibility,
+        });
         setToastDisplayed(true);
       }
       setValue("");
diff --git a/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx b/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
index 3ea21d36..58bc1a97 100644
--- a/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
+++ b/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
@@ -47,24 +47,13 @@ export const BoardSettings = () => {
     setPassword(newPassword);
     if (newPassword.length >= MIN_PASSWORD_LENGTH) {
       store.dispatch(Actions.editBoard({accessPolicy: "BY_PASSPHRASE", passphrase: newPassword}));
-      navigator.clipboard.writeText(newPassword).then(() =>
-        Toast.success(
-          <div>
-            <div>{t("Toast.passwordCopied")}</div>
-          </div>,
-          TOAST_TIMER_SHORT
-        )
-      );
+      navigator.clipboard.writeText(newPassword).then(() => Toast.success({title: t("Toast.passwordCopied"), autoClose: TOAST_TIMER_SHORT}));
       setIsProtected(true);
     } else if (isProtected || isProtectedOnInitialSettingsOpen) {
       store.dispatch(Actions.editBoard({accessPolicy: "PUBLIC"}));
       setIsProtectedOnInitialSettingsOpen(false);
       setIsProtected(false);
-      Toast.info(
-        <div>
-          <div>{t("Toast.boardMadePublic")}</div>
-        </div>
-      );
+      Toast.info({title: t("Toast.boardMadePublic"), autoClose: TOAST_TIMER_SHORT});
     }
   };
 
diff --git a/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx b/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx
index 0d9c41e3..4c939400 100644
--- a/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx
+++ b/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx
@@ -63,7 +63,9 @@ export const ExportBoard: VFC = () => {
           className="export-board__button-reverse-order"
           onClick={() => {
             getMarkdownExport(boardId).then((result) => {
-              navigator.clipboard.writeText(result).then(() => Toast.success(t("ExportBoardOption.copyToClipboardSuccess"), TOAST_TIMER_SHORT));
+              navigator.clipboard.writeText(result).then(() => {
+                Toast.success({title: t("ExportBoardOption.copyToClipboardSuccess"), autoClose: TOAST_TIMER_SHORT});
+              });
             });
           }}
           data-testid="export-markdown"
diff --git a/src/components/SettingsDialog/ProfileSettings/ProfileSettings.scss b/src/components/SettingsDialog/ProfileSettings/ProfileSettings.scss
index 61382395..f103df97 100644
--- a/src/components/SettingsDialog/ProfileSettings/ProfileSettings.scss
+++ b/src/components/SettingsDialog/ProfileSettings/ProfileSettings.scss
@@ -74,7 +74,9 @@ $settings__item-height: 48px;
   > svg {
     width: $icon--medium;
     height: $icon--medium;
-    stroke: $color-dark-gray;
+    path {
+      stroke: $color-dark-gray;
+    }
   }
 
   > p {
diff --git a/src/components/Timer/Timer.tsx b/src/components/Timer/Timer.tsx
index 237def7a..2f0bdb51 100644
--- a/src/components/Timer/Timer.tsx
+++ b/src/components/Timer/Timer.tsx
@@ -9,8 +9,8 @@ import {Toast} from "utils/Toast";
 import useSound from "use-sound";
 import {API} from "api";
 import {Timer as TimerUtils} from "utils/timer";
-import {Button} from "../Button";
 import "./Timer.scss";
+import {TOAST_TIMER_DEFAULT} from "constants/misc";
 
 type TimerProps = {
   startTime: Date;
@@ -65,15 +65,12 @@ export const Timer = (props: TimerProps) => {
       playTimesUpSound();
       hideTimerAfterSeconds(HIDE_TIMER_AFTER_SECONDS);
       if (isModerator && anyReady) {
-        Toast.info(
-          <div>
-            {t("Toast.moderatorResetReadyStates")}
-            <Button style={{marginTop: "1rem"}} onClick={() => API.updateReadyStates(boardId, false)}>
-              {t("Toast.moderatorResetReadyStatesButton")}
-            </Button>
-          </div>,
-          false
-        );
+        Toast.info({
+          title: t("Toast.moderatorResetReadyStates"),
+          buttons: [t("Toast.moderatorResetReadyStatesButton")],
+          firstButtonOnClick: () => API.updateReadyStates(boardId, false),
+          autoClose: false,
+        });
       }
     }
 
@@ -97,12 +94,7 @@ export const Timer = (props: TimerProps) => {
 
   useEffect(() => {
     if (isModerator && allParticipantsReady && Object.values(timeLeft).some((time) => time > 0)) {
-      Toast.success(
-        <div>
-          <div>{t("Toast.allParticipantsDone")}</div>
-        </div>,
-        5000
-      );
+      Toast.info({title: t("Toast.allParticipantsDone"), autoClose: TOAST_TIMER_DEFAULT});
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [allParticipantsReady, isModerator]);
diff --git a/src/constants/misc.ts b/src/constants/misc.ts
index 1e1fefac..9fbfdbd5 100644
--- a/src/constants/misc.ts
+++ b/src/constants/misc.ts
@@ -4,5 +4,5 @@ export const PLACEHOLDER_PASSWORD = "••••••••••";
 export const TEMPORARY_COLUMN_ID = "TEMP_ID";
 export const DEFAULT_URL = "https://scrumlr.io";
 
-export const TOAST_TIMER_DEFAULT = 3000;
-export const TOAST_TIMER_SHORT = 1500;
+export const TOAST_TIMER_DEFAULT = 5000;
+export const TOAST_TIMER_SHORT = 3000;
diff --git a/src/index.scss b/src/index.scss
index e12dec69..187b7049 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -39,31 +39,7 @@ code {
   font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
 }
 
-.toast-container__container {
-  --toastify-icon-color-info: #{$color-backlog-blue};
-  --toastify-icon-color-success: #{$color-grooming-green};
-  --toastify-icon-color-warning: #{$color-online-orange};
-  --toastify-icon-color-error: #{$color-warning-red};
-}
-
-.toast-container__container .toast-container__toast {
-  margin-bottom: 0;
-  font-size: $text-size--medium;
-
-  background: $color-white;
-  color: $color-black;
-}
-
-.toast-container__container .toast-container__toast + .toast-container__toast {
-  margin-top: $margin--small;
-}
-
 [theme="dark"] {
-  .toast-container__container .toast-container__toast {
-    background: $color-dark-mode-note;
-    color: $color-white;
-  }
-
   body,
   #root {
     background: $color-dark-mode;
diff --git a/src/index.tsx b/src/index.tsx
index 41df4ba9..2e40f7d3 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -50,7 +50,7 @@ root.render(
       <Provider store={store}>
         <Html />
         <Suspense fallback={<LoadingScreen />}>
-          <ToastContainer className="toast-container__container" toastClassName="toast-container__toast" bodyClassName="toast-container__body" limit={2} />
+          <ToastContainer limit={2} />
           <DndProvider options={HTML5toTouch}>
             <Router />
           </DndProvider>
diff --git a/src/routes/LoginBoard/LoginBoard.tsx b/src/routes/LoginBoard/LoginBoard.tsx
index 546df9a5..e8605074 100644
--- a/src/routes/LoginBoard/LoginBoard.tsx
+++ b/src/routes/LoginBoard/LoginBoard.tsx
@@ -42,7 +42,7 @@ export const LoginBoard = () => {
         await Auth.signInAnonymously(displayName);
         navigate(redirectPath);
       } catch (err) {
-        Toast.error(t("LoginBoard.errorOnRedirect"));
+        Toast.error({title: t("LoginBoard.errorOnRedirect")});
       }
     }
     setSubmitted(true);
diff --git a/src/routes/StackView/StackView.tsx b/src/routes/StackView/StackView.tsx
index 3d7730ba..78c334ae 100644
--- a/src/routes/StackView/StackView.tsx
+++ b/src/routes/StackView/StackView.tsx
@@ -189,22 +189,14 @@ export const StackView = () => {
   useEffect(() => {
     if (note && prevNote.current && !prevNote.current.position.stack && note.position.stack) {
       navigate(`/board/${boardId}/note/${note.position.stack}/stack`);
-      Toast.info(
-        <div>
-          <div>{t("Toast.noteMovedToStack")}</div>
-        </div>
-      );
+      Toast.info({title: t("Toast.noteMovedToStack")});
     }
   }, [boardId, navigate, note, t]);
 
   // show toast if note has been deleted
   useLayoutEffect(() => {
     if (prevNote.current && !note) {
-      Toast.error(
-        <div>
-          <div>{t("Toast.noteDeleted")}</div>
-        </div>
-      );
+      Toast.error({title: t("Toast.noteDeleted")});
     }
   }, [note, t]);
 
diff --git a/src/store/middleware/auth.tsx b/src/store/middleware/auth.tsx
index 3f9465fe..5dcb9f61 100644
--- a/src/store/middleware/auth.tsx
+++ b/src/store/middleware/auth.tsx
@@ -5,8 +5,7 @@ import {AuthAction} from "store/action/auth";
 import {API} from "api";
 import {ViewAction} from "store/action/view";
 import {Toast} from "utils/Toast";
-import i18n from "i18next";
-import {Button} from "components/Button";
+import i18n from "i18n";
 import store from "store";
 
 export const passAuthMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
@@ -19,13 +18,14 @@ export const passAuthMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
         dispatch(Actions.userCheckCompleted(true));
       })
       .catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.serverConnection")}</div>
-            <Button onClick={() => store.dispatch(Actions.initApplication())}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        i18n.on("loaded", () => {
+          Toast.error({
+            title: i18n.t("Error.serverConnection"),
+            buttons: [i18n.t("Error.retry")],
+            firstButtonOnClick: () => store.dispatch(Actions.initApplication()),
+            autoClose: false,
+          });
+        });
         dispatch(Actions.userCheckCompleted(false));
       });
   }
@@ -37,13 +37,14 @@ export const passAuthMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
         location.reload();
       })
       .catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.logout")}</div>
-            <Button onClick={() => store.dispatch(Actions.signOut())}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        i18n.on("loaded", () => {
+          Toast.error({
+            title: i18n.t("Error.logout"),
+            buttons: [i18n.t("Error.retry")],
+            firstButtonOnClick: () => store.dispatch(Actions.signOut()),
+            autoClose: false,
+          });
+        });
       });
   }
 };
diff --git a/src/store/middleware/board.tsx b/src/store/middleware/board.tsx
index 22d439ab..9fea1af1 100644
--- a/src/store/middleware/board.tsx
+++ b/src/store/middleware/board.tsx
@@ -8,7 +8,6 @@ import {API} from "api";
 import {Timer} from "utils/timer";
 import {Toast} from "../../utils/Toast";
 import i18n from "../../i18n";
-import {Button} from "../../components/Button";
 import {SERVER_WEBSOCKET_URL} from "../../config";
 
 let socket: Socket | undefined;
@@ -116,12 +115,13 @@ export const passBoardMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Applicatio
       showNotesOfOtherUsers: action.board.showNotesOfOtherUsers,
       name: action.board.name == null ? currentState.name : action.board.name,
     }).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.editBoard")}</div>
-          <Button onClick={() => store.dispatch(Actions.editBoard(action.board))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      i18n.on("loaded", () => {
+        Toast.error({
+          title: i18n.t("Error.editBoard"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.editBoard(action.board)),
+        });
+      });
     });
   }
 
@@ -146,12 +146,13 @@ export const passBoardMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Applicatio
       timerStart: Timer.removeOffsetFromDate(currentState.timerStart, stateAPI.getState().view.serverTimeOffset),
       timerEnd: Timer.removeOffsetFromDate(currentState.timerEnd, stateAPI.getState().view.serverTimeOffset),
     }).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.shareNote")}</div>
-          <Button onClick={() => store.dispatch(Actions.shareNote(action.note))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      i18n.on("loaded", () => {
+        Toast.error({
+          title: i18n.t("Error.shareNote"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.shareNote(action.note)),
+        });
+      });
     });
   }
 
@@ -163,12 +164,13 @@ export const passBoardMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Applicatio
       timerStart: Timer.removeOffsetFromDate(currentState.timerStart, stateAPI.getState().view.serverTimeOffset),
       timerEnd: Timer.removeOffsetFromDate(currentState.timerEnd, stateAPI.getState().view.serverTimeOffset),
     }).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.unshareNote")}</div>
-          <Button onClick={() => store.dispatch(Actions.stopSharing())}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      i18n.on("loaded", () => {
+        Toast.error({
+          title: i18n.t("Error.unshareNote"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.stopSharing()),
+        });
+      });
     });
   }
 
@@ -178,12 +180,13 @@ export const passBoardMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Applicatio
         document.location.pathname = "/";
       })
       .catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.deleteBoard")}</div>
-            <Button onClick={() => store.dispatch(Actions.deleteBoard())}>{i18n.t("Error.retry")}</Button>
-          </div>
-        );
+        i18n.on("loaded", () => {
+          Toast.error({
+            title: i18n.t("Error.deleteBoard"),
+            buttons: [i18n.t("Error.retry")],
+            firstButtonOnClick: () => store.dispatch(Actions.deleteBoard()),
+          });
+        });
       });
   }
 };
diff --git a/src/store/middleware/column.tsx b/src/store/middleware/column.tsx
index 3d3f00e8..ba07b405 100644
--- a/src/store/middleware/column.tsx
+++ b/src/store/middleware/column.tsx
@@ -4,38 +4,34 @@ import {Action, Actions, ReduxAction} from "store/action";
 import {ApplicationState} from "types";
 import {Toast} from "../../utils/Toast";
 import i18n from "../../i18n";
-import {Button} from "../../components/Button";
 import store from "../index";
 
 export const passColumnMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
   if (action.type === Action.EditColumn) {
     API.editColumn(action.context.board!, action.id, action.column).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.editColumn")}</div>
-          <Button onClick={() => store.dispatch(Actions.editColumn(action.id, action.column))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.editColumn"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.editColumn(action.id, action.column)),
+      });
     });
   }
   if (action.type === Action.CreateColumn) {
     API.createColumn(action.context.board!, action.column).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.createColumn")}</div>
-          <Button onClick={() => store.dispatch(Actions.createColumn(action.column))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.createColumn"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.createColumn(action.column)),
+      });
     });
   }
   if (action.type === Action.DeleteColumn) {
     API.deleteColumn(action.context.board!, action.id).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.deleteColumn")}</div>
-          <Button onClick={() => store.dispatch(Actions.deleteColumn(action.id))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.deleteColumn"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.deleteColumn(action.id)),
+      });
     });
   }
 };
diff --git a/src/store/middleware/note.tsx b/src/store/middleware/note.tsx
index 9fa97a43..ea319465 100644
--- a/src/store/middleware/note.tsx
+++ b/src/store/middleware/note.tsx
@@ -4,18 +4,16 @@ import {Action, Actions, ReduxAction} from "store/action";
 import {API} from "api";
 import {Toast} from "../../utils/Toast";
 import i18n from "../../i18n";
-import {Button} from "../../components/Button";
 import store from "../index";
 
 export const passNoteMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
   if (action.type === Action.AddNote) {
     API.addNote(action.context.board!, action.columnId, action.text).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.addNote")}</div>
-          <Button onClick={() => store.dispatch(Actions.addNote(action.columnId, action.text))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.addNote"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.addNote(action.columnId, action.text)),
+      });
     });
   }
 
@@ -25,32 +23,25 @@ export const passNoteMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
     const {moderating} = stateAPI.getState().view;
 
     if (sharedNoteId === currentNoteId && !moderating) {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.deleteNoteWhenShared")}</div>
-        </div>,
-        5000
-      );
+      Toast.error({title: i18n.t("Error.deleteNoteWhenShared")});
     } else {
       API.deleteNote(action.context.board!, action.noteId, action.deleteStack).catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.deleteNote")}</div>
-            <Button onClick={() => store.dispatch(Actions.deleteNote(action.noteId))}>{i18n.t("Error.retry")}</Button>
-          </div>
-        );
+        Toast.error({
+          title: i18n.t("Error.deleteNote"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.deleteNote(action.noteId)),
+        });
       });
     }
   }
 
   if (action.type === Action.EditNote) {
     API.editNote(action.context.board!, action.note, action.request).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.editNote")}</div>
-          <Button onClick={() => store.dispatch(Actions.editNote(action.note, action.request))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.editNote"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.editNote(action.note, action.request)),
+      });
     });
   }
 
@@ -59,12 +50,11 @@ export const passNoteMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
     const parent = stateAPI.getState().notes.find((n) => n.id === note.position.stack)!;
 
     API.editNote(action.context.board!, action.note, {position: {column: note.position.column, stack: null, rank: Math.max(parent.position.rank - 1, 0)}}).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.unstackNote")}</div>
-          <Button onClick={() => store.dispatch(Actions.unstackNote(action.note))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.unstackNote"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.unstackNote(action.note)),
+      });
     });
   }
 };
diff --git a/src/store/middleware/participants.tsx b/src/store/middleware/participants.tsx
index ff21a9d6..4df43c02 100644
--- a/src/store/middleware/participants.tsx
+++ b/src/store/middleware/participants.tsx
@@ -4,40 +4,36 @@ import {Action, Actions, ReduxAction} from "store/action";
 import {API} from "api";
 import {Toast} from "../../utils/Toast";
 import i18n from "../../i18n";
-import {Button} from "../../components/Button";
 import store from "../index";
 
 export const passParticipantsMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
   if (action.type === Action.SetRaisedHandStatus) {
     API.editParticipant(action.context.board!, action.user, {raisedHand: action.raisedHand}).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.setRaiseHand")}</div>
-          <Button onClick={() => store.dispatch(Actions.setRaisedHand(action.user, action.raisedHand))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.setRaiseHand"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.setRaisedHand(action.user, action.raisedHand)),
+      });
     });
   }
 
   if (action.type === Action.SetUserReadyStatus) {
     API.editParticipant(action.context.board!, action.user, {ready: action.ready}).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.setUserReady")}</div>
-          <Button onClick={() => store.dispatch(Actions.setUserReadyStatus(action.user, action.ready))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.setUserReady"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.setUserReadyStatus(action.user, action.ready)),
+      });
     });
   }
 
   if (action.type === Action.SetShowHiddenColumns) {
     API.editParticipant(action.context.board!, action.context.user!, {showHiddenColumns: action.showHiddenColumns}).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.setShowHiddenColumns")}</div>
-          <Button onClick={() => store.dispatch(Actions.setShowHiddenColumns(action.showHiddenColumns))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.setShowHiddenColumns"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.setShowHiddenColumns(action.showHiddenColumns)),
+      });
     });
   }
 
@@ -45,23 +41,21 @@ export const passParticipantsMiddleware = (stateAPI: MiddlewareAPI<Dispatch, App
     API.editParticipant(action.context.board!, action.userId, {
       role: action.moderator ? "MODERATOR" : "PARTICIPANT",
     }).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.changePermission")}</div>
-          <Button onClick={() => store.dispatch(Actions.changePermission(action.userId, action.moderator))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.changePermission"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.changePermission(action.userId, action.moderator)),
+      });
     });
   }
 
   if (action.type === Action.EditSelf) {
     API.editUser(action.user).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.editSelf")}</div>
-          <Button onClick={() => store.dispatch(Actions.editSelf(action.user))}>{i18n.t("Error.retry")}</Button>
-        </div>
-      );
+      Toast.error({
+        title: i18n.t("Error.editSelf"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.editSelf(action.user)),
+      });
     });
   }
 };
diff --git a/src/store/middleware/request.tsx b/src/store/middleware/request.tsx
index 39d720ca..8e040fd5 100644
--- a/src/store/middleware/request.tsx
+++ b/src/store/middleware/request.tsx
@@ -6,7 +6,6 @@ import {Action, Actions, ReduxAction} from "store/action";
 import {API} from "api";
 import i18n from "i18next";
 import {Toast} from "../../utils/Toast";
-import {Button} from "../../components/Button";
 import {SERVER_WEBSOCKET_PROTOCOL} from "../../config";
 
 let socket: Socket | undefined;
@@ -28,13 +27,12 @@ export const passRequestMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Applicat
         }
       })
       .catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.joinBoard")}</div>
-            <Button onClick={() => store.dispatch(Actions.joinBoard(action.boardId, action.passphrase))}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        Toast.error({
+          title: i18n.t("Error.joinBoard"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.joinBoard(action.boardId, action.passphrase)),
+          autoClose: false,
+        });
       });
   }
 
@@ -67,13 +65,12 @@ export const passRequestMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Applicat
   if (action.type === Action.AcceptJoinRequests) {
     action.userIds.forEach((userId) => {
       API.acceptJoinRequest(action.context.board!, userId).catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.acceptJoinRequests")}</div>
-            <Button onClick={() => store.dispatch(Actions.acceptJoinRequests(action.userIds))}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        Toast.error({
+          title: i18n.t("Error.acceptJoinRequests"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.acceptJoinRequests(action.userIds)),
+          autoClose: false,
+        });
       });
     });
   }
@@ -81,13 +78,12 @@ export const passRequestMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Applicat
   if (action.type === Action.RejectJoinRequests) {
     action.userIds.forEach((userId) => {
       API.rejectJoinRequest(action.context.board!, userId).catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.rejectJoinRequest")}</div>
-            <Button onClick={() => store.dispatch(Actions.rejectJoinRequests(action.userIds))}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        Toast.error({
+          title: i18n.t("Error.rejectJoinRequest"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.rejectJoinRequests(action.userIds)),
+          autoClose: false,
+        });
       });
     });
   }
diff --git a/src/store/middleware/view.tsx b/src/store/middleware/view.tsx
index 5b178912..539ae085 100644
--- a/src/store/middleware/view.tsx
+++ b/src/store/middleware/view.tsx
@@ -2,9 +2,8 @@ import {Dispatch, MiddlewareAPI} from "@reduxjs/toolkit";
 import {ApplicationState} from "types";
 import {Action, Actions, ReduxAction} from "store/action";
 import {API} from "api";
-import i18n from "i18next";
+import i18n from "i18n";
 import {Toast} from "../../utils/Toast";
-import {Button} from "../../components/Button";
 import store from "../index";
 
 export const passViewMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
@@ -14,13 +13,14 @@ export const passViewMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
         dispatch(Actions.setServerInfo(r.authProvider || [], new Date(r.serverTime), r.feedbackEnabled));
       })
       .catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.initApplication")}</div>
-            <Button onClick={() => store.dispatch(Actions.initApplication())}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        i18n.on("loaded", () => {
+          Toast.error({
+            title: i18n.t("Error.initApplication"),
+            buttons: [i18n.t("Error.retry")],
+            firstButtonOnClick: () => store.dispatch(Actions.initApplication()),
+            autoClose: false,
+          });
+        });
       });
   }
 };
diff --git a/src/store/middleware/vote.tsx b/src/store/middleware/vote.tsx
index 16067b92..5dbe9e95 100644
--- a/src/store/middleware/vote.tsx
+++ b/src/store/middleware/vote.tsx
@@ -4,7 +4,6 @@ import {Action, Actions, ReduxAction} from "store/action";
 import {API} from "api";
 import i18n from "i18next";
 import {Toast} from "../../utils/Toast";
-import {Button} from "../../components/Button";
 import store from "../index";
 
 export const passVoteMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
@@ -14,13 +13,12 @@ export const passVoteMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
         dispatch(Actions.createdVote(r));
       })
       .catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.addVote")}</div>
-            <Button onClick={() => store.dispatch(Actions.addVote(action.note))}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        Toast.error({
+          title: i18n.t("Error.addVote"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.addVote(action.note)),
+          autoClose: false,
+        });
       });
   }
 
@@ -30,13 +28,12 @@ export const passVoteMiddleware = (stateAPI: MiddlewareAPI<Dispatch, Application
         dispatch(Actions.deletedVote({voting: action.context.voting!, note: action.note}));
       })
       .catch(() => {
-        Toast.error(
-          <div>
-            <div>{i18n.t("Error.deleteVote")}</div>
-            <Button onClick={() => store.dispatch(Actions.deleteVote(action.note))}>{i18n.t("Error.retry")}</Button>
-          </div>,
-          false
-        );
+        Toast.error({
+          title: i18n.t("Error.deleteVote"),
+          buttons: [i18n.t("Error.retry")],
+          firstButtonOnClick: () => store.dispatch(Actions.deleteVote(action.note)),
+          autoClose: false,
+        });
       });
   }
 };
diff --git a/src/store/middleware/votings.tsx b/src/store/middleware/votings.tsx
index 4c073fdd..5210e4d6 100644
--- a/src/store/middleware/votings.tsx
+++ b/src/store/middleware/votings.tsx
@@ -4,44 +4,40 @@ import {Action, Actions, ReduxAction} from "store/action";
 import {API} from "api";
 import i18n from "i18next";
 import {Toast} from "../../utils/Toast";
-import {Button} from "../../components/Button";
 import store from "../index";
 
 export const passVotingMiddleware = (stateAPI: MiddlewareAPI<Dispatch, ApplicationState>, dispatch: Dispatch, action: ReduxAction) => {
   if (action.type === Action.CreateVoting) {
     API.createVoting(action.context.board!, action.voting).catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.createVoting")}</div>
-          <Button onClick={() => store.dispatch(Actions.createVoting(action.voting))}>{i18n.t("Error.retry")}</Button>
-        </div>,
-        false
-      );
+      Toast.error({
+        title: i18n.t("Error.createVoting"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.createVoting(action.voting)),
+        autoClose: false,
+      });
     });
   }
 
   if (action.type === Action.CloseVoting) {
     API.changeVotingStatus(action.context.board!, action.voting, "CLOSED").catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.closeVoting")}</div>
-          <Button onClick={() => store.dispatch(Actions.closeVoting(action.voting))}>{i18n.t("Error.retry")}</Button>
-        </div>,
-        false
-      );
+      Toast.error({
+        title: i18n.t("Error.closeVoting"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.closeVoting(action.voting)),
+        autoClose: false,
+      });
     });
     API.updateReadyStates(action.context.board!, false);
   }
 
   if (action.type === Action.AbortVoting) {
     API.changeVotingStatus(action.context.board!, action.voting, "ABORTED").catch(() => {
-      Toast.error(
-        <div>
-          <div>{i18n.t("Error.abortVoting")}</div>
-          <Button onClick={() => store.dispatch(Actions.abortVoting(action.voting))}>{i18n.t("Error.retry")}</Button>
-        </div>,
-        false
-      );
+      Toast.error({
+        title: i18n.t("Error.abortVoting"),
+        buttons: [i18n.t("Error.retry")],
+        firstButtonOnClick: () => store.dispatch(Actions.abortVoting(action.voting)),
+        autoClose: false,
+      });
     });
     API.updateReadyStates(action.context.board!, false);
   }
diff --git a/src/utils/Toast.tsx b/src/utils/Toast.tsx
index aa9acecc..e74ec97f 100644
--- a/src/utils/Toast.tsx
+++ b/src/utils/Toast.tsx
@@ -1,7 +1,11 @@
 import {toast, ToastOptions} from "react-toastify";
 import "react-toastify/dist/ReactToastify.css";
-import {ReactNode} from "react";
+import "../components/CustomToast/CustomToast.scss";
 import {TOAST_TIMER_DEFAULT} from "constants/misc";
+import {CustomToast} from "components/CustomToast/CustomToast";
+import {ReactComponent as InfoIcon} from "assets/icon-info.svg";
+import {ReactComponent as SuccessIcon} from "assets/icon-success.svg";
+import {ReactComponent as ErrorIcon} from "assets/icon-cancel.svg";
 
 const toastConfig: ToastOptions = {
   position: "bottom-right",
@@ -9,31 +13,143 @@ const toastConfig: ToastOptions = {
   closeOnClick: true,
 };
 
+export type ToastTypes = "info" | "success" | "error";
+
+export type Options = {
+  title: string;
+  message?: string;
+  hintMessage?: string;
+  hintOnClick?: () => void;
+  buttons?: string[];
+  firstButtonOnClick?: () => void;
+  secondButtonOnClick?: () => void;
+  autoClose?: number | false;
+  icon?: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
+  iconName?: string;
+};
+
 /**
- * Display success message via toast.
+ * Displays a success toast notification.
  *
- * @param content Success message.
+ * @param options An object containing options for the toast notification.
+ * @param options.title The title of the toast notification.
+ * @param options.message The message to display in the toast notification.
+ * @param options.hintMessage An optional hint message to display in the toast notification.
+ * @param options.hintOnClick An optional callback function to execute when the hint message/checkbox is clicked.
+ * @param options.buttons An array of button-labels to display in the toast notification. (for e.g. ["Save", "Cancel"])
+ * @param options.firstButtonOnClick An optional callback function to execute when the first button is clicked.
+ * @param options.secondButtonOnClick An optional callback function to execute when the second button is clicked.
+ * @param options.autoClose The time in milliseconds after which the toast notification should automatically close.
+ * @param options.icon An optional icon to display in the toast notification.
+ * @param options.iconName An optional icon Name. Needs to be set to a any different name than "success", "error", or "info" when an icon is passed aswell.
  */
-function success(content: ReactNode, autoClose: number | false = TOAST_TIMER_DEFAULT) {
-  toast.success(content, {...toastConfig, autoClose});
+function success(options: Options) {
+  const {
+    title,
+    message,
+    hintMessage,
+    hintOnClick,
+    buttons,
+    firstButtonOnClick,
+    secondButtonOnClick,
+    autoClose = TOAST_TIMER_DEFAULT,
+    icon = SuccessIcon,
+    iconName = "success",
+  } = options;
+  return toast(
+    <CustomToast
+      title={title}
+      message={message}
+      hintMessage={hintMessage}
+      hintOnClick={hintOnClick}
+      buttons={buttons}
+      firstButtonOnClick={firstButtonOnClick}
+      secondButtonOnClick={secondButtonOnClick}
+      icon={icon}
+      iconName={iconName}
+      type="success"
+    />,
+    {...toastConfig, autoClose}
+  );
 }
 
 /**
- * Display error message via toast.
+ * Displays an error toast notification.
  *
- * @param content Error message.
+ * @param options An object containing options for the toast notification.
+ * @param options.title The title of the toast notification.
+ * @param options.message The message to display in the toast notification.
+ * @param options.hintMessage An optional hint message to display in the toast notification.
+ * @param options.hintOnClick An optional callback function to execute when the hint message/checkbox is clicked.
+ * @param options.buttons An array of button-labels to display in the toast notification. (for e.g. ["Save", "Cancel"])
+ * @param options.firstButtonOnClick An optional callback function to execute when the first button is clicked.
+ * @param options.secondButtonOnClick An optional callback function to execute when the second button is clicked.
+ * @param options.autoClose The time in milliseconds after which the toast notification should automatically close.
+ * @param options.icon An optional icon to display in the toast notification.
+ * @param options.iconName An optional icon Name. Needs to be set to a any different name than "success", "error", or "info" when an icon is passed aswell.
  */
-function error(content: ReactNode, autoClose: number | false = TOAST_TIMER_DEFAULT) {
-  toast.error(content, {...toastConfig, autoClose});
+function error(options: Options) {
+  const {
+    title,
+    message,
+    hintMessage,
+    hintOnClick,
+    buttons,
+    firstButtonOnClick,
+    secondButtonOnClick,
+    autoClose = TOAST_TIMER_DEFAULT,
+    icon = ErrorIcon,
+    iconName = "error",
+  } = options;
+  toast(
+    <CustomToast
+      title={title}
+      message={message}
+      hintMessage={hintMessage}
+      hintOnClick={hintOnClick}
+      buttons={buttons}
+      firstButtonOnClick={firstButtonOnClick}
+      secondButtonOnClick={secondButtonOnClick}
+      icon={icon}
+      iconName={iconName}
+      type="error"
+    />,
+    {...toastConfig, autoClose}
+  );
 }
 
 /**
- * Display info message via toast.
+ * Displays an info toast notification.
  *
- * @param content Info message.
+ * @param options An object containing options for the toast notification.
+ * @param options.title The title of the toast notification.
+ * @param options.message The message to display in the toast notification.
+ * @param options.hintMessage An optional hint message to display in the toast notification.
+ * @param options.hintOnClick An optional callback function to execute when the hint message/checkbox is clicked.
+ * @param options.buttons An array of button-labels to display in the toast notification. (for e.g. ["Save", "Cancel"])
+ * @param options.firstButtonOnClick An optional callback function to execute when the first button is clicked.
+ * @param options.secondButtonOnClick An optional callback function to execute when the second button is clicked.
+ * @param options.autoClose The time in milliseconds after which the toast notification should automatically close.
+ * @param options.icon An optional icon to display in the toast notification.
+ * @param options.iconName An optional icon Name. Needs to be set to a any different name than "success", "error", or "info" when an icon is passed aswell.
  */
-function info(content: ReactNode, autoClose: number | false = TOAST_TIMER_DEFAULT) {
-  toast.info(content, {...toastConfig, autoClose});
+function info(options: Options) {
+  const {title, message, hintMessage, hintOnClick, buttons, firstButtonOnClick, secondButtonOnClick, autoClose = TOAST_TIMER_DEFAULT, icon = InfoIcon, iconName = "info"} = options;
+  toast(
+    <CustomToast
+      title={title}
+      message={message}
+      hintMessage={hintMessage}
+      hintOnClick={hintOnClick}
+      buttons={buttons}
+      firstButtonOnClick={firstButtonOnClick}
+      secondButtonOnClick={secondButtonOnClick}
+      icon={icon}
+      iconName={iconName}
+      type="info"
+    />,
+    {...toastConfig, autoClose}
+  );
 }
 
 export const Toast = {
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
index 683173bf..a9c17afc 100644
--- a/src/utils/auth.ts
+++ b/src/utils/auth.ts
@@ -20,7 +20,7 @@ const signInAnonymously = async (displayName: string) => {
     }
     return true;
   } catch (err) {
-    Toast.error(i18n.t("Toast.authenticationError"));
+    Toast.error({title: i18n.t("Toast.authenticationError")});
     return null;
   }
 };


Test patch:
diff --git a/src/utils/__tests__/toast.test.tsx b/src/utils/__tests__/toast.test.tsx
new file mode 100644
index 00000000..00932e62
--- /dev/null
+++ b/src/utils/__tests__/toast.test.tsx
@@ -0,0 +1,27 @@
+import {render, screen} from "@testing-library/react";
+import {ToastContainer} from "react-toastify";
+import {Toast, Options} from "utils/Toast";
+
+const testOptions: Options = {
+  title: "Titeliger titel",
+  message: "1 heftige Message mit gaanz super Buchstabän drinnnen",
+  hintMessage: "wennd mogsch zeig ichs nemmer o",
+  hintOnClick: jest.fn(),
+  buttons: ["save", "cancel"],
+  firstButtonOnClick: jest.fn(),
+  secondButtonOnClick: jest.fn(),
+  autoClose: false,
+};
+
+describe("Toast", () => {
+  it("should render the toast", async () => {
+    // arrange
+    render(<ToastContainer />);
+    // act
+    const toastId = Toast.success(testOptions);
+    // assert
+    expect(toastId).toBe("1");
+    const toastElement = await screen.findByText(testOptions.title);
+    expect(toastElement).toBeInTheDocument();
+  });
+});