diff --git a/front/packages/primitives/src/snackbar.tsx b/front/packages/primitives/src/snackbar.tsx
new file mode 100644
index 00000000..81216d14
--- /dev/null
+++ b/front/packages/primitives/src/snackbar.tsx
@@ -0,0 +1,114 @@
+/*
+ * Kyoo - A portable and vast media library solution.
+ * Copyright (c) Kyoo.
+ *
+ * See AUTHORS.md and LICENSE file in the project root for full license information.
+ *
+ * Kyoo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * Kyoo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Kyoo. If not, see .
+ */
+
+import { usePortal } from "@gorhom/portal";
+import { ReactElement, createContext, useCallback, useContext, useRef } from "react";
+import { SwitchVariant } from "./themes";
+import { P } from "@expo/html-elements";
+import { View } from "react-native";
+import { min, percent, px } from "yoshiki/native";
+import { ts } from "./utils";
+import { Button } from "./button";
+import { imageBorderRadius } from "./constants";
+
+export type Snackbar = {
+ key?: string;
+ label: string;
+ duration: number;
+ actions: Action[];
+};
+
+export type Action = {
+ label: string;
+ icon: ReactElement;
+ action: () => void;
+};
+
+const SnackbarContext = createContext<(snackbar: Snackbar) => void>(null!);
+
+export const SnackbarProvider = ({ children }: { children: ReactElement | ReactElement[] }) => {
+ const { addPortal, removePortal } = usePortal();
+ const snackbars = useRef([]);
+ const timeout = useRef(null);
+
+ const createSnackbar = useCallback(
+ (snackbar: Snackbar) => {
+ if (snackbar.key) snackbars.current = snackbars.current.filter((x) => snackbar.key !== x.key);
+ snackbars.current.unshift(snackbar);
+
+ if (timeout.current) return;
+ const updatePortal = () => {
+ const top = snackbars.current.pop();
+ if (!top) {
+ timeout.current = null;
+ return;
+ }
+
+ addPortal("snackbar", );
+ timeout.current = setTimeout(() => {
+ removePortal("snackbar");
+ updatePortal();
+ }, snackbar.duration * 1000);
+ };
+ updatePortal();
+ },
+ [addPortal, removePortal],
+ );
+
+ return {children};
+};
+
+export const useSnackbar = () => {
+ return useContext(SnackbarContext);
+};
+
+const Snackbar = ({ label, actions }: Snackbar) => {
+ // TODO: take navbar height into account for setting the position of the snacbar.
+ return (
+
+ {({ css }) => (
+
+ theme.background,
+ maxWidth: { sm: percent(75), md: percent(45), lg: px(500) },
+ margin: ts(1),
+ padding: ts(1),
+ flexDirection: "row",
+ borderRadius: imageBorderRadius,
+ })}
+ >
+