Repository: umbraco/Umbraco-CMS
Pull request #18273: Feature: Use uui invalid color
Author: login: nielslyngsoe
Labels: name: category/ux
description: User experience
name: type/feature
name: area/frontend
name: release/15.4.0
Created: 2025-02-10T09:48:26Z | Merged: 2025-04-09T10:14:27Z

Fixes https://github.com/umbraco/Umbraco-CMS/issues/17089
Fixes https://github.com/umbraco/Umbraco-CMS/issues/18416

This PR. runs validation + server validation when saving Documents, without preventing save from happening.
Therefor this adjusts the validation feedback color for Saving vs. Publishing. So its not danger-red when Saving, just yellow.


Diff:
diff --git a/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts
index 8fd8587c34d4..e328d10067ab 100644
--- a/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts
+++ b/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts
@@ -1,10 +1,10 @@
 import { html, customElement, css, state, when } from '@umbraco-cms/backoffice/external/lit';
 import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
 import { UMB_VALIDATION_CONTEXT, umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation';
+import type { UmbValidationMessage } from '@umbraco-cms/backoffice/validation';
 
 @customElement('umb-example-validation-context-dashboard')
-export class UmbExampleValidationContextDashboard extends UmbLitElement {
-
+export class UmbExampleValidationContextDashboardElement extends UmbLitElement {
 	readonly validation = new UmbValidationContext(this);
 
 	@state()
@@ -20,7 +20,7 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
 	country = '';
 
 	@state()
-  messages? : any[]
+	messages?: UmbValidationMessage[];
 
 	@state()
 	totalErrorCount = 0;
@@ -32,87 +32,101 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
 	tab2ErrorCount = 0;
 
 	@state()
-	tab = "1";
+	tab = '1';
 
 	constructor() {
 		super();
 
-		this.consumeContext(UMB_VALIDATION_CONTEXT,(validationContext)=>{
-
-      this.observe(validationContext.messages.messages,(messages)=>{
-        this.messages = messages;
-      },'observeValidationMessages')
+		this.consumeContext(UMB_VALIDATION_CONTEXT, (validationContext) => {
+			this.observe(
+				validationContext.messages.messages,
+				(messages) => {
+					this.messages = messages;
+				},
+				'observeValidationMessages',
+			);
 
 			// Observe all errors
-			this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value)=>{
-				this.totalErrorCount = [...new Set(value.map(x=>x.path))].length;
+			this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value) => {
+				this.totalErrorCount = [...new Set(value.map((x) => x.path))].length;
 			});
 
 			// Observe errors for tab1, note that we only use part of the full JSONPath
-			this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value)=>{
-				this.tab1ErrorCount = [...new Set(value.map(x=>x.path))].length;
+			this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value) => {
+				this.tab1ErrorCount = [...new Set(value.map((x) => x.path))].length;
 			});
 
 			// Observe errors for tab2, note that we only use part of the full JSONPath
-			this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value)=>{
-				this.tab2ErrorCount = [...new Set(value.map(x=>x.path))].length;
+			this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value) => {
+				this.tab2ErrorCount = [...new Set(value.map((x) => x.path))].length;
 			});
-
-    });
+		});
 	}
 
-	#onTabChange(e:Event) {
+	#onTabChange(e: Event) {
 		this.tab = (e.target as HTMLElement).getAttribute('data-tab') as string;
 	}
 
 	#handleSave() {
-
 		// fake server validation-errors for all fields
