{Array.from({ length: total }, (_, i) => {
const step = i + 1;
diff --git a/frontend/editor/src/core/ui/Tabs.css b/frontend/editor/src/core/ui/Tabs.css
index 9fa18ce5b4..65501a4d21 100644
--- a/frontend/editor/src/core/ui/Tabs.css
+++ b/frontend/editor/src/core/ui/Tabs.css
@@ -42,7 +42,7 @@
border-radius: var(--radius-pill);
}
.sui-tabs--pill .sui-tabs__tab.is-active {
- color: var(--sui-tab-accent, var(--c-primary));
+ color: var(--sui-tab-accent, var(--c-accent-text));
background: var(--c-primary-tint);
border-color: var(--sui-tab-accent, var(--c-primary-border));
font-weight: 500;
@@ -56,7 +56,7 @@
margin-bottom: -1px;
}
.sui-tabs--underline .sui-tabs__tab.is-active {
- color: var(--sui-tab-accent, var(--c-primary));
+ color: var(--sui-tab-accent, var(--c-accent-text));
border-bottom-color: var(--sui-tab-accent, var(--c-primary));
font-weight: 500;
}
diff --git a/frontend/editor/src/core/ui/ToggleSwitch.tsx b/frontend/editor/src/core/ui/ToggleSwitch.tsx
index 8e418d7dc5..12f94fdac4 100644
--- a/frontend/editor/src/core/ui/ToggleSwitch.tsx
+++ b/frontend/editor/src/core/ui/ToggleSwitch.tsx
@@ -6,6 +6,8 @@ export interface ToggleSwitchProps {
onChange: (checked: boolean) => void;
/** Accessible label associated to the control. */
label?: string;
+ /** Names the switch from text rendered elsewhere (e.g. a SettingsRow label). */
+ "aria-labelledby"?: string;
/** Optional helper text rendered next to the label. */
description?: string;
disabled?: boolean;
@@ -23,6 +25,7 @@ export function ToggleSwitch({
checked,
onChange,
label,
+ "aria-labelledby": ariaLabelledBy,
description,
disabled,
size = "md",
@@ -39,6 +42,7 @@ export function ToggleSwitch({
id={controlId}
type="checkbox"
role="switch"
+ aria-labelledby={ariaLabelledBy}
checked={checked}
disabled={disabled}
onChange={(e) => onChange(e.target.checked)}
diff --git a/frontend/editor/src/core/ui/accents.css b/frontend/editor/src/core/ui/accents.css
index cf83edacaf..b5b530ee6c 100644
--- a/frontend/editor/src/core/ui/accents.css
+++ b/frontend/editor/src/core/ui/accents.css
@@ -9,7 +9,8 @@
var(--c-btn-inverse)
);
--_on: var(--c-btn-inverse);
- --_text: var(--c-primary-hover);
+ /* Deeper than --c-primary-hover, which lands at 4.28:1 on the accent tint. */
+ --_text: var(--c-accent-text);
--_bd: color-mix(in srgb, var(--c-primary) 38%, var(--c-surface));
--_tint: color-mix(in srgb, var(--c-primary) 12%, transparent);
--_solid-2: var(--c-btn-secondary);
@@ -26,24 +27,33 @@
/* Danger is pinned to a fixed deep red (not the theme-lightened coral), so the
fill and the outline/text are the SAME red in both light and dark. */
.sui-acc-danger {
- --_solid: var(--p-red-500);
- --_solid-hover: var(--p-red-600);
+ /* One step deeper than the base red so the white label on the filled
+ variant clears 4.5:1. */
+ --_solid: var(--p-red-600);
+ --_solid-hover: var(--p-red-700);
--_on: #ffffff;
- --_text: var(--p-red-500);
+ /* Theme-aware like the other accents: the mid red only clears 3.6:1 as text
+ on a light surface, so light mode needs the deeper shade while dark mode
+ needs the lighter one. */
+ --_text: var(--color-red-dark);
--_bd: color-mix(in srgb, var(--p-red-500) 45%, transparent);
--_tint: color-mix(in srgb, var(--p-red-500) 12%, transparent);
}
.sui-acc-success {
- --_solid: var(--color-green);
- --_solid-hover: var(--color-green-dark);
+ /* The base green is a tint/border colour; as a fill behind white text it
+ only reaches 2.3:1, so the solid variant uses the deeper shade. */
+ --_solid: var(--p-green-700);
+ --_solid-hover: var(--p-green-800);
--_on: #ffffff;
--_text: var(--color-green-dark);
--_bd: var(--color-green-border);
--_tint: color-mix(in srgb, var(--color-green) 12%, transparent);
}
.sui-acc-warning {
- --_solid: var(--color-amber);
- --_solid-hover: var(--color-amber-dark);
+ /* Deepened so the white label clears 4.5:1, matching how danger and success
+ resolve the same problem — every filled accent reads white-on-colour. */
+ --_solid: var(--p-amber-700);
+ --_solid-hover: var(--p-amber-800);
--_on: #ffffff;
--_text: var(--color-amber-dark);
--_bd: var(--color-amber-border);
@@ -106,7 +116,9 @@
var(--p-cyan-400) 100%
);
--_on: #ffffff;
- --_text: var(--p-indigo-500);
+ /* The gradient's mid indigo is a fill colour; as text on the light surface
+ it only reaches 4.1:1 (3.6:1 on its own tint). */
+ --_text: var(--p-indigo-600);
--_bd: var(--p-indigo-200);
--_tint: color-mix(in srgb, var(--p-indigo-500) 10%, transparent);
}
diff --git a/frontend/editor/src/portal/components/AssistantPanel.tsx b/frontend/editor/src/portal/components/AssistantPanel.tsx
index b31e6bec86..c48579a037 100644
--- a/frontend/editor/src/portal/components/AssistantPanel.tsx
+++ b/frontend/editor/src/portal/components/AssistantPanel.tsx
@@ -84,7 +84,9 @@ export function AssistantPanel() {
if (!assistantOpen) return null;
return (
-
+
);
}
diff --git a/frontend/editor/src/portal/components/ChatFABWidget.stories.tsx b/frontend/editor/src/portal/components/ChatFABWidget.stories.tsx
index 1bc485299b..e7d2584915 100644
--- a/frontend/editor/src/portal/components/ChatFABWidget.stories.tsx
+++ b/frontend/editor/src/portal/components/ChatFABWidget.stories.tsx
@@ -90,7 +90,7 @@ function MockChatContent({
maxWidth: "82%",
background:
m.role === "user"
- ? "var(--c-primary)"
+ ? "var(--c-accent-solid)"
: "var(--c-surface-sunken)",
color: m.role === "user" ? "#fff" : "inherit",
borderRadius: 10,
@@ -249,7 +249,9 @@ function ChatFABFullFlowDemo() {
padding: "4px 10px",
borderRadius: 6,
background:
- step === s ? "var(--c-primary)" : "var(--c-surface-sunken)",
+ step === s
+ ? "var(--c-accent-solid)"
+ : "var(--c-surface-sunken)",
color: step === s ? "#fff" : "inherit",
fontWeight: step === s ? 600 : 400,
}}
diff --git a/frontend/editor/src/portal/components/DownloadEditorModal.css b/frontend/editor/src/portal/components/DownloadEditorModal.css
index 4742833be8..ae369b67d4 100644
--- a/frontend/editor/src/portal/components/DownloadEditorModal.css
+++ b/frontend/editor/src/portal/components/DownloadEditorModal.css
@@ -48,7 +48,7 @@
height: 2.25rem;
flex-shrink: 0;
border-radius: 0.625rem;
- color: var(--c-primary);
+ color: var(--c-accent-text);
background: var(--c-primary-tint);
}
@@ -106,7 +106,7 @@
background: none;
font-size: 0.8125rem;
font-weight: 600;
- color: var(--c-primary);
+ color: var(--c-accent-text);
cursor: pointer;
}
.portal-install__guide:hover {
diff --git a/frontend/editor/src/portal/components/NotificationsDropdown.css b/frontend/editor/src/portal/components/NotificationsDropdown.css
index ba14ec9c60..87f6ebd7b4 100644
--- a/frontend/editor/src/portal/components/NotificationsDropdown.css
+++ b/frontend/editor/src/portal/components/NotificationsDropdown.css
@@ -34,7 +34,7 @@
.portal-notif__count {
font-size: 0.6875rem;
font-weight: 500;
- color: var(--c-primary);
+ color: var(--c-accent-text);
padding: 0.125rem 0.375rem;
background: var(--c-primary-tint);
border-radius: var(--radius-pill);
@@ -110,7 +110,7 @@
.portal-notif__action {
font-size: 0.75rem;
font-weight: 500;
- color: var(--c-primary);
+ color: var(--c-accent-text);
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
transition: background var(--motion-fast);
diff --git a/frontend/editor/src/portal/components/ProcessorFlow.css b/frontend/editor/src/portal/components/ProcessorFlow.css
index 16acbf91af..e24b4d7f3c 100644
--- a/frontend/editor/src/portal/components/ProcessorFlow.css
+++ b/frontend/editor/src/portal/components/ProcessorFlow.css
@@ -148,7 +148,9 @@
.portal-pf__node--soon {
background: transparent;
border-style: dashed;
- opacity: 0.75;
+ /* The dashed border and muted ink mark this as not-yet-available; fading the
+ whole node with opacity would drop its label below the contrast floor. */
+ color: var(--c-text-muted);
}
.portal-pf__node-icon {
diff --git a/frontend/editor/src/portal/components/ProcessorFlow.tsx b/frontend/editor/src/portal/components/ProcessorFlow.tsx
index 547ec21ca7..ad1f604a6a 100644
--- a/frontend/editor/src/portal/components/ProcessorFlow.tsx
+++ b/frontend/editor/src/portal/components/ProcessorFlow.tsx
@@ -102,7 +102,9 @@ export function ProcessorFlow({ dataOverride }: ProcessorFlowProps = {}) {
return (