diff --git a/src/components/file/file.mdx b/src/components/file/file.mdx
new file mode 100644
index 0000000000..8194dc1bbe
--- /dev/null
+++ b/src/components/file/file.mdx
@@ -0,0 +1,13 @@
+---
+name: File
+route: /file
+menu: Components
+---
+
+# File
+
+
+
+## Example
+
+
diff --git a/src/components/file/file.tsx b/src/components/file/file.tsx
new file mode 100644
index 0000000000..af205a12fc
--- /dev/null
+++ b/src/components/file/file.tsx
@@ -0,0 +1,142 @@
+import { MDCTextField } from '@lime-material/textfield';
+import { Component, Element, Event, EventEmitter, Prop } from '@stencil/core';
+import { createRandomString } from '../../util/random-string';
+import { Chip } from '../chip-set/chip';
+
+const CHIP_SET_TAG_NAME = 'limel-chip-set';
+const DEFAULT_FILE_CHIP: Chip = {
+ id: null,
+ text: null,
+ removable: true,
+ icon: 'note',
+ iconColor: 'var(--lime-light-grey)',
+};
+
+@Component({
+ tag: 'limel-file',
+ shadow: true,
+})
+export class File {
+ @Prop()
+ public value: Chip;
+
+ @Prop({ reflectToAttr: true })
+ public label: string;
+
+ @Prop({ reflectToAttr: true })
+ public required: boolean = false;
+
+ /**
+ * True if the input should be disabled
+ */
+ @Prop({ reflectToAttr: true })
+ public disabled: boolean = false;
+
+ /**
+ * Dispatched when a file is selected/deselected
+ */
+ @Event()
+ private change: EventEmitter;
+
+ @Element()
+ private element: HTMLElement;
+
+ private fileInput: HTMLInputElement;
+ private fileInputId = createRandomString();
+ private chipSet;
+ private mdcTextField;
+
+ constructor() {
+ this.handleFileSelection = this.handleFileSelection.bind(this);
+ this.handleFileChange = this.handleFileChange.bind(this);
+ this.handleChipSetChange = this.handleChipSetChange.bind(this);
+ this.handleFileDrop = this.handleFileDrop.bind(this);
+ }
+
+ public componentDidLoad() {
+ this.fileInput = this.element.shadowRoot.getElementById(
+ this.fileInputId
+ ) as HTMLInputElement;
+ this.chipSet = this.element.shadowRoot.querySelector(CHIP_SET_TAG_NAME);
+ this.mdcTextField = new MDCTextField(
+ this.chipSet.shadowRoot.querySelector('.mdc-text-field')
+ );
+ }
+
+ public componentDidUnload() {
+ if (this.mdcTextField) {
+ this.mdcTextField.destroy();
+ }
+ }
+
+ public render() {
+ const chipArray = this.value
+ ? [{ ...DEFAULT_FILE_CHIP, ...this.value }]
+ : [];
+ return [
+ ,
+ ,
+ ];
+ }
+
+ private handleFileSelection(event) {
+ event.stopPropagation();
+ if (!this.value) {
+ this.fileInput.click();
+ }
+ }
+
+ private handleFileChange(event) {
+ if (this.fileInput.files.length > 0) {
+ event.stopPropagation();
+ this.handleFile(this.fileInput.files[0]);
+ }
+ }
+
+ private handleFile(file) {
+ this.change.emit(file);
+ this.chipSet.blur();
+ this.mdcTextField.valid = true;
+ }
+
+ private handleChipSetChange(event) {
+ event.stopPropagation();
+ const chip = !event.detail.length ? event.detail[0] : null;
+ this.chipSet.blur();
+ if (!chip) {
+ this.fileInput.value = '';
+ this.change.emit(chip);
+ if (this.required) {
+ this.mdcTextField.valid = false;
+ }
+ }
+ }
+
+ private handleFileDrop(event) {
+ this.preventAndStop(event);
+ const dataTransfer = event.dataTransfer;
+ this.handleFile(dataTransfer.files[0]);
+ }
+
+ private preventAndStop(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+}
diff --git a/src/examples/file/file.tsx b/src/examples/file/file.tsx
new file mode 100644
index 0000000000..721983e6fe
--- /dev/null
+++ b/src/examples/file/file.tsx
@@ -0,0 +1,39 @@
+import { Component, State } from '@stencil/core';
+import { Chip } from '../../components/chip-set/chip';
+
+@Component({
+ tag: 'limel-example-file',
+ shadow: true,
+})
+export class FileExample {
+ @State()
+ private value: Chip = { id: 'bla', text: 'bla.jpg' };
+
+ @State()
+ private required = false;
+
+ public render() {
+ return [
+ {
+ this.required = !this.required;
+ }}
+ />,
+ ,
+ ];
+ }
+
+ private handleChange(event) {
+ console.log('onChange', event.detail);
+ this.value = event.detail
+ ? { id: event.detail.name, text: event.detail.name }
+ : null;
+ }
+}