diff --git a/index.js b/index.js
index c3ce681..848add2 100644
--- a/index.js
+++ b/index.js
@@ -145,22 +145,12 @@ module.exports = function (browserify, options) {
   });
 
   // create a loader for this entry file
-  if (!loadersByFile[cssOutFilename]) {
-    loadersByFile[cssOutFilename] = new FileSystemLoader(rootDir, plugins);
+  var loader = loadersByFile[cssOutFilename];
+  if (!loader) {
+    loader = loadersByFile[cssOutFilename] = new FileSystemLoader(rootDir, plugins);
   }
 
-  // TODO: clean this up so there's less scope crossing
-  Cmify.prototype._flush = function (callback) {
-    var self = this;
-    var filename = this._filename;
-
-    // only handle .css files
-    if (!this.isCssFile(filename)) { return callback(); }
-
-    // grab the correct loader
-    var loader = loadersByFile[this._cssOutFilename];
-
-    // convert css to js before pushing
+  function createCssModuleSource (filename, cb) {
     // reset the `tokensByFile` cache
     var relFilename = path.relative(rootDir, filename);
     tokensByFile[filename] = loader.tokensByFile[filename] = null;
@@ -188,11 +178,28 @@ module.exports = function (browserify, options) {
 
       assign(tokensByFile, loader.tokensByFile);
 
-      self.push(output.join('\n'));
-      return callback();
+      return cb(null, output.join('\n'));
     }).catch(function (err) {
-      self.push('console.error("' + err + '");');
-      self.emit('error', err);
+      return cb(err);
+    });
+  }
+
+  // TODO: clean this up so there's less scope crossing
+  Cmify.prototype._flush = function (callback) {
+    var self = this;
+    var filename = this._filename;
+
+    // only handle .css files
+    if (!this.isCssFile(filename)) { return callback(); }
+
+    createCssModuleSource(filename, function (err, source) {
+      if (err) {
+        self.push('console.error("' + err + '");');
+        self.emit('error', err);
+        return callback();
+      }
+
+      self.push(source);
       return callback();
     });
   };
@@ -201,7 +208,48 @@ module.exports = function (browserify, options) {
 
   // ----
 
+  function invalidateFile (filename) {
+    Object.keys(loadersByFile).forEach(function (loaderKey) {
+      var loader = loadersByFile[loaderKey];
+
+      // clear the token cache for the changed file
+      tokensByFile[filename] = loader.tokensByFile[filename] = null;
+
+      // also clear the cache for any modules depending on this one
+      try {
+        var deps = loader.deps.dependantsOf(filename);
+        deps.forEach(function (dep) {
+          tokensByFile[dep] = loader.tokensByFile[dep] = null;
+        });
+      }
+      catch (err) {}
+    });
+  }
+
   function addHooks () {
+    browserify.pipeline.get('deps').push(through.obj(function write (row, enc, next) {
+      // css modules need to be regenerated at this point
+      // (so that imported @value updates are carried through hmr)
+      var self = this;
+      if (!/\.css$/.test(row.id)) {
+        return next(null, row)
+      }
+
+      invalidateFile(row.id)
+      return createCssModuleSource(row.id, function (err, source) {
+        if (err) {
+          row.source = 'console.error("' + err + '");';
+          self.emit('error', err);
+          return next(null, row)
+        }
+
+        row.source = source
+        next(null, row)
+      })
+    }, function end (done) {
+      done();
+    }));
+
     browserify.pipeline.get('pack').push(through(function write (row, enc, next) {
       next(null, row)
     }, function end (cb) {
@@ -241,6 +289,11 @@ module.exports = function (browserify, options) {
   browserify.on('reset', addHooks);
   addHooks();
 
+  browserify.on('update', function (files) {
+    // invalidate cache of any changed css modules
+    files.forEach(invalidateFile);
+  })
+
   return browserify;
 };