-		if(this.name == '')
-			this.validation.messages.addMessage('server','$.form.tab1.name','Name server-error message','4875e113-cd0c-4c57-ac92-53d677ba31ec');
-		if(this.email == '')
-			this.validation.messages.addMessage('server','$.form.tab1.email','Email server-error message','a47e287b-4ce6-4e8b-8e05-614e7cec1a2a');
-		if(this.city == '')
-			this.validation.messages.addMessage('server','$.form.tab2.city','City server-error message','8dfc2f15-fb9a-463b-bcec-2c5d3ba2d07d');
-		if(this.country == '')
-			this.validation.messages.addMessage('server','$.form.tab2.country','Country server-error message','d98624f6-82a2-4e94-822a-776b44b01495');
+		if (this.name == '')
+			this.validation.messages.addMessage(
+				'server',
+				'$.form.tab1.name',
+				'Name server-error message',
+				'4875e113-cd0c-4c57-ac92-53d677ba31ec',
+			);
+		if (this.email == '')
+			this.validation.messages.addMessage(
+				'server',
+				'$.form.tab1.email',
+				'Email server-error message',
+				'a47e287b-4ce6-4e8b-8e05-614e7cec1a2a',
+			);
+		if (this.city == '')
+			this.validation.messages.addMessage(
+				'server',
+				'$.form.tab2.city',
+				'City server-error message',
+				'8dfc2f15-fb9a-463b-bcec-2c5d3ba2d07d',
+			);
+		if (this.country == '')
+			this.validation.messages.addMessage(
+				'server',
+				'$.form.tab2.country',
+				'Country server-error message',
+				'd98624f6-82a2-4e94-822a-776b44b01495',
+			);
 	}
 
 	override render() {
 		return html`
 			<uui-box>
-				This is a demo of how the Validation Context can be used to validate a form with multiple steps. Start typing in the form or press Save to trigger validation.
-				<hr/>
+				This is a demo of how the Validation Context can be used to validate a form with multiple steps. Start typing in
+				the form or press Save to trigger validation.
+				<hr />
 				Total errors: ${this.totalErrorCount}
-				<hr/>
+				<hr />
 				<uui-tab-group @click=${this.#onTabChange}>
-          <uui-tab ?active=${this.tab == '1'} data-tab="1">
+					<uui-tab ?active=${this.tab == '1'} data-tab="1">
 						Tab 1
-						${when(this.tab1ErrorCount,()=>html`
-							<uui-badge color="danger">${this.tab1ErrorCount}</uui-badge>
-						`)}
+						${when(this.tab1ErrorCount, () => html` <uui-badge color="invalid">${this.tab1ErrorCount}</uui-badge> `)}
 					</uui-tab>
-          <uui-tab ?active=${this.tab == '2'} data-tab="2">
+					<uui-tab ?active=${this.tab == '2'} data-tab="2">
 						Tab 2
-						${when(this.tab2ErrorCount,()=>html`
-							<uui-badge color="danger">${this.tab2ErrorCount}</uui-badge>
-						`)}
+						${when(this.tab2ErrorCount, () => html` <uui-badge color="invalid">${this.tab2ErrorCount}</uui-badge> `)}
 					</uui-tab>
-        </uui-tab-group>
+				</uui-tab-group>
 
-				${when(this.tab=='1',()=>html`
-					${this.#renderTab1()}
-				`)}
-				${when(this.tab=='2',()=>html`
-					${this.#renderTab2()}
-				`)}
+				${when(this.tab == '1', () => html` ${this.#renderTab1()} `)}
+				${when(this.tab == '2', () => html` ${this.#renderTab2()} `)}
 
 				<uui-button look="primary" color="positive" @click=${this.#handleSave}>Save</uui-button>
-				<hr/>
+				<hr />
 				<h3>Validation Context Messages</h3>
-				<pre>${JSON.stringify(this.messages ?? [],null,3)}</pre>
+				<pre>${JSON.stringify(this.messages ?? [], null, 3)}</pre>
 			</uui-box>
-		`
+		`;
 	}
 
 	#renderTab1() {
@@ -120,28 +134,28 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
 			<uui-form>
 				<form>
 					<div>
-					<label>Name</label>
-					<uui-form-validation-message>
-						<uui-input
-							type="text"
-							.value=${this.name}
-							@input=${(e: InputEvent)=>this.name = (e.target as HTMLInputElement).value}
-							${umbBindToValidation(this,'$.form.tab1.name',this.name)}
-							required></uui-input>
-					</uui-form-validation-message>
+						<label>Name</label>
+						<uui-form-validation-message>
+							<uui-input
+								type="text"
+								.value=${this.name}
+								@input=${(e: InputEvent) => (this.name = (e.target as HTMLInputElement).value)}
+								${umbBindToValidation(this, '$.form.tab1.name', this.name)}
+								required></uui-input>
+						</uui-form-validation-message>
 					</div>
 					<label>E-mail</label>
 					<uui-form-validation-message>
 						<uui-input
 							type="email"
 							.value=${this.email}
-							@input=${(e: InputEvent)=>this.email = (e.target as HTMLInputElement).value}
-							${umbBindToValidation(this,'$.form.tab1.email',this.email)}
+							@input=${(e: InputEvent) => (this.email = (e.target as HTMLInputElement).value)}
+							${umbBindToValidation(this, '$.form.tab1.email', this.email)}
 							required></uui-input>
 					</uui-form-validation-message>
 				</form>
 			</uui-form>
-		`
+		`;
 	}
 
 	#renderTab2() {
@@ -149,71 +163,68 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
 			<uui-form>
 				<form>
 					<div>
-					<label>City</label>
-					<uui-form-validation-message>
-						<uui-input
-							type="text"
-							.value=${this.city}
-							@input=${(e: InputEvent)=>this.city = (e.target as HTMLInputElement).value}
-							${umbBindToValidation(this,'$.form.tab2.city',this.city)}
-							required></uui-input>
-					</uui-form-validation-message>
+						<label>City</label>
+						<uui-form-validation-message>
+							<uui-input
+								type="text"
+								.value=${this.city}
+								@input=${(e: InputEvent) => (this.city = (e.target as HTMLInputElement).value)}
+								${umbBindToValidation(this, '$.form.tab2.city', this.city)}
+								required></uui-input>
+						</uui-form-validation-message>
 					</div>
 					<label>Country</label>
 					<uui-form-validation-message>
 						<uui-input
 							type="text"
 							.value=${this.country}
-							@input=${(e: InputEvent)=>this.country = (e.target as HTMLInputElement).value}
-							${umbBindToValidation(this,'$.form.tab2.country',this.country)}
+							@input=${(e: InputEvent) => (this.country = (e.target as HTMLInputElement).value)}
+							${umbBindToValidation(this, '$.form.tab2.country', this.country)}
 							required></uui-input>
 					</uui-form-validation-message>
 				</form>
 			</uui-form>
-		`
+		`;
 	}
 
-
-
-	static override styles = [css`
-
-		uui-badge {
-			top:0;
-			right:0;
-			font-size:10px;
-			min-width:17px;
-			min-height:17px;
-
-		}
-
-		label {
-			display:block;
-		}
-
-    uui-box {
-      margin:20px;
-    }
-
-		uui-button {
-			margin-top:1rem;
-		}
-
-		pre {
-      text-align:left;
-      padding:10px;
-      border:1px dotted #6f6f6f;
-      background: #f2f2f2;
-      font-size: 11px;
-      line-height: 1.3em;
-    }
-
-  `]
+	static override styles = [
+		css`
+			uui-badge {
+				top: 0;
+				right: 0;
+				font-size: 10px;
+				min-width: 17px;
+				min-height: 17px;
+			}
+
+			label {
+				display: block;
+			}
+
+			uui-box {
+				margin: 20px;
+			}
+
+			uui-button {
+				margin-top: 1rem;
+			}
+
+			pre {
+				text-align: left;
+				padding: 10px;
+				border: 1px dotted #6f6f6f;
+				background: #f2f2f2;
+				font-size: 11px;
+				line-height: 1.3em;
+			}
+		`,
+	];
 }
 
-export default UmbExampleValidationContextDashboard;
+export default UmbExampleValidationContextDashboardElement;
 
 declare global {
 	interface HTMLElementTagNameMap {
-		'umb-example-validation-context-dashboard': UmbExampleValidationContextDashboard;
+		'umb-example-validation-context-dashboard': UmbExampleValidationContextDashboardElement;
 	}
 }
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts
index 290129dc4a65..d721602a662b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts
@@ -427,6 +427,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
 		return html`
 			<uui-button
 				look="placeholder"
+				color=${this.pristine === false && this.validity.valid === false ? 'invalid' : 'default'}
 				label=${this._configCreateLabel ?? this._createLabel ?? ''}
 				href=${this.#context.getPathForCreateBlock(-1) ?? ''}
 				?disabled=${this._isReadOnly}></uui-button>
@@ -492,13 +493,6 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
 				--umb-block-grid--create-button--is-dragging--variable: var(--umb-block-grid--is-dragging) none;
 				display: var(--umb-block-grid--create-button--is-dragging--variable, grid);
 			}
-			:host(:not([pristine]):invalid) #createButton {
-				--uui-button-contrast: var(--uui-color-danger);
-				--uui-button-contrast-hover: var(--uui-color-danger);
-				--uui-color-default-emphasis: var(--uui-color-danger);
-				--uui-button-border-color: var(--uui-color-danger);
-				--uui-button-border-color-hover: var(--uui-color-danger);
-			}
 
 			.umb-block-grid__layout-container[data-area-length='0'] {
 				--umb-block-grid--layout-container--is-dragging--variable: var(--umb-block-grid--is-dragging) 1;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
index 03a7da86eae3..edf7e97a2aff 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
@@ -456,7 +456,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
 						>
 						${this.#renderActionBar()}
 						${!this._showContentEdit && this._contentInvalid
-							? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
+							? html`<uui-badge attention color="invalid" label="Invalid content">!</uui-badge>`
 							: nothing}
 						${this._invalidLocation
 							? html`<uui-tag id="invalidLocation" color="danger"
@@ -562,7 +562,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
 					href=${this._workspaceEditContentPath}>
 					<uui-icon name=${this._exposed === false ? 'icon-add' : 'icon-edit'}></uui-icon>
 					${this._contentInvalid
-						? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
+						? html`<uui-badge attention color="invalid" label="Invalid content">!</uui-badge>`
 						: nothing}
 				</uui-button>`
 			: this._showContentEdit === false && this._exposed === false
@@ -581,11 +581,11 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
 				? html`<uui-button
 						label="Edit settings"
 						look="secondary"
-						color=${this._settingsInvalid ? 'danger' : ''}
+						color=${this._settingsInvalid ? 'invalid' : ''}
 						href=${this._workspaceEditSettingsPath}>
 						<uui-icon name="icon-settings"></uui-icon>
 						${this._settingsInvalid
-							? html`<uui-badge attention color="danger" label="Invalid settings">!</uui-badge>`
+							? html`<uui-badge attention color="invalid" label="Invalid settings">!</uui-badge>`
 							: nothing}
 					</uui-button>`
 				: nothing}
@@ -638,7 +638,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
 			:host([location-invalid])::after,
 			:host([settings-invalid])::after,
 			:host([content-invalid])::after {
-				border-color: var(--uui-color-danger);
+				border-color: var(--uui-color-invalid);
 			}
 
 			#invalidLocation {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
index b657c375c2aa..49ef9333a094 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
@@ -413,7 +413,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
 							${this.#renderCopyToClipboardAction()} ${this.#renderDeleteAction()}
 						</uui-action-bar>
 						${!this._showContentEdit && this._contentInvalid
-							? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
+							? html`<uui-badge attention color="invalid" label="Invalid content">!</uui-badge>`
 							: nothing}
 					</div>
 				`
@@ -425,11 +425,11 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
 			? html`<uui-button
 					label="edit"
 					look="secondary"
-					color=${this._contentInvalid ? 'danger' : ''}
+					color=${this._contentInvalid ? 'invalid' : ''}
 					href=${this._workspaceEditContentPath}>
 					<uui-icon name=${this._exposed === false && this._isReadOnly === false ? 'icon-add' : 'icon-edit'}></uui-icon>
 					${this._contentInvalid
-						? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
+						? html`<uui-badge attention color="invalid" label="Invalid content">!</uui-badge>`
 						: nothing}
 				</uui-button>`
 			: this._showContentEdit === false && this._exposed === false
@@ -448,11 +448,11 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
 				? html`<uui-button
 						label="Edit settings"
 						look="secondary"
-						color=${this._settingsInvalid ? 'danger' : ''}
+						color=${this._settingsInvalid ? 'invalid' : ''}
 						href=${this._workspaceEditSettingsPath}>
 						<uui-icon name="icon-settings"></uui-icon>
 						${this._settingsInvalid
-							? html`<uui-badge attention color="danger" label="Invalid settings">!</uui-badge>`
+							? html`<uui-badge attention color="invalid" label="Invalid settings">!</uui-badge>`
 							: nothing}
 					</uui-button>`
 				: nothing}
