feat(web): add semantic CSS custom-property color layer (#8316)

* feat(web): add semantic CSS custom-property color layer

Introduce a :root set of semantic color tokens (surfaces, text, borders, accent, state) in global.less and route the recurring, directly-used colors across the LESS files through them.
Light-mode values are byte-equal to the literals they replace, so this is a visual no-op; it exists to give a future dark theme a single place to override.
Colors that LESS functions (lighten/darken/fadeout) consume, and one-off decorative colors, are intentionally left as literals because those functions cannot operate on var().

Part of #7789.

* [autofix.ci] apply automated fixes

* refactor(web): route remaining hardcoded colors through color tokens

Route the remaining directly-used background, text, and border literals across the LESS files through the existing semantic tokens introduced in the color layer, reusing tokens rather than adding new ones.
Light-mode colors stay visually unchanged; one-off decorative colors and LESS-function inputs are left as literals.

* refactor(web): tokenize recurring colors and drop dead styles

Add a --mitmweb-highlight token for the shared row-hover / suggestion-selection color (used in header.less and command.less) and a --mitmweb-gray token for the neutral gray reused across mode.less.
Remove the unused .command-suggestion rule and the dead rgba() border fallback in dropdown.less, which was overriding the tokenized border and rendering as translucent black.

* refactor(web): use the accent token for the first line and special tab

Replace the near-accent literals #428bca (flow detail first line) and #396cad (special tab) with --mitmweb-accent so the accent lives in exactly one place.
Both were within a few percent of the token, so this is a small deliberate light-theme shift rather than a visual no-op.
The special tab derived its hover from lighten(@special-color, 10%); since LESS functions cannot operate on var(), it now uses --mitmweb-accent-active, the same hover shade .btn-primary already uses.

* refactor(web): tokenize the mode green left border

Add a --mitmweb-green token for the #77c77a left border in mode.less, mirroring the --mitmweb-gray token already used by the neighbouring .gray-left-border.

* refactor(web): tokenize flow table row state colors

Add --mitmweb-row-* tokens for the highlighted, selected, and selected+highlighted row shades and route flowtable.less through them.
The base rows reuse the existing --mitmweb-bg / --mitmweb-bg-alt surfaces, whose values are byte-equal to the hsl() literals they replace.
Values are unchanged, so this is a visual no-op; it gives the dark theme one place to override the row states.

* docs: drop the changelog entry for the CSS color token layer

The entry conflicts with the upstream CHANGELOG.md on the PR.
Removing it for now; the entry will be re-added once the branch is rebased.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Sleeyax 2026-07-18 14:16:31 +02:00 committed by GitHub
parent 6fa0a50ac8
commit a4b234a3e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 193 additions and 128 deletions

View file

@ -1,4 +1,4 @@
.CodeMirror {
border: 1px solid #ccc;
border: 1px solid var(--mitmweb-border);
height: auto !important;
}

View file

@ -1,23 +1,19 @@
.command-title {
background-color: #f2f2f2;
border: 1px solid #aaa;
background-color: var(--mitmweb-bg-alt);
border: 1px solid var(--mitmweb-border-strong);
}
.command-result {
display: block;
margin: 0px;
background-color: #fcfcfc;
background-color: var(--mitmweb-bg);
height: 100px;
max-height: 100px;
overflow: auto;
}
.command-suggestion {
background-color: #9c9c9c;
}
.argument-suggestion {
background-color: hsla(209, 52%, 84%, 0.5) !important;
background-color: var(--mitmweb-highlight) !important;
}
.command > .popover {

View file

@ -5,11 +5,11 @@
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
color: #333;
color: var(--mitmweb-fg);
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
background-color: var(--mitmweb-bg-alt);
border: 1px solid var(--mitmweb-border);
border-radius: 4px;
overflow: auto;
}

View file

@ -11,9 +11,8 @@
list-style: none;
font-size: 14px;
text-align: left;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 15%);
background-color: var(--mitmweb-bg);
border: 1px solid var(--mitmweb-border);
border-radius: 4px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 17.5%);
background-clip: padding-box;
@ -31,7 +30,7 @@
clear: both;
font-weight: 400;
line-height: 1.42857143;
color: #333;
color: var(--mitmweb-fg);
white-space: nowrap;
text-decoration: none;
}
@ -39,8 +38,8 @@
> li > a:hover,
> li > a:focus {
text-decoration: none;
color: #262626;
background-color: #f5f5f5;
color: var(--mitmweb-fg-strong);
background-color: var(--mitmweb-bg-muted);
}
max-height: 250px;
@ -51,5 +50,5 @@
height: 1px;
margin: 9px 0;
overflow: hidden;
background-color: #e5e5e5;
background-color: var(--mitmweb-border-lighter);
}

