diff --git a/Makefile b/Makefile
index 2a08c76339..45e07364d8 100644
--- a/Makefile
+++ b/Makefile
@@ -107,7 +107,7 @@ endif
 
 GO_SOURCES_OWN := $(filter-out vendor/% %/bindata.go, $(GO_SOURCES))
 
-FOMANTIC_CONFIGS := semantic.json web_src/fomantic/theme.config.less web_src/fomantic/_site/globals/site.variables
+FOMANTIC_CONFIGS := semantic.json web_src/fomantic/theme.config.less web_src/fomantic/_site/globals/site.variables web_src/fomantic/css.js
 FOMANTIC_DEST := public/fomantic/semantic.min.js public/fomantic/semantic.min.css
 FOMANTIC_DEST_DIR := public/fomantic
 
@@ -589,7 +589,8 @@ fomantic: $(FOMANTIC_DEST)
 $(FOMANTIC_DEST): $(FOMANTIC_CONFIGS) package-lock.json | node_modules
 	rm -rf $(FOMANTIC_DEST_DIR)
 	cp web_src/fomantic/theme.config.less node_modules/fomantic-ui/src/theme.config
-	cp web_src/fomantic/_site/globals/* node_modules/fomantic-ui/src/_site/globals/
+	cp -r web_src/fomantic/_site/* node_modules/fomantic-ui/src/_site/
+	cp web_src/fomantic/css.js node_modules/fomantic-ui/tasks/build/css.js
 	npx gulp -f node_modules/fomantic-ui/gulpfile.js build
 	@touch $(FOMANTIC_DEST)
 
@@ -654,4 +655,4 @@ golangci-lint:
 	golangci-lint run --timeout 5m
 
 # This endif closes the if at the top of the file
-endif
\ No newline at end of file
+endif
diff --git a/package-lock.json b/package-lock.json
index 9169f83a8b..1c10bac388 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2910,50 +2910,6 @@
         }
       }
     },
-    "copy-webpack-plugin": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz",
-      "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==",
-      "requires": {
-        "cacache": "^12.0.3",
-        "find-cache-dir": "^2.1.0",
-        "glob-parent": "^3.1.0",
-        "globby": "^7.1.1",
-        "is-glob": "^4.0.1",
-        "loader-utils": "^1.2.3",
-        "minimatch": "^3.0.4",
-        "normalize-path": "^3.0.0",
-        "p-limit": "^2.2.1",
-        "schema-utils": "^1.0.0",
-        "serialize-javascript": "^2.1.2",
-        "webpack-log": "^2.0.0"
-      },
-      "dependencies": {
-        "p-limit": {
-          "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-          "requires": {
-            "p-try": "^2.0.0"
-          }
-        },
-        "p-try": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
-          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
-        },
-        "schema-utils": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
-          "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
-          "requires": {
-            "ajv": "^6.1.0",
-            "ajv-errors": "^1.0.0",
-            "ajv-keywords": "^3.1.0"
-          }
-        }
-      }
-    },
     "core-js": {
       "version": "3.6.5",
       "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
@@ -3620,14 +3576,6 @@
         "randombytes": "^2.0.0"
       }
     },
-    "dir-glob": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
-      "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
-      "requires": {
-        "path-type": "^3.0.0"
-      }
-    },
     "doctrine": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -5926,26 +5874,6 @@
       "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
       "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
     },
-    "globby": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
-      "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
-      "requires": {
-        "array-union": "^1.0.1",
-        "dir-glob": "^2.0.0",
-        "glob": "^7.1.2",
-        "ignore": "^3.3.5",
-        "pify": "^3.0.0",
-        "slash": "^1.0.0"
-      },
-      "dependencies": {
-        "pify": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
-        }
-      }
-    },
     "globjoin": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz",
@@ -7289,11 +7217,6 @@
       "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
       "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
     },
-    "ignore": {
-      "version": "3.3.10",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
-      "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug=="
-    },
     "image-size": {
       "version": "0.5.5",
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@@ -9945,21 +9868,6 @@
       "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz",
       "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0="
     },
-    "path-type": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
-      "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
-      "requires": {
-        "pify": "^3.0.0"
-      },
-      "dependencies": {
-        "pify": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
-        }
-      }
-    },
     "pbkdf2": {
       "version": "3.0.17",
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
@@ -12371,11 +12279,6 @@
         }
       }
     },
-    "slash": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
-      "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
-    },
     "slice-ansi": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
@@ -14483,7 +14386,8 @@
     "uuid": {
       "version": "3.4.0",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
-      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "optional": true
     },
     "v-tooltip": {
       "version": "2.0.3",
@@ -15125,15 +15029,6 @@
       "resolved": "https://registry.npmjs.org/webpack-fix-style-only-entries/-/webpack-fix-style-only-entries-0.4.0.tgz",
       "integrity": "sha512-6TDa56V/xSOw6CBVlhFm6J+xXY2oJzx7CEgH0dmex2Xe1rwb95KkLl3rXvSNpO4wyahwD3YnYqffDNR0LH1BNQ=="
     },
-    "webpack-log": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
-      "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
-      "requires": {
-        "ansi-colors": "^3.0.0",
-        "uuid": "^3.3.2"
-      }
-    },
     "webpack-sources": {
       "version": "1.4.3",
       "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
diff --git a/package.json b/package.json
index ec661a90ba..d520fa38a8 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,6 @@
     "@primer/octicons": "9.6.0",
     "babel-loader": "8.1.0",
     "clipboard": "2.0.6",
-    "copy-webpack-plugin": "5.1.1",
     "core-js": "3.6.5",
     "css-loader": "3.5.2",
     "cssnano": "4.1.10",
diff --git a/web_src/fomantic/css.js b/web_src/fomantic/css.js
new file mode 100644
index 0000000000..2d753f0613
--- /dev/null
+++ b/web_src/fomantic/css.js
@@ -0,0 +1,260 @@
+/*******************************
+ *          Build Task
+ *******************************/
+
+const
+  gulp         = require('gulp'),
+
+  // node dependencies
+  console      = require('better-console'),
+
+  // gulp dependencies
+  autoprefixer = require('gulp-autoprefixer'),
+  chmod        = require('gulp-chmod'),
+  concatCSS    = require('gulp-concat-css'),
+  dedupe       = require('gulp-dedupe'),
+  flatten      = require('gulp-flatten'),
+  gulpif       = require('gulp-if'),
+  header       = require('gulp-header'),
+  less         = require('gulp-less'),
+  minifyCSS    = require('gulp-clean-css'),
+  normalize    = require('normalize-path'),
+  plumber      = require('gulp-plumber'),
+  print        = require('gulp-print').default,
+  rename       = require('gulp-rename'),
+  replace      = require('gulp-replace'),
+  replaceExt   = require('replace-ext'),
+  rtlcss       = require('gulp-rtlcss'),
+
+  // config
+  config       = require('./../config/user'),
+  docsConfig   = require('./../config/docs'),
+  tasks        = require('../config/tasks'),
+  install      = require('../config/project/install'),
+
+  // shorthand
+  globs        = config.globs,
+  assets       = config.paths.assets,
+
+  banner       = tasks.banner,
+  filenames    = tasks.filenames,
+  comments     = tasks.regExp.comments,
+  log          = tasks.log,
+  settings     = tasks.settings
+;
+
+/**
+ * Builds the css
+ * @param src
+ * @param type
+ * @param compress
+ * @param config
+ * @param opts
+ * @return {*}
+ */
+function build(src, type, compress, config, opts) {
+  let fileExtension;
+  if (type === 'rtl' && compress) {
+    fileExtension = settings.rename.rtlMinCSS;
+  } else if (type === 'rtl') {
+    fileExtension = settings.rename.rtlCSS;
+  } else if (compress) {
+    fileExtension = settings.rename.minCSS;
+  }
+
+  return gulp.src(src, opts)
+    .pipe(plumber(settings.plumber.less))
+    .pipe(less(settings.less))
+    .pipe(autoprefixer(settings.prefix))
+    .pipe(gulpif(type === 'rtl', rtlcss()))
+    .pipe(replace(comments.variables.in, comments.variables.out))
+    .pipe(replace(comments.license.in, comments.license.out))
+    .pipe(replace(comments.large.in, comments.large.out))
+    .pipe(replace(comments.small.in, comments.small.out))
+    .pipe(replace(comments.tiny.in, comments.tiny.out))
+    .pipe(flatten())
+    .pipe(replace(config.paths.assets.source,
+      compress ? config.paths.assets.compressed : config.paths.assets.uncompressed))
+    .pipe(gulpif(compress, minifyCSS(settings.minify)))
+    .pipe(gulpif(fileExtension, rename(fileExtension)))
+    .pipe(gulpif(config.hasPermissions, chmod(config.parsedPermissions)))
+    .pipe(gulp.dest(compress ? config.paths.output.compressed : config.paths.output.uncompressed))
+    .pipe(print(log.created))
+    ;
+}
+
+/**
+ * Packages the css files in dist
+ * @param {string} type - type of the css processing (none, rtl, docs)
+ * @param {boolean} compress - should the output be compressed
+ */
+function pack(type, compress) {
+  const output       = type === 'docs' ? docsConfig.paths.output : config.paths.output;
+  const ignoredGlobs = type === 'rtl' ? globs.ignoredRTL + '.rtl.css' : globs.ignored + '.css';
+
+  let concatenatedCSS;
+  if (type === 'rtl') {
+    concatenatedCSS = compress ? filenames.concatenatedMinifiedRTLCSS : filenames.concatenatedRTLCSS;
+  } else {
+    concatenatedCSS = compress ? filenames.concatenatedMinifiedCSS : filenames.concatenatedCSS;
+  }
+
+  return gulp.src(output.uncompressed + '/**/' + globs.components + ignoredGlobs)
+    .pipe(plumber())
+    .pipe(dedupe())
+    .pipe(replace(assets.uncompressed, assets.packaged))
+    .pipe(concatCSS(concatenatedCSS, settings.concatCSS))
+    .pipe(gulpif(config.hasPermissions, chmod(config.parsedPermissions)))
+    .pipe(gulpif(compress, minifyCSS(settings.concatMinify)))
+    .pipe(header(banner, settings.header))
+    .pipe(gulp.dest(output.packaged))
+    .pipe(print(log.created))
+    ;
+}
+
+function buildCSS(src, type, config, opts, callback) {
+  if (!install.isSetup()) {
+    console.error('Cannot build CSS files. Run "gulp install" to set-up Semantic');
+    callback();
+    return;
+  }
+
+  if (callback === undefined) {
+    callback = opts;
+    opts     = config;
+    config   = type;
+    type     = src;
+    src      = config.paths.source.definitions + '/**/' + config.globs.components + '.less';
+  }
+
+  const buildUncompressed       = () => build(src, type, false, config, opts);
+  buildUncompressed.displayName = 'Building uncompressed CSS';
+
+  const buildCompressed       = () => build(src, type, true, config, opts);
+  buildCompressed.displayName = 'Building compressed CSS';
+
+  const packUncompressed       = () => pack(type, false);
+  packUncompressed.displayName = 'Packing uncompressed CSS';
+
+  const packCompressed       = () => pack(type, true);
+  packCompressed.displayName = 'Packing compressed CSS';
+
+  gulp.parallel(
+    gulp.series(
+      buildUncompressed,
+      gulp.parallel(packUncompressed, packCompressed)
+    ),
+    gulp.series(buildCompressed)
+  )(callback);
+}
+
+function rtlAndNormal(src, callback) {
+  if (callback === undefined) {
+    callback = src;
+    src      = config.paths.source.definitions + '/**/' + config.globs.components + '.less';
+  }
+
+  const rtl       = (callback) => buildCSS(src, 'rtl', config, {}, callback);
+  rtl.displayName = "CSS Right-To-Left";
+  const css       = (callback) => buildCSS(src, 'default', config, {}, callback);
+  css.displayName = "CSS";
+
+  if (config.rtl === true || config.rtl === 'Yes') {
+    rtl(callback);
+  } else if (config.rtl === 'both') {
+    gulp.series(rtl, css)(callback);
+  } else {
+    css(callback);
+  }
+}
+
+function docs(src, callback) {
+  if (callback === undefined) {
+    callback = src;
+    src      = config.paths.source.definitions + '/**/' + config.globs.components + '.less';
+  }
+
+  const func       = (callback) => buildCSS(src, 'docs', config, {}, callback);
+  func.displayName = "CSS Docs";
+
+  func(callback);
+}
+
+// Default tasks
+module.exports = rtlAndNormal;
+
+// We keep the changed files in an array to call build with all of them at the same time
+let timeout, files = [];
+
+/**
+ * Watch changes in CSS files and call the correct build pipe
+ * @param type
+ * @param config
+ */
+module.exports.watch = function (type, config) {
+  const method = type === 'docs' ? docs : rtlAndNormal;
+
+  // Watch theme.config file
+  gulp.watch([
+    normalize(config.paths.source.config),
+    normalize(config.paths.source.site + '/**/site.variables'),
+    normalize(config.paths.source.themes + '/**/site.variables')
+  ])
+    .on('all', function () {
+      // Clear timeout and reset files
+      timeout && clearTimeout(timeout);
+      files = [];
+      return gulp.series(method)();
+    });
+
+  // Watch any less / overrides / variables files
+  gulp.watch([
+    normalize(config.paths.source.definitions + '/**/*.less'),
+    normalize(config.paths.source.site + '/**/*.{overrides,variables}'),
+    normalize(config.paths.source.themes + '/**/*.{overrides,variables}')
+  ])
+    .on('all', function (event, path) {
+      // We don't handle deleted files yet
+      if (event === 'unlink' || event === 'unlinkDir') {
+        return;
+      }
+
+      // Clear timeout
+      timeout && clearTimeout(timeout);
+
+      // Determine which LESS file has to be recompiled
+      let lessPath;
+      if(path.indexOf('site.variables') !== -1)  {
+        return;
+      } else if (path.indexOf(config.paths.source.themes) !== -1) {
+        console.log('Change detected in packaged theme');
+        lessPath = replaceExt(path, '.less');
+        lessPath = lessPath.replace(tasks.regExp.theme, config.paths.source.definitions);
+      } else if (path.indexOf(config.paths.source.site) !== -1) {
+        console.log('Change detected in site theme');
+        lessPath = replaceExt(path, '.less');
+        lessPath = lessPath.replace(config.paths.source.site, config.paths.source.definitions);
+      } else {
+        console.log('Change detected in definition');
+        lessPath = path;
+      }
+
+      // Add file to internal changed files array
+      if (!files.includes(lessPath)) {
+        files.push(lessPath);
+      }
+
+      // Update timeout
+      timeout = setTimeout(() => {
+        // Copy files to build in another array
+        const buildFiles = [...files];
+        // Call method
+        gulp.series((callback) => method(buildFiles, callback))();
+        // Reset internal changed files array
+        files = [];
+      }, 1000);
+    });
+};
+
+// Expose build css method
+module.exports.buildCSS = buildCSS;
diff --git a/webpack.config.js b/webpack.config.js
index 77680cb379..e87dd770cb 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,6 +1,5 @@
 const cssnano = require('cssnano');
 const fastGlob = require('fast-glob');
-const CopyPlugin = require('copy-webpack-plugin');
 const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
@@ -210,10 +209,6 @@ module.exports = {
     new SpriteLoaderPlugin({
       plainSprite: true,
     }),
-    new CopyPlugin([
-      // workaround for https://github.com/go-gitea/gitea/issues/10653
-      {from: 'node_modules/fomantic-ui/dist/semantic.min.css', to: 'fomantic/semantic.min.css'},
-    ]),
   ],
   performance: {
     hints: false,