feat: add CSS variables for status border and text

The variables like `--color-error` now are based on our secondary
theming, thus they are less "aggressive" colors. But there are two
usecases for primary based status colors:
- borders
- error text messages (e.g. validation errors in forms)

To simplify app changes due to the secondary color theme change this
introduces 3 new variables:
- `--color-text-error` this shall be used if text should have error
  status theming and is displayed on normal background (while
  `--color-error-text` is only for text shown on `--color-error` similar
  as primary and secondary colors)
- `--color-border-error` and `--color-border-success` those should be
  used for element borders if there is one of those statuses to be
  reported (we use this for validation errors as well as for indicating a
  value was saved)

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/54492/head
Ferdinand Thiessen 2025-08-18 18:11:34 +07:00
parent d779255c5f
commit d79e18ab18
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
6 changed files with 102 additions and 79 deletions

@ -18,28 +18,33 @@
--color-text-maxcontrast: #6b6b6b;
--color-text-maxcontrast-default: #6b6b6b;
--color-text-maxcontrast-background-blur: #595959;
--color-text-error: #c90000;
/** @deprecated use ` --color-main-text` instead */
--color-text-light: var(--color-main-text);
/** @deprecated use `--color-text-maxcontrast` instead */
--color-text-lighter: var(--color-text-maxcontrast);
--color-scrollbar: var(--color-border-maxcontrast) transparent;
--color-error: #FFE7E7;
--color-error-rgb: 255,231,231;
--color-error-hover: #ffc3c3;
--color-error-text: #8A0000;
--color-warning: #FFEEC5;
--color-warning-rgb: 255,238,197;
--color-warning-hover: #ffe4a1;
--color-warning-text: #664700;
--color-success: #D8F3DA;
--color-success-rgb: 216,243,218;
--color-success-hover: #bdebc0;
--color-success-text: #005416;
--color-info: #D5F1FA;
--color-info-rgb: 213,241,250;
--color-info-hover: #b5e6f6;
--color-info-text: #0066AC;
--color-favorite: #A37200;
/** @deprecated use css color functions */
--color-error-rgb: 255,231,231;
/** @deprecated use css color functions */
--color-warning-rgb: 255,238,197;
/** @deprecated use css color functions */
--color-success-rgb: 216,243,218;
/** @deprecated use css color functions */
--color-info-rgb: 213,241,250;
--color-loading-light: #cccccc;
--color-loading-dark: #444444;
--color-box-shadow-rgb: 77,77,77;
@ -47,6 +52,8 @@
--color-border: #ededed;
--color-border-dark: #dbdbdb;
--color-border-maxcontrast: #7d7d7d;
--color-border-error: #c90000;
--color-border-success: #099f05;
--font-face: system-ui, -apple-system, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', 'Noto Sans', 'Liberation Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--default-font-size: 15px;
--font-size-small: 13px;

@ -66,6 +66,7 @@ class DarkHighContrastTheme extends DarkTheme implements ITheme {
'--color-text-maxcontrast' => $colorMainText,
'--color-text-maxcontrast-background-blur' => $colorMainText,
'--color-text-error' => $this->util->lighten($colorError, 65),
'--color-text-light' => $colorMainText,
'--color-text-lighter' => $colorMainText,
@ -101,6 +102,8 @@ class DarkHighContrastTheme extends DarkTheme implements ITheme {
'--color-border' => $this->util->lighten($colorMainBackground, 50),
'--color-border-dark' => $this->util->lighten($colorMainBackground, 50),
'--color-border-maxcontrast' => $this->util->lighten($colorMainBackground, 55),
'--color-border-error' => $this->util->lighten($colorError, 30),
'--color-border-success' => $this->util->lighten($colorSuccess, 30),
]
);
}