@@ -505,7 +505,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
 
 			:host([settings-invalid])::after,
 			:host([content-invalid])::after {
-				border-color: var(--uui-color-danger);
+				border-color: var(--uui-color-invalid);
 			}
 
 			uui-action-bar {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts
index 4708b444a374..a056c7045f86 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts
@@ -270,7 +270,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
 						</umb-extension-slot>
 						<uui-action-bar> ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} </uui-action-bar>
 						${!this._showContentEdit && this._contentInvalid
-							? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
+							? html`<uui-badge attention color="invalid" label="Invalid content">!</uui-badge>`
 							: nothing}
 					</div>
 				`
@@ -291,11 +291,11 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
 			? html`<uui-button
 					label="edit"
 					look="secondary"
-					color=${this._contentInvalid ? 'danger' : ''}
+					color=${this._contentInvalid ? 'invalid' : ''}
 					href=${this._workspaceEditContentPath}>
 					<uui-icon name=${this._exposed === false ? 'icon-add' : 'icon-edit'}></uui-icon>
 					${this._contentInvalid
-						? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
+						? html`<uui-badge attention color="invalid" label="Invalid content">!</uui-badge>`
 						: nothing}
 				</uui-button>`
 			: this._showContentEdit === false && this._exposed === false
@@ -314,11 +314,11 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
 				? html`<uui-button
 						label="Edit settings"
 						look="secondary"
