From 0fe1e066456a07b0743cb233842fde958f1951ae Mon Sep 17 00:00:00 2001 From: Guillem Arias Fauste Date: Mon, 4 May 2026 00:50:52 +0200 Subject: [PATCH] refactor(design-system): migrate fg-* utilities to text-* and remove namespace (#1626) * refactor(design-system): migrate fg-* utilities to text-* and remove namespace The design system carried two parallel namespaces for foreground colors: text-* (canonical, ~2,000 uses) and fg-* (32 uses). Most fg-* tokens were 1:1 duplicates of a text-* counterpart. fg-gray was nearly identical to text-secondary, with a one-step shade difference in dark mode. This PR migrates all 32 usages to their text-* equivalents and removes the fg-* block from the design tokens. Closes #1606. Mapping: - fg-inverse -> text-inverse (20 usages, identical light/dark values) - fg-gray -> text-secondary (7 usages; light values match, dark is one step lighter: gray-300 vs gray-400) - fg-primary -> text-primary (3 usages, identical values) - fg-subdued -> text-subdued (2 usages, identical values) The four other fg-* tokens (fg-contrast, fg-primary-variant, fg-secondary, fg-secondary-variant) had zero usages despite being defined; they are removed without replacement. JSON / build: - design/tokens/sure.tokens.json: $version 1.0.0 -> 2.0.0 (breaking schema change per the policy added in #1620). 8 fg-* token definitions removed. - button-bg-ghost-hover's dark value still references "fg-inverse" internally; rewritten to "bg-gray-800 text-inverse" so the cleanup doesn't break that utility. - _generated.css regenerated. 42 utility blocks now (was 50). Lookbook tokens preview: - The Text & foregrounds section dropped its split between text-* (canonical) and fg-* (legacy). Now a single section listing the five text-* utilities. The "(legacy)" framing is gone since there's no legacy left. README: - design/tokens/README.md's button-bg-ghost-hover edge-case example updated to reflect the new "bg-gray-800 text-inverse" dark value. Visual review needed in dark mode: - Anywhere icons use the application_helper#icon helper with color: "default" (most icons in the app). The default class moved from fg-gray (gray-400 dark) to text-secondary (gray-300 dark), so default-color icons render slightly lighter in dark mode. - DS::Buttonish icons in secondary buttons (same shade shift). - DS::Link icons (same). - Time series chart axes (same). - All tooltips, account add flow, settings hostings buttons, invitations, AI consent, family export, danger-zone buttons -- these used fg-inverse, which is identical to text-inverse, so no visual change expected. * fix(design-system): use inverse pair on tooltips for readable dark mode * fix(lookbook): use semantic tokens in menu preview header text * fix(lookbook): set text-primary on layout body so previews inherit theme * fix(design-system): keep shadows dark-toned in dark mode Inverting shadows to white|8% on dark surfaces produces a halo effect rather than an elevation cue, and stacks redundantly with the alpha-white 1px ring already in shadow-border-*. Switch dark-mode shadows to black at progressively higher alpha (25%/30%/35%/40%/50% for xs..xl) so they read as actual cast shadows on near-black surfaces. Surface-tint differences and the existing alpha-white border ring continue to handle elevation hierarchy and edge definition. Approach matches Material 3, Apple HIG, IBM Carbon, Refactoring UI, and the dark-mode shadows used in Linear/Vercel/Stripe. * fix(design-system): set text-primary on DS::Dialog element Browser UA stylesheets apply color: black directly to , which overrides ancestor inheritance even when a body or html ancestor sets a theme-aware color. Unstyled child content then renders black regardless of theme. Setting text-primary on the dialog element itself defeats the UA override and lets descendants inherit the semantic token. * fix(lookbook): use shadow css vars in effects preview so dark theme renders * Revert "fix(design-system): keep shadows dark-toned in dark mode" This reverts commit 3e9d76ed0beb5ac5f2acbad61e4d1c39eadc9ac2. * fix(design-system): use opacity-70 instead of text-inverse/70 in value tooltip The custom @utility text-inverse expands to @apply text-white and isn't modifier-aware, so text-inverse/70 produced no CSS at all and the muted labels fell through to inherited color (invisible on the white pill in dark mode). Replace with text-inverse + opacity-70. Same visual effect, works with the existing utility definition. --- .../sure-design-system/_generated.css | 74 +++---------------- app/components/DS/buttonish.rb | 14 ++-- app/components/DS/dialog.html.erb | 2 +- app/components/DS/link.rb | 2 +- app/components/DS/tooltip.html.erb | 4 +- app/helpers/application_helper.rb | 2 +- .../time_series_chart_controller.js | 6 +- app/views/accounts/_account_type.html.erb | 2 +- app/views/accounts/new.html.erb | 2 +- app/views/chats/_ai_consent.html.erb | 2 +- app/views/family_exports/new.html.erb | 2 +- .../holdings/_missing_price_tooltip.html.erb | 4 +- app/views/investments/_value_tooltip.html.erb | 18 ++--- app/views/invitations/new.html.erb | 2 +- app/views/layouts/lookbooks.html.erb | 2 +- app/views/pages/changelog.html.erb | 2 +- .../hostings/_assistant_settings.html.erb | 4 +- .../hostings/_danger_zone_settings.html.erb | 4 +- app/views/settings/profiles/show.html.erb | 2 +- app/views/shared/_money_field.html.erb | 2 +- app/views/shared/_text_tooltip.erb | 4 +- app/views/shared/notifications/_cta.html.erb | 2 +- design/tokens/README.md | 2 +- design/tokens/sure.tokens.json | 14 +--- .../previews/design_tokens_preview.rb | 5 +- .../design_tokens_preview/effects.html.erb | 2 +- .../design_tokens_preview/text.html.erb | 71 ++++++------------ .../previews/menu_component_preview.rb | 6 +- 28 files changed, 83 insertions(+), 175 deletions(-) diff --git a/app/assets/tailwind/sure-design-system/_generated.css b/app/assets/tailwind/sure-design-system/_generated.css index bb473b187..dc4e8f855 100644 --- a/app/assets/tailwind/sure-design-system/_generated.css +++ b/app/assets/tailwind/sure-design-system/_generated.css @@ -302,70 +302,6 @@ @apply bg-surface-inset animate-pulse; } -@utility fg-gray { - @apply text-gray-500; - - @variant theme-dark { - @apply text-gray-400; - } -} - -@utility fg-contrast { - @apply text-gray-400; - - @variant theme-dark { - @apply text-gray-500; - } -} - -@utility fg-inverse { - @apply text-white; - - @variant theme-dark { - @apply text-gray-900; - } -} - -@utility fg-primary { - @apply text-gray-900; - - @variant theme-dark { - @apply text-white; - } -} - -@utility fg-primary-variant { - @apply text-gray-800; - - @variant theme-dark { - @apply text-gray-50; - } -} - -@utility fg-secondary { - @apply text-gray-50; - - @variant theme-dark { - @apply text-gray-400; - } -} - -@utility fg-secondary-variant { - @apply text-gray-100; - - @variant theme-dark { - @apply text-gray-500; - } -} - -@utility fg-subdued { - @apply text-gray-400; - - @variant theme-dark { - @apply text-gray-500; - } -} - @utility text-primary { @apply text-gray-900; @@ -498,6 +434,14 @@ } } +@utility border-inverse { + @apply border-alpha-white-200; + + @variant theme-dark { + @apply border-alpha-black-300; + } +} + @utility button-bg-primary { @apply bg-gray-900; @@ -558,7 +502,7 @@ @apply bg-gray-50; @variant theme-dark { - @apply bg-gray-800 fg-inverse; + @apply bg-gray-800 text-inverse; } } diff --git a/app/components/DS/buttonish.rb b/app/components/DS/buttonish.rb index 0c68d1dbe..e85169191 100644 --- a/app/components/DS/buttonish.rb +++ b/app/components/DS/buttonish.rb @@ -2,11 +2,11 @@ class DS::Buttonish < DesignSystemComponent VARIANTS = { primary: { container_classes: "text-inverse bg-inverse hover:bg-inverse-hover disabled:bg-gray-500 theme-dark:disabled:bg-gray-400", - icon_classes: "fg-inverse" + icon_classes: "text-inverse" }, secondary: { container_classes: "text-primary bg-gray-200 theme-dark:bg-gray-700 hover:bg-gray-300 theme-dark:hover:bg-gray-600 disabled:bg-gray-200 theme-dark:disabled:bg-gray-600", - icon_classes: "fg-primary" + icon_classes: "text-primary" }, destructive: { container_classes: "text-inverse bg-red-500 theme-dark:bg-red-400 hover:bg-red-600 theme-dark:hover:bg-red-500 disabled:bg-red-200 theme-dark:disabled:bg-red-600", @@ -14,23 +14,23 @@ class DS::Buttonish < DesignSystemComponent }, outline: { container_classes: "text-primary border border-secondary bg-transparent hover:bg-surface-hover", - icon_classes: "fg-gray" + icon_classes: "text-secondary" }, outline_destructive: { container_classes: "text-destructive border border-secondary bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700", - icon_classes: "fg-gray" + icon_classes: "text-secondary" }, ghost: { container_classes: "text-primary bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700", - icon_classes: "fg-gray" + icon_classes: "text-secondary" }, icon: { container_classes: "hover:bg-gray-100 theme-dark:hover:bg-gray-700", - icon_classes: "fg-gray" + icon_classes: "text-secondary" }, icon_inverse: { container_classes: "bg-inverse hover:bg-inverse-hover", - icon_classes: "fg-inverse" + icon_classes: "text-inverse" } }.freeze diff --git a/app/components/DS/dialog.html.erb b/app/components/DS/dialog.html.erb index 307303970..e6892b1ce 100644 --- a/app/components/DS/dialog.html.erb +++ b/app/components/DS/dialog.html.erb @@ -1,5 +1,5 @@ <%= wrapper_element do %> - <%= tag.dialog class: "w-full h-full bg-transparent theme-dark:backdrop:bg-alpha-black-900 backdrop:bg-overlay pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] #{(drawer? || responsive?) ? "lg:p-3" : "lg:p-1"}", **merged_opts do %> + <%= tag.dialog class: "w-full h-full bg-transparent text-primary theme-dark:backdrop:bg-alpha-black-900 backdrop:bg-overlay pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] #{(drawer? || responsive?) ? "lg:p-3" : "lg:p-1"}", **merged_opts do %> <%= tag.div class: dialog_outer_classes do %> <%= tag.div class: dialog_inner_classes, data: { DS__dialog_target: "content" } do %>
"> diff --git a/app/components/DS/link.rb b/app/components/DS/link.rb index 209f86b09..41ea3f3f2 100644 --- a/app/components/DS/link.rb +++ b/app/components/DS/link.rb @@ -6,7 +6,7 @@ class DS::Link < DS::Buttonish VARIANTS = VARIANTS.reverse_merge( default: { container_classes: "", - icon_classes: "fg-gray" + icon_classes: "text-secondary" } ).freeze diff --git a/app/components/DS/tooltip.html.erb b/app/components/DS/tooltip.html.erb index 3bd7a1ba1..a10c1147f 100644 --- a/app/components/DS/tooltip.html.erb +++ b/app/components/DS/tooltip.html.erb @@ -1,8 +1,8 @@ <%= helpers.icon icon_name, size: size, color: color %> - -