diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html
index e396195075a7cf9ebd660fcdf76133c4a029d9e6..eaf87bd581d33395a14c0c6dc65f04544ce01960 100644
--- a/vipra-ui/app/html/index.html
+++ b/vipra-ui/app/html/index.html
@@ -29,7 +29,10 @@
   </div>
   <div class="row row-spaced">
     <div class="col-md-12">
-      <input type="text" class="form-control input-lg" placeholder="Search..." ng-model="search" ng-model-options="{debounce:500}">
+      <div class="form-group has-feedback">
+        <input type="text" class="form-control input-lg" placeholder="Search..." ng-model="search" ng-model-options="{debounce:500}" id="searchBox">
+        <i class="form-control-feedback glyphicon glyphicon-search text-muted"></i>
+      </div>
     </div>
   </div>
   <div class="row row-spaced">
diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html
index f52e82571551a8ba9abc2ede86184c4d1104d415..80951609cf22fc7ffc86b08b6cff780287604d0c 100644
--- a/vipra-ui/app/index.html
+++ b/vipra-ui/app/index.html
@@ -65,11 +65,15 @@
             <a ui-sref="topics">Topics</a>
           </li>
         </ul>
+        <form class="navbar-form navbar-left" role="search" ng-hide="$state.current.name === 'index'">
+          <div class="form-group has-feedback">
+            <input type="text" class="form-control" placeholder="Search..." ng-model="rootModels.search" ng-enter="menubarSearch(rootModels.search)" id="menuSearchBox">
+            <i class="form-control-feedback glyphicon glyphicon-search text-muted"></i>
+          </div>
+        </form>
         <ul class="nav navbar-nav navbar-right">
-          <li>
-            <a data-toggle="modal" data-target="#topicModelModal">
-              Models
-            </a>
+          <li ng-class="{'text-italic':rootModels.topicModel}">
+            <a data-toggle="modal" data-target="#topicModelModal" ng-bind-template="{{rootModels.topicModel ? rootModels.topicModel.id : 'Models'}}" ng-attr-title="{{rootModels.topicModel.modelConfig.description}}"></a>
           </li>
           <li ui-sref-active="active">
             <a ui-sref="about">
@@ -81,7 +85,7 @@
     </div>
   </nav>
   <div class="main" ui-view ng-cloak></div>
-  <div id="topicModelModal" class="modal fade" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false">
+  <div id="topicModelModal" class="modal" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false">
     <div class="modal-dialog modal-lg">
       <div class="modal-content">
         <div class="modal-header">
@@ -90,7 +94,7 @@
         </div>
         <div class="modal-body">
           <ul class="list-group" ng-show="topicModels.length">
-            <button type="button" class="list-group-item" ng-repeat="topicModel in topicModels" ng-click="changeTopicModel(topicModel)" ng-class="{active:rootModels.topicModel.id===topicModel.id}">
+            <button type="button" class="list-group-item topic-model" ng-repeat="topicModel in topicModels" ng-click="changeTopicModel(topicModel)" ng-class="{'active selected-model':rootModels.topicModel.id===topicModel.id}">
               <span class="badge" ng-bind="topicModel.articleCount" ng-show="topicModel.articleCount" ng-attr-title="{{topicModel.articleCount + ' article(s)'}}"></span>
               <span class="badge" ng-bind="topicModel.topicCount" ng-show="topicModel.topicCount" ng-attr-title="{{topicModel.topicCount + ' topic(s)'}}"></span>
               <span ng-bind="topicModel.id"></span>
diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js
index fe66def8df8c2dfd03fe74a459b7f5d89b9ff3f6..d67c058af5ca1d70c4100e6e1de4cc3c9d6b2035 100644
--- a/vipra-ui/app/js/app.js
+++ b/vipra-ui/app/js/app.js
@@ -2,7 +2,7 @@
  * Vipra Application
  * Main application file
  ******************************************************************************/
