diff --git a/lib/tasks/tests/visual/run.js b/lib/tasks/tests/visual/run.js
index f955fc6521c6436fb397a2e02a04324c5d254b81..1c09af9e3fb6135ddf663577365d92b915c6e016 100644
--- a/lib/tasks/tests/visual/run.js
+++ b/lib/tasks/tests/visual/run.js
@@ -91,8 +91,7 @@ export default (gulp, config, args) => {
 
           /* Start Gemini and return runner upon finish */
           return new Gemini(gemini).test(`${config.tests.visual}/suites`, {
-            reporters: [process.env.CI ? "flat" : "html"],
-            grep: args.grep ? new RegExp(args.grep, "i") : null,
+            reporters: ["flat"].concat(process.env.CI ? [] : ["html"]),
             browsers: args.browsers ? [].concat(args.browsers) : null
           })
 
diff --git a/scripts/build b/scripts/build
index 1f77f8097f2e81698cb53076036ba3301996dfdd..8037cbda3efc4299dc2e24828e3dd4125f6a12f8 100755
--- a/scripts/build
+++ b/scripts/build
@@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
 fi
 
 # Run command
-`npm bin`/gulp build --clean --optimize --revision $@
+`npm bin`/gulp build --clean --optimize --revision "$@"
diff --git a/scripts/clean b/scripts/clean
index 9cb9d0f33c911533c8dc1d73159f01824d766013..309567950cfa058d306b8e2bfd07ee86eb49685b 100755
--- a/scripts/clean
+++ b/scripts/clean
@@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
 fi
 
 # Run command
-`npm bin`/gulp clean $@
+`npm bin`/gulp clean "$@"
diff --git a/scripts/start b/scripts/start
index 385e72923c24fba8c67f4a4c40de85569df136bf..0dc23e37c0051f0b6cce115a2618f72a475a1017 100755
--- a/scripts/start
+++ b/scripts/start
@@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
 fi
 
 # Run command
-`npm bin`/gulp watch --no-lint $@
+`npm bin`/gulp watch --no-lint "$@"
diff --git a/scripts/test/visual/run b/scripts/test/visual/run
index a5691bb5dc5219e279000ccd80ba1a539eb27d70..04c45871e6ecab1d8862a8fc17af9a462658b5a1 100755
--- a/scripts/test/visual/run
+++ b/scripts/test/visual/run
@@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
 fi
 
 # Run command
-`npm bin`/gulp tests:visual:run --clean $@
+`npm bin`/gulp tests:visual:run --clean "$@"
diff --git a/scripts/test/visual/session b/scripts/test/visual/session
index 61803861c8baf726ae5e86d341eb26c7bde7a399..5d30bd2e1ad45044eace8f2fe5c30047cf69915f 100755
--- a/scripts/test/visual/session
+++ b/scripts/test/visual/session
@@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
 fi
 
 # Run command
-`npm bin`/gulp tests:visual:session $@
+`npm bin`/gulp tests:visual:session "$@"
diff --git a/scripts/test/visual/update b/scripts/test/visual/update
index eabae85b3f315fc5bb2cc2fb0abb12371b0b6212..61789f24d5c1aa237d58e64ede627ff0e53ed955 100755
--- a/scripts/test/visual/update
+++ b/scripts/test/visual/update
@@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
 fi
 
 # Run command
-`npm bin`/gulp tests:visual:update $@
+`npm bin`/gulp tests:visual:update "$@"
diff --git a/tests/visual/helpers/spec.js b/tests/visual/helpers/spec.js
index 760ca5ba42b281708f31297f686259fb1d03d1bc..2ab6eeadd062d5b938cbc7eec38a149a2573d871 100644
--- a/tests/visual/helpers/spec.js
+++ b/tests/visual/helpers/spec.js
@@ -22,6 +22,14 @@
 
 import config from "../config.json"
 import path from "path"
+import yargs from "yargs"
+
+/* ----------------------------------------------------------------------------
+ * Configuration and arguments
+ * ------------------------------------------------------------------------- */
+
+/* Parse arguments from command line */
+const args = yargs.argv
 
 /* ----------------------------------------------------------------------------
  * Functions
@@ -61,7 +69,50 @@ const resolve = (breakpoints, expr) => {
 }
 
 /**
- * Generate a Gemini test suite for the component
+ * Filter a set of test suites using a regular expression
+ *
+ * @param {Array.<object>} components - Component specifications
+ * @param {Array.<string>} parent - Parent test suite names
+ * @return {boolean} Whether at least one suite was kept
+ */
+const filter = (components, parent = []) => {
+  const regexp = new RegExp(args.grep.replace(" ", ".*?"), "i")
+  return Object.keys(components).reduce((match, name) => {
+    const component = components[name]
+
+    /* Deep-copy current path and call recursive */
+    const temp = parent.slice(0).concat(name)
+    const keep = filter(component.suite || {}, temp)
+
+    /* Remove all states that do not match the regular expression */
+    component.states = (component.states || [{ name: "", wait: 0 }]).reduce(
+      (states, state) => {
+        const fullname = temp.slice(0)
+          .concat(state.name.length ? [state.name] : [])
+          .join(" ")
+        if (regexp.test(fullname))
+          states.push(state)
+        return states
+      }, [])
+
+    /* Keep komponent, if there is at least one state or the component has
+       matching subsuites, so it needs to be kept */
+    if (component.states.length || keep) {
+      if (keep) {
+        delete component.capture
+        delete component.break
+      }
+      return true
+    }
+
+    /* Otherwise, delete component */
+    delete components[name]
+    return match
+  }, false)
+}
+
+/**
+ * Generate Gemini test suites for the given components
  *
  * @param {string} dirname - Directory of the test suite
  * @param {Array.<object>} components - Component specifications                // TODO: document syntax and specificagtion
@@ -81,11 +132,11 @@ const generate = (dirname, components) => {
           "_",  component.url ? component.url  : ""))
 
       /* The capture selector is assumed to exist */
-      suite.setCaptureElements(component.capture)
+      if (component.capture)
+        suite.setCaptureElements(component.capture)
 
       /* Generate a subsuite for every state */
-      const states = component.states || [{ name: "", wait: 0 }]
-      for (const state of states) {
+      for (const state of component.states) {
         const test = subsuite => {
 
           /* Resolve and apply relevant breakpoints */
@@ -129,10 +180,22 @@ const generate = (dirname, components) => {
   }
 }
 
+/**
+ * Register Gemini test suites for the given components
+ *
+ * @param {string} dirname - Directory of the test suite
+ * @param {Array.<object>} components - Component specifications
+ */
+const register = (dirname, components) => {
+  if (args.grep)
+    filter(components)
+  generate(dirname, components)
+}
+
 /* ----------------------------------------------------------------------------
  * Exports
  * ------------------------------------------------------------------------- */
 
 export default {
-  generate
+  register
 }
diff --git a/tests/visual/suites/extensions/admonition/suite.js b/tests/visual/suites/extensions/admonition/suite.js
index 42b5fd3cb90814ff1230e1cf583b1c13716d355d..7bdbb33c5042192f932486768502a7982b8d8908 100644
--- a/tests/visual/suites/extensions/admonition/suite.js
+++ b/tests/visual/suites/extensions/admonition/suite.js
@@ -32,7 +32,7 @@ import spec from "~/tests/visual/helpers/spec"
  * The admonition block looks the same on everything above tablet
  * portrait, so we can save a few test cases.
  */
-spec.generate(__dirname, {
+spec.register(__dirname, {
   "admonition": {
     "url": "/",
     "capture": "#default + .admonition",
diff --git a/tests/visual/suites/layout/nav/suite.js b/tests/visual/suites/layout/nav/suite.js
index 29d272aa5fb8e2f6d925bfd0248e6b624882328d..aea23679cef1b64866bc4079429a2c1ef4926a26 100644
--- a/tests/visual/suites/layout/nav/suite.js
+++ b/tests/visual/suites/layout/nav/suite.js
@@ -41,7 +41,7 @@ const open = () => {
 /*
  * Main navigation
  */
-spec.generate(__dirname, {
+spec.register(__dirname, {
   "md-nav--primary": {
     "url": "/",
     "capture": ".md-nav--primary",
@@ -84,7 +84,7 @@ spec.generate(__dirname, {
           /* Last list item */
           ":last-child": {
             "capture":
-              ".md-nav--primary > .md-nav__list >" +
+              ".md-nav--primary > .md-nav__list > " +
               ".md-nav__item:last-child",
             "break": "+@tablet-landscape",
             "states": [