From 5b5e9220697a3d80cadde04df5e4ec4e5fac79cf Mon Sep 17 00:00:00 2001
From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Date: Thu, 27 Aug 2026 15:57:15 +0000
Subject: [PATCH] Make the upgrade banner neutral instead of gradient purple
(#7696)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What
The `promo` banner tone was a full-bleed `indigo-500 → purple-500`
gradient with white text and a black drop-shadow on the CTA. It was the
only saturated fill in the app, and against the warm neutral palette it
read as a foreign object above the workbench.
The bar is now app chrome:
| | Before | After |
|---|---|---|
| Background | 135° indigo→purple gradient | `--c-bg-raised` |
| Border | `transparent` | `--c-border-subtle` hairline |
| Icon | white glyph, no container | neutral glyph in a
`--c-surface-sunken` chip |
| Text | forced white | `--c-text` / `--c-text-muted` |
| CTA | `premium` accent (violet gradient) | `default` accent (same
primary button as the rest of the app) |
Before
After
Only caller is the friendly variant of `UpgradeBanner` (self-hosted,
under the free-tier user limit).
## Notes
- **No new theme tokens.** Every value is an existing `--c-*` semantic
token, so light and dark both follow automatically with no per-theme
overrides.
- The `premium` accent itself is untouched, so the upgrade CTAs in
`OfflineActivationCard` and `PairingPanel` are unaffected.
- `--c-hue-indigo` / `--c-hue-purple` are still used by
`SaaSOnboardingSlides`, `PaygFree` and `UpgradeModal`, so no tokens are
orphaned.
- Deleted comments describe rules that no longer exist (the gradient,
the white-on-gradient text overrides, the CTA shadow). No new comments
added.
## Verification
- `task frontend:check:all` passes (typecheck, oxlint, all four theme
linters, stylelint, format, tests, build, storybook build).
- `task frontend:storybook:a11y:changed` passes light and dark: 7
AppBanner stories, 0 violations. Both a11y baselines are empty, so this
is zero known violations rather than a baselined pass.
- Checked in Storybook under **Shared / AppBanner → All Top Bars**,
which renders every top bar the app can show side by side, in both
themes.
---
.../src/core/components/shared/AppBanner.css | 31 ++++++++-----------
.../src/core/components/shared/AppBanner.tsx | 2 +-
2 files changed, 14 insertions(+), 19 deletions(-)
diff --git a/frontend/editor/src/core/components/shared/AppBanner.css b/frontend/editor/src/core/components/shared/AppBanner.css
index b3f3f9343a..8151fe916a 100644
--- a/frontend/editor/src/core/components/shared/AppBanner.css
+++ b/frontend/editor/src/core/components/shared/AppBanner.css
@@ -24,17 +24,10 @@
--app-banner-icon: var(--c-accent-fg, var(--c-primary));
}
-/* The one bar meant to pop, so it takes the feature gradient rather than a tint.
- Fixed hues by design — it doesn't follow the chosen accent. */
.app-banner--promo {
- --app-banner-bg: linear-gradient(
- 135deg,
- var(--c-hue-indigo) 0%,
- var(--c-hue-purple) 100%
- );
- --app-banner-border: transparent;
- --app-banner-icon: var(--color-text-on-accent);
- color: var(--color-text-on-accent);
+ --app-banner-bg: var(--c-bg-raised);
+ --app-banner-border: var(--c-border-subtle);
+ --app-banner-icon: var(--c-text-muted);
}
.app-banner--warning {
@@ -86,16 +79,18 @@
font-size: 0.75rem;
}
-/* On the gradient everything is white; muted grey would disappear. */
-.app-banner--promo .app-banner__message,
-.app-banner--promo .app-banner__actions .sui-btn--tertiary,
-.app-banner--promo .app-banner__actions .sui-ai {
- color: var(--color-text-on-accent);
+.app-banner--promo .app-banner__icon {
+ width: 1.75rem;
+ height: 1.75rem;
+ justify-content: center;
+ border-radius: var(--radius-md);
+ background: var(--c-surface-sunken);
+ box-shadow: inset 0 0 0 1px var(--c-border-subtle);
}
-/* Lifts the premium CTA off the gradient it sits on. */
-.app-banner--promo .app-banner__actions .sui-btn--primary {
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
+.app-banner--promo.app-banner--compact .app-banner__icon {
+ width: 1.5rem;
+ height: 1.5rem;
}
.app-banner__actions {
diff --git a/frontend/editor/src/core/components/shared/AppBanner.tsx b/frontend/editor/src/core/components/shared/AppBanner.tsx
index ee0ba03741..79ff268dd2 100644
--- a/frontend/editor/src/core/components/shared/AppBanner.tsx
+++ b/frontend/editor/src/core/components/shared/AppBanner.tsx
@@ -11,7 +11,7 @@ export type AppBannerTone = "info" | "promo" | "warning" | "danger";
/** Tone decides the button too, so the CTA can't drift from the bar it sits on. */
const TONE_BUTTON = {
info: { variant: "secondary", accent: "default" },
- promo: { variant: "primary", accent: "premium" },
+ promo: { variant: "primary", accent: "default" },
warning: { variant: "primary", accent: "warning" },
danger: { variant: "primary", accent: "danger" },
} as const;