-/* globals angular, $ */
+/* globals angular */
 (function() {
 
   "use strict";
@@ -11,6 +11,7 @@
     'ngResource',
     'ngSanitize',
     'ui.router',
+    'cfp.hotkeys',
     'nya.bootstrap.select',
     'vipra.controllers',
     'vipra.directives',
@@ -27,9 +28,10 @@
       // states
 
       $stateProvider.state('index', {
-        url: '/',
+        url: '/?q',
         templateUrl: 'html/index.html',
-        controller: 'IndexController'
+        controller: 'IndexController',
+        reloadOnSearch: false
       });
 
       $stateProvider.state('about', {
@@ -53,9 +55,10 @@
       // states: articles
 
       $stateProvider.state('articles', {
-        url: '/articles',
+        url: '/articles?p',
         templateUrl: 'html/articles/index.html',
-        controller: 'ArticlesIndexController'
+        controller: 'ArticlesIndexController',
+        reloadOnSearch: false
       });
 
       $stateProvider.state('articles.show', {
@@ -67,9 +70,10 @@
       // states: topics
 
       $stateProvider.state('topics', {
-        url: '/topics',
+        url: '/topics?p',
         templateUrl: 'html/topics/index.html',
-        controller: 'TopicsIndexController'
+        controller: 'TopicsIndexController',
+        reloadOnSearch: false
       });
 
       $stateProvider.state('topics.show', {
diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js
index aae45f1142d6e080ed40330138b6317f8456b4c5..e2493affbc3117036ddc424c42f502a4a4a5530c 100644
--- a/vipra-ui/app/js/controllers.js
+++ b/vipra-ui/app/js/controllers.js
@@ -7,30 +7,17 @@
 
   "use strict";
 
-  var app = angular.module('vipra.controllers', [
-    'ui.router',
-    'vipra.factories'
-  ]);
+  var app = angular.module('vipra.controllers', []);
 
-  app.controller('RootController', ['$scope', '$state', 'TopicModelFactory',
-    function($scope, $state, TopicModelFactory) {
+  app.controller('RootController', ['$scope', '$state', '$location', 'hotkeys', 'TopicModelFactory',
+    function($scope, $state, $location, hotkeys, TopicModelFactory) {
 
       $scope.$state = $state;
       $scope.rootModels = {
-        topicModel: null
+        topicModel: null,
+        search: null
       };
 
-      if (localStorage.topicModel) {
-        try {
-          var topicModel = JSON.parse(localStorage.topicModel);
-          TopicModelFactory.get({
-            id: topicModel.id
-          }, function(data) {
-            $scope.rootModels.topicModel = data;
-          });
-        } catch (e) {}
-      }
-
       TopicModelFactory.query({
         fields: '_all'
       }, function(data) {
@@ -40,29 +27,90 @@
       });
 
       $scope.chooseTopicModel = function() {
+        $scope.rootModels.topicModelModalOpen = true;
         $('#topicModelModal').modal();
+        if ($scope.rootModels.topicModel)
+          $('.selected-model').focus();
+        else
+          $('.topic-model').first().focus();
       };
 
       $scope.changeTopicModel = function(topicModel) {
         $scope.rootModels.topicModel = topicModel;
-        localStorage.topicModel = JSON.stringify(topicModel);
         $('#topicModelModal').modal('hide');
       };
 
+      $scope.menubarSearch = function(query) {
+        $state.transitionTo('index', {
+          q: query
+        });
+      };
+
+      $scope.$on('$stateChangeSuccess', function() {
+        $scope.rootModels.search = null;
+      });
+
+      hotkeys.add({
+        combo: 's',
+        description: 'Search for articles',
+        callback: function($event) {
+          if ($event.stopPropagation) $event.stopPropagation();
+          if ($event.preventDefault) $event.preventDefault();
+          if ($state.current.name === 'index')
+            $('#searchBox').focus();
+          else
+            $('#menuSearchBox').focus();
+        }
+      });
+
+      hotkeys.add({
+        combo: 'e',
+        description: 'Go to explorer',
+        callback: function() {
+          if ($state.current.name !== 'explorer')
+            $state.transitionTo('explorer');
+        }
+      });
+
+      hotkeys.add({
+        combo: 'a',
+        description: 'Go to articles',
+        callback: function() {
+          if ($state.current.name !== 'articles')
+            $state.transitionTo('articles');
+        }
+      });
+
+      hotkeys.add({
+        combo: 't',
+        description: 'Go to topics',
+        callback: function() {
+          if ($state.current.name !== 'topics')
+            $state.transitionTo('topics');
+        }
+      });
+
+      hotkeys.add({
+        combo: 'm',
+        description: 'Choose a topic model',
+        callback: function() {
+          $scope.chooseTopicModel();
+        }
+      });
     }
   ]);
 
   /**
    * Index controller
    */
-  app.controller('IndexController', ['$scope', '$location', 'ArticleFactory', 'TopicFactory', 'SearchFactory',
-    function($scope, $location, ArticleFactory, TopicFactory, SearchFactory) {
+  app.controller('IndexController', ['$scope', '$stateParams', '$location', 'ArticleFactory', 'TopicFactory', 'SearchFactory',
+    function($scope, $stateParams, $location, ArticleFactory, TopicFactory, SearchFactory) {
 
       // page was reloaded, choose topic model
       if (!$scope.rootModels.topicModel)
         $scope.chooseTopicModel();
 
-      $scope.search = $location.search().query;
+      $scope.search = $stateParams.q || $scope.search;
 
       $scope.$watch('rootModels.topicModel', function() {
         if (!$scope.rootModels.topicModel) return;
@@ -90,25 +138,29 @@
 
       $scope.$watchGroup(['search', 'rootModels.topicModel'], function() {
         if ($scope.search && $scope.rootModels.topicModel) {
-          $location.search('query', $scope.search);
-          $scope.searching = true;
-
-          SearchFactory.query({
-            topicModel: $scope.rootModels.topicModel.id,
-            limit: 10,
-            query: $scope.search
-          }, function(data) {
-            $scope.searching = false;
-            $scope.searchResults = data;
-          }, function(err) {
-            $scope.errors = err;
-          });
+          $location.search('q', $scope.search);
+          $scope.goSearch();
         } else {
-          $location.search('query', null);
+          $location.search('q', null);
           $scope.searchResults = [];
         }
       });
 
+      $scope.goSearch = function() {
+        $scope.searching = true;
+
+        SearchFactory.query({
+          topicModel: $scope.rootModels.topicModel.id,
+          limit: 10,
+          query: $scope.search
+        }, function(data) {
+          $scope.searching = false;
+          $scope.searchResults = data;
+        }, function(err) {
+          $scope.errors = err;
+        });
+      };
+
     }
   ]);
 
@@ -517,8 +569,8 @@
   /**
    * Article Index route
    */
-  app.controller('ArticlesIndexController', ['$scope', '$state', '$location', 'ArticleFactory',
-    function($scope, $state, $location, ArticleFactory) {
+  app.controller('ArticlesIndexController', ['$scope', '$state', 'ArticleFactory',
+    function($scope, $state, ArticleFactory) {
 
       // page was reloaded, choose topic model
       if (!$scope.rootModels.topicModel && $state.current.name === 'articles')
@@ -527,7 +579,7 @@
       $scope.articlesIndexModels = {
         sortkey: 'date',
         sortdir: true,
-        page: Math.max($location.search().page || 1, 1),
+        page: 1,
         limit: 100
       };
 
@@ -627,8 +679,8 @@
   /**
    * Topic Index route
    */
-  app.controller('TopicsIndexController', ['$scope', '$state', '$location', 'TopicFactory',
-    function($scope, $state, $location, TopicFactory) {
+  app.controller('TopicsIndexController', ['$scope', '$state', 'TopicFactory',
+    function($scope, $state, TopicFactory) {
 
       // page was reloaded, choose topic model
       if (!$scope.rootModels.topicModel && $state.current.name === 'topics')
@@ -637,7 +689,7 @@
       $scope.topicsIndexModels = {
         sortkey: 'name',
         sortdir: true,
-        page: Math.max($location.search().page || 1, 1),
+        page: 1,
         limit: 100
       };
 
@@ -832,9 +884,6 @@
   app.controller('PaginationController', ['$scope',
     function($scope) {
 
-      if (!$scope.page)
-        $scope.page = 1;
-
       $scope.calculatePages = function() {
         var pages = [],
           max = Math.ceil($scope.total / $scope.limit * 1.0),
diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js
index 0757812a9fdca5848a11dfa697feccb4ef3c08be..5638c5691a07065ad67920c8746cbb3d19aa52be 100644
--- a/vipra-ui/app/js/directives.js
+++ b/vipra-ui/app/js/directives.js
@@ -234,7 +234,19 @@
         }, 10);
       }
     };
-
   }]);
 
+  app.directive('ngEnter', function() {
+    return function($scope, $elem, $attrs) {
+      $elem.bind("keydown keypress", function(event) {
+        if (event.which === 13) {
+          $scope.$apply(function() {
+            $scope.$eval($attrs.ngEnter);
+          });
+          event.preventDefault();
+        }
+      });
+    };
+  });
+
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js
index e4076cbd99c5b138fade37f6f3b66c74c1cafdfe..31f9b64b4016ec220007ca70220b2990cc375541 100644
--- a/vipra-ui/app/js/factories.js
+++ b/vipra-ui/app/js/factories.js
@@ -9,41 +9,67 @@
 
   var app = angular.module('vipra.factories', []);
 
-  app.factory('ArticleFactory', ['$resource', function($resource) {
-    return $resource(Vipra.config.restUrl + '/articles/:id', {}, {
-      similar: {
-        isArray: true,
-        url: Vipra.config.restUrl + '/articles/:id/similar'
-      }
-    });
+  app.factory('$myResource', ['$resource', function($resource) {
+    return function(url, paramDefaults, actions) {
+      actions = angular.merge({}, {
+        get: {
+          method: 'GET',
+          cache: true
+        },
+        save: {
+          method: 'POST'
+        },
+        query: {
+          method: 'GET',
+          isArray: true,
+          cache: true
+        },
+        remove: {
+          method: 'DELETE'
+        },
+        delete: {
+          method: 'DELETE'
+        },
+        update: {
+          method: 'PUT'
+        }
+      }, actions);
+      return $resource(url, paramDefaults, actions);
+    };
   }]);
 
-  app.factory('TopicFactory', ['$resource', function($resource) {
-    return $resource(Vipra.config.restUrl + '/topics/:id', {}, {
-      update: {
-        method: 'PUT'
-      },
+  app.factory('ArticleFactory', ['$myResource', function($myResource) {
+    return $myResource(Vipra.config.restUrl + '/articles/:id');
+  }]);
+
+  app.factory('TopicFactory', ['$myResource', function($myResource) {
+    return $myResource(Vipra.config.restUrl + '/topics/:id', {}, {
       articles: {
+        cache: true,
         isArray: true,
         url: Vipra.config.restUrl + '/topics/:id/articles'
       }
     });
   }]);
 
-  app.factory('SequenceFactory', ['$resource', function($resource) {
-    return $resource(Vipra.config.restUrl + '/sequences/:id');
+  app.factory('SequenceFactory', ['$myResource', function($myResource) {
+    return $myResource(Vipra.config.restUrl + '/sequences/:id');
   }]);
 
-  app.factory('SearchFactory', ['$resource', function($resource) {
-    return $resource(Vipra.config.restUrl + '/search');
+  app.factory('SearchFactory', ['$myResource', function($myResource) {
+    return $myResource(Vipra.config.restUrl + '/search', {}, {
+      query: {
+        cache: false
+      }
+    });
   }]);
 
-  app.factory('InfoFactory', ['$resource', function($resource) {
-    return $resource(Vipra.config.restUrl + '/info');
+  app.factory('InfoFactory', ['$myResource', function($myResource) {
+    return $myResource(Vipra.config.restUrl + '/info');
   }]);
 
-  app.factory('TopicModelFactory', ['$resource', function($resource) {
-    return $resource(Vipra.config.restUrl + '/topicmodels/:id');
+  app.factory('TopicModelFactory', ['$myResource', function($myResource) {
+    return $myResource(Vipra.config.restUrl + '/topicmodels/:id');
   }]);
 
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less
index 6feb0298cfd1f60cdcb2253bb55efd71c904d759..28b0aceefed40d77770937a8afe4c9f33ea4130c 100644
--- a/vipra-ui/app/less/app.less
+++ b/vipra-ui/app/less/app.less
@@ -389,6 +389,7 @@ topic-menu {
   width: 19px;
   padding: 5px 0;
   vertical-align: middle;
+  position: relative;
 
   > i {
     visibility: hidden;
@@ -406,6 +407,10 @@ topic-menu {
   }
 }
 
+.text-italic {
+  font-style: italic;
+}
+
 @-moz-keyframes spin {
   100% {
     -moz-transform: rotateY(360deg);
@@ -460,3 +465,7 @@ topic-menu {
     opacity: 1;
   }
 }
+
+[ng\:cloak], [ng-cloak], .ng-cloak {
+  display: none !important;
+}
\ No newline at end of file
diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json
index f46a67d2bdca67f7e0251ab95151465d9f33a0d4..11a5e687589b3f2c35b34590fffe563712a062fc 100644
--- a/vipra-ui/bower.json
+++ b/vipra-ui/bower.json
@@ -30,6 +30,7 @@
     "font-awesome": "^4.x",
     "awesome-bootstrap-checkbox": "^0.x",
     "randomcolor": "randomColor#^0.x",
-    "bootbox.js": "bootbox#^4.4.0"
+    "bootbox.js": "bootbox#^4.x",
+    "angular-hotkeys": "chieffancypants/angular-hotkeys#^1.x"
   }
-}
+}
\ No newline at end of file
diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js
index 39729e97f35768caff4a7f46c62ad03ccca9e979..5ad351f6952a55416b56a8ab28c45f0834589a0a 100644
--- a/vipra-ui/gulpfile.js
+++ b/vipra-ui/gulpfile.js
@@ -15,6 +15,7 @@ var assets = {
     'bower_components/angular/angular.min.js',
     'bower_components/angular-resource/angular-resource.min.js',
     'bower_components/angular-sanitize/angular-sanitize.min.js',
+    'bower_components/angular-hotkeys/build/hotkeys.min.js',
     'bower_components/angular-ui-router/release/angular-ui-router.min.js',
     'bower_components/bootstrap/dist/js/bootstrap.min.js',
     'bower_components/highcharts/highstock.js',
@@ -29,7 +30,8 @@ var assets = {
     'bower_components/font-awesome/css/font-awesome.min.css',
     'bower_components/vis/dist/vis.min.css',
     'bower_components/nya-bootstrap-select/dist/css/nya-bs-select.min.css',
-    'bower_components/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css'
+    'bower_components/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css',
+    'bower_components/angular-hotkeys/build/hotkeys.min.css'
   ],
   fonts: [
     'bower_components/bootstrap/dist/fonts/*',