-						color=${this._settingsInvalid ? 'danger' : ''}
+						color=${this._settingsInvalid ? 'invalid' : ''}
 						href=${this._workspaceEditSettingsPath}>
 						<uui-icon name="icon-settings"></uui-icon>
 						${this._settingsInvalid
-							? html`<uui-badge attention color="danger" label="Invalid settings">!</uui-badge>`
+							? html`<uui-badge attention color="invalid" label="Invalid settings">!</uui-badge>`
 							: nothing}
 					</uui-button>`
 				: nothing}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts
index 86315f5ce864..d9a961b5aaf3 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts
@@ -131,10 +131,10 @@ export class UmbInputNumberRangeElement extends UmbFormControlMixin(UmbLitElemen
 
 	static override styles = css`
 		:host(:invalid:not([pristine])) {
-			color: var(--uui-color-danger);
+			color: var(--uui-color-invalid);
 		}
 		:host(:invalid:not([pristine])) uui-input {
-			border-color: var(--uui-color-danger);
+			border-color: var(--uui-color-invalid);
 		}
 	`;
 }
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts
index c854fb9dea85..fc9ae5ba82f2 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts
@@ -147,10 +147,10 @@ export class UmbInputWithAliasElement extends UmbFormControlMixin<string, typeof
 		}
 
 		:host(:invalid:not([pristine])) {
-			color: var(--uui-color-danger);
+			color: var(--uui-color-invalid);
 		}
 		:host(:invalid:not([pristine])) > uui-input {
-			border-color: var(--uui-color-danger);
+			border-color: var(--uui-color-invalid);
 		}
 	`;
 }
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts
index e16d1f821044..47d05e9cbe8a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts
@@ -188,7 +188,6 @@ export class UmbMultipleColorPickerItemInputElement extends UUIFormControlMixin(
 						() => html`
 							<uui-button
 								compact
-								color="danger"
 								label=${this.localize.term('actions_delete')}
 								look="primary"
 								?disabled=${this.disabled}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts
index 070943b428bf..6d469111a281 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts
@@ -106,7 +106,6 @@ export class UmbInputMultipleTextStringItemElement extends UUIFormControlMixin(U
 				() => html`
 					<uui-button
 						compact
-						color="danger"
 						label="${this.localize.term('general_remove')} ${this.value}"
 						look="outline"
 						?disabled=${this.disabled}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
index 8b7c275c438c..1609a1844546 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
@@ -671,7 +671,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
 			}
 
 			.invaild {
-				color: var(--uui-color-danger, #d42054);
+				color: var(--uui-color-danger, var(--uui-color-invalid));
 			}
 
 			.trash {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts
index 562b3c467a30..f33ea7e40921 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts
@@ -64,6 +64,7 @@ export interface UmbContentDetailWorkspaceContextArgs<
 	contentTypeDetailRepository: UmbDetailRepositoryConstructor<ContentTypeDetailModelType>;
 	contentValidationRepository?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
 	skipValidationOnSubmit?: boolean;
+	ignoreValidationResultOnSubmit?: boolean;
 	contentVariantScaffold: VariantModelType;
 	contentTypePropertyName: string;
 	saveModalToken?: UmbModalToken<UmbContentVariantPickerData<VariantOptionModelType>, UmbContentVariantPickerValue>;
@@ -151,6 +152,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
 	}
 
 	#validateOnSubmit: boolean;
+	#ignoreValidationResultOnSubmit: boolean;
 	#serverValidation = new UmbServerModelValidatorContext(this);
 	#validationRepositoryClass?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
 	#validationRepository?: UmbContentValidationRepository<DetailModelType>;
@@ -178,6 +180,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
 		const contentTypeDetailRepository = new args.contentTypeDetailRepository(this);
 		this.#validationRepositoryClass = args.contentValidationRepository;
 		this.#validateOnSubmit = args.skipValidationOnSubmit ? !args.skipValidationOnSubmit : true;
+		this.#ignoreValidationResultOnSubmit = args.ignoreValidationResultOnSubmit ?? false;
 		this.structure = new UmbContentTypeStructureManager<ContentTypeDetailModelType>(this, contentTypeDetailRepository);
 		this.variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture);
 		this.variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment);
