diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index c33b638b5a6520cdc98cdda4fd2a0318da725806..21ce1418210dae45ee1ad7a276fa03862848fa89 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -19,6 +19,12 @@ window.FileList = {
 	breadcrumb: null,
 	initialized: false,
 
+	// number of files per page
+	pageSize: 20,
+	// zero based page number
+	pageNumber: 0,
+	totalPages: 0,
+
 	/**
 	 * Initialize the file list and its components
 	 */
@@ -62,6 +68,16 @@ window.FileList = {
 		}
 	},
 
+	_onScroll: function(e) {
+		if (this.pageNumber + 1 >= this.totalPages) {
+			return;
+		}
+		var target = $(document);
+		if ($(window).scrollTop() + $(window).height() > $(document).height() - 20) {
+			this._nextPage(true);
+		}
+	},
+
 	/**
 	 * Event handler when dropping on a breadcrumb
 	 */
@@ -129,6 +145,42 @@ window.FileList = {
 		// use filterAttr to avoid escaping issues
 		return this.$fileList.find('tr').filterAttr('data-file', fileName);
 	},
+
+	/**
+	 * Appends the next page of files into the table
+	 * @param animate true to animate the new elements
+	 */
+	_nextPage: function(animate) {
+		var tr, index, count = this.pageSize,
+			newTrs = [];
+
+		if (this.pageNumber + 1 >= this.totalPages) {
+			return;
+		}
+
+		this.pageNumber++;
+		index = this.pageNumber * this.pageSize;
+
+		while (count > 0 && index < this.files.length) {
+			tr = this.add(this.files[index], {updateSummary: false});
+			if (animate) {
+				tr.addClass('appear transparent'); // TODO
+				newTrs.push(tr);
+			}
+			index++;
+			count--;
+		}
+
+		if (animate) {
+			// defer, for animation
+			window.setTimeout(function() {
+				for (var i = 0; i < newTrs.length; i++ ) {
+					newTrs[i].removeClass('transparent');
+				}
+			}, 0);
+		}
+	},
+
 	/**
 	 * Sets the files to be displayed in the list.
 	 * This operation will rerender the list and update the summary.
@@ -136,14 +188,15 @@ window.FileList = {
 	 */
 	setFiles:function(filesArray) {
 		// detach to make adding multiple rows faster
-		this.$fileList.detach();
+		this.files = filesArray;
+		this.pageNumber = -1;
+		this.totalPages = Math.ceil(filesArray.length / this.pageSize);
 
+		this.$fileList.detach();
 		this.$fileList.empty();
 
-		this.isEmpty = filesArray.length === 0;
-		for (var i = 0; i < filesArray.length; i++) {
-			this.add(filesArray[i], {updateSummary: false});
-		}
+		this.isEmpty = this.files.length === 0;
+		this._nextPage();
 
 		this.$el.find('thead').after(this.$fileList);
 
@@ -1255,6 +1308,8 @@ $(document).ready(function() {
 		}
 	};
 
+	$(window).scroll(function(e) {FileList._onScroll(e);});
+
 	var dir = parseCurrentDirFromUrl();
 	// trigger ajax load, deferred to let sub-apps do their overrides first
 	setTimeout(function() {
diff --git a/core/css/apps.css b/core/css/apps.css
index a8dfc5b7ed12881a795c1a1e144cb715cb66e5dc..a0bb262854dcbbb4ca97b03cc911021649cd5aa0 100644
--- a/core/css/apps.css
+++ b/core/css/apps.css
@@ -243,7 +243,6 @@ button.loading {
 	padding-right: 30px;
 }
 
-
 /* general styles for the content area */
 .section {
 	display: block;
@@ -264,3 +263,14 @@ button.loading {
 	vertical-align: -2px;
 	margin-right: 4px;
 }
+.appear {
+	opacity: 1;
+	transition: opacity 500ms ease 0s;
+	-moz-transition: opacity 500ms ease 0s;
+	-ms-transition: opacity 500ms ease 0s;
+	-o-transition: opacity 500ms ease 0s;
+	-webkit-transition: opacity 500ms ease 0s;
+}
+.appear.transparent {
+	opacity: 0;
+}