diff --git a/src/components/FileInput.tsx b/src/components/FileInput.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5c0f9f3168dfd8627d42b66757ccc755801752ed
--- /dev/null
+++ b/src/components/FileInput.tsx
@@ -0,0 +1,66 @@
+import {
+  ChangeEvent,
+  Dispatch,
+  SetStateAction,
+  useEffect,
+  useRef,
+  InputHTMLAttributes,
+  useId,
+} from "react";
+import Typography from "src/components/Typography";
+
+interface Props extends InputHTMLAttributes<HTMLInputElement> {
+  label: string;
+  files: File[];
+  onFilesChange: Dispatch<SetStateAction<File[]>>;
+}
+
+/**
+ * Controlled File Input component
+ * @param props
+ * @constructor
+ */
+const FileInput = (props: Props) => {
+  const inputRef = useRef<HTMLInputElement>(null);
+  const inputId = useId();
+
+  const { files, onFilesChange, label, ...rest } = props;
+
+  /**
+   * Workaround for not being able to set the value directly on <input type="file">
+   *   Using the https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
+   *   API to construct a valid file list; not available in IE!
+   */
+  useEffect(() => {
+    const dataTransfer = new DataTransfer();
+    files.forEach((file: File) => dataTransfer.items.add(file));
+    if (inputRef.current) inputRef.current.files = dataTransfer.files;
+  }, [files]);
+
+  const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
+    const fileList = evt.currentTarget.files;
+    if (!fileList) {
+      onFilesChange([]);
+    } else {
+      onFilesChange(Array.from(fileList));
+    }
+  };
+
+  return (
+    <div className="flex flex-col">
+      <label htmlFor={inputId} className="-mt-3.5">
+        <Typography text={label} variant="paragraph" />
+      </label>
+      <input
+        className="mt-3"
+        id={inputId}
+        ref={inputRef}
+        type="file"
+        onChange={handleChange}
+        {...rest}
+      />
+    </div>
+  );
+};
+
+export default FileInput;
diff --git a/src/index.ts b/src/index.ts
index 5bcf613893aa80f072c055b200db36164169839b..db265d61e63be01ee8688ce762889a702c05834b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,6 +6,7 @@ export { default as Button } from "./components/Button";
 export { default as Checkbox } from "./components/Checkbox";
 export { default as DateInput } from "./components/DateInput";
 export { default as Dropdown } from "./components/Dropdown";
+export { default as FileInput } from "./components/FileInput";
 export { default as Icon } from "./components/Icon";
 export { default as Modal } from "./components/Modal";
 export { default as ProgressBar } from "./components/ProgressBar";
diff --git a/src/stories/FileInput.stories.tsx b/src/stories/FileInput.stories.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b98f2999df13128c920d1e984d004f9da8ee1633
--- /dev/null
+++ b/src/stories/FileInput.stories.tsx
@@ -0,0 +1,75 @@
+import { Meta } from "@storybook/react";
+import { useState } from "react";
+import FileInput from "src/components/FileInput";
+
+const meta = {
+  title: "Components/File Input",
+  component: FileInput,
+} satisfies Meta;
+
+export default meta;
+
+export const SimpleFileInput = () => {
+  const [files, setFiles] = useState<File[]>([]);
+
+  return (
+    <FileInput label="My File Input" files={files} onFilesChange={setFiles} />
+  );
+};
+
+export const UseSelectedFile = () => {
+  const [files, setFiles] = useState<File[]>([]);
+
+  return (
+    <div>
+      <FileInput label="Managed file" files={files} onFilesChange={setFiles} />
+      <div>
+        Selected file:
+        <br />
+        {files.length === 0 ? "No file selected." : files[0].name}
+      </div>
+    </div>
+  );
+};
+
+export const MultipleFilesInput = () => {
+  const [files, setFiles] = useState<File[]>([]);
+
+  return (
+    <div>
+      <FileInput
+        label="Show me them files!"
+        files={files}
+        onFilesChange={setFiles}
+        multiple
+      />
+      <ul>
+        <li>Selected files:</li>
+        {files.length === 0 && <li>No files selected.</li>}
+        {files.map((f) => (
+          <li key={f.name}>{f.name}</li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export const AcceptOnlyTxtFiles = () => {
+  const [files, setFiles] = useState<File[]>([]);
+
+  return (
+    <div>
+      <FileInput
+        label="Text Only!"
+        files={files}
+        onFilesChange={setFiles}
+        accept="text/plain"
+      />
+      <div>
+        Selected file:
+        <br />
+        {files.length === 0 ? "No file selected." : files[0].name}
+      </div>
+    </div>
+  );
+};