@@ -727,7 +730,11 @@ export abstract class UmbContentDetailWorkspaceContextBase<
 					return this.performCreateOrUpdate(variantIds, saveData);
 				},
 				async (reason?: any) => {
-					return this.invalidSubmit(reason);
+					if (this.#ignoreValidationResultOnSubmit) {
+						return this.performCreateOrUpdate(variantIds, saveData);
+					} else {
+						return this.invalidSubmit(reason);
+					}
 				},
 			);
 		} else {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.element.ts
index e7e1b41dc5f7..fac3aa45bcfb 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.element.ts
@@ -74,7 +74,10 @@ export class UmbPropertyLayoutElement extends UmbLitElement {
 			<div id="headerColumn">
 				<uui-label id="label" title=${this.alias} ?required=${this.mandatory}>
 					${this.localize.string(this.label)}
-					${when(this.invalid, () => html`<uui-badge color="danger" attention>!</uui-badge>`)}
+					${when(
+						this.invalid,
+						() => html`<div id="invalid-badge"><uui-badge color="invalid" attention>!</uui-badge></div>`,
+					)}
 				</uui-label>
 				<slot name="action-menu"></slot>
 				${this.#renderDescription()}
@@ -129,15 +132,28 @@ export class UmbPropertyLayoutElement extends UmbLitElement {
 			}
 			/*}*/
 
+			:host {
+				/* TODO: Temp solution to not get a yellow asterisk when invalid. */
+				--umb-temp-uui-color-invalid: var(--uui-color-invalid);
+			}
+
 			#label {
 				position: relative;
 				word-break: break-word;
+				/* TODO: Temp solution to not get a yellow asterisk when invalid. */
+				--uui-color-invalid: var(--uui-color-danger);
 			}
-			:host([invalid]) #label {
-				color: var(--uui-color-danger);
+			#invalid-badge {
+				display: inline-block;
+				position: relative;
+				width: 18px;
+				height: 1em;
+				margin-right: 6px;
 			}
 			uui-badge {
-				right: -30px;
+				//height: var(--uui-color-invalid);
+				background-color: var(--umb-temp-uui-color-invalid);
+				color: var(--uui-color-invalid-contrast);
 			}
 
 			#description {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts
index 38a4fb1181b0..e8b1806b50ae 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts
@@ -85,7 +85,7 @@ export class UmbFormValidationMessageElement extends UmbLitElement {
 	static override styles = [
 		css`
 			#messages {
-				color: var(--uui-color-danger-standalone);
+				color: var(--uui-color-invalid-standalone);
 			}
 		`,
 	];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts
index 40fd96f0ef07..873e5c30875b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts
@@ -7,7 +7,22 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
 @customElement('umb-workspace-modal')
 export class UmbWorkspaceModalElement extends UmbLitElement {
 	@property({ attribute: false })
-	data?: UmbWorkspaceModalData;
+	public get data(): UmbWorkspaceModalData | undefined {
+		return this._data;
+	}
+	public set data(value: UmbWorkspaceModalData | undefined) {
+		this._data = value;
+		if (value?.inheritValidationLook) {
+			// Do nothing.
+		} else {
+			const elementStyle = this.style;
+			elementStyle.setProperty('--uui-color-invalid', 'var(--uui-color-danger)');
+			elementStyle.setProperty('--uui-color-invalid-emphasis', 'var(--uui-color-danger-emphasis)');
+			elementStyle.setProperty('--uui-color-invalid-standalone', 'var(--uui-color-danger-standalone)');
+			elementStyle.setProperty('--uui-color-invalid-contrast', 'var(--uui-color-danger-contrast)');
+		}
+	}
+	private _data?: UmbWorkspaceModalData | undefined;
 
 	/**
 	 * TODO: Consider if this binding and events integration is the right for communicating back the modal handler. Or if we should go with some Context API. like a Modal Context API.
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.token.ts
index b78617a648d5..c1ee724432cb 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.token.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.token.ts
@@ -3,6 +3,7 @@ export interface UmbWorkspaceModalData<DataModelType = unknown> {
 	entityType: string;
 	preset: Partial<DataModelType>;
 	baseDataPath?: string;
+	inheritValidationLook?: boolean;
 }
 
 export type UmbWorkspaceModalValue =
diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts
index 5f98391fa722..2f2b69fb85cd 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts
@@ -115,9 +115,9 @@ export class UmbInputDataTypeElement extends UmbFormControlMixin(UmbLitElement,
 				--uui-button-padding-bottom-factor: 4;
 			}
 			:host(:invalid:not([pristine])) #empty-state-button {
-				--uui-button-border-color: var(--uui-color-danger);
+				--uui-button-border-color: var(--uui-color-invalid);
 				--uui-button-border-width: 2px;
-				--uui-button-contrast: var(--uui-color-danger);
+				--uui-button-contrast: var(--uui-color-invalid);
 			}
 		`,
 	];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts
index 5d678830625b..fee74f7c7436 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts
@@ -72,6 +72,11 @@ export class UmbDocumentPublishingWorkspaceContext extends UmbContextBase<UmbDoc
 	 * @memberof UmbDocumentPublishingWorkspaceContext
 	 */
 	public async saveAndPublish(): Promise<void> {
+		const elementStyle = (this.getHostElement() as HTMLElement).style;
+		elementStyle.removeProperty('--uui-color-invalid');
+		elementStyle.removeProperty('--uui-color-invalid-emphasis');
+		elementStyle.removeProperty('--uui-color-invalid-standalone');
+		elementStyle.removeProperty('--uui-color-invalid-contrast');
 		return this.#handleSaveAndPublish();
 	}
 
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts
index 2134817942f8..0116cd18c7cb 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts
@@ -80,7 +80,8 @@ export class UmbDocumentWorkspaceContext
 			detailRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS,
 			contentTypeDetailRepository: UmbDocumentTypeDetailRepository,
 			contentValidationRepository: UmbDocumentValidationRepository,
-			skipValidationOnSubmit: true,
+			skipValidationOnSubmit: false,
+			ignoreValidationResultOnSubmit: true,
 			contentVariantScaffold: UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD,
 			contentTypePropertyName: 'documentType',
 			saveModalToken: UMB_DOCUMENT_SAVE_MODAL,
@@ -256,11 +257,11 @@ export class UmbDocumentWorkspaceContext
 	 * @returns {Promise<void>} a promise which resolves once it has been completed.
 	 */
 	public override requestSubmit() {
-		return this._handleSubmit();
-	}
-
-	// Because we do not make validation prevent submission this also submits the workspace. [NL]
-	public override invalidSubmit() {
+		const elementStyle = (this.getHostElement() as HTMLElement).style;
+		elementStyle.setProperty('--uui-color-invalid', 'var(--uui-color-warning)');
+		elementStyle.setProperty('--uui-color-invalid-emphasis', 'var(--uui-color-warning-emphasis)');
+		elementStyle.setProperty('--uui-color-invalid-standalone', 'var(--uui-color-warning-standalone)');
+		elementStyle.setProperty('--uui-color-invalid-contrast', 'var(--uui-color-warning-contrast)');
 		return this._handleSubmit();
 	}
 
diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts
index 00060a0a99c7..3292e55b0c04 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts
@@ -180,7 +180,7 @@ export class UmbPropertyEditorUIDropdownElement
 			}
 
 			.error {
-				color: var(--uui-color-danger);
+				color: var(--uui-color-invalid);
 				font-size: var(--uui-font-size-small);
 			}
 		`,
diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts
index 12078ec55a34..1057f9099c0c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts
@@ -240,9 +240,9 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
 			:host(:not([pristine]):invalid),
 			/* polyfill support */
 			:host(:not([pristine])[internals-invalid]) {
-				--umb-tiptap-edge-border-color: var(--uui-color-danger);
+				--umb-tiptap-edge-border-color: var(--uui-color-invalid);
 				#editor {
-					border-color: var(--uui-color-danger);
+					border-color: var(--uui-color-invalid);
 				}
 			}
 


Reviews:
user: login: madsrasmussen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T06:55:16Z
user: login: nielslyngsoe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T12:10:29Z
user: login: nielslyngsoe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T12:10:40Z
user: login: madsrasmussen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T16:51:04Z
user: login: madsrasmussen
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-09T09:32:35Z

Review comments:
diff_hunk: @@ -64,6 +64,7 @@ export interface UmbContentDetailWorkspaceContextArgs<
 	contentTypeDetailRepository: UmbDetailRepositoryConstructor<ContentTypeDetailModelType>;
 	contentValidationRepository?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
 	skipValidationOnSubmit?: boolean;
+	ignorerValidationOnSubmit?: boolean;
path: src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts
user: login: madsrasmussen
body: I think there might be some confusion about the difference between " skip " and " ignore " 🤔 Additionally, an extra "r" has sneaked into "ignore.".
created_at: 2025-04-07T06:55:16Z
author_association: MEMBER
diff_hunk: @@ -64,6 +64,7 @@ export interface UmbContentDetailWorkspaceContextArgs<
 	contentTypeDetailRepository: UmbDetailRepositoryConstructor<ContentTypeDetailModelType>;
 	contentValidationRepository?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
 	skipValidationOnSubmit?: boolean;
+	ignorerValidationOnSubmit?: boolean;
path: src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts
user: login: nielslyngsoe
body: Yes, that is a good perspective.

But there is a difference, the intention here is not to be breaking.. so you can still choose to skipValidation.

And now we can then choose to do validation, but ignore the result of it.
created_at: 2025-04-07T12:10:29Z
author_association: MEMBER
diff_hunk: @@ -64,6 +64,7 @@ export interface UmbContentDetailWorkspaceContextArgs<
 	contentTypeDetailRepository: UmbDetailRepositoryConstructor<ContentTypeDetailModelType>;
 	contentValidationRepository?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
 	skipValidationOnSubmit?: boolean;
+	ignorerValidationOnSubmit?: boolean;
path: src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts
user: login: nielslyngsoe
body: (i have corrected the r mistake)
created_at: 2025-04-07T12:10:40Z
author_association: MEMBER
diff_hunk: @@ -64,6 +64,7 @@ export interface UmbContentDetailWorkspaceContextArgs<
 	contentTypeDetailRepository: UmbDetailRepositoryConstructor<ContentTypeDetailModelType>;
 	contentValidationRepository?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
 	skipValidationOnSubmit?: boolean;
+	ignorerValidationOnSubmit?: boolean;
path: src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts
user: login: madsrasmussen
body: I actually think it will be more clear if you called it "ignoreValidationResultOnSubmit" like the way you describe it. What do you think of that?
created_at: 2025-04-07T16:51:04Z
author_association: MEMBER

PR comments:
user: login: Welander1994
created_at: 2025-04-02T11:43:26Z
body: The content node does get saved, but the feedback color is still danger-red and not yellow

![Screenshot 2025-04-02 at 13 39 47](https://github.com/user-attachments/assets/133da42e-997a-40d9-b3a3-0588051ca30c)

author_association: CONTRIBUTOR
user: login: nielslyngsoe
created_at: 2025-04-03T19:25:46Z
body: I just had to test it does work here, maybe installing latest npm dependencies was missing when Welander tested.

![image](https://github.com/user-attachments/assets/f3f191f6-f8c7-4bfc-a806-75b1e4940142)

It does thought not inherit the yellow color scheme in the Block Workspace. But there validation issues are also preventing the user from submitting, so for now that is okay IMO. then we will correct that part of the experience later when we know if we like the user to be allowed to close modals with issues.
author_association: MEMBER
user: login: madsrasmussen
created_at: 2025-04-07T07:34:45Z
body: It is not related to this PR, but related to "the saving experience." 😄 

I don't think it makes much sense to disable the save button until the user has entered something. We already allow saving an invalid document. Also, the "Save and Publish" button is enabled, which makes it even more confusing.

<img width="1281" alt="Screenshot 2025-04-07 at 09 29 29" src="https://github.com/user-attachments/assets/1444bc5f-af82-497d-8f71-56019bf3a43e" />

author_association: MEMBER
user: login: madsrasmussen
created_at: 2025-04-07T07:42:23Z
body: When saving a document, the saving process gets blocked by validation, which shouldn't be the case. I correctly see the "invalid" notifications. See the screenshot below. The save request doesn't get sent.

A small detail: I don't think the mandatory marker (*) should change color from red to yellow.

<img width="1278" alt="Screenshot 2025-04-07 at 09 38 03" src="https://github.com/user-attachments/assets/f100a3cf-271e-4b10-9b51-835ce530cbbe" />
author_association: MEMBER
user: login: nielslyngsoe
created_at: 2025-04-07T13:29:38Z
body: @madsrasmussen I have corrected your suggestions. Though not the disabled save button, as it is not possible to save without a name.
author_association: MEMBER