View file

@ -6,10 +6,10 @@
flex-direction: column;
> div {
background-color: #f2f2f2;
background-color: var(--mitmweb-bg-alt);
padding: 0 5px;
flex: 0 0 auto;
border-top: 1px solid #aaa;
border-top: 1px solid var(--mitmweb-border-strong);
cursor: row-resize;
}
@ -20,7 +20,7 @@
border-radius: 0;
overflow-x: auto;
overflow-y: scroll;
background-color: #fcfcfc;
background-color: var(--mitmweb-bg);
}
.eventlog-actions {

View file

@ -5,7 +5,7 @@
flex-direction: column;
nav {
background-color: #f2f2f2;
background-color: var(--mitmweb-bg-alt);
}
section {
@ -39,7 +39,7 @@
.first-line {
font-family: @font-family-monospace;
background-color: #428bca;
background-color: var(--mitmweb-accent);
color: white;
margin: 0 -8px 2px;
padding: 4px 8px;
@ -71,7 +71,7 @@
hr {
border: 0;
border-top: 1px solid #eee;
border-top: 1px solid var(--mitmweb-border-lighter);
margin: 0;
}
}
@ -128,7 +128,7 @@
word-break: break-all;
tr:not(:first-child) td {
border-top: 1px solid #f7f7f7;
border-top: 1px solid var(--mitmweb-border-lighter);
}
td {
@ -163,12 +163,12 @@
}
.inline-input {
background-color: white;
background-color: var(--mitmweb-bg);
}
.kv-add-row {
opacity: 0;
color: #666;
color: var(--mitmweb-fg-muted);
position: absolute;
bottom: 4px;
right: 4px;

View file

@ -20,8 +20,8 @@
}
thead tr {
background-color: #f2f2f2;
border-bottom: solid #bebebe 1px;
background-color: var(--mitmweb-bg-alt);
border-bottom: solid var(--mitmweb-border) 1px;
line-height: 23px;
}
@ -59,30 +59,32 @@
cursor: pointer;
& {
background-color: hsl(0, 0%, 100%);
background-color: var(--mitmweb-bg);
&:nth-child(even) {
background-color: hsl(0, 0%, 95%);
background-color: var(--mitmweb-bg-alt);
}
}
&.highlighted {
background-color: hsl(48, 100%, 80%);
background-color: var(--mitmweb-row-highlight);
&:nth-child(even) {
background-color: hsl(48, 100%, 75%);
background-color: var(--mitmweb-row-highlight-alt);
}
}
&.selected {
background-color: hsl(209, 51%, 92%) !important;
background-color: var(--mitmweb-row-selected) !important;
&:nth-child(even) {
background-color: hsl(209, 51%, 86%) !important;
background-color: var(--mitmweb-row-selected-alt) !important;
}
}
&.selected.highlighted {
background-color: hsl(209, 96%, 74%) !important;
background-color: var(--mitmweb-row-selected-highlight) !important;
&:nth-child(even) {
background-color: hsl(209, 96%, 68%) !important;
background-color: var(
--mitmweb-row-selected-highlight-alt
) !important;
}
}
}

View file

@ -1,3 +1,72 @@
// Semantic color tokens.
// Light values below are byte-equal to the literals they replace, so this change is visually a no-op.
// A future dark theme overrides these under a `[data-theme="dark"]` selector; colors that LESS functions
// (lighten/darken/fadeout) consume are left as literals here because those functions cannot operate on var().
:root {
// Surfaces
--mitmweb-bg: #fff;
--mitmweb-bg-alt: #f2f2f2;
--mitmweb-bg-muted: #eee;
--mitmweb-bg-hover: #e6e6e6;
--mitmweb-bg-active: #e5e5e5;
// Text
--mitmweb-fg: #333;
--mitmweb-fg-input: #555;
--mitmweb-fg-muted: #777;
--mitmweb-fg-strong: #000;
// Neutral gray (muted border + text)
--mitmweb-gray: #b2b2b2;
// Green (mode left border)
--mitmweb-green: #77c77a;
// Borders
--mitmweb-border: #ccc;
--mitmweb-border-light: #ddd;
--mitmweb-border-lighter: #e5e5e5;
--mitmweb-border-strong: #adadad;
// Accent (primary)
--mitmweb-accent: #337ab7;
--mitmweb-accent-hover: #23527c;
--mitmweb-accent-active: #286090;
--mitmweb-accent-border: #2e6da4;
--mitmweb-accent-border-active: #204d74;
--mitmweb-focus-ring: #66afe9;
// State — solid backgrounds
--mitmweb-info: #5bc0de;
--mitmweb-info-hover: #31b0d5;
--mitmweb-info-border: #46b8da;
--mitmweb-info-border-hover: #269abc;
--mitmweb-success: #5cb85c;
--mitmweb-danger: #d9534f;
--mitmweb-warning: #f0ad4e;
// State — text
--mitmweb-text-success: #3c763d;
--mitmweb-text-info: #31708f;
--mitmweb-text-warning: #8a6d3b;
--mitmweb-text-danger: #a94442;
// Highlight (row hover / suggestion selection)
--mitmweb-highlight: hsla(209, 52%, 84%, 0.5);
// Flow table row states (base rows reuse --mitmweb-bg / --mitmweb-bg-alt; -alt is the even-row shade)
--mitmweb-row-highlight: hsl(48, 100%, 80%);
--mitmweb-row-highlight-alt: hsl(48, 100%, 75%);
--mitmweb-row-selected: hsl(209, 51%, 92%);
--mitmweb-row-selected-alt: hsl(209, 51%, 86%);
--mitmweb-row-selected-highlight: hsl(209, 96%, 74%);
--mitmweb-row-selected-highlight-alt: hsl(209, 96%, 68%);
// Alert
--mitmweb-alert-warning-bg: #fcf8e3;
--mitmweb-alert-warning-border: #faebcc;
}
// www.paulirish.com/2012/box-sizing-border-box-ftw/
html {
box-sizing: border-box;
@ -32,8 +101,8 @@ body {
font-family: @font-family-sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
color: var(--mitmweb-fg);
background-color: var(--mitmweb-bg);
}
h1,
@ -133,38 +202,38 @@ textarea {
}
.text-primary {
color: #337ab7;
color: var(--mitmweb-accent);
}
.docs-link,
.docs-link:visited {
color: #337ab7;
color: var(--mitmweb-accent);
text-decoration: none;
}
.docs-link:hover,
.docs-link:focus {
color: #23527c;
color: var(--mitmweb-accent-hover);
}
.text-success {
color: #3c763d;
color: var(--mitmweb-text-success);
}
.text-info {
color: #31708f;
color: var(--mitmweb-text-info);
}
.text-warning {
color: #8a6d3b;
color: var(--mitmweb-text-warning);
}
.text-danger {
color: #a94442;
color: var(--mitmweb-text-danger);
}
.text-muted {
color: #777;
color: var(--mitmweb-fg-muted);
}
.hidden {
@ -203,7 +272,7 @@ textarea {
.btn:focus,
.btn:hover {
color: #333;
color: var(--mitmweb-fg);
text-decoration: none;
}
@ -221,42 +290,42 @@ textarea {
}
.btn-default {
color: #333;
background-color: #fff;
border-color: #ccc;
color: var(--mitmweb-fg);
background-color: var(--mitmweb-bg);
border-color: var(--mitmweb-border);
}
.btn-default:hover,
.btn-default:focus {
color: #333;
background-color: #e6e6e6;
border-color: #adadad;
color: var(--mitmweb-fg);
background-color: var(--mitmweb-bg-hover);
border-color: var(--mitmweb-border-strong);
}
.btn-primary {
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
background-color: var(--mitmweb-accent);
border-color: var(--mitmweb-accent-border);
}
.btn-primary:hover,
.btn-primary:focus {
color: #fff;
background-color: #286090;
border-color: #204d74;
background-color: var(--mitmweb-accent-active);
border-color: var(--mitmweb-accent-border-active);
}
.btn-info {
color: #fff;
background-color: #5bc0de;
border-color: #46b8da;
background-color: var(--mitmweb-info);
border-color: var(--mitmweb-info-border);
}
.btn-info:hover,
.btn-info:focus {
color: #fff;
background-color: #31b0d5;
border-color: #269abc;
background-color: var(--mitmweb-info-hover);
border-color: var(--mitmweb-info-border-hover);
}
.btn-sm {
@ -287,19 +356,19 @@ textarea {
}
.label-default {
background-color: #777;
background-color: var(--mitmweb-fg-muted);
}
.label-primary {
background-color: #337ab7;
background-color: var(--mitmweb-accent);
}
.label-success {
background-color: #5cb85c;
background-color: var(--mitmweb-success);
}
.label-danger {
background-color: #d9534f;
background-color: var(--mitmweb-danger);
}
.alert {
@ -310,9 +379,9 @@ textarea {
}
.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
color: var(--mitmweb-text-warning);
background-color: var(--mitmweb-alert-warning-bg);
border-color: var(--mitmweb-alert-warning-border);
}
.input-group {
@ -342,10 +411,10 @@ textarea {
font-size: 14px;
font-weight: 400;
line-height: 1;
color: #555;
color: var(--mitmweb-fg-input);
text-align: center;
background-color: #eee;
border: 1px solid #ccc;
background-color: var(--mitmweb-bg-muted);
border: 1px solid var(--mitmweb-border);
border-radius: 4px;
}
@ -369,7 +438,7 @@ textarea {
max-width: 276px;
padding: 1px;
text-align: left;
background-color: #fff;
background-color: var(--mitmweb-bg);
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 20%);
border-radius: 6px;
@ -413,7 +482,7 @@ textarea {
bottom: 1px;
margin-left: -10px;
border-width: 10px 10px 0;
border-top-color: #fff;
border-top-color: var(--mitmweb-bg);
}
.popover.bottom {
@ -432,7 +501,7 @@ textarea {
top: 1px;
margin-left: -10px;
border-width: 0 10px 10px;
border-bottom-color: #fff;
border-bottom-color: var(--mitmweb-bg);
}
.close {
@ -440,7 +509,7 @@ textarea {
font-size: 21px;
font-weight: 700;
line-height: 1;
color: #000;
color: var(--mitmweb-fg-strong);
text-shadow: 0 1px 0 #fff;
opacity: 0.2;
background: transparent;
@ -451,7 +520,7 @@ textarea {
.close:hover,
.close:focus {
color: #000;
color: var(--mitmweb-fg-strong);
text-decoration: none;
cursor: pointer;
opacity: 0.5;
@ -463,8 +532,8 @@ textarea {
height: auto;
padding: 4px;
line-height: 1.42857143;
background-color: #fff;
border: 1px solid #ddd;
background-color: var(--mitmweb-bg);
border: 1px solid var(--mitmweb-border-light);
border-radius: 4px;
transition: all 0.2s ease-in-out;
}
@ -476,9 +545,9 @@ textarea {
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
color: var(--mitmweb-fg-input);
background-color: var(--mitmweb-bg);
border: 1px solid var(--mitmweb-border);
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 7.5%);
transition:
@ -487,7 +556,7 @@ textarea {
}
.input:focus {
border-color: #66afe9;
border-color: var(--mitmweb-focus-ring);
outline: 0;
box-shadow:
inset 0 1px 1px rgba(0, 0, 0, 7.5%),
@ -503,5 +572,5 @@ textarea.input {
}
.has-error .input {
border-color: #a94442;
border-color: var(--mitmweb-text-danger);
}

View file

@ -1,8 +1,8 @@
@screen-xs-max: 767px;
@label-info-bg: #5bc0de;
@label-success-bg: #5cb85c;
@label-danger-bg: #d9534f;
@label-warning-bg: #f0ad4e;
@label-info-bg: var(--mitmweb-info);
@label-success-bg: var(--mitmweb-success);
@label-danger-bg: var(--mitmweb-danger);
@label-warning-bg: var(--mitmweb-warning);
.connection-label-base() {
display: inline;
@ -21,7 +21,7 @@
header {
padding-top: 6px;
background-color: white;
background-color: var(--mitmweb-bg);
@separator-color: lighten(grey, 15%);
> div:not(:empty) {
display: block;
@ -96,7 +96,7 @@ header {
}
.menu-legend {
color: #777;
color: var(--mitmweb-fg-muted);
height: @menu-legend-height;
text-align: center;
font-size: 12px;
@ -159,27 +159,27 @@ header {
}
a {
color: #337ab7;
color: var(--mitmweb-accent);
}
a:hover,
a:focus {
color: #23527c;
color: var(--mitmweb-accent-hover);
}
tbody tr + tr {
border-top: 1px solid #ddd;
border-top: 1px solid var(--mitmweb-border-light);
}
tbody tr:first-child {
border-top: 1px solid #ddd;
border-top: 1px solid var(--mitmweb-border-light);
}
}
tr {
cursor: pointer;
&:hover {
background-color: hsla(209, 52%, 84%, 0.5) !important;
background-color: var(--mitmweb-highlight) !important;
}
}
}

View file

@ -43,7 +43,7 @@ body,
.splitter {
flex: 0 0 1px;
background-color: #aaa;
background-color: var(--mitmweb-border-strong);
position: relative;
> div {

View file

@ -53,7 +53,7 @@
.modal-content {
position: relative;
background-color: #fff;
background-color: var(--mitmweb-bg);
border: 1px solid rgba(0, 0, 0, 20%);
border-radius: 6px;
box-shadow: 0 3px 9px rgba(0, 0, 0, 50%);
@ -63,7 +63,7 @@
.modal-header {
padding: 15px;
border-bottom: 1px solid #e5e5e5;
border-bottom: 1px solid var(--mitmweb-border-lighter);
min-height: 16.42857143px;
}
@ -80,7 +80,7 @@
.modal-footer {
padding: 15px;
text-align: right;
border-top: 1px solid #e5e5e5;
border-top: 1px solid var(--mitmweb-border-lighter);
}
.form-horizontal .form-row {

View file

@ -13,11 +13,11 @@
}
.green-left-border {
border-left: 10px solid #77c77a;
border-left: 10px solid var(--mitmweb-green);
}
.gray-left-border {
border-left: 10px solid #b2b2b2;
border-left: 10px solid var(--mitmweb-gray);
}
.modes-container {
@ -32,7 +32,7 @@
}
.mode-description {
color: #b2b2b2;
color: var(--mitmweb-gray);
margin-top: -10px;
}
@ -76,7 +76,7 @@
transition: color 120ms ease-in-out;
&:hover {
color: #333;
color: var(--mitmweb-fg);
}
}
.processes-container {
@ -96,7 +96,7 @@
display: flex;
flex-direction: row;
gap: 5px;
background-color: #e0e0e0;
background-color: var(--mitmweb-bg-active);
padding: 5px;
border-radius: 4px;
height: 30px;
@ -122,7 +122,7 @@
.dropdown-header {
padding: 5px;
border: 1px solid #ccc;
border: 1px solid var(--mitmweb-border);
border-radius: 4px;
cursor: pointer;
display: flex;
@ -164,11 +164,11 @@
}
.dropdown-item.selected {
background-color: #e0e0e0;
background-color: var(--mitmweb-bg-active);
}
.dropdown-item:hover {
background-color: #f0f0f0;
background-color: var(--mitmweb-bg-muted);
}
.process-details {
@ -187,7 +187,7 @@
.process-name {
font-weight: 500;
color: #333;
color: var(--mitmweb-fg);
}
.process-separator {
@ -237,7 +237,7 @@
}
.mode-input {
border: 1px solid #ccc;
border: 1px solid var(--mitmweb-border);
margin-left: 10px;
border-radius: 4px;
min-width: 120px;
@ -245,7 +245,7 @@
}
.mode-reverse-input {
border: 1px solid #ccc;
border: 1px solid var(--mitmweb-border);
margin-left: 10px;
margin-right: 10px;
border-radius: 4px;
@ -254,7 +254,7 @@
}
.mode-upstream-input {
border: 1px solid #ccc;
border: 1px solid var(--mitmweb-border);
margin-left: 10px;
margin-right: 5px;
border-radius: 4px;
@ -264,7 +264,7 @@
.mode-reverse-dropdown {
margin: 0 5px;
border: 1px solid #ccc;
border: 1px solid var(--mitmweb-border);
border-radius: 4px;
height: 25px;
}
@ -315,13 +315,13 @@
}
> button .icon {
color: #777;
color: var(--mitmweb-fg-muted);
width: 20px;
height: 20px;
transition: color 120ms ease-in-out;
&:hover {
color: #333;
color: var(--mitmweb-fg);
}
}
@ -350,10 +350,10 @@
}
> div {
border: 1px solid #ccc;
border: 1px solid var(--mitmweb-border);
border-radius: 4px;
padding: 10px 15px;
background-color: #f9f9f9;
background-color: var(--mitmweb-bg);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
&:popover-open {
@ -370,14 +370,14 @@
}
> .mode-input {
text-align: right;
background-color: white;
background-color: var(--mitmweb-bg);
margin: 0;
}
}
}
.missing-mode-container {
color: #b2b2b2;
color: var(--mitmweb-gray);
.title-icon-container {
display: flex;

View file

@ -5,7 +5,7 @@
> a {
display: inline-block;
color: #337ab7;
color: var(--mitmweb-accent);
border: solid transparent 1px;
text-decoration: none;
//text-transform: uppercase;
@ -13,22 +13,21 @@
&:hover,
&:focus {
color: #23527c;
color: var(--mitmweb-accent-hover);
}
&.active {
color: #337ab7;
background-color: white;
color: var(--mitmweb-accent);
background-color: var(--mitmweb-bg);
border-color: @separator-color;
border-bottom-color: white;
border-bottom-color: var(--mitmweb-bg);
}
&.special {
@special-color: #396cad;
color: white;
background-color: @special-color;
border-bottom-color: @special-color;
background-color: var(--mitmweb-accent);
border-bottom-color: var(--mitmweb-accent);
&:hover {
background-color: lighten(@special-color, 10%);
background-color: var(--mitmweb-accent-active);
}
}
}