diff --git a/.travis.yml b/.travis.yml
index b17bbb8332183cebef1f5c2c5d256e97a1c69ae7..af749ff25dd40bfe830e0307a911c67ffc280df8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,7 +18,7 @@ before_install:
   - if [ "$TRAVIS_REPO_SLUG" = twbs-savage/bootstrap ]; then export TWBS_DO_VALIDATOR=0; fi
 install:
   - bundle install --deployment --jobs=3
-  - cp grunt/npm-shrinkwrap.json ./
+  - cp gulp/npm-shrinkwrap.json ./
   - npm install
 cache:
   directories:
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 0deece20ce61f00c1dc12a27de20380b3d26a7fa..0000000000000000000000000000000000000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,402 +0,0 @@
-/*!
- * Bootstrap's Gruntfile
- * http://getbootstrap.com
- * Copyright 2013-2016 The Bootstrap Authors
- * Copyright 2013-2016 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- */
-
-module.exports = function (grunt) {
-  'use strict';
-
-  // Force use of Unix newlines
-  grunt.util.linefeed = '\n';
-
-  RegExp.quote = function (string) {
-    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
-  };
-
-  var fs = require('fs');
-  var path = require('path');
-  var isTravis = require('is-travis');
-
-  var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });
-
-  Object.keys(configBridge.paths).forEach(function (key) {
-    configBridge.paths[key].forEach(function (val, i, arr) {
-      arr[i] = path.join('./docs', val);
-    });
-  });
-
-  // Project configuration.
-  grunt.initConfig({
-
-    // Metadata.
-    pkg: grunt.file.readJSON('package.json'),
-    banner: '/*!\n' +
-            ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
-            ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
-            ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
-            ' */\n',
-    jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
-                 '  throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')\n' +
-                 '}\n',
-    jqueryVersionCheck: '+function ($) {\n' +
-                        '  var version = $.fn.jquery.split(\' \')[0].split(\'.\')\n' +
-                        '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 4)) {\n' +
-                        '    throw new Error(\'Bootstrap\\\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0\')\n' +
-                        '  }\n' +
-                        '}(jQuery);\n\n',
-
-    // Task configuration.
-    clean: {
-      dist: 'dist',
-      docs: 'docs/dist'
-    },
-
-    // JS build configuration
-    babel: {
-      dev: {
-        options: {
-          sourceMap: true,
-          modules: 'ignore'
-        },
-        files: {
-          'js/dist/util.js'      : 'js/src/util.js',
-          'js/dist/alert.js'     : 'js/src/alert.js',
-          'js/dist/button.js'    : 'js/src/button.js',
-          'js/dist/carousel.js'  : 'js/src/carousel.js',
-          'js/dist/collapse.js'  : 'js/src/collapse.js',
-          'js/dist/dropdown.js'  : 'js/src/dropdown.js',
-          'js/dist/modal.js'     : 'js/src/modal.js',
-          'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
-          'js/dist/tab.js'       : 'js/src/tab.js',
-          'js/dist/tooltip.js'   : 'js/src/tooltip.js',
-          'js/dist/popover.js'   : 'js/src/popover.js'
-        }
-      },
-      dist: {
-        options: {
-          modules: 'ignore'
-        },
-        files: {
-          '<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
-        }
-      }
-    },
-
-    stamp: {
-      options: {
-        banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>\n+function ($) {\n',
-        footer: '\n}(jQuery);'
-      },
-      bootstrap: {
-        files: {
-          src: '<%= concat.bootstrap.dest %>'
-        }
-      }
-    },
-
-    concat: {
-      options: {
-        // Custom function to remove all export and import statements
-        process: function (src) {
-          return src.replace(/^(export|import).*/gm, '');
-        },
-        stripBanners: false
-      },
-      bootstrap: {
-        src: [
-          'js/src/util.js',
-          'js/src/alert.js',
-          'js/src/button.js',
-          'js/src/carousel.js',
-          'js/src/collapse.js',
-          'js/src/dropdown.js',
-          'js/src/modal.js',
-          'js/src/scrollspy.js',
-          'js/src/tab.js',
-          'js/src/tooltip.js',
-          'js/src/popover.js'
-        ],
-        dest: 'dist/js/<%= pkg.name %>.js'
-      }
-    },
-
-    uglify: {
-      options: {
-        compress: {
-          warnings: false
-        },
-        mangle: true,
-        preserveComments: /^!|@preserve|@license|@cc_on/i
-      },
-      core: {
-        src: '<%= concat.bootstrap.dest %>',
-        dest: 'dist/js/<%= pkg.name %>.min.js'
-      },
-      docsJs: {
-        src: configBridge.paths.docsJs,
-        dest: 'docs/assets/js/docs.min.js'
-      }
-    },
-
-    qunit: {
-      options: {
-        inject: 'js/tests/unit/phantom.js'
-      },
-      files: 'js/tests/index.html'
-    },
-
-    // CSS build configuration
-    scsslint: {
-      options: {
-        bundleExec: true,
-        config: 'scss/.scss-lint.yml',
-        reporterOutput: null
-      },
-      core: {
-        src: ['scss/*.scss', '!scss/_normalize.scss']
-      },
-      docs: {
-        src: ['docs/assets/scss/*.scss', '!docs/assets/scss/docs.scss']
-      }
-    },
-
-    cssmin: {
-      options: {
-        // TODO: disable `zeroUnits` optimization once clean-css 3.2 is released
-        //    and then simplify the fix for https://github.com/twbs/bootstrap/issues/14837 accordingly
-        compatibility: 'ie9',
-        keepSpecialComments: '*',
-        sourceMap: true,
-        advanced: false
-      },
-      core: {
-        files: [
-          {
-            expand: true,
-            cwd: 'dist/css',
-            src: ['*.css', '!*.min.css'],
-            dest: 'dist/css',
-            ext: '.min.css'
-          }
-        ]
-      },
-      docs: {
-        src: 'docs/assets/css/docs.min.css',
-        dest: 'docs/assets/css/docs.min.css'
-      }
-    },
-
-    copy: {
-      docs: {
-        expand: true,
-        cwd: 'dist/',
-        src: [
-          '**/*'
-        ],
-        dest: 'docs/dist/'
-      }
-    },
-
-    connect: {
-      server: {
-        options: {
-          port: 3000,
-          base: '.'
-        }
-      }
-    },
-
-    jekyll: {
-      options: {
-        bundleExec: true,
-        config: '_config.yml',
-        incremental: false
-      },
-      docs: {},
-      github: {
-        options: {
-          raw: 'github: true'
-        }
-      }
-    },
-
-    htmllint: {
-      options: {
-        ignore: [
-          'Attribute “autocomplete” is only allowed when the input type is “color”, “date”, “datetime”, “datetime-local”, “email”, “hidden”, “month”, “number”, “password”, “range”, “search”, “tel”, “text”, “time”, “url”, or “week”.',
-          'Attribute “autocomplete” not allowed on element “button” at this point.',
-          'Consider using the “h1” element as a top-level heading only (all “h1” elements are treated as top-level headings by many screen readers and other tools).',
-          'Element “div” not allowed as child of element “progress” in this context. (Suppressing further errors from this subtree.)',
-          'Element “img” is missing required attribute “src”.',
-          'The “color” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
-          'The “date” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
-          'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
-          'The “datetime-local” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
-          'The “month” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
-          'The “time” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
-          'The “week” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
-        ]
-      },
-      src: ['_gh_pages/**/*.html', 'js/tests/visual/*.html']
-    },
-
-    watch: {
-      src: {
-        files: '<%= concat.bootstrap.src %>',
-        tasks: ['babel:dev']
-      },
-      sass: {
-        files: 'scss/**/*.scss',
-        tasks: ['dist-css', 'docs']
-      },
-      docs: {
-        files: 'docs/assets/scss/**/*.scss',
-        tasks: ['dist-css', 'docs']
-      }
-    },
-
-    'saucelabs-qunit': {
-      all: {
-        options: {
-          build: process.env.TRAVIS_JOB_ID,
-          concurrency: 10,
-          maxRetries: 3,
-          maxPollRetries: 4,
-          urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
-          browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
-        }
-      }
-    },
-
-    exec: {
-      postcss: {
-        command: 'npm run postcss'
-      },
-      'postcss-docs': {
-        command: 'npm run postcss-docs'
-      },
-      htmlhint: {
-        command: 'npm run htmlhint'
-      },
-      'upload-preview': {
-        command: './grunt/upload-preview.sh'
-      }
-    },
-
-    buildcontrol: {
-      options: {
-        dir: '_gh_pages',
-        commit: true,
-        push: true,
-        message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
-      },
-      pages: {
-        options: {
-          remote: 'git@github.com:twbs/derpstrap.git',
-          branch: 'gh-pages'
-        }
-      }
-    },
-
-    compress: {
-      main: {
-        options: {
-          archive: 'bootstrap-<%= pkg.version %>-dist.zip',
-          mode: 'zip',
-          level: 9,
-          pretty: true
-        },
-        files: [
-          {
-            expand: true,
-            cwd: 'dist/',
-            src: ['**'],
-            dest: 'bootstrap-<%= pkg.version %>-dist'
-          }
-        ]
-      }
-    }
-
-  });
-
-
-  // These plugins provide necessary tasks.
-  require('load-grunt-tasks')(grunt, { scope: 'devDependencies',
-    // Exclude Sass compilers. We choose the one to load later on.
-    pattern: ['grunt-*', '!grunt-sass', '!grunt-contrib-sass'] });
-  require('time-grunt')(grunt);
-
-  // Docs HTML validation task
-  grunt.registerTask('validate-html', ['jekyll:docs', 'htmllint', 'exec:htmlhint']);
-
-  var runSubset = function (subset) {
-    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
-  };
-  var isUndefOrNonZero = function (val) {
-    return val === undefined || val !== '0';
-  };
-
-  // Test task.
-  var testSubtasks = [];
-  // Skip core tests if running a different subset of the test suite
-  if (runSubset('core') &&
-    // Skip core tests if this is a Savage build
-    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
-    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'qunit', 'docs']);
-  }
-  // Skip HTML validation if running a different subset of the test suite
-  if (runSubset('validate-html') &&
-      isTravis &&
-      // Skip HTML5 validator when [skip validator] is in the commit message
-      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
-    testSubtasks.push('validate-html');
-  }
-  // Only run Sauce Labs tests if there's a Sauce access key
-  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
-      // Skip Sauce if running a different subset of the test suite
-      runSubset('sauce-js-unit')) {
-    testSubtasks = testSubtasks.concat(['dist', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs', 'exec:upload-preview']);
-    // Skip Sauce on Travis when [skip sauce] is in the commit message
-    if (isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
-      testSubtasks.push('connect');
-      testSubtasks.push('saucelabs-qunit');
-    }
-  }
-  grunt.registerTask('test', testSubtasks);
-
-  // JS distribution task.
-  grunt.registerTask('dist-js', ['babel:dev', 'concat', 'babel:dist', 'stamp', 'uglify:core']);
-
-  grunt.registerTask('test-scss', ['scsslint:core']);
-
-  // CSS distribution task.
-  // Supported Compilers: sass (Ruby) and libsass.
-  (function (sassCompilerName) {
-    require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
-  })(process.env.TWBS_SASS || 'libsass');
-  // grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
-  grunt.registerTask('sass-compile', ['sass:core', 'sass:docs']);
-
-  grunt.registerTask('dist-css', ['sass-compile', 'exec:postcss', 'cssmin:core', 'cssmin:docs']);
-
-  // Full distribution task.
-  grunt.registerTask('dist', ['clean:dist', 'dist-css', 'dist-js']);
-
-  // Default task.
-  grunt.registerTask('default', ['clean:dist', 'test']);
-
-  // Docs task.
-  grunt.registerTask('docs-css', ['cssmin:docs', 'exec:postcss-docs']);
-  grunt.registerTask('lint-docs-css', ['scsslint:docs']);
-  grunt.registerTask('docs-js', ['uglify:docsJs']);
-  grunt.registerTask('docs', ['lint-docs-css', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs']);
-  grunt.registerTask('docs-github', ['jekyll:github']);
-
-  grunt.registerTask('prep-release', ['dist', 'docs', 'docs-github', 'compress']);
-
-  // Publish to GitHub
-  grunt.registerTask('publish', ['buildcontrol:pages']);
-};
diff --git a/Gulpfile.js b/Gulpfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..3d972f08fa2be4d9af0de6d1b6b37a6ce1c538a2
--- /dev/null
+++ b/Gulpfile.js
@@ -0,0 +1,212 @@
+/*!
+ * Bootstrap's Gulpfile
+ * http://getbootstrap.com
+ * Copyright 2013-2016 The Bootstrap Authors
+ * Copyright 2013-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+ 
+var autoprefix = require('gulp-autoprefixer'),
+    babel      = require('gulp-babel'),
+    build      = require('build-control'),
+    clean      = require('gulp-clean'),
+    cleancss   = require('gulp-clean-css'),
+    concat     = require('gulp-concat'),
+    debug      = require('gulp-debug'),
+    eslint     = require('gulp-eslint'),
+    exec       = require('gulp-exec'),
+    flexbox    = require('postcss-flexbox-fixes'),
+    gulp       = require('gulp'),
+    htmlhint   = require('gulp-html-hint'),
+    htmllint   = require('gulp-html'),
+    isTravis   = require('is-travis'),
+    jscs       = require('gulp-jscs'),
+    jshint     = require('gulp-jshint'),
+    postcss    = require('gulp-postcss'),
+    rename     = require('gulp-rename'),
+    sass       = require('gulp-ruby-sass'),
+    scsslint   = require('gulp-scss-lint'),
+    stamp      = require('gulp-stamp'),
+    sourcemaps = require('gulp-sourcemaps'),
+    uglify     = require('gulp-uglify');
+
+// Equivalent to time-grunt
+require("time-require");
+
+// Project configuration.
+var config = require('./gulp/config.js');
+config.autoprefixer = require('./gulp/postcss.js').autoprefixer;
+
+// Build SCSS
+gulp.task('sass:core', () =>) {
+    var scss = gulp.src('scss/bootstrap.scss', { base: config.core.files.cwd });
+    scss
+        .pipe(debug({ title: 'sassc:' }))
+        .pipe(sourcemaps.init())
+        .pipe(sass().on('error', sass.logError))
+        .pipe(autoprefix(config.autoprefixer.browsers))
+        .pipe(postcss([flexbox]))
+        .pipe(sourcemaps.write())
+        .pipe(debug({ title: 'copy:' }))
+        .pipe(gulp.dest('dist/css/'));
+
+    scss
+        .pipe(debug({ title: 'sassc:' }))
+        .pipe(sourcemaps.init())
+        .pipe(sass().on('error', sass.logError))
+        .pipe(autoprefix(config.autoprefixer.browsers))
+        .pipe(postcss([flexbox]))
+        .pipe(cleancss(config.cssmin.options))
+        .pipe(sourcemaps.write())
+        .pipe(debug({ title: 'copy:' }))
+        .pipe(gulp.dest('dist/css/'));
+});
+
+gulp.task('sass:docs', () => {
+    return gulp.src('docs/assets/scss/docs.scss');
+        .pipe(debug({ title: 'sassc:' }))
+        .pipe(sass().on('error', sass.logError))
+        .pipe(autoprefix(config.autoprefixer.browsers))
+        .pipe(postcss([flexbox]))
+        .pipe(cleancss(config.cssmin.options))
+        .pipe(debug({ title: 'copy:' }))
+        .pipe(gulp.dest('docs/assets/css/'));
+});
+
+gulp.task('exec:postcss-docs', () => {
+    return gulp.src('docs/examples/**/*.css')
+        .pipe(autoprefix(config.autoprefixer.browsers))
+        .pipe(postcss([flexbox]));
+});
+
+gulp.task('clean:dist', () => {
+    return gulp.src('dist/**')
+        .pipe(clean());
+});
+
+gulp.task('clean:docs', () => {
+    return gulp.src('docs/dist/**')
+        .pipe(clean());
+});
+
+gulp.task('cssmin:docs', () => {
+    return gulp.src(config.cssmin.docs.src)
+        .pipe(cleancss(config.cssmin.options))
+        .pipe(gulp.dest(config.cssmin.docs.dest));
+});
+
+gulp.task('htmllint', () => {
+    return gulp.src(config.htmllint.src)
+        .pipe(htmllint());
+});
+
+gulp.task('htmlhint', () => {
+    return gulp.src('_gh_pages/**/*.md')
+        .pipe(htmlhint('docs/.htmlhintrc'));
+});
+
+gulp.task('jekyll:docs', (cb) => {
+    exec('jekyll build --incremental', function(err, stdout, stderr) {
+        console.log(stdout);
+        console.log(stderr);
+        cb(err);
+    });
+});
+
+gulp.task('babel:dev', () => {
+    return gulp.src('js/src/*.js')
+        .pipe(sourcemaps.init())
+        .pipe(concat('bootstrap.js'))
+        .pipe(babel(config.babel.options))
+        .pipe(sourcemaps.write())
+        .pipe(gulp.dest('js/dist/'));
+});
+
+gulp.task('concat', () => {
+    gulp.src(config.concat.bootstrap.src)
+        .pipe(concat('bootstrap.js'))
+        .pipe(gulp.dest('dist/js/'));
+});
+
+gulp.task('babel:dist', () => {
+    return gulp.src(config.concat.bootstrap.dest)
+        .pipe(babel())
+        .pipe(gulp.dest('dist/js/'));
+});
+
+gulp.task('stamp', () => {
+    return gulp.src(config.concat.bootstrap.dest)
+        .pipe(stamp(config.stamp.options))
+        .pipe(gulp.dest('dist/js/'));
+});
+
+gulp.task('uglify:core', () => {
+    return gulp.src(config.concat.bootstrap.dest)
+        .pipe(uglify(config.uglfy.options))
+        .pipe(rename('bootstrap.min.js'))
+        .pipe('dist/js/');
+});
+
+gulp.task('watch', () => {
+    gulp.watch(config.watch.sass.files, config.watch.sass.tasks);
+    gulp.watch(config.watch.src.files, config.watch.src.tasks);
+    gulp.watch(config.watch.docs.files, config.watch.docs.tasks);
+});
+
+// Docs HTML validation task
+gulp.task('validate-html', ['jekyll:docs', 'htmllint', 'htmlhint']);
+// JS distribution task.
+gulp.task('dist-js', ['babel:dev', 'concat', 'babel:dist', 'stamp', 'uglify:core']);
+
+gulp.task('test-scss', () => {
+    return gulp.src(config.scsslint.core.src)
+        .pipe(scsslint(config.scsslint.options));
+});
+
+// CSS distribution task.
+// gulp.task('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
+gulp.task('sass-compile', ['sass:core', 'sass:docs']);
+
+gulp.task('dist-css', ['sass-compile', 'cssmin:docs']);
+
+// Full distribution task.
+gulp.task('dist', ['clean:dist', 'dist-css', 'dist-js']);
+
+// Default task.
+gulp.task('default', ['clean:dist', 'test']);
+gulp.task('test', ['eslint', 'jscs', ]);
+gulp.task('eslint', () => {
+    var options = require('./js/.eslintrc.json');
+    return gulp.src('js/src/*.js')
+        .pipe(eslint(options))
+        .pipe(eslint.format())
+        .pipe(eslint.failAfterError());
+});
+gulp.task('jscs', () => {
+    var options = require('./js/.jscsrc');
+    return gulp.src(['js/src/*', 'js/tests/unit', 'docs/assets/js/src/*', 'gulp/*', 'Gulpfile.js', 'docs/assets/js/ie-emulation-modes-warning.js', 'docs/assets/js/ie10-viewport-bug-workaround.js'])
+        .pipe(jscs(options))
+        .pipe(jscs.reporter());
+});
+// Docs task.
+gulp.task('docs-css', ['cssmin:docs', 'exec:postcss-docs']);
+gulp.task('lint-docs-css', () => {
+    return gulp.src(config.scsslint.docs.src)
+        .pipe(scsslint(config.scsslint.options));
+});
+gulp.task('docs-js', ['uglify:docsJs']);
+gulp.task('docs', ['lint-docs-css', 'docs-css', 'docs-js', 'clean:docs', 'copy:docs']);
+gulp.task('docs-github', (cb) => {
+    exec('jekyll build --incremental', (err, stdout, stderr) => {
+        console.log(stdout);
+        console.log(stderr);
+        cb(err);
+    });
+});
+
+gulp.task('prep-release', ['dist', 'docs', 'docs-github', 'compress']);
+
+// Publish to GitHub
+gulp.task('publish', () => {
+    build(config.buildcontrol.options);
+});
diff --git a/grunt/bs-sass-compile/libsass.js b/gulp/bs-sass-compile/libsass.js
similarity index 100%
rename from grunt/bs-sass-compile/libsass.js
rename to gulp/bs-sass-compile/libsass.js
diff --git a/grunt/bs-sass-compile/sass.js b/gulp/bs-sass-compile/sass.js
similarity index 100%
rename from grunt/bs-sass-compile/sass.js
rename to gulp/bs-sass-compile/sass.js
diff --git a/grunt/change-version.js b/gulp/change-version.js
similarity index 100%
rename from grunt/change-version.js
rename to gulp/change-version.js
diff --git a/gulp/config.js b/gulp/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd63ea3f20698e28083377ece65011877d7e525d
--- /dev/null
+++ b/gulp/config.js
@@ -0,0 +1,291 @@
+module.exports = {
+
+  // Metadata.
+  pkg: require('../package.json'),
+  banner: '/*!\n' +
+          ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
+          ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
+          ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
+          ' */\n',
+  jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
+               '  throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')\n' +
+               '}\n',
+  jqueryVersionCheck: '+function ($) {\n' +
+                      '  var version = $.fn.jquery.split(\' \')[0].split(\'.\')\n' +
+                      '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 4)) {\n' +
+                      '    throw new Error(\'Bootstrap\\\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0\')\n' +
+                      '  }\n' +
+                      '}(jQuery);\n\n',
+
+  // Task configuration.
+  clean: {
+    dist: 'dist',
+    docs: 'docs/dist'
+  },
+
+  // JS build configuration
+  babel: {
+    dev: {
+      options: {
+        sourceMap: true,
+        modules: 'ignore'
+      },
+      files: {
+        'js/dist/util.js'      : 'js/src/util.js',
+        'js/dist/alert.js'     : 'js/src/alert.js',
+        'js/dist/button.js'    : 'js/src/button.js',
+        'js/dist/carousel.js'  : 'js/src/carousel.js',
+        'js/dist/collapse.js'  : 'js/src/collapse.js',
+        'js/dist/dropdown.js'  : 'js/src/dropdown.js',
+        'js/dist/modal.js'     : 'js/src/modal.js',
+        'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
+        'js/dist/tab.js'       : 'js/src/tab.js',
+        'js/dist/tooltip.js'   : 'js/src/tooltip.js',
+        'js/dist/popover.js'   : 'js/src/popover.js'
+      }
+    },
+    dist: {
+      options: {
+        modules: 'ignore'
+      },
+      files: {
+        '<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
+      }
+    }
+  },
+
+  stamp: {
+    options: {
+      banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>\n+function ($) {\n',
+      footer: '\n}(jQuery);'
+    },
+    bootstrap: {
+      files: {
+        src: '<%= concat.bootstrap.dest %>'
+      }
+    }
+  },
+
+  concat: {
+    options: {
+      // Custom function to remove all export and import statements
+      process: function (src) {
+        return src.replace(/^(export|import).*/gm, '');
+      },
+      stripBanners: false
+    },
+    bootstrap: {
+      src: [
+        'js/src/util.js',
+        'js/src/alert.js',
+        'js/src/button.js',
+        'js/src/carousel.js',
+        'js/src/collapse.js',
+        'js/src/dropdown.js',
+        'js/src/modal.js',
+        'js/src/scrollspy.js',
+        'js/src/tab.js',
+        'js/src/tooltip.js',
+        'js/src/popover.js'
+      ],
+      dest: 'dist/js/<%= pkg.name %>.js'
+    }
+  },
+
+  uglify: {
+    options: {
+      compress: {
+        warnings: false
+      },
+      mangle: true,
+      preserveComments: /^!|@preserve|@license|@cc_on/i
+    },
+    core: {
+      src: '<%= concat.bootstrap.dest %>',
+      dest: 'dist/js/<%= pkg.name %>.min.js'
+    },
+    docsJs: {
+      src: configBridge.paths.docsJs,
+      dest: 'docs/assets/js/docs.min.js'
+    }
+  },
+
+  qunit: {
+    options: {
+      inject: 'js/tests/unit/phantom.js'
+    },
+    files: 'js/tests/index.html'
+  },
+
+  // CSS build configuration
+  scsslint: {
+    options: {
+      bundleExec: true,
+      config: 'scss/.scss-lint.yml',
+      reporterOutput: null
+    },
+    core: {
+      src: ['scss/*.scss', '!scss/_normalize.scss']
+    },
+    docs: {
+      src: ['docs/assets/scss/*.scss', '!docs/assets/scss/docs.scss']
+    }
+  },
+
+  cssmin: {
+    options: {
+      // TODO: disable `zeroUnits` optimization once clean-css 3.2 is released
+      //    and then simplify the fix for https://github.com/twbs/bootstrap/issues/14837 accordingly
+      compatibility: 'ie9',
+      keepSpecialComments: '*',
+      sourceMap: true,
+      advanced: false
+    },
+    core: {
+      files: [
+        {
+          expand: true,
+          cwd: 'dist/css',
+          src: ['*.css', '!*.min.css'],
+          dest: 'dist/css',
+          ext: '.min.css'
+        }
+      ]
+    },
+    docs: {
+      src: 'docs/assets/css/docs.min.css',
+      dest: 'docs/assets/css/docs.min.css'
+    }
+  },
+
+  copy: {
+    docs: {
+      expand: true,
+      cwd: 'dist/',
+      src: [
+        '**/*'
+      ],
+      dest: 'docs/dist/'
+    }
+  },
+
+  connect: {
+    server: {
+      options: {
+        port: 3000,
+        base: '.'
+      }
+    }
+  },
+
+  jekyll: {
+    options: {
+      bundleExec: true,
+      config: '_config.yml',
+      incremental: false
+    },
+    docs: {},
+    github: {
+      options: {
+        raw: 'github: true'
+      }
+    }
+  },
+
+  htmllint: {
+    options: {
+      ignore: [
+        'Attribute “autocomplete” is only allowed when the input type is “color”, “date”, “datetime”, “datetime-local”, “email”, “hidden”, “month”, “number”, “password”, “range”, “search”, “tel”, “text”, “time”, “url”, or “week”.',
+        'Attribute “autocomplete” not allowed on element “button” at this point.',
+        'Consider using the “h1” element as a top-level heading only (all “h1” elements are treated as top-level headings by many screen readers and other tools).',
+        'Element “div” not allowed as child of element “progress” in this context. (Suppressing further errors from this subtree.)',
+        'Element “img” is missing required attribute “src”.',
+        'The “color” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
+        'The “date” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
+        'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
+        'The “datetime-local” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
+        'The “month” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
+        'The “time” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
+        'The “week” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
+      ]
+    },
+    src: ['_gh_pages/**/*.html', 'js/tests/visual/*.html']
+  },
+
+  watch: {
+    src: {
+      files: '<%= concat.bootstrap.src %>',
+      tasks: ['babel:dev']
+    },
+    sass: {
+      files: 'scss/**/*.scss',
+      tasks: ['dist-css', 'docs']
+    },
+    docs: {
+      files: 'docs/assets/scss/**/*.scss',
+      tasks: ['dist-css', 'docs']
+    }
+  },
+
+  'saucelabs-qunit': {
+    all: {
+      options: {
+        build: process.env.TRAVIS_JOB_ID,
+        concurrency: 10,
+        maxRetries: 3,
+        maxPollRetries: 4,
+        urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
+        browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
+      }
+    }
+  },
+
+  exec: {
+    postcss: {
+      command: 'npm run postcss'
+    },
+    'postcss-docs': {
+      command: 'npm run postcss-docs'
+    },
+    htmlhint: {
+      command: 'npm run htmlhint'
+    },
+    'upload-preview': {
+      command: './grunt/upload-preview.sh'
+    }
+  },
+
+  buildcontrol: {
+    options: {
+      dir: '_gh_pages',
+      commit: true,
+      push: true,
+      message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
+    },
+    pages: {
+      options: {
+        remote: 'git@github.com:twbs/derpstrap.git',
+        branch: 'gh-pages'
+      }
+    }
+  },
+
+  compress: {
+    main: {
+      options: {
+        archive: 'bootstrap-<%= pkg.version %>-dist.zip',
+        mode: 'zip',
+        level: 9,
+        pretty: true
+      },
+      files: [
+        {
+          expand: true,
+          cwd: 'dist/',
+          src: ['**'],
+          dest: 'bootstrap-<%= pkg.version %>-dist'
+        }
+      ]
+    }
+  }
+};
diff --git a/grunt/configBridge.json b/gulp/configBridge.json
similarity index 100%
rename from grunt/configBridge.json
rename to gulp/configBridge.json
diff --git a/grunt/gcp-key.json.enc b/gulp/gcp-key.json.enc
similarity index 100%
rename from grunt/gcp-key.json.enc
rename to gulp/gcp-key.json.enc
diff --git a/grunt/npm-shrinkwrap.json b/gulp/npm-shrinkwrap.json
similarity index 100%
rename from grunt/npm-shrinkwrap.json
rename to gulp/npm-shrinkwrap.json
diff --git a/grunt/postcss.js b/gulp/postcss.js
similarity index 100%
rename from grunt/postcss.js
rename to gulp/postcss.js
diff --git a/grunt/sauce_browsers.yml b/gulp/sauce_browsers.yml
similarity index 100%
rename from grunt/sauce_browsers.yml
rename to gulp/sauce_browsers.yml
diff --git a/grunt/upload-preview.sh b/gulp/upload-preview.sh
similarity index 100%
rename from grunt/upload-preview.sh
rename to gulp/upload-preview.sh
diff --git a/package.json b/package.json
index 2d6b598d2d454e12fbc47a1e92d4634a7317316c..26017ef209f3c4cc7dc7e63e914de4adcd1fe24a 100644
--- a/package.json
+++ b/package.json
@@ -17,14 +17,8 @@
     "Twitter, Inc."
   ],
   "scripts": {
-    "change-version": "node grunt/change-version.js",
-    "eslint": "eslint --config js/.eslintrc.json js/src",
-    "jscs": "jscs --config=js/.jscsrc js/src js/tests/unit docs/assets/js/src grunt Gruntfile.js docs/assets/js/ie-emulation-modes-warning.js docs/assets/js/ie10-viewport-bug-workaround.js",
-    "htmlhint": "htmlhint --config docs/.htmlhintrc _gh_pages/",
-    "postcss": "postcss --config grunt/postcss.js --replace dist/css/*.css",
-    "postcss-docs": "postcss --config grunt/postcss.js --no-map --replace docs/assets/css/docs.min.css && postcss --config grunt/postcss.js --no-map --replace docs/examples/**/*.css",
-    "shrinkwrap": "npm shrinkwrap --dev && shx mv ./npm-shrinkwrap.json ./grunt/npm-shrinkwrap.json",
-    "test": "npm run eslint && npm run jscs && grunt test"
+    "change-version": "node gulp/change-version.js",
+    "shrinkwrap": "npm shrinkwrap --dev && shx mv ./npm-shrinkwrap.json ./gulp/npm-shrinkwrap.json"
   },
   "style": "dist/css/bootstrap.css",
   "sass": "scss/bootstrap.scss",
