diff --git a/ui/src/components/ErrorAlert.tsx b/ui/src/components/ErrorAlert.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8dbcd41a91686c132bb6be0f1c65cab9162c1fff
--- /dev/null
+++ b/ui/src/components/ErrorAlert.tsx
@@ -0,0 +1,18 @@
+import {Paper} from "@material-ui/core";
+
+export interface ErrorAlertProps {
+    severity: string,
+    error: unknown,
+}
+
+export function ErrorAlert({severity, error}: ErrorAlertProps) {
+    if (!error) {
+        return null;
+    }
+
+    return (
+        <Paper variant="outlined" color={severity}>
+            <strong>Error</strong>: {"" + error}
+        </Paper>
+    )
+}
diff --git a/ui/src/components/ErrorContext.tsx b/ui/src/components/ErrorContext.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..988ae5f0aa1b880011fd8cb1ba71c76bd51c35c6
--- /dev/null
+++ b/ui/src/components/ErrorContext.tsx
@@ -0,0 +1,42 @@
+import {createContext, FC, FunctionComponent, PropsWithChildren, useCallback, useContext, useState} from "react";
+import {createPortal} from "react-dom";
+
+type ChildrenComponent = FunctionComponent<PropsWithChildren<{}>>;
+
+const ErrorContext = createContext<ChildrenComponent | null>(null);
+
+export function useErrorDisplay(): [FC, ChildrenComponent] {
+    const [ref, setRef] = useState<HTMLDivElement | null>(null);
+    const errorDisplay = useCallback(
+        () => (
+            <div ref={setRef}/>
+        ),
+        []
+    );
+    const errorRenderer: ChildrenComponent = useCallback(
+        ({children}: PropsWithChildren<{}>) => ref && createPortal(
+            children,
+            ref
+        ),
+        [ref]
+    );
+    const errorWrapper = useCallback(
+        ({children}: PropsWithChildren<{}>) => (
+            <ErrorContext.Provider value={errorRenderer}>
+                {children}
+            </ErrorContext.Provider>
+        ),
+        [errorRenderer]
+    );
+
+    return [errorDisplay, errorWrapper];
+}
+
+export const ErrorPortal: ChildrenComponent = (props: PropsWithChildren<{}>) => {
+    const errorRenderer = useContext(ErrorContext);
+    if (!errorRenderer) {
+        return null;
+    }
+
+    return errorRenderer(props);
+}
diff --git a/ui/src/components/UploadView.tsx b/ui/src/components/UploadView.tsx
index eae8360c5c2b5cec3c60167c74e26944de17b708..8c998007f848112a61c4c5132e1325614e24a402 100644
--- a/ui/src/components/UploadView.tsx
+++ b/ui/src/components/UploadView.tsx
@@ -1,14 +1,22 @@
 import {useUploadImage} from "../api/useUploadImage";
+import {ErrorPortal} from "./ErrorContext";
+import {ErrorAlert} from "./ErrorAlert";
+import {LinearProgress} from "@material-ui/core";
 
 export default function UploadView() {
-    const {mutate: upload, error: uploadError, isLoading: uploadLoading} = useUploadImage();
+    const {mutate: upload, error, isLoading} = useUploadImage();
 
     return (
         <div>
-            <pre>Error: {JSON.stringify(uploadError, null, 2)}</pre>
-            <pre>Loading: {JSON.stringify(uploadLoading, null, 2)}</pre>
+            {isLoading && (
+                <LinearProgress/>
+            )}
+            <ErrorPortal>
+                <ErrorAlert severity="error" error={error}/>
+            </ErrorPortal>
             <input
                 type="file"
+                disabled={isLoading}
                 onChange={async ({target}) => {
                     if (target.files) {
                         await upload(target.files)