diff --git a/.dockerignore b/.dockerignore
index 7143c039fd..b299c7313d 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -78,7 +78,6 @@ cpu.out
 /public/assets/css
 /public/assets/fonts
 /public/assets/img/avatar
-/public/assets/img/webpack
 /vendor
 /web_src/fomantic/node_modules
 /web_src/fomantic/build/*
diff --git a/.gitignore b/.gitignore
index abf9565cff..501fef7dcf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,7 +77,6 @@ cpu.out
 /public/assets/css
 /public/assets/fonts
 /public/assets/licenses.txt
-/public/assets/img/webpack
 /vendor
 /web_src/fomantic/node_modules
 /web_src/fomantic/build/*
diff --git a/Makefile b/Makefile
index b4fa62e05e..8489520920 100644
--- a/Makefile
+++ b/Makefile
@@ -119,7 +119,7 @@ FOMANTIC_WORK_DIR := web_src/fomantic
 WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
 WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
 WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
-WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
+WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
 
 BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
 BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
diff --git a/package-lock.json b/package-lock.json
index fa4f80fbe8..25fe14e1a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,11 +9,11 @@
         "@citation-js/plugin-bibtex": "0.7.9",
         "@citation-js/plugin-csl": "0.7.9",
         "@citation-js/plugin-software-formats": "0.6.1",
-        "@claviska/jquery-minicolors": "2.3.6",
         "@github/markdown-toolbar-element": "2.2.3",
         "@github/relative-time-element": "4.4.0",
         "@github/text-expander-element": "2.6.1",
         "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
+        "@melloware/coloris": "0.23.0",
         "@primer/octicons": "19.9.0",
         "add-asset-webpack-plugin": "2.0.1",
         "ansi_up": "6.0.2",
@@ -394,14 +394,6 @@
         "node": ">=14.0.0"
       }
     },
-    "node_modules/@claviska/jquery-minicolors": {
-      "version": "2.3.6",
-      "resolved": "https://registry.npmjs.org/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.6.tgz",
-      "integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==",
-      "peerDependencies": {
-        "jquery": ">= 1.7.x"
-      }
-    },
     "node_modules/@csstools/css-parser-algorithms": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
@@ -1297,6 +1289,11 @@
         "@mcaptcha/core-glue": "^0.1.0-alpha-5"
       }
     },
+    "node_modules/@melloware/coloris": {
+      "version": "0.23.0",
+      "resolved": "https://registry.npmjs.org/@melloware/coloris/-/coloris-0.23.0.tgz",
+      "integrity": "sha512-VGIjI9+IQwg6BHjIE10yl0K2ARYz5bsjn6BgFEs1y1ErPAQymgdoxwVcSVL4Ai5t9OVs8xaCB7JKHqFu2N96Ow=="
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
diff --git a/package.json b/package.json
index b5bfda9dc6..d5a1d46056 100644
--- a/package.json
+++ b/package.json
@@ -8,11 +8,11 @@
     "@citation-js/plugin-bibtex": "0.7.9",
     "@citation-js/plugin-csl": "0.7.9",
     "@citation-js/plugin-software-formats": "0.6.1",
-    "@claviska/jquery-minicolors": "2.3.6",
     "@github/markdown-toolbar-element": "2.2.3",
     "@github/relative-time-element": "4.4.0",
     "@github/text-expander-element": "2.6.1",
     "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
+    "@melloware/coloris": "0.23.0",
     "@primer/octicons": "19.9.0",
     "add-asset-webpack-plugin": "2.0.1",
     "ansi_up": "6.0.2",
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl
index b45174b086..33dd758c79 100644
--- a/templates/projects/view.tmpl
+++ b/templates/projects/view.tmpl
@@ -42,8 +42,8 @@
 
 						<div class="field color-field">
 							<label for="new_project_column_color_picker">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
-							<div class="color picker column">
-								<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
+							<div class="js-color-picker-input column">
+								<input maxlength="7" placeholder="#c320f6" id="new_project_column_color_picker" name="color">
 								{{template "repo/issue/label_precolors"}}
 							</div>
 						</div>
@@ -114,8 +114,8 @@
 
 											<div class="field color-field">
 												<label for="new_project_column_color">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
-												<div class="color picker column">
-													<input class="color-picker" maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">
+												<div class="js-color-picker-input column">
+													<input maxlength="7" placeholder="#c320f6" id="new_project_column_color" name="color" value="{{.Color}}">
 													{{template "repo/issue/label_precolors"}}
 												</div>
 											</div>
diff --git a/templates/repo/issue/labels/edit_delete_label.tmpl b/templates/repo/issue/labels/edit_delete_label.tmpl
index 98e0f47020..fcf69217ea 100644
--- a/templates/repo/issue/labels/edit_delete_label.tmpl
+++ b/templates/repo/issue/labels/edit_delete_label.tmpl
@@ -52,8 +52,8 @@
 			</div>
 			<div class="field color-field">
 				<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
-				<div class="color picker column">
-					<input class="color-picker" name="color" value="#70c24a" required maxlength="7">
+				<div class="column js-color-picker-input">
+					<input name="color" value="#70c24a"placeholder="#c320f6" required maxlength="7">
 					{{template "repo/issue/label_precolors"}}
 				</div>
 			</div>
diff --git a/templates/repo/issue/labels/label_new.tmpl b/templates/repo/issue/labels/label_new.tmpl
index 2b2b2336c4..32fd8e76d7 100644
--- a/templates/repo/issue/labels/label_new.tmpl
+++ b/templates/repo/issue/labels/label_new.tmpl
@@ -27,8 +27,8 @@
 			</div>
 			<div class="field color-field">
 				<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
-				<div class="color picker column">
-					<input class="color-picker" name="color" value="#70c24a" required maxlength="7">
+				<div class="js-color-picker-input column">
+					<input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7">
 					{{template "repo/issue/label_precolors"}}
 				</div>
 			</div>
diff --git a/web_src/css/base.css b/web_src/css/base.css
index 368bc56126..21090f67ba 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -1436,11 +1436,6 @@ table th[data-sortt-desc] .svg {
   vertical-align: -0.15em;
 }
 
-/* for the jquery.minicolors plugin */
-.minicolors-panel {
-  background: var(--color-secondary-dark-1) !important;
-}
-
 .ui.tabular.menu {
   border-color: var(--color-secondary);
 }
