diff --git a/scm-ui/ui-components/.storybook/preview-body.html b/scm-ui/ui-components/.storybook/preview-body.html
new file mode 100644
index 0000000000..d2084fbba6
--- /dev/null
+++ b/scm-ui/ui-components/.storybook/preview-body.html
@@ -0,0 +1,2 @@
+
+
diff --git a/scm-ui/ui-components/src/index.ts b/scm-ui/ui-components/src/index.ts
index e039411842..46ccbb48ec 100644
--- a/scm-ui/ui-components/src/index.ts
+++ b/scm-ui/ui-components/src/index.ts
@@ -66,6 +66,7 @@ export * from "./modals";
export * from "./navigation";
export * from "./repos";
export * from "./table";
+export * from "./toast";
export {
File,
diff --git a/scm-ui/ui-components/src/toast/Toast.tsx b/scm-ui/ui-components/src/toast/Toast.tsx
new file mode 100644
index 0000000000..cbbb2c58b6
--- /dev/null
+++ b/scm-ui/ui-components/src/toast/Toast.tsx
@@ -0,0 +1,61 @@
+import React, { FC } from "react";
+import { createPortal } from "react-dom";
+import styled from "styled-components";
+import { getTheme, Themeable, ToastThemeContext, Type } from "./themes";
+
+type Props = {
+ type: Type;
+ title: string;
+};
+
+const rootElement = document.getElementById("toastRoot");
+
+const Container = styled.div`
+ z-index: 99999;
+ position: fixed;
+ padding: 1.5rem;
+ right: 1.5rem;
+ bottom: 1.5rem;
+ color: ${props => props.theme.primary};
+ background-color: ${props => props.theme.secondary};
+ max-width: 18rem;
+ font-size: 0.75rem;
+ border-radius: 5px;
+ animation: 0.5s slide-up;
+
+ & > p {
+ margin-bottom: 0.5rem;
+ }
+
+ @keyframes slide-up {
+ from {
+ bottom: -10rem;
+ }
+ to {
+ bottom: 1.5rem;
+ }
+ }
+`;
+
+const Title = styled.h1`
+ margin-bottom: 0.25rem;
+ font-weight: bold;
+`;
+
+const Toast: FC = ({ children, title, type }) => {
+ if (!rootElement) {
+ throw new Error("could not find toast container #toastRoot");
+ }
+
+ const theme = getTheme(type);
+ const content = (
+
+ {title}
+ {children}
+
+ );
+
+ return createPortal(content, rootElement);
+};
+
+export default Toast;
diff --git a/scm-ui/ui-components/src/toast/ToastButton.tsx b/scm-ui/ui-components/src/toast/ToastButton.tsx
new file mode 100644
index 0000000000..76ae63cd73
--- /dev/null
+++ b/scm-ui/ui-components/src/toast/ToastButton.tsx
@@ -0,0 +1,37 @@
+import React, { FC, useContext } from "react";
+import { ToastThemeContext, Themeable } from "./themes";
+import styled from "styled-components";
+
+type Props = {
+ icon?: string;
+};
+
+const ThemedButton = styled.div.attrs(props => ({
+ className: "button"
+}))`
+ color: ${props => props.theme.primary};
+ border-color: ${props => props.theme.primary};
+ background-color: ${props => props.theme.secondary};
+ font-size: 0.75rem;
+
+ &:hover {
+ color: ${props => props.theme.primary};
+ border-color: ${props => props.theme.tertiary};
+ background-color: ${props => props.theme.tertiary};
+ }
+`;
+
+const ToastButtonIcon = styled.i`
+ margin-right: 0.25rem;
+`;
+
+const ToastButton: FC = ({ icon, children }) => {
+ const theme = useContext(ToastThemeContext);
+ return (
+
+ {icon && } {children}
+
+ );
+};
+
+export default ToastButton;
diff --git a/scm-ui/ui-components/src/toast/ToastButtons.tsx b/scm-ui/ui-components/src/toast/ToastButtons.tsx
new file mode 100644
index 0000000000..4444a2e14a
--- /dev/null
+++ b/scm-ui/ui-components/src/toast/ToastButtons.tsx
@@ -0,0 +1,20 @@
+import React, { FC } from "react";
+import styled from "styled-components";
+
+const Buttons = styled.div`
+ display: flex;
+ padding-top: 0.5rem;
+ width: 100%;
+
+ & > * {
+ flex-grow: 1;
+ }
+
+ & > *:not(:last-child) {
+ margin-right: 0.5rem;
+ }
+`;
+
+const ToastButtons: FC = ({ children }) => {children};
+
+export default ToastButtons;
diff --git a/scm-ui/ui-components/src/toast/index.stories.tsx b/scm-ui/ui-components/src/toast/index.stories.tsx
new file mode 100644
index 0000000000..af17964d2e
--- /dev/null
+++ b/scm-ui/ui-components/src/toast/index.stories.tsx
@@ -0,0 +1,42 @@
+import React, { useState } from "react";
+import { storiesOf } from "@storybook/react";
+import Toast from "./Toast";
+import ToastButtons from "./ToastButtons";
+import ToastButton from "./ToastButton";
+import { types } from "./themes";
+
+const toastStories = storiesOf("Toast", module);
+
+const AnimatedToast = () => (
+
+ Awesome animated Toast
+
+);
+
+const Animator = () => {
+ const [display, setDisplay] = useState(false);
+
+ return (
+
+ {display &&
}
+
+
+ );
+};
+
+toastStories.add("Open/Close", () => );
+
+types.forEach(type => {
+ toastStories.add(type.charAt(0).toUpperCase() + type.slice(1), () => (
+
+ The underlying Pull-Request has changed. Press reload to see the changes.
+ Warning: Non saved modification will be lost.
+
+ Reload
+ Ignore
+
+
+ ));
+});
diff --git a/scm-ui/ui-components/src/toast/index.ts b/scm-ui/ui-components/src/toast/index.ts
new file mode 100644
index 0000000000..f7b618a3ac
--- /dev/null
+++ b/scm-ui/ui-components/src/toast/index.ts
@@ -0,0 +1,3 @@
+export { default as Toast } from "./Toast";
+export { default as ToastButton } from "./ToastButton";
+export { default as ToastButtons } from "./ToastButtons";
diff --git a/scm-ui/ui-components/src/toast/themes.ts b/scm-ui/ui-components/src/toast/themes.ts
new file mode 100644
index 0000000000..1b2744870c
--- /dev/null
+++ b/scm-ui/ui-components/src/toast/themes.ts
@@ -0,0 +1,53 @@
+import * as React from "react";
+
+export type ToastTheme = {
+ primary: string;
+ secondary: string;
+ tertiary: string;
+};
+
+export type Themeable = {
+ theme: ToastTheme;
+};
+
+export type Type = "info" | "primary" | "success" | "warning" | "danger";
+
+export const types: Type[] = ["info", "primary", "success", "warning", "danger"];
+
+const themes: { [name in Type]: ToastTheme } = {
+ info: {
+ primary: "#363636",
+ secondary: "#99d8f3",
+ tertiary: "white"
+ },
+ primary: {
+ primary: "#363636",
+ secondary: "#7fe8ef",
+ tertiary: "white"
+ },
+ success: {
+ primary: "#363636",
+ secondary: "#7fe3cd",
+ tertiary: "white"
+ },
+ warning: {
+ primary: "#905515",
+ secondary: "#ffeeab",
+ tertiary: "white"
+ },
+ danger: {
+ primary: "#363636",
+ secondary: "#ff9baf",
+ tertiary: "white"
+ }
+};
+
+export const getTheme = (name: Type) => {
+ const theme = themes[name];
+ if (!theme) {
+ throw new Error(`could not find theme with name ${name}`);
+ }
+ return theme;
+};
+
+export const ToastThemeContext = React.createContext(themes.warning);
diff --git a/scm-ui/ui-webapp/public/index.mustache b/scm-ui/ui-webapp/public/index.mustache
index d94bc552ee..86193387ce 100644
--- a/scm-ui/ui-webapp/public/index.mustache
+++ b/scm-ui/ui-webapp/public/index.mustache
@@ -34,6 +34,7 @@
+