@ -52,14 +52,16 @@ class DarkTheme extends DefaultTheme implements ITheme {
$colorBoxShadow = $this->util->darken($colorMainBackground, 70);
$colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
$colorError = '#FFCCCC';
$colorErrorElement = '#552121';
$colorWarning = '#FFEEC5';
$colorWarningElement = '#3D3010';
$colorSuccess = '#D5F2DC';
$colorSuccessElement = '#11321A';
$colorInfo = '#00AEFF';
$colorInfoElement = '#003553';
$colorError = '#552121';
$colorErrorText = '#FFCCCC';
$colorErrorElement = '#ff6c69';
$colorWarning = '#3D3010';
$colorWarningText = '#FFEEC5';
$colorSuccess = '#11321A';
$colorSuccessText = '#D5F2DC';
$colorSuccessElement = '#3B973B';
$colorInfo = '#003553';
$colorInfoText = '#00AEFF';
return array_merge(
$defaultVariables,
@ -80,26 +82,28 @@ class DarkTheme extends DefaultTheme implements ITheme {
'--color-text-maxcontrast' => $colorTextMaxcontrast,
'--color-text-maxcontrast-default' => $colorTextMaxcontrast,
'--color-text-maxcontrast-background-blur' => $this->util->lighten($colorTextMaxcontrast, 6),
'--color-text-error' => $colorErrorElement,
'--color-text-light' => 'var(--color-main-text)', // deprecated
'--color-text-lighter' => 'var(--color-text-maxcontrast)', // deprecated
'--color-error' => $colorErrorElement,
'--color-error-rgb' => join(',', $this->util->hexToRGB($colorErrorElement)),
'--color-error-hover' => $this->util->lighten($colorErrorElement, 10),
'--color-error-text' => $colorError,
'--color-warning' => $colorWarningElement,
'--color-warning-rgb' => join(',', $this->util->hexToRGB($colorWarningElement)),
'--color-warning-hover' => $this->util->lighten($colorWarningElement, 10),
'--color-warning-text' => $colorWarning,
'--color-success' => $colorSuccessElement,
'--color-success-rgb' => join(',', $this->util->hexToRGB($colorSuccessElement)),
'--color-success-hover' => $this->util->lighten($colorSuccessElement, 10),
'--color-success-text' => $colorSuccess,
'--color-info' => $colorInfoElement,
'--color-info-rgb' => join(',', $this->util->hexToRGB($colorInfoElement)),
'--color-info-hover' => $this->util->lighten($colorInfoElement, 10),
'--color-info-text' => $colorInfo,
'--color-error' => $colorError,
'--color-error-hover' => $this->util->lighten($colorError, 10),
'--color-error-text' => $colorErrorText,
'--color-warning' => $colorWarning,
'--color-warning-hover' => $this->util->lighten($colorWarning, 10),
'--color-warning-text' => $colorWarningText,
'--color-success' => $colorSuccess,
'--color-success-hover' => $this->util->lighten($colorSuccess, 10),
'--color-success-text' => $colorSuccessText,
'--color-info' => $colorInfo,
'--color-info-hover' => $this->util->lighten($colorInfo, 10),
'--color-info-text' => $colorInfoText,
'--color-favorite' => '#ffde00',
// deprecated
'--color-error-rgb' => join(',', $this->util->hexToRGB($colorError)),
'--color-warning-rgb' => join(',', $this->util->hexToRGB($colorWarning)),
'--color-success-rgb' => join(',', $this->util->hexToRGB($colorSuccess)),
'--color-info-rgb' => join(',', $this->util->hexToRGB($colorInfo)),
// used for the icon loading animation
'--color-loading-light' => '#777',
@ -111,6 +115,8 @@ class DarkTheme extends DefaultTheme implements ITheme {
'--color-border' => $this->util->lighten($colorMainBackground, 7),
'--color-border-dark' => $this->util->lighten($colorMainBackground, 14),
'--color-border-maxcontrast' => $this->util->lighten($colorMainBackground, 40),
'--color-border-error' => $colorErrorElement,
'--color-border-success' => $colorSuccessElement,
'--background-invert-if-dark' => 'invert(100%)',
'--background-invert-if-bright' => 'no',

@ -77,14 +77,16 @@ class DefaultTheme implements ITheme {
$colorBoxShadow = $this->util->darken($colorMainBackground, 70);
$colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
$colorError = '#8A0000';
$colorErrorElement = '#FFE7E7';
$colorWarning = '#664700';
$colorWarningElement = '#FFEEC5';
$colorSuccess = '#005416';
$colorSuccessElement = '#D8F3DA';
$colorInfo = '#0066AC';
$colorInfoElement = '#D5F1FA';
$colorError = '#FFE7E7';
$colorErrorText = '#8A0000';
$colorErrorElement = '#c90000';
$colorWarning = '#FFEEC5';
$colorWarningText = '#664700';
$colorSuccess = '#D8F3DA';
$colorSuccessText = '#005416';
$colorSuccessElement = '#099f05';
$colorInfo = '#D5F1FA';
$colorInfoText = '#0066AC';
$user = $this->userSession->getUser();
// Chromium based browsers currently (2024) have huge performance issues with blur filters
@ -126,29 +128,31 @@ class DefaultTheme implements ITheme {
'--color-text-maxcontrast' => $colorTextMaxcontrast,
'--color-text-maxcontrast-default' => $colorTextMaxcontrast,
'--color-text-maxcontrast-background-blur' => $this->util->darken($colorTextMaxcontrast, 7),
'--color-text-error' => $colorErrorElement,
'--color-text-light' => 'var(--color-main-text)', // deprecated
'--color-text-lighter' => 'var(--color-text-maxcontrast)', // deprecated
'--color-scrollbar' => 'var(--color-border-maxcontrast) transparent',
// error/warning/success/info feedback colours
'--color-error' => $colorErrorElement,
'--color-error-rgb' => join(',', $this->util->hexToRGB($colorErrorElement)),
'--color-error-hover' => $this->util->darken($colorErrorElement, 7),
'--color-error-text' => $colorError,
'--color-warning' => $colorWarningElement,
'--color-warning-rgb' => join(',', $this->util->hexToRGB($colorWarningElement)),
'--color-warning-hover' => $this->util->darken($colorWarningElement, 7),
'--color-warning-text' => $colorWarning,
'--color-success' => $colorSuccessElement,
'--color-success-rgb' => join(',', $this->util->hexToRGB($colorSuccessElement)),
'--color-success-hover' => $this->util->darken($colorSuccessElement, 7),
'--color-success-text' => $colorSuccess,
'--color-info' => $colorInfoElement,
'--color-info-rgb' => join(',', $this->util->hexToRGB($colorInfoElement)),
'--color-info-hover' => $this->util->darken($colorInfoElement, 7),
'--color-info-text' => $colorInfo,
// error/warning/success/info feedback colors
'--color-error' => $colorError,
'--color-error-hover' => $this->util->darken($colorError, 7),
'--color-error-text' => $colorErrorText,
'--color-warning' => $colorWarning,
'--color-warning-hover' => $this->util->darken($colorWarning, 7),
'--color-warning-text' => $colorWarningText,
'--color-success' => $colorSuccess,
'--color-success-hover' => $this->util->darken($colorSuccess, 7),
'--color-success-text' => $colorSuccessText,
'--color-info' => $colorInfo,
'--color-info-hover' => $this->util->darken($colorInfo, 7),
'--color-info-text' => $colorInfoText,
'--color-favorite' => '#A37200',
// deprecated
'--color-error-rgb' => join(',', $this->util->hexToRGB($colorError)),
'--color-warning-rgb' => join(',', $this->util->hexToRGB($colorWarning)),
'--color-success-rgb' => join(',', $this->util->hexToRGB($colorSuccess)),
'--color-info-rgb' => join(',', $this->util->hexToRGB($colorInfo)),
// used for the icon loading animation
'--color-loading-light' => '#cccccc',
@ -160,6 +164,8 @@ class DefaultTheme implements ITheme {
'--color-border' => $this->util->darken($colorMainBackground, 7),
'--color-border-dark' => $this->util->darken($colorMainBackground, 14),
'--color-border-maxcontrast' => $this->util->darken($colorMainBackground, 51),
'--color-border-error' => $colorErrorElement,
'--color-border-success' => $colorSuccessElement,
'--font-face' => "system-ui, -apple-system, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', 'Noto Sans', 'Liberation Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
'--default-font-size' => '15px',

@ -69,6 +69,7 @@ class HighContrastTheme extends DefaultTheme implements ITheme {
'--color-text-maxcontrast' => $colorMainText,
'--color-text-maxcontrast-background-blur' => $colorMainText,
'--color-text-error' => $this->util->darken($colorError, 65),
'--color-text-light' => $colorMainText,
'--color-text-lighter' => $colorMainText,
@ -106,6 +107,8 @@ class HighContrastTheme extends DefaultTheme implements ITheme {
'--color-border' => $this->util->darken($colorMainBackground, 50),
'--color-border-dark' => $this->util->darken($colorMainBackground, 50),
'--color-border-maxcontrast' => $this->util->darken($colorMainBackground, 56),
'--color-border-error' => $this->util->darken($colorError, 42),
'--color-border-success' => $this->util->darken($colorSuccess, 55),
// remove the gradient from the app icons
'--header-menu-icon-mask' => 'none',

@ -64,17 +64,6 @@ class AccessibleThemeTestCase extends TestCase {
],
$elementContrast,
],
// Those two colors are used for borders which will be `color-main-text` on focussed state, thus need 3:1 contrast to it
'success-error-border-colors' => [
[
'--color-error',
'--color-success',
],
[
'--color-main-text',
],
$elementContrast,
],
'primary-element-text' => [
[
'--color-primary-element-text',
@ -121,21 +110,6 @@ class AccessibleThemeTestCase extends TestCase {
],
$textContrast,
],
'status-text-on-background' => [
[
'--color-error-text',
'--color-warning-text',
'--color-success-text',
'--color-info-text',
],
[
'--color-main-background',
'--color-background-hover',
'--color-background-dark',
'--color-main-background-blur',
],
$textContrast,
],
'text-on-status-background' => [
[
'--color-main-text',
@ -161,6 +135,30 @@ class AccessibleThemeTestCase extends TestCase {
],
$textContrast,
],
'status-border-colors-on-background' => [
[
'--color-border-error',
'--color-border-success',
],
[
'--color-main-background',
'--color-background-hover',
'--color-background-dark',
],
$elementContrast,
],
'error-text-on-background' => [
[
'--color-text-error',
],
[
'--color-main-background',
'--color-background-hover',
'--color-background-dark',
'--color-main-background-blur',
],
$textContrast,
],
'error-text-on-error-background' => [
['--color-error-text'],
[