diff --git a/web_src/css/features/colorpicker.css b/web_src/css/features/colorpicker.css
new file mode 100644
index 0000000000..0c651cfeb3
--- /dev/null
+++ b/web_src/css/features/colorpicker.css
@@ -0,0 +1,164 @@
+/* This is a stripped-down version of coloris's CSS tailored to our needs. It does only include
+   opaqua colors, and if more features like opacity are needed, the CSS needs to be extended
+   based on upstream: https://github.com/mdbassit/Coloris/blob/main/src/coloris.css. */
+
+.js-color-picker-input {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.js-color-picker-input input {
+  padding-top: 8px !important;
+  padding-bottom: 8px !important;
+  padding-left: 32px !important;
+}
+
+.clr-picker {
+  display: none;
+  flex-wrap: wrap;
+  position: absolute;
+  width: 200px;
+  z-index: 1002; /* above .ui.modal which has 1001 */
+  border-radius: var(--border-radius);
+  background-color: var(--color-menu);
+  justify-content: flex-end;
+  direction: ltr;
+  box-shadow: 0 5px 20px var(--color-shadow);
+  user-select: none;
+}
+
+.clr-picker.clr-open {
+  display: flex;
+}
+
+.clr-gradient {
+  position: relative;
+  width: 100%;
+  height: 100px;
+  border-radius: 3px 3px 0 0;
+  background: linear-gradient(rgba(0,0,0,0), #000), linear-gradient(90deg, #fff, currentcolor); /* stylelint-disable-line scale-unlimited/declaration-strict-value */
+  cursor: pointer;
+}
+
+.clr-marker {
+  position: absolute;
+  width: 12px;
+  height: 12px;
+  margin: -6px 0 0 -6px;
+  border: 1px solid var(--color-white);
+  border-radius: 50%;
+  background-color: currentcolor;
+  cursor: pointer;
+}
+
+.clr-picker input[type="range"]::-webkit-slider-runnable-track {
+  width: 100%;
+  height: 16px;
+}
+
+.clr-picker input[type="range"]::-webkit-slider-thumb {
+  width: 16px;
+  height: 16px;
+  -webkit-appearance: none;
+}
+
+.clr-picker input[type="range"]::-moz-range-track {
+  width: 100%;
+  height: 16px;
+  border: 0;
+}
+
+.clr-picker input[type="range"]::-moz-range-thumb {
+  width: 16px;
+  height: 16px;
+  border: 0;
+}
+
+.clr-hue {
+  background: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%); /* stylelint-disable-line scale-unlimited/declaration-strict-value */
+  position: relative;
+  width: calc(100% - 40px);
+  height: 10px;
+  margin: 10px 20px;
+  border-radius: 4px;
+}
+
+.clr-hue input[type="range"] {
+  position: absolute;
+  width: calc(100% + 32px);
+  margin: 0;
+  background-color: transparent;
+  opacity: 0;
+  cursor: pointer;
+  appearance: none;
+}
+
+.clr-hue div {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  left: 0;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  border: 2px solid var(--color-white);
+  border-radius: 50%;
+  background-color: currentcolor;
+  box-shadow: 0 0 1px var(--color-shadow);
+  pointer-events: none;
+}
+
+.clr-field {
+  flex: 1;
+  position: relative;
+  color: transparent;
+}
+
+.clr-field button {
+  position: absolute;
+  aspect-ratio: 1;
+  height: 16px;
+  left: 10px;
+  top: 50%;
+  transform: translateY(-50%);
+  margin: 0;
+  padding: 0;
+  border: 0;
+  color: inherit;
+  pointer-events: none;
+  border-radius: 2px;
+  background: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); /* stylelint-disable-line scale-unlimited/declaration-strict-value */
+  background-position: 0 0, 4px 4px;
+  background-size: 8px 8px;
+}
+
+.clr-field button::after {
+  content: "";
+  display: block;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  border-radius: inherit;
+  background-color: currentcolor;
+}
+
+.clr-marker:focus {
+  outline: none;
+}
+
+.clr-keyboard-nav .clr-marker:focus,
+.clr-keyboard-nav .clr-hue input:focus + div,
+.clr-keyboard-nav .clr-alpha input:focus + div {
+  outline: none;
+  box-shadow: 0 0 2px 2px var(--color-white);
+}
+
+.clr-picker .clr-preview,
+.clr-picker .clr-clear,
+.clr-picker .clr-swatches,
+.clr-picker .clr-format,
+.clr-picker .clr-alpha,
+.clr-picker .clr-color {
+  display: none;
+}
diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css
index 30df994c38..cec5e6fc64 100644
--- a/web_src/css/features/projects.css
+++ b/web_src/css/features/projects.css
@@ -102,26 +102,3 @@
 .card-ghost * {
   opacity: 0;
 }
