- {Constants.tagColors.map((color) => (
+ {tagColors.map((color) => (
update('color', e.color)}
diff --git a/frontend/web/styles/_primitives.scss b/frontend/web/styles/_primitives.scss
new file mode 100644
index 000000000000..c58ec27ae603
--- /dev/null
+++ b/frontend/web/styles/_primitives.scss
@@ -0,0 +1,105 @@
+// =============================================================================
+// Primitive Colour Palette
+// =============================================================================
+// Full tonal scales (50–950) for every colour family.
+// These are NOT for direct use in components — use semantic tokens instead.
+// See _tokens.scss for the semantic layer.
+//
+// Scale structure matches Tailwind convention (11 steps per hue).
+// Anchored values are locked to existing Flagsmith colours; generated steps
+// were interpolated in HSL to fill the gaps.
+// =============================================================================
+
+// Slate (neutrals) — custom scale, not Tailwind-aligned
+$slate-0: #ffffff;
+$slate-50: #fafafb;
+$slate-100: #eff1f4;
+$slate-200: #e0e3e9;
+$slate-300: #9da4ae;
+$slate-400: #767d85;
+$slate-500: #656d7b;
+$slate-600: #1a2634;
+$slate-700: #2d3443;
+$slate-800: #202839;
+$slate-850: #161d30;
+$slate-900: #15192b;
+$slate-950: #101628;
+$slate-1000: #000000;
+
+// Purple (brand)
+$purple-50: #f5f0ff;
+$purple-100: #e8dbff;
+$purple-200: #d4bcff;
+$purple-300: #b794ff;
+$purple-400: #906af6;
+$purple-500: #7a4dfc;
+$purple-600: #6837fc;
+$purple-700: #4e25db;
+$purple-800: #3919b7;
+$purple-900: #2a2054;
+$purple-950: #1e163e;
+
+// Red (danger)
+$red-50: #fef2f1;
+$red-100: #fce5e4;
+$red-200: #f9cbc9;
+$red-300: #f5a5a2;
+$red-400: #f57c78;
+$red-500: #ef4d56;
+$red-600: #e61b26;
+$red-700: #bb1720;
+$red-800: #90141b;
+$red-900: #701116;
+$red-950: #500d11;
+
+// Green (success)
+$green-50: #eef9f6;
+$green-100: #d6f1eb;
+$green-200: #b5e5da;
+$green-300: #87d4c4;
+$green-400: #56ccad;
+$green-500: #27ab95;
+$green-600: #13787b;
+$green-700: #116163;
+$green-800: #0e4a4c;
+$green-900: #0c3a3b;
+$green-950: #09292a;
+
+// Gold (secondary)
+$gold-50: #fefbf0;
+$gold-100: #fdf6e0;
+$gold-200: #faeec5;
+$gold-300: #fae392;
+$gold-400: #f9dc80;
+$gold-500: #f7d56e;
+$gold-600: #e5c55f;
+$gold-700: #d4b050;
+$gold-800: #b38f30;
+$gold-900: #8b7027;
+$gold-950: #64511e;
+
+// Blue (info)
+$blue-50: #eef8fb;
+$blue-100: #d6eef5;
+$blue-200: #b3e0ed;
+$blue-300: #7ecde2;
+$blue-400: #45bce0;
+$blue-500: #0aaddf;
+$blue-600: #0b8bb2;
+$blue-700: #0b7190;
+$blue-800: #0b576e;
+$blue-900: #094456;
+$blue-950: #07313e;
+
+// Orange (warning)
+$orange-50: #fff5ec;
+$orange-100: #ffe9d4;
+$orange-200: #ffd7b5;
+$orange-300: #ffc08a;
+$orange-400: #efb47c;
+$orange-500: #ff9f43;
+$orange-600: #fa810c;
+$orange-700: #d06907;
+$orange-800: #9f5208;
+$orange-900: #7b4008;
+$orange-950: #592f07;
diff --git a/frontend/web/styles/_token-utilities.scss b/frontend/web/styles/_token-utilities.scss
new file mode 100644
index 000000000000..ece63a0b6f9b
--- /dev/null
+++ b/frontend/web/styles/_token-utilities.scss
@@ -0,0 +1,76 @@
+// =============================================================================
+// Token Utility Classes — AUTO-GENERATED from common/theme/tokens.json
+// Do not edit manually. Run: npm run generate:tokens
+// Dark mode is automatic — CSS custom properties resolve differently under
+// :root vs .dark, so no dark-prefixed classes are needed.
+// =============================================================================
+
+// Surface
+.bg-surface-action { background-color: var(--color-surface-action); }
+.bg-surface-action-active { background-color: var(--color-surface-action-active); }
+.bg-surface-action-hover { background-color: var(--color-surface-action-hover); }
+.bg-surface-action-muted { background-color: var(--color-surface-action-muted); }
+.bg-surface-action-subtle { background-color: var(--color-surface-action-subtle); }
+.bg-surface-active { background-color: var(--color-surface-active); }
+.bg-surface-danger { background-color: var(--color-surface-danger); }
+.bg-surface-default { background-color: var(--color-surface-default); }
+.bg-surface-emphasis { background-color: var(--color-surface-emphasis); }
+.bg-surface-hover { background-color: var(--color-surface-hover); }
+.bg-surface-info { background-color: var(--color-surface-info); }
+.bg-surface-muted { background-color: var(--color-surface-muted); }
+.bg-surface-subtle { background-color: var(--color-surface-subtle); }
+.bg-surface-success { background-color: var(--color-surface-success); }
+.bg-surface-warning { background-color: var(--color-surface-warning); }
+
+// Text
+.text-action { color: var(--color-text-action); }
+.text-danger { color: var(--color-text-danger); }
+.text-default { color: var(--color-text-default); }
+.text-disabled { color: var(--color-text-disabled); }
+.text-info { color: var(--color-text-info); }
+.text-secondary { color: var(--color-text-secondary); }
+.text-success { color: var(--color-text-success); }
+.text-tertiary { color: var(--color-text-tertiary); }
+.text-warning { color: var(--color-text-warning); }
+
+// Border
+.border-action { border-color: var(--color-border-action); }
+.border-danger { border-color: var(--color-border-danger); }
+.border-default { border-color: var(--color-border-default); }
+.border-disabled { border-color: var(--color-border-disabled); }
+.border-info { border-color: var(--color-border-info); }
+.border-strong { border-color: var(--color-border-strong); }
+.border-success { border-color: var(--color-border-success); }
+.border-warning { border-color: var(--color-border-warning); }
+
+// Icon
+.icon-action { color: var(--color-icon-action); fill: var(--color-icon-action); }
+.icon-danger { color: var(--color-icon-danger); fill: var(--color-icon-danger); }
+.icon-default { color: var(--color-icon-default); fill: var(--color-icon-default); }
+.icon-disabled { color: var(--color-icon-disabled); fill: var(--color-icon-disabled); }
+.icon-info { color: var(--color-icon-info); fill: var(--color-icon-info); }
+.icon-secondary { color: var(--color-icon-secondary); fill: var(--color-icon-secondary); }
+.icon-success { color: var(--color-icon-success); fill: var(--color-icon-success); }
+.icon-warning { color: var(--color-icon-warning); fill: var(--color-icon-warning); }
+
+// Radius
+.rounded-2xl { border-radius: var(--radius-2xl); }
+.rounded-full { border-radius: var(--radius-full); }
+.rounded-lg { border-radius: var(--radius-lg); }
+.rounded-md { border-radius: var(--radius-md); }
+.rounded-none { border-radius: var(--radius-none); }
+.rounded-sm { border-radius: var(--radius-sm); }
+.rounded-xl { border-radius: var(--radius-xl); }
+.rounded-xs { border-radius: var(--radius-xs); }
+
+// Shadow
+.shadow-lg { box-shadow: var(--shadow-lg); }
+.shadow-md { box-shadow: var(--shadow-md); }
+.shadow-sm { box-shadow: var(--shadow-sm); }
+.shadow-xl { box-shadow: var(--shadow-xl); }
+.shadow-none { box-shadow: none; }
+
+// Transitions
+.transition-fast { transition-duration: var(--duration-fast); transition-timing-function: var(--easing-standard); }
+.transition-normal { transition-duration: var(--duration-normal); transition-timing-function: var(--easing-standard); }
+.transition-slow { transition-duration: var(--duration-slow); transition-timing-function: var(--easing-standard); }
diff --git a/frontend/web/styles/_tokens.scss b/frontend/web/styles/_tokens.scss
new file mode 100644
index 000000000000..68db7cadb0f4
--- /dev/null
+++ b/frontend/web/styles/_tokens.scss
@@ -0,0 +1,198 @@
+// =============================================================================
+// Design Tokens — AUTO-GENERATED from common/theme/tokens.json
+// Do not edit manually. Run: npm run generate:tokens
+// =============================================================================
+
+:root {
+ // Primitives
+ --blue-100: #d6eef5;
+ --blue-200: #b3e0ed;
+ --blue-300: #7ecde2;
+ --blue-400: #45bce0;
+ --blue-50: #eef8fb;
+ --blue-500: #0aaddf;
+ --blue-600: #0b8bb2;
+ --blue-700: #0b7190;
+ --blue-800: #0b576e;
+ --blue-900: #094456;
+ --blue-950: #07313e;
+ --gold-100: #fdf6e0;
+ --gold-200: #faeec5;
+ --gold-300: #fae392;
+ --gold-400: #f9dc80;
+ --gold-50: #fefbf0;
+ --gold-500: #f7d56e;
+ --gold-600: #e5c55f;
+ --gold-700: #d4b050;
+ --gold-800: #b38f30;
+ --gold-900: #8b7027;
+ --gold-950: #64511e;
+ --green-100: #d6f1eb;
+ --green-200: #b5e5da;
+ --green-300: #87d4c4;
+ --green-400: #56ccad;
+ --green-50: #eef9f6;
+ --green-500: #27ab95;
+ --green-600: #13787b;
+ --green-700: #116163;
+ --green-800: #0e4a4c;
+ --green-900: #0c3a3b;
+ --green-950: #09292a;
+ --orange-100: #ffe9d4;
+ --orange-200: #ffd7b5;
+ --orange-300: #ffc08a;
+ --orange-400: #efb47c;
+ --orange-50: #fff5ec;
+ --orange-500: #ff9f43;
+ --orange-600: #fa810c;
+ --orange-700: #d06907;
+ --orange-800: #9f5208;
+ --orange-900: #7b4008;
+ --orange-950: #592f07;
+ --purple-100: #e8dbff;
+ --purple-200: #d4bcff;
+ --purple-300: #b794ff;
+ --purple-400: #906af6;
+ --purple-50: #f5f0ff;
+ --purple-500: #7a4dfc;
+ --purple-600: #6837fc;
+ --purple-700: #4e25db;
+ --purple-800: #3919b7;
+ --purple-900: #2a2054;
+ --purple-950: #1e163e;
+ --red-100: #fce5e4;
+ --red-200: #f9cbc9;
+ --red-300: #f5a5a2;
+ --red-400: #f57c78;
+ --red-50: #fef2f1;
+ --red-500: #ef4d56;
+ --red-600: #e61b26;
+ --red-700: #bb1720;
+ --red-800: #90141b;
+ --red-900: #701116;
+ --red-950: #500d11;
+ --slate-0: #ffffff;
+ --slate-100: #eff1f4;
+ --slate-1000: #000000;
+ --slate-200: #e0e3e9;
+ --slate-300: #9da4ae;
+ --slate-400: #767d85;
+ --slate-50: #fafafb;
+ --slate-500: #656d7b;
+ --slate-600: #1a2634;
+ --slate-700: #2d3443;
+ --slate-800: #202839;
+ --slate-850: #161d30;
+ --slate-900: #15192b;
+ --slate-950: #101628;
+
+ // Surface
+ --color-surface-action: var(--purple-600);
+ --color-surface-action-active: var(--purple-800);
+ --color-surface-action-hover: var(--purple-700);
+ --color-surface-action-muted: oklch(from var(--purple-600) l c h / 0.16);
+ --color-surface-action-subtle: oklch(from var(--purple-600) l c h / 0.08);
+ --color-surface-active: oklch(from var(--slate-1000) l c h / 0.16);
+ --color-surface-danger: oklch(from var(--red-500) l c h / 0.08);
+ --color-surface-default: var(--slate-0);
+ --color-surface-emphasis: var(--slate-200);
+ --color-surface-hover: oklch(from var(--slate-1000) l c h / 0.08);
+ --color-surface-info: oklch(from var(--blue-500) l c h / 0.08);
+ --color-surface-muted: var(--slate-100);
+ --color-surface-subtle: var(--slate-50);
+ --color-surface-success: oklch(from var(--green-500) l c h / 0.08);
+ --color-surface-warning: oklch(from var(--orange-500) l c h / 0.08);
+
+ // Text
+ --color-text-action: var(--purple-600);
+ --color-text-danger: var(--red-500);
+ --color-text-default: var(--slate-600);
+ --color-text-disabled: var(--slate-300);
+ --color-text-info: var(--blue-500);
+ --color-text-secondary: var(--slate-500);
+ --color-text-success: var(--green-500);
+ --color-text-tertiary: var(--slate-300);
+ --color-text-warning: var(--orange-500);
+
+ // Border
+ --color-border-action: var(--purple-600);
+ --color-border-danger: var(--red-500);
+ --color-border-default: oklch(from var(--slate-500) l c h / 0.16);
+ --color-border-disabled: oklch(from var(--slate-500) l c h / 0.08);
+ --color-border-info: var(--blue-500);
+ --color-border-strong: oklch(from var(--slate-500) l c h / 0.24);
+ --color-border-success: var(--green-500);
+ --color-border-warning: var(--orange-500);
+
+ // Icon
+ --color-icon-action: var(--purple-600);
+ --color-icon-danger: var(--red-500);
+ --color-icon-default: var(--slate-600);
+ --color-icon-disabled: var(--slate-300);
+ --color-icon-info: var(--blue-500);
+ --color-icon-secondary: var(--slate-500);
+ --color-icon-success: var(--green-500);
+ --color-icon-warning: var(--orange-500);
+
+ // Radius
+ --radius-2xl: 18px;
+ --radius-full: 9999px;
+ --radius-lg: 8px;
+ --radius-md: 6px;
+ --radius-none: 0px;
+ --radius-sm: 4px;
+ --radius-xl: 10px;
+ --radius-xs: 2px;
+
+ // Shadow
+ --shadow-lg: 0px 8px 16px oklch(from var(--slate-1000) l c h / 0.15);
+ --shadow-md: 0px 4px 8px oklch(from var(--slate-1000) l c h / 0.12);
+ --shadow-sm: 0px 1px 2px oklch(from var(--slate-1000) l c h / 0.05);
+ --shadow-xl: 0px 12px 24px oklch(from var(--slate-1000) l c h / 0.20);
+
+ // Duration
+ --duration-fast: 100ms;
+ --duration-normal: 200ms;
+ --duration-slow: 300ms;
+
+ // Easing
+ --easing-entrance: cubic-bezier(0.0, 0, 0.38, 0.9);
+ --easing-exit: cubic-bezier(0.2, 0, 1, 0.9);
+ --easing-standard: cubic-bezier(0.2, 0, 0.38, 0.9);
+
+}
+
+.dark {
+ --color-surface-action: var(--purple-400);
+ --color-surface-action-active: var(--purple-700);
+ --color-surface-action-hover: var(--purple-600);
+ --color-surface-action-muted: oklch(from var(--slate-0) l c h / 0.16);
+ --color-surface-action-subtle: oklch(from var(--slate-0) l c h / 0.08);
+ --color-surface-active: oklch(from var(--slate-0) l c h / 0.16);
+ --color-surface-danger: oklch(from var(--red-500) 0.18 0.02 h);
+ --color-surface-default: var(--slate-950);
+ --color-surface-emphasis: var(--slate-800);
+ --color-surface-hover: oklch(from var(--slate-0) l c h / 0.08);
+ --color-surface-info: oklch(from var(--blue-500) 0.18 0.02 h);
+ --color-surface-muted: var(--slate-850);
+ --color-surface-subtle: var(--slate-900);
+ --color-surface-success: oklch(from var(--green-500) 0.18 0.02 h);
+ --color-surface-warning: oklch(from var(--orange-500) 0.18 0.02 h);
+ --color-text-action: var(--purple-400);
+ --color-text-default: var(--slate-0);
+ --color-text-disabled: oklch(from var(--slate-0) l c h / 0.32);
+ --color-text-secondary: var(--slate-300);
+ --color-text-tertiary: oklch(from var(--slate-0) l c h / 0.48);
+ --color-border-action: var(--purple-400);
+ --color-border-default: oklch(from var(--slate-0) l c h / 0.16);
+ --color-border-disabled: oklch(from var(--slate-0) l c h / 0.08);
+ --color-border-strong: oklch(from var(--slate-0) l c h / 0.24);
+ --color-icon-action: var(--purple-400);
+ --color-icon-default: var(--slate-0);
+ --color-icon-disabled: oklch(from var(--slate-0) l c h / 0.32);
+ --color-icon-secondary: var(--slate-300);
+ --shadow-lg: 0px 8px 16px oklch(from var(--slate-1000) l c h / 0.35);
+ --shadow-md: 0px 4px 8px oklch(from var(--slate-1000) l c h / 0.30);
+ --shadow-sm: 0px 1px 2px oklch(from var(--slate-1000) l c h / 0.20);
+ --shadow-xl: 0px 12px 24px oklch(from var(--slate-1000) l c h / 0.40);
+}
diff --git a/frontend/web/styles/_variables.scss b/frontend/web/styles/_variables.scss
index b91e7b92c8ee..aa17beed42d1 100644
--- a/frontend/web/styles/_variables.scss
+++ b/frontend/web/styles/_variables.scss
@@ -81,13 +81,13 @@ $danger-solid-dark-alert: rgba(34, 23, 40);
// Alphas
$info-alfa-8: rgba(10, 173, 223, 0.08);
$info-alfa-24: rgba(10, 173, 223, 0.24);
-$danger-alfa-8: rgba(255, 66, 75, 0.08);
-$danger-alfa-16: rgba(255, 66, 75, 0.16);
-$warning-alfa-8: rgba(255, 159, 0, 0.08);
-$primary-alfa-8: rgba(149, 108, 255, 0.08);
-$primary-alfa-16: rgba(149, 108, 255, 0.16);
-$primary-alfa-24: rgba(149, 108, 255, 0.24);
-$primary-alfa-32: rgba(149, 108, 255, 0.32);
+$danger-alfa-8: rgba(239, 77, 86, 0.08);
+$danger-alfa-16: rgba(239, 77, 86, 0.16);
+$warning-alfa-8: rgba(255, 159, 67, 0.08);
+$primary-alfa-8: rgba(104, 55, 252, 0.08);
+$primary-alfa-16: rgba(104, 55, 252, 0.16);
+$primary-alfa-24: rgba(104, 55, 252, 0.24);
+$primary-alfa-32: rgba(104, 55, 252, 0.32);
$basic-alpha-8: rgba(101, 109, 123, 0.08);
$basic-alpha-16: rgba(101, 109, 123, 0.16);
$basic-alpha-24: rgba(101, 109, 123, 0.24);
@@ -278,7 +278,7 @@ $switcher-dark-mode-width: 22px;
$checkbox-width: 20px;
$checkbox-border-color: rgba(101, 109, 123, 0.24);
$checkbox-border-color-dark: rgba(255, 255, 255, 0.24);
-$checkbox-focus-bg: rgba(149, 108, 255, 0.08);
+$checkbox-focus-bg: rgba(104, 55, 252, 0.08);
$checkbox-focus-border-color: $primary400;
$checkbox-hover-border-color: $primary;
$checkbox-checked-hover-border-color: $primary600;
diff --git a/frontend/web/styles/styles.scss b/frontend/web/styles/styles.scss
index 674a2fcefaef..89e6a95a64ba 100644
--- a/frontend/web/styles/styles.scss
+++ b/frontend/web/styles/styles.scss
@@ -1,4 +1,6 @@
@import "variables";
+@import "tokens";
+@import "token-utilities";
@import "3rdParty/index";
@import "components/index";
@import "flexbox/index";