@@ -42,38 +36,38 @@
     "tether": "^1.1.1"
   },
   "devDependencies": {
-    "autoprefixer": "^6.0.3",
     "babel-eslint": "^6.0.4",
+    "build-control": "^0.3.8",
     "eslint": "^3.0.0",
-    "grunt": "^1.0.1",
-    "grunt-babel": "^5.0.3",
-    "grunt-build-control": "^0.7.0",
-    "grunt-contrib-clean": "^1.0.0",
-    "grunt-contrib-compress": "^1.1.0",
-    "grunt-contrib-concat": "^1.0.0",
-    "grunt-contrib-connect": "^1.0.0",
-    "grunt-contrib-copy": "^1.0.0",
-    "grunt-contrib-cssmin": "^1.0.0",
-    "grunt-contrib-qunit": "^1.0.1",
-    "grunt-contrib-sass": "^1.0.0",
-    "grunt-contrib-uglify": "^1.0.0",
-    "grunt-contrib-watch": "^1.0.0",
-    "grunt-exec": "^1.0.0",
-    "grunt-html": "^8.0.1",
-    "grunt-jekyll": "^0.4.2",
-    "grunt-sass": "^1.0.0",
-    "grunt-saucelabs": "^9.0.0",
-    "grunt-scss-lint": "^0.3.8",
-    "grunt-stamp": "^0.3.0",
+    "gulp": "^3.9.1",
+    "gulp-babel": "^6.1.2",
+    "gulp-clean": "^0.3.2",
+    "gulp-clean-css": "^2.0.11",
+    "gulp-concat": "^2.6.0",
+    "gulp-connect": "^4.1.0",
+    "gulp-debug": "^2.1.2",
+    "gulp-eslint": "^3.0.1",
+    "gulp-exec": "^2.1.2",
+    "gulp-html": "^0.4.4",
+    "gulp-htmlhint": "^0.3.1",
+    "gulp-jscs": "^4.0.0",
+    "gulp-jshint": "^2.0.1",
+    "gulp-postcss": "^6.1.1",
+    "gulp-qunit": "^1.4.0",
+    "gulp-rename": "^1.2.2",
+    "gulp-ruby-sass": "^2.0.6",
+    "gulp-scss-lint": "^0.4.0",
+    "gulp-sourcemaps": "^1.6.0",
+    "gulp-stamp": "0.0.9",
+    "gulp-uglify": "^1.5.4",
     "htmlhint": "^0.9.13",
     "is-travis": "^1.0.0",
     "jscs": "^3.0.4",
-    "load-grunt-tasks": "^3.4.0",
-    "postcss-cli": "^2.5.2",
+    "jshint": "^2.9.2",
     "postcss-flexbugs-fixes": "^2.0.0",
     "shelljs": "^0.7.0",
     "shx": "^0.1.2",
-    "time-grunt": "^1.2.1"
+    "time-require": "^0.1.2"
   },
   "engines": {
     "node": ">=4"