-
-.color-field .minicolors.minicolors-theme-default {
-  display: block;
-}
-
-.color-field .minicolors.minicolors-theme-default .minicolors-input {
-  height: 38px;
-  padding-left: 2rem;
-}
-
-.color-field .minicolors.minicolors-theme-default .minicolors-swatch {
-  top: 10px;
-}
-
-.edit-project-column-modal .color.picker.column,
-.new-project-column-modal .color.picker.column {
-  display: flex;
-}
-
-.edit-project-column-modal .color.picker.column .minicolors,
-.new-project-column-modal .color.picker.column .minicolors {
-  flex: 1;
-}
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 18f28dc4a6..780093fb7f 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -2260,24 +2260,6 @@
   padding-top: 15px;
 }
 
-.edit-label.modal .form .color.picker.column,
-.new-label.modal .form .color.picker.column {
-  display: flex;
-}
-
-.edit-label.modal .form .color.picker.column .minicolors,
-.new-label.modal .form .color.picker.column .minicolors {
-  flex: 1;
-}
-
-.edit-label.modal .form .minicolors-swatch.minicolors-sprite,
-.new-label.modal .form .minicolors-swatch.minicolors-sprite {
-  top: 10px;
-  left: 10px;
-  width: 15px;
-  height: 15px;
-}
-
 .tab-size-1 {
   tab-size: 1 !important;
   -moz-tab-size: 1 !important;
diff --git a/web_src/js/features/colorpicker.js b/web_src/js/features/colorpicker.js
index df0353376d..f342598e66 100644
--- a/web_src/js/features/colorpicker.js
+++ b/web_src/js/features/colorpicker.js
@@ -1,12 +1,31 @@
-import $ from 'jquery';
+export async function initColorPickers(selector = '.js-color-picker-input input', opts = {}) {
+  const inputEls = document.querySelectorAll(selector);
+  if (!inputEls.length) return;
 
-export async function createColorPicker(els) {
-  if (!els.length) return;
-
-  await Promise.all([
-    import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors'),
-    import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors/jquery.minicolors.css'),
+  const [{coloris, init}] = await Promise.all([
+    import(/* webpackChunkName: "colorpicker" */'@melloware/coloris'),
+    import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'),
   ]);
 
-  return $(els).minicolors();
+  init();
+  coloris({
+    el: selector,
+    alpha: false,
+    focusInput: true,
+    selectInput: false,
+    ...opts,
+  });
+
+  for (const inputEl of inputEls) {
+    const parent = inputEl.closest('.js-color-picker-input');
+    // prevent tabbing on the color preview `button` inside the input
+    parent.querySelector('button').tabIndex = -1;
+    // init precolors
+    for (const el of parent.querySelectorAll('.precolors .color')) {
+      el.addEventListener('click', (e) => {
+        inputEl.value = e.target.getAttribute('data-color-hex');
+        inputEl.dispatchEvent(new Event('input', {bubbles: true}));
+      });
+    }
+  }
 }
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
index 18849ba7c1..ce702f041f 100644
--- a/web_src/js/features/common-global.js
+++ b/web_src/js/features/common-global.js
@@ -2,7 +2,6 @@ import $ from 'jquery';
 import '../vendor/jquery.are-you-sure.js';
 import {clippie} from 'clippie';
 import {createDropzone} from './dropzone.js';
-import {initCompColorPicker} from './comp/ColorPicker.js';
 import {showGlobalErrorMessage} from '../bootstrap.js';
 import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
 import {svg} from '../svg.js';
@@ -379,10 +378,7 @@ function initGlobalShowModal() {
         $attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
       }
     }
-    const $colorPickers = $modal.find('.color-picker');
-    if ($colorPickers.length > 0) {
-      initCompColorPicker(); // FIXME: this might cause duplicate init
-    }
+
     $modal.modal('setting', {
       onApprove: () => {
         // "form-fetch-action" can handle network errors gracefully,
diff --git a/web_src/js/features/comp/ColorPicker.js b/web_src/js/features/comp/ColorPicker.js
deleted file mode 100644
index d7e7038803..0000000000
--- a/web_src/js/features/comp/ColorPicker.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import $ from 'jquery';
-import {createColorPicker} from '../colorpicker.js';
-
-export function initCompColorPicker() {
-  (async () => {
-    await createColorPicker(document.querySelectorAll('.color-picker'));
-
-    for (const el of document.querySelectorAll('.precolors .color')) {
-      el.addEventListener('click', (e) => {
-        const color = e.target.getAttribute('data-color-hex');
-        const parent = e.target.closest('.color.picker');
-        $(parent.querySelector('.color-picker')).minicolors('value', color);
-      });
-    }
-  })();
-}
diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js
index 843657a6b6..2cc75cc6b0 100644
--- a/web_src/js/features/comp/LabelEdit.js
+++ b/web_src/js/features/comp/LabelEdit.js
@@ -1,5 +1,4 @@
 import $ from 'jquery';
-import {initCompColorPicker} from './ColorPicker.js';
 
 function isExclusiveScopeName(name) {
   return /.*[^/]\/[^/].*/.test(name);
@@ -28,13 +27,17 @@ function updateExclusiveLabelEdit(form) {
 
 export function initCompLabelEdit(selector) {
   if (!$(selector).length) return;
-  initCompColorPicker();
 
   // Create label
   $('.new-label.button').on('click', () => {
     updateExclusiveLabelEdit('.new-label');
     $('.new-label.modal').modal({
       onApprove() {
+        const form = document.querySelector('.new-label.form');
+        if (!form.checkValidity()) {
+          form.reportValidity();
+          return false;
+        }
         $('.new-label.form').trigger('submit');
       },
     }).modal('show');
@@ -60,10 +63,18 @@ export function initCompLabelEdit(selector) {
     updateExclusiveLabelEdit('.edit-label');
 
     $('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
-    $('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color'));
+
+    const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
+    colorInput.value = this.getAttribute('data-color');
+    colorInput.dispatchEvent(new Event('input', {bubbles: true}));
 
     $('.edit-label.modal').modal({
       onApprove() {
+        const form = document.querySelector('.edit-label.form');
+        if (!form.checkValidity()) {
+          form.reportValidity();
+          return false;
+        }
         $('.edit-label.form').trigger('submit');
       },
     }).modal('show');
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 4c707486bd..fc2f6b9b0b 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -86,6 +86,7 @@ import {initRepoRecentCommits} from './features/recent-commits.js';
 import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
 import {initDirAuto} from './modules/dirauto.js';
 import {initRepositorySearch} from './features/repo-search.js';
+import {initColorPickers} from './features/colorpicker.js';
 
 // Init Gitea's Fomantic settings
 initGiteaFomantic();
@@ -188,4 +189,5 @@ onDomReady(() => {
   initRepoDiffView();
   initPdfViewer();
   initScopedAccessTokenCategories();
+  initColorPickers();
 });
diff --git a/webpack.config.js b/webpack.config.js
index 0b0e7403e8..fdf80a5313 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -192,13 +192,6 @@ export default {
           filename: 'fonts/[name].[contenthash:8][ext]',
         },
       },
-      {
-        test: /\.png$/i,
-        type: 'asset/resource',
-        generator: {
-          filename: 'img/webpack/[name].[contenthash:8][ext]',
-        },
-      },
     ],
   },
   plugins: [