Conversation
📖 Storybook Preview |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning MetaMask internal reviewing guidelines:
Ignoring alerts on:
|
698afa6 to
55c6251
Compare
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: New typography classes have no CSS utility definitions
- Added @Utility definitions for all new text-* variants in theme.css with responsive metrics so font-size, line-height, and letter-spacing apply.
- ✅ Fixed: Test expects wrong text-color conflict winner
- Corrected the test to expect text-muted as the conflict winner per tailwind-merge’s last-wins behavior.
Or push these changes by commenting:
@cursor push 0a19cee029
Preview (0a19cee029)
diff --git a/packages/design-system-react/src/utils/tw-merge.test.ts b/packages/design-system-react/src/utils/tw-merge.test.ts
--- a/packages/design-system-react/src/utils/tw-merge.test.ts
+++ b/packages/design-system-react/src/utils/tw-merge.test.ts
@@ -91,7 +91,7 @@
const result = twMerge(
'text-body-md text-heading-lg text-default text-muted',
);
- expect(result).toBe('text-heading-lg text-default');
+ expect(result).toBe('text-heading-lg text-muted');
});
});
});
diff --git a/packages/design-tokens/src/tailwind/theme.css b/packages/design-tokens/src/tailwind/theme.css
--- a/packages/design-tokens/src/tailwind/theme.css
+++ b/packages/design-tokens/src/tailwind/theme.css
@@ -502,3 +502,187 @@
@utility shadow-default { --shadow-color: var(--color-shadow-default) !important; }
@utility shadow-primary { --shadow-color: var(--color-shadow-primary) !important; }
@utility shadow-error { --shadow-color: var(--color-shadow-error) !important; }
+
+/* New typography shortcut utilities (unprefixed) */
+@utility text-display-lg {
+ font-size: var(--typography-s-display-lg-font-size);
+ line-height: var(--typography-s-display-lg-line-height);
+ letter-spacing: var(--typography-s-display-lg-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-display-lg {
+ font-size: var(--typography-l-display-lg-font-size);
+ line-height: var(--typography-l-display-lg-line-height);
+ letter-spacing: var(--typography-l-display-lg-letter-spacing);
+ }
+}
+
+@utility text-display-md {
+ font-size: var(--typography-s-display-md-font-size);
+ line-height: var(--typography-s-display-md-line-height);
+ letter-spacing: var(--typography-s-display-md-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-display-md {
+ font-size: var(--typography-l-display-md-font-size);
+ line-height: var(--typography-l-display-md-line-height);
+ letter-spacing: var(--typography-l-display-md-letter-spacing);
+ }
+}
+
+@utility text-heading-lg {
+ font-size: var(--typography-s-heading-lg-font-size);
+ line-height: var(--typography-s-heading-lg-line-height);
+ letter-spacing: var(--typography-s-heading-lg-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-heading-lg {
+ font-size: var(--typography-l-heading-lg-font-size);
+ line-height: var(--typography-l-heading-lg-line-height);
+ letter-spacing: var(--typography-l-heading-lg-letter-spacing);
+ }
+}
+
+@utility text-heading-md {
+ font-size: var(--typography-s-heading-md-font-size);
+ line-height: var(--typography-s-heading-md-line-height);
+ letter-spacing: var(--typography-s-heading-md-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-heading-md {
+ font-size: var(--typography-l-heading-md-font-size);
+ line-height: var(--typography-l-heading-md-line-height);
+ letter-spacing: var(--typography-l-heading-md-letter-spacing);
+ }
+}
+
+@utility text-heading-sm {
+ font-size: var(--typography-s-heading-sm-font-size);
+ line-height: var(--typography-s-heading-sm-line-height);
+ letter-spacing: var(--typography-s-heading-sm-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-heading-sm {
+ font-size: var(--typography-l-heading-sm-font-size);
+ line-height: var(--typography-l-heading-sm-line-height);
+ letter-spacing: var(--typography-l-heading-sm-letter-spacing);
+ }
+}
+
+@utility text-body-lg {
+ /* Use medium metrics to align with default BodyLg weight */
+ font-size: var(--typography-s-body-lg-medium-font-size);
+ line-height: var(--typography-s-body-lg-medium-line-height);
+ letter-spacing: var(--typography-s-body-lg-medium-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-body-lg {
+ font-size: var(--typography-l-body-lg-medium-font-size);
+ line-height: var(--typography-l-body-lg-medium-line-height);
+ letter-spacing: var(--typography-l-body-lg-medium-letter-spacing);
+ }
+}
+
+@utility text-body-md {
+ font-size: var(--typography-s-body-md-font-size);
+ line-height: var(--typography-s-body-md-line-height);
+ letter-spacing: var(--typography-s-body-md-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-body-md {
+ font-size: var(--typography-l-body-md-font-size);
+ line-height: var(--typography-l-body-md-line-height);
+ letter-spacing: var(--typography-l-body-md-letter-spacing);
+ }
+}
+
+@utility text-body-sm {
+ font-size: var(--typography-s-body-sm-font-size);
+ line-height: var(--typography-s-body-sm-line-height);
+ letter-spacing: var(--typography-s-body-sm-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-body-sm {
+ font-size: var(--typography-l-body-sm-font-size);
+ line-height: var(--typography-l-body-sm-line-height);
+ letter-spacing: var(--typography-l-body-sm-letter-spacing);
+ }
+}
+
+@utility text-body-xs {
+ font-size: var(--typography-s-body-xs-font-size);
+ line-height: var(--typography-s-body-xs-line-height);
+ letter-spacing: var(--typography-s-body-xs-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-body-xs {
+ font-size: var(--typography-l-body-xs-font-size);
+ line-height: var(--typography-l-body-xs-line-height);
+ letter-spacing: var(--typography-l-body-xs-letter-spacing);
+ }
+}
+
+@utility text-page-heading {
+ font-size: var(--typography-s-page-heading-font-size);
+ line-height: var(--typography-s-page-heading-line-height);
+ letter-spacing: var(--typography-s-page-heading-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-page-heading {
+ font-size: var(--typography-l-page-heading-font-size);
+ line-height: var(--typography-l-page-heading-line-height);
+ letter-spacing: var(--typography-l-page-heading-letter-spacing);
+ }
+}
+
+@utility text-section-heading {
+ font-size: var(--typography-s-section-heading-font-size);
+ line-height: var(--typography-s-section-heading-line-height);
+ letter-spacing: var(--typography-s-section-heading-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-section-heading {
+ font-size: var(--typography-l-section-heading-font-size);
+ line-height: var(--typography-l-section-heading-line-height);
+ letter-spacing: var(--typography-l-section-heading-letter-spacing);
+ }
+}
+
+@utility text-button-label-md {
+ font-size: var(--typography-s-button-label-md-font-size);
+ line-height: var(--typography-s-button-label-md-line-height);
+ letter-spacing: var(--typography-s-button-label-md-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-button-label-md {
+ font-size: var(--typography-l-button-label-md-font-size);
+ line-height: var(--typography-l-button-label-md-line-height);
+ letter-spacing: var(--typography-l-button-label-md-letter-spacing);
+ }
+}
+
+@utility text-button-label-lg {
+ font-size: var(--typography-s-button-label-lg-font-size);
+ line-height: var(--typography-s-button-label-lg-line-height);
+ letter-spacing: var(--typography-s-button-label-lg-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-button-label-lg {
+ font-size: var(--typography-l-button-label-lg-font-size);
+ line-height: var(--typography-l-button-label-lg-line-height);
+ letter-spacing: var(--typography-l-button-label-lg-letter-spacing);
+ }
+}
+
+@utility text-amount-display-lg {
+ font-size: var(--typography-s-amount-display-lg-font-size);
+ line-height: var(--typography-s-amount-display-lg-line-height);
+ letter-spacing: var(--typography-s-amount-display-lg-letter-spacing);
+}
+@media (min-width: 48rem) {
+ .text-amount-display-lg {
+ font-size: var(--typography-l-amount-display-lg-font-size);
+ line-height: var(--typography-l-amount-display-lg-line-height);
+ letter-spacing: var(--typography-l-amount-display-lg-letter-spacing);
+ }
+}b93e3c8 to
d5ccea6
Compare
georgewrmarshall
left a comment
There was a problem hiding this comment.
Left some comments. I'm not sure we need to change anything in the design-system-tailwind-preset package
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: cleancss corrupts Tailwind v4 directives in published theme
- Replaced clean-css minification with a direct copy for theme.css to preserve Tailwind v4 at-rules.
- ✅ Fixed: Web ESLint drops correctness rules for Tailwind classes
- Enabled the better-tailwindcss correctness preset in the web ESLint config to restore class validation rules.
Or push these changes by commenting:
@cursor push 7d3b2ced3f
Preview (7d3b2ced3f)
diff --git a/eslint.config.mjs b/eslint.config.mjs
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -233,6 +233,7 @@
plugins: {
'better-tailwindcss': betterTailwind,
},
+ extends: [betterTailwind.configs.correctness],
rules: {
'better-tailwindcss/sort-classes': 'error',
},
diff --git a/packages/design-tokens/package.json b/packages/design-tokens/package.json
--- a/packages/design-tokens/package.json
+++ b/packages/design-tokens/package.json
@@ -39,7 +39,7 @@
"scripts": {
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references && yarn build:css",
"build:css": "cleancss -o dist/styles.css src/css/index.css && yarn build:css:tailwind",
- "build:css:tailwind": "mkdir -p dist/tailwind && cleancss -o dist/tailwind/theme.css src/tailwind/theme.css",
+ "build:css:tailwind": "mkdir -p dist/tailwind && cp src/tailwind/theme.css dist/tailwind/theme.css",
"check:tailwind-theme-parity": "tsx scripts/check-tailwind-theme-parity.ts",
"changelog:update": "../../scripts/update-changelog.sh @metamask/design-tokens",
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/design-tokens",
📖 Storybook Preview |
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Conflicting classes lint rule downgraded from error to warn
- Updated eslint rule to 'error' for better-tailwindcss/no-conflicting-classes to match previous enforcement.
- ✅ Fixed: Default Tailwind font sizes and weights not disabled
- Added --font-size-: initial and --font-weight-: initial in @theme to clear defaults.
Or push these changes by commenting:
@cursor push 1e04a9d74c
Preview (1e04a9d74c)
diff --git a/eslint.config.mjs b/eslint.config.mjs
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -235,7 +235,7 @@
},
rules: {
'better-tailwindcss/sort-classes': 'error',
- 'better-tailwindcss/no-conflicting-classes': 'warn',
+ 'better-tailwindcss/no-conflicting-classes': 'error',
'better-tailwindcss/no-unregistered-classes': 'error',
},
},
diff --git a/packages/design-tokens/src/tailwind/theme.css b/packages/design-tokens/src/tailwind/theme.css
--- a/packages/design-tokens/src/tailwind/theme.css
+++ b/packages/design-tokens/src/tailwind/theme.css
@@ -3,6 +3,9 @@
@import '../css/shadow.css';
@theme {
+ /* Disable default Tailwind font sizes and weights */
+ --font-size-*: initial;
+ --font-weight-*: initial;
/* Essential Tailwind colors required for basic utilities */
--color-inherit: inherit;
--color-current: currentColor;
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing
.lightCSS selector breaks theme switching- Added explicit
[data-theme='light'], .lightblock intheme.cssreapplying light theme variables so.lightcan override.darkin nested contexts.
- Added explicit
Or push these changes by commenting:
@cursor push bcd1e57523
Preview (bcd1e57523)
diff --git a/packages/design-tokens/src/tailwind/theme.css b/packages/design-tokens/src/tailwind/theme.css
--- a/packages/design-tokens/src/tailwind/theme.css
+++ b/packages/design-tokens/src/tailwind/theme.css
@@ -344,6 +344,173 @@
--color-shadow-error: #ff758433;
}
+/**
+ * Light Theme Colors
+ * Explicitly re-apply light values so `.light` (or [data-theme='light'])
+ * can override a surrounding `.dark` context.
+ */
+[data-theme='light'],
+.light {
+ /* Background default should be the darkest shade, 0 elevation.
+ Section is +1 elevation, subsection is +2 elevation.
+ Alternative should be deprecated. */
+ --color-background-default: var(--brand-colors-grey-grey000);
+ --color-background-section: var(--brand-colors-grey-grey050);
+ --color-background-subsection: var(--brand-colors-grey-grey000);
+ --color-background-alternative: var(--brand-colors-grey-grey050);
+
+ /* Applied to interactive elements, such as buttons.
+ For light mode, we use 8% increments of opacity to offer
+ sufficient affordance for usability. */
+ --color-background-muted: #b4b4b528;
+ --color-background-muted-hover: #b4b4b53d;
+ --color-background-muted-pressed: #b4b4b552;
+
+ /* Ensures visual consistency with section and subsection. */
+ --color-background-default-hover: var(--brand-colors-grey-grey050);
+ --color-background-default-pressed: var(--brand-colors-grey-grey100);
+
+ /* These colors should be deprecated eventually for simplicity */
+ --color-background-alternative-hover: #ebedf1;
+ --color-background-alternative-pressed: #e1e4ea;
+
+ /* These have opacities of pure white for general usage.
+ Visually, they align with section and subsection.*/
+ --color-background-hover: #b4b4b528;
+ --color-background-pressed: #b4b4b53d;
+
+ /* These are our content colors.
+ Contrast ratio of alternative: 5.7 on default, 5.1 on section.
+ Contrast ratio of muted: 1.9 on default, 1.7 on section.*/
+ --color-text-default: var(--brand-colors-grey-grey900);
+ --color-text-alternative: var(--brand-colors-grey-grey500);
+ --color-text-muted: var(--brand-colors-grey-grey200);
+
+ --color-icon-default: var(--brand-colors-grey-grey900);
+ --color-icon-default-hover: #2a2b2c;
+ --color-icon-default-pressed: #414243;
+
+ --color-icon-alternative: var(--brand-colors-grey-grey500);
+ --color-icon-muted: var(--brand-colors-grey-grey200);
+ --color-icon-inverse: var(--brand-colors-grey-grey000);
+
+ /* Border default has a 3:3 ratio when applied on bg-default
+ and 3.0 on section. We use opacity for border-muted so it
+ maintains sufficient contrast on bg-default and bg-section.*/
+ --color-border-default: var(--brand-colors-grey-grey400);
+ --color-border-muted: #b4b4b566;
+
+ /* Derived from the background hue, 264.5, for consistency.
+ Opacity for default is 36%, alternative is 57%. Default is meant
+ to be the inverse of dark mode so the layering feels consistent
+ across themes. Alternative is relatively darker in light mode for
+ better contrast.*/
+ --color-overlay-default: #0a0d135c;
+ --color-overlay-alternative: #0a0d1392;
+ --color-overlay-inverse: var(--brand-colors-grey-grey000);
+
+ /* For primary semantic elements: interactive, active, selected (#4459ff) */
+ --color-primary-default: var(--brand-colors-blue-blue500);
+ /* Stronger color for primary semantic elements (#2c3dc5) */
+ --color-primary-alternative: var(--brand-colors-blue-blue600);
+ /* Muted color for primary semantic elements (#4459ff1a) */
+ --color-primary-muted: #4459ff1a;
+ /* For elements placed on top of primary/default (#ffffff) */
+ --color-primary-inverse: var(--brand-colors-grey-grey000);
+ /* Hover state surface for primary/default (#384df5) */
+ --color-primary-default-hover: #384df5;
+ /* Pressed state surface for primary/default (#2b3eda) */
+ --color-primary-default-pressed: #2b3eda;
+ /* Hover state surface for primary/muted (#4459ff26) */
+ --color-primary-muted-hover: #4459ff26;
+ /* Pressed state surface for primary/muted (#4459ff33) */
+ --color-primary-muted-pressed: #4459ff33;
+ /* For danger semantic elements: error, critical, destructive (#ca3542) */
+ --color-error-default: var(--brand-colors-red-red500);
+ /* Stronger color for error semantic (#952731) */
+ --color-error-alternative: var(--brand-colors-red-red600);
+ /* Muted color for error semantic (#ca35421a) */
+ --color-error-muted: #ca35421a;
+ /* For elements placed on top of error/default (#ffffff) */
+ --color-error-inverse: var(--brand-colors-grey-grey000);
+ /* Hover state surface for error/default (#ba313d) */
+ --color-error-default-hover: #ba313d;
+ /* Pressed state surface for error/default (#9a2832) */
+ --color-error-default-pressed: #9a2832;
+ /* Hover state surface for error/muted (#ca354226) */
+ --color-error-muted-hover: #ca354226;
+ /* Pressed state surface for error/muted (#ca354233) */
+ --color-error-muted-pressed: #ca354233;
+ /* For warning semantic elements: caution, attention, precaution (#9a6300) */
+ --color-warning-default: var(--brand-colors-yellow-yellow500);
+ /* Muted color option for warning semantic (#9a63001a) */
+ --color-warning-muted: #9a63001a;
+ /* For elements placed on top of warning/default (#ffffff) */
+ --color-warning-inverse: var(--brand-colors-grey-grey000);
+ /* Hover state surface for warning/default (#855500) */
+ --color-warning-default-hover: #855500;
+ /* Pressed state surface for warning/default (#5c3b00) */
+ --color-warning-default-pressed: #5c3b00;
+ /* Hover state surface for warning/muted (#9a630026) */
+ --color-warning-muted-hover: #9a630026;
+ /* Pressed state surface for warning/muted (#9a630033) */
+ --color-warning-muted-pressed: #9a630033;
+ /* For positive semantic elements: success, confirm, complete, safe (#457A39) */
+ --color-success-default: var(--brand-colors-lime-lime500);
+ /* Muted color for positive semantic (#457a391a) */
+ --color-success-muted: #457a391a;
+ /* For elements placed on top of success/default (#ffffff) */
+ --color-success-inverse: var(--brand-colors-grey-grey000);
+ /* Hover state surface for success/default (#3d6c32) */
+ --color-success-default-hover: #3d6c32;
+ /* Pressed state surface for success/default (#2d5025) */
+ --color-success-default-pressed: #2d5025;
+ /* Hover state surface for success/muted (#457a3926) */
+ --color-success-muted-hover: #457a3926;
+ /* Pressed state surface for success/muted (#457a3933) */
+ --color-success-muted-pressed: #457a3933;
+ /* For informational read-only elements: info, reminder, hint (#4459ff) */
+ --color-info-default: var(--brand-colors-blue-blue500);
+ /* Muted color for informational semantic (#4459ff1a) */
+ --color-info-muted: #4459ff1a;
+ /* For elements placed on top of info/default (#ffffff) */
+ --color-info-inverse: var(--brand-colors-grey-grey000);
+ /* Expressive color in light orange (#ffa680) */
+ --color-accent01-light: var(--brand-colors-orange-orange200);
+ /* Expressive color in orange (#ff5c16) */
+ --color-accent01-normal: var(--brand-colors-orange-orange400);
+ /* Expressive color in dark orange (#661800) */
+ --color-accent01-dark: var(--brand-colors-orange-orange700);
+ /* Expressive color in light purple (#eac2ff) */
+ --color-accent02-light: var(--brand-colors-purple-purple100);
+ /* Expressive color in purple (#d075ff) */
+ --color-accent02-normal: var(--brand-colors-purple-purple300);
+ /* Expressive color in dark purple (#3d065f) */
+ --color-accent02-dark: var(--brand-colors-purple-purple800);
+ /* Expressive color in light lime (#e5ffc3) */
+ --color-accent03-light: var(--brand-colors-lime-lime050);
+ /* Expressive color in lime (#baf24a) */
+ --color-accent03-normal: var(--brand-colors-lime-lime100);
+ /* Expressive color in dark lime (#013330) */
+ --color-accent03-dark: var(--brand-colors-lime-lime700);
+ /* Expressive color in light indigo (#) */
+ --color-accent04-light: var(--brand-colors-indigo-indigo100);
+ /* Expressive color in indigo (#) */
+ --color-accent04-normal: var(--brand-colors-indigo-indigo200);
+ /* Expressive color in dark indigo (#) */
+ --color-accent04-dark: var(--brand-colors-indigo-indigo800);
+ /* For Flask primary accent color (#8f44e4) */
+ --color-flask-default: var(--brand-colors-purple-purple500);
+ /* For elements placed on top of flask/default (#ffffff) */
+ --color-flask-inverse: var(--brand-colors-grey-grey000);
+ /* For neutral drop shadow color (black-10% | black-40%) */
+ --color-shadow-default: #0000001a;
+ /* For primary drop shadow color (blue500-20% | blue300-20%) */
+ --color-shadow-primary: #4459ff33;
+ /* For critical/danger drop shadow color (red50-20% | red300-20%) */
+ --color-shadow-error: #ca354266;
+}
+
/* Color Shortcut Utilities - Enable shorter class names */
/* Text shortcuts: text-default instead of text-text-default */
@utility text-default {
📖 Storybook Preview |
📖 Storybook Preview |
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Duplicated color tokens risk silent v3/v4 divergence
- Replaced the duplicated .light/.dark variable blocks in theme.css with imports of the source light/dark theme CSS files so values come from a single source of truth.
Or push these changes by commenting:
@cursor push 151c5fac02
Preview (151c5fac02)
diff --git a/packages/design-tokens/src/tailwind/theme.css b/packages/design-tokens/src/tailwind/theme.css
--- a/packages/design-tokens/src/tailwind/theme.css
+++ b/packages/design-tokens/src/tailwind/theme.css
@@ -1,6 +1,8 @@
@import '../css/brand-colors.css';
@import '../css/typography.css';
@import '../css/shadow.css';
+@import '../css/light-theme-colors.css';
+@import '../css/dark-theme-colors.css';
@theme {
--font-size-*: initial;
@@ -183,257 +185,6 @@
var(--shadow-color, var(--color-shadow-default));
}
-/**
- * Light Theme Colors
- * Explicitly scoped so .light resets variables when nested inside .dark.
- * The :root values come from @theme above — this block only needs
- * the class/attribute selectors for runtime theme switching.
- */
-[data-theme='light'],
-.light {
- --color-background-default: var(--brand-colors-grey-grey000);
- --color-background-section: var(--brand-colors-grey-grey050);
- --color-background-subsection: var(--brand-colors-grey-grey000);
- --color-background-alternative: var(--brand-colors-grey-grey050);
- --color-background-muted: #b4b4b528;
- --color-background-muted-hover: #b4b4b53d;
- --color-background-muted-pressed: #b4b4b552;
- --color-background-default-hover: var(--brand-colors-grey-grey050);
- --color-background-default-pressed: var(--brand-colors-grey-grey100);
- --color-background-alternative-hover: #ebedf1;
- --color-background-alternative-pressed: #e1e4ea;
- --color-background-hover: #b4b4b528;
- --color-background-pressed: #b4b4b53d;
- --color-text-default: var(--brand-colors-grey-grey900);
- --color-text-alternative: var(--brand-colors-grey-grey500);
- --color-text-muted: var(--brand-colors-grey-grey200);
- --color-icon-default: var(--brand-colors-grey-grey900);
- --color-icon-default-hover: #2a2b2c;
- --color-icon-default-pressed: #414243;
- --color-icon-alternative: var(--brand-colors-grey-grey500);
- --color-icon-muted: var(--brand-colors-grey-grey200);
- --color-icon-inverse: var(--brand-colors-grey-grey000);
- --color-border-default: var(--brand-colors-grey-grey400);
- --color-border-muted: #b4b4b566;
- --color-overlay-default: #0a0d135c;
- --color-overlay-alternative: #0a0d1392;
- --color-overlay-inverse: var(--brand-colors-grey-grey000);
- --color-primary-default: var(--brand-colors-blue-blue500);
- --color-primary-alternative: var(--brand-colors-blue-blue600);
- --color-primary-muted: #4459ff1a;
- --color-primary-inverse: var(--brand-colors-grey-grey000);
- --color-primary-default-hover: #384df5;
- --color-primary-default-pressed: #2b3eda;
- --color-primary-muted-hover: #4459ff26;
- --color-primary-muted-pressed: #4459ff33;
- --color-error-default: var(--brand-colors-red-red500);
- --color-error-alternative: var(--brand-colors-red-red600);
- --color-error-muted: #ca35421a;
- --color-error-inverse: var(--brand-colors-grey-grey000);
- --color-error-default-hover: #ba313d;
- --color-error-default-pressed: #9a2832;
- --color-error-muted-hover: #ca354226;
- --color-error-muted-pressed: #ca354233;
- --color-warning-default: var(--brand-colors-yellow-yellow500);
- --color-warning-muted: #9a63001a;
- --color-warning-inverse: var(--brand-colors-grey-grey000);
- --color-warning-default-hover: #855500;
- --color-warning-default-pressed: #5c3b00;
- --color-warning-muted-hover: #9a630026;
- --color-warning-muted-pressed: #9a630033;
- --color-success-default: var(--brand-colors-lime-lime500);
- --color-success-muted: #457a391a;
- --color-success-inverse: var(--brand-colors-grey-grey000);
- --color-success-default-hover: #3d6c32;
- --color-success-default-pressed: #2d5025;
- --color-success-muted-hover: #457a3926;
- --color-success-muted-pressed: #457a3933;
- --color-info-default: var(--brand-colors-blue-blue500);
- --color-info-muted: #4459ff1a;
- --color-info-inverse: var(--brand-colors-grey-grey000);
- --color-accent01-light: var(--brand-colors-orange-orange200);
- --color-accent01-normal: var(--brand-colors-orange-orange400);
- --color-accent01-dark: var(--brand-colors-orange-orange700);
- --color-accent02-light: var(--brand-colors-purple-purple100);
- --color-accent02-normal: var(--brand-colors-purple-purple300);
- --color-accent02-dark: var(--brand-colors-purple-purple800);
- --color-accent03-light: var(--brand-colors-lime-lime050);
- --color-accent03-normal: var(--brand-colors-lime-lime100);
- --color-accent03-dark: var(--brand-colors-lime-lime700);
- --color-accent04-light: var(--brand-colors-indigo-indigo100);
- --color-accent04-normal: var(--brand-colors-indigo-indigo200);
- --color-accent04-dark: var(--brand-colors-indigo-indigo800);
- --color-flask-default: var(--brand-colors-purple-purple500);
- --color-flask-inverse: var(--brand-colors-grey-grey000);
- --color-shadow-default: #0000001a;
- --color-shadow-primary: #4459ff33;
- --color-shadow-error: #ca354266;
-}
-
-/**
- * Dark Theme Colors
- */
-[data-theme='dark'],
-.dark {
- /* Background default should be the darkest shade, 0 elevation.
- Section is +1 elevation, subsection is +2 elevation.
- Alternative should be deprecated. */
- --color-background-default: var(--brand-colors-grey-grey900);
- --color-background-section: var(--brand-colors-grey-grey800);
- --color-background-subsection: var(--brand-colors-grey-grey700);
- --color-background-alternative: var(--brand-colors-grey-grey1000);
-
- /* Applied to interactive elements, such as buttons.
- For dark mode, we apply pure white with 4% opacity so these
- tokens inherit the background hue of 264.5. */
- --color-background-muted: #ffffff0a;
- --color-background-muted-hover: #ffffff14;
- --color-background-muted-pressed: #ffffff1f;
-
- /* Ensures visual consistency with section and subsection. */
- --color-background-default-hover: var(--brand-colors-grey-grey800);
- --color-background-default-pressed: var(--brand-colors-grey-grey700);
-
- /* Hover state surface for background/alternative (#0d0d0e) */
- --color-background-alternative-hover: #0d0d0e;
- --color-background-alternative-pressed: #161617;
-
- /* These have opacities of pure white for general usage.
- We set 8% for hover and 12% for pressed so these tokens pick up
- background hues and are consistent with +1 and +2 elevations.*/
- --color-background-hover: #ffffff0a;
- --color-background-pressed: #ffffff1f;
-
- /* These are our content colors.
- Contrast ratio of alternative: 7.4 on default, 8.5 on section.
- Contrast ratio of muted: 2.0 on default, 1.8 on section.*/
- --color-text-default: var(--brand-colors-grey-grey000);
- --color-text-alternative: var(--brand-colors-grey-grey300);
- --color-text-muted: var(--brand-colors-grey-grey600);
-
- --color-icon-default: var(--brand-colors-grey-grey000);
- --color-icon-default-hover: #f0f0f0;
- --color-icon-default-pressed: #d0d0d0;
-
- --color-icon-alternative: var(--brand-colors-grey-grey300);
- --color-icon-muted: var(--brand-colors-grey-grey600);
- --color-icon-inverse: var(--brand-colors-grey-grey900);
-
- /* Contrast of border-default: 3:3 on bg-default, 3.0 on section.
- We use 8% opacify of pure white for border-muted so it maintains
- sufficient contrast on bg-default and bg-section.*/
- --color-border-default: var(--brand-colors-grey-grey500);
- --color-border-muted: #ffffff14;
-
- /* Derived from the same hue as bg-default, 264.5, for visual
- consistency. Ensures we don't have too much "red".
- Opacities are 72% and 84% for default and alternative. */
- --color-overlay-default: #030304b8;
- --color-overlay-alternative: #030304d6;
- --color-overlay-inverse: var(--brand-colors-grey-grey000);
-
- /* For primary semantic elements: interactive, active, selected (#8b99ff) */
- --color-primary-default: var(--brand-colors-blue-blue300);
- /* Stronger color for primary semantic elements (#adb6fe) */
- --color-primary-alternative: var(--brand-colors-blue-blue200);
- /* Muted color for primary semantic elements (#8b99ff26) */
- --color-primary-muted: #8b99ff26;
- /* For elements placed on top of primary/default (#121314) */
- --color-primary-inverse: var(--brand-colors-grey-grey900);
- /* Hover state surface for primary/default (#9eaaff) */
- --color-primary-default-hover: #9eaaff;
- /* Pressed state surface for primary/default (#c7ceff) */
- --color-primary-default-pressed: #c7ceff;
- /* Hover state surface for primary/muted (#8b99ff33) */
- --color-primary-muted-hover: #8b99ff33;
- /* Pressed state surface for primary/muted (#8b99ff40) */
- --color-primary-muted-pressed: #8b99ff40;
- /* For danger semantic elements: error, critical, destructive (#ff7584) */
- --color-error-default: var(--brand-colors-red-red300);
- /* Stronger color for error semantic (#ffa1aa) */
- --color-error-alternative: var(--brand-colors-red-red200);
- /* Muted color for error semantic (#ff758426) */
- --color-error-muted: #ff758426;
- /* For elements placed on top of error/default (#121314) */
- --color-error-inverse: var(--brand-colors-grey-grey900);
- /* Hover state surface for error/default (#ff8a96) */
- --color-error-default-hover: #ff8a96;
- /* Pressed state surface for error/default (#ffb2bb) */
- --color-error-default-pressed: #ffb2bb;
- /* Hover state surface for error/muted (#ff758433) */
- --color-error-muted-hover: #ff758433;
- /* Pressed state surface for error/muted (#ff758440) */
- --color-error-muted-pressed: #ff758440;
- /* For warning semantic elements: caution, attention, precaution (#f0b034) */
- --color-warning-default: var(--brand-colors-yellow-yellow200);
- /* Muted color option for warning semantic (#f0b03426) */
- --color-warning-muted: #f0b03426;
- /* For elements placed on top of warning/default (#121314) */
- --color-warning-inverse: var(--brand-colors-grey-grey900);
- /* Hover state surface for warning/default (#f3be59) */
- --color-warning-default-hover: #f3be59;
- /* Pressed state surface for warning/default (#f6cd7f) */
- --color-warning-default-pressed: #f6cd7f;
- /* Hover state surface for warning/muted (#f0b03433) */
- --color-warning-muted-hover: #f0b03433;
- /* Pressed state surface for warning/muted (#f0b03440) */
- --color-warning-muted-pressed: #f0b03440;
- /* For positive semantic elements: success, confirm, complete, safe (#baf24a) */
- --color-success-default: var(--brand-colors-lime-lime100);
- /* Muted color for positive semantic (#baf24a26) */
- --color-success-muted: #baf24a26;
- /* For elements placed on top of success/default (#121314) */
- --color-success-inverse: var(--brand-colors-grey-grey900);
- /* Hover state surface for success/default (#c9f570) */
- --color-success-default-hover: #c9f570;
- /* Pressed state surface for success/default (#d7f796) */
- --color-success-default-pressed: #d7f796;
- /* Hover state surface for success/muted (#baf24a33) */
- --color-success-muted-hover: #baf24a33;
- /* Pressed state surface for success/muted (#baf24a40) */
- --color-success-muted-pressed: #baf24a40;
- /* For informational read-only elements: info, reminder, hint (#8b99ff) */
- --color-info-default: var(--brand-colors-blue-blue300);
- /* Muted color for informational semantic (#8b99ff26) */
- --color-info-muted: #8b99ff26;
- /* For elements placed on top of info/default (#121314) */
- --color-info-inverse: var(--brand-colors-grey-grey900);
- /* Expressive color in light orange (#ffa680) */
- --color-accent01-light: var(--brand-colors-orange-orange200);
- /* Expressive color in orange (#ff5c16) */
- --color-accent01-normal: var(--brand-colors-orange-orange400);
- /* Expressive color in dark orange (#661800) */
- --color-accent01-dark: var(--brand-colors-orange-orange700);
- /* Expressive color in light purple (#eac2ff) */
- --color-accent02-light: var(--brand-colors-purple-purple100);
- /* Expressive color in purple (#d075ff) */
- --color-accent02-normal: var(--brand-colors-purple-purple300);
- /* Expressive color in dark purple (#3d065f) */
- --color-accent02-dark: var(--brand-colors-purple-purple800);
- /* Expressive color in light lime (#e5ffc3) */
- --color-accent03-light: var(--brand-colors-lime-lime050);
- /* Expressive color in lime (#baf24a) */
- --color-accent03-normal: var(--brand-colors-lime-lime100);
- /* Expressive color in dark lime (#013330) */
- --color-accent03-dark: var(--brand-colors-lime-lime700);
- /* Expressive color in light indigo (#cce7ff) */
- --color-accent04-light: var(--brand-colors-indigo-indigo100);
- /* Expressive color in indigo (#89b0ff) */
- --color-accent04-normal: var(--brand-colors-indigo-indigo200);
- /* Expressive color in dark indigo (#190066) */
- --color-accent04-dark: var(--brand-colors-indigo-indigo800);
- /* For Flask primary accent color (#d27dff) */
- --color-flask-default: var(--brand-colors-purple-purple300);
- /* For elements placed on top of flask/default (#121314) */
- --color-flask-inverse: var(--brand-colors-grey-grey900);
- /* For neutral drop shadow color (black-40%) */
- --color-shadow-default: #00000066;
- /* For primary drop shadow color (#8b99ff33) */
- --color-shadow-primary: #8b99ff33;
- /* For critical/danger drop shadow color (#ff758433) */
- --color-shadow-error: #ff758433;
-}
-
/* Color Shortcut Utilities - Enable shorter class names */
/* Text shortcuts: text-default instead of text-text-default */
@utility text-default {
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Removing
outlineclass breaks focus visibility for v3 consumers- Re-added
focus-visible:outlineto Primary, Secondary, and Tertiary buttons to restore focus outline style in Tailwind v3 while remaining harmless in v4.
- Re-added
Or push these changes by commenting:
@cursor push f7c20b63fa
Preview (f7c20b63fa)
diff --git a/packages/design-system-react/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx b/packages/design-system-react/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx
--- a/packages/design-system-react/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx
+++ b/packages/design-system-react/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx
@@ -59,6 +59,7 @@
isDanger && ['hover:bg-default-hover', 'active:bg-default-pressed'],
],
'focus-visible:ring-0',
+ 'focus-visible:outline',
isInverse
? 'focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-background-default'
: 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default',
diff --git a/packages/design-system-react/src/components/Button/variants/ButtonSecondary/ButtonSecondary.tsx b/packages/design-system-react/src/components/Button/variants/ButtonSecondary/ButtonSecondary.tsx
--- a/packages/design-system-react/src/components/Button/variants/ButtonSecondary/ButtonSecondary.tsx
+++ b/packages/design-system-react/src/components/Button/variants/ButtonSecondary/ButtonSecondary.tsx
@@ -55,6 +55,7 @@
isDanger && ['hover:bg-default-hover', 'active:bg-default-pressed'],
],
'focus-visible:ring-0',
+ 'focus-visible:outline',
isInverse
? 'focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-background-default'
: 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default',
diff --git a/packages/design-system-react/src/components/Button/variants/ButtonTertiary/ButtonTertiary.tsx b/packages/design-system-react/src/components/Button/variants/ButtonTertiary/ButtonTertiary.tsx
--- a/packages/design-system-react/src/components/Button/variants/ButtonTertiary/ButtonTertiary.tsx
+++ b/packages/design-system-react/src/components/Button/variants/ButtonTertiary/ButtonTertiary.tsx
@@ -57,6 +57,7 @@
],
],
'focus-visible:ring-0',
+ 'focus-visible:outline',
isInverse
? 'focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-background-default'
: 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-default',
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: tailwind-merge v3 breaks Tailwind v3 consumer compatibility
- Reverted packages/design-system-react to use tailwind-merge ^2.x and updated yarn.lock to restore Tailwind v3 compatibility.
Or push these changes by commenting:
@cursor push 52feed80ab
Preview (52feed80ab)
diff --git a/packages/design-system-react/package.json b/packages/design-system-react/package.json
--- a/packages/design-system-react/package.json
+++ b/packages/design-system-react/package.json
@@ -54,7 +54,7 @@
"@metamask/jazzicon": "^2.0.0",
"@radix-ui/react-slot": "^1.1.0",
"blo": "^2.0.0",
- "tailwind-merge": "^3.0.0"
+ "tailwind-merge": "^2.0.0"
},
"devDependencies": {
"@figma/code-connect": "^1.0.0",
diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -3488,7 +3488,7 @@
jest: "npm:^29.7.0"
jest-environment-jsdom: "npm:^29.7.0"
rimraf: "npm:^5.0.5"
- tailwind-merge: "npm:^3.0.0"
+ tailwind-merge: "npm:^2.0.0"
ts-jest: "npm:^29.2.5"
tsx: "npm:^4.20.6"
typescript: "npm:~5.2.2"
@@ -20673,10 +20673,10 @@
languageName: node
linkType: hard
-"tailwind-merge@npm:^3.0.0":
- version: 3.5.0
- resolution: "tailwind-merge@npm:3.5.0"
- checksum: 10/888861e2fc685dc282e6c0d735a2ca7656c3f13da6947896401f50aea924e481ab6dc7629b6f6a28125505f1b06ee97381ad792849204f34ee1ede375b119e0c
+"tailwind-merge@npm:^2.0.0":
+ version: 2.6.1
+ resolution: "tailwind-merge@npm:2.6.1"
+ checksum: 10/b68e9e63f0d8e4f8e8b9801b978c2d6c78136a20e407586b3bf4c905759ccbfa4ba9d0b6af06b0241816578866cb3dce660078fc29847a8a1dfabb87d315b80b
languageName: node
linkType: hard|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
|
@SocketSecurity ignore npm/@tailwindcss/oxide@4.1.14 @SocketSecurity ignore npm/@emnapi/core@1.5.0 @SocketSecurity ignore npm/postcss-import@16.1.1 Tailwind v4 core packages — first-party WASM runtime dependencies — transitive deps of ESLint plugin dependencies — transitive deps of |
📖 Storybook Preview |
📖 Storybook Preview |
- Restore no-custom-classname: error for React Native ESLint config - Move @tailwindcss/postcss and @tailwindcss/vite to devDependencies - Relocate parity check script to design-tokens/scripts/ as TypeScript - Add TODO for twrnc parity validation across v3, v4, and React Native Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
- Make design-system-tailwind-preset an optional peer dep in design-system-react via peerDependenciesMeta, signaling that v4 consumers can use design-tokens/tailwind/theme.css instead - Add tailwindcss v3 as devDep to tailwind-preset for build isolation from hoisted v4 Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
…able overrides Tailwind v4's @theme inline inlines color values directly into utility classes, making them ignore .dark scope CSS variable overrides. Switching to @theme generates proper CSS custom properties on :root that the .dark block can override, fixing color contrast violations in dark mode for Button and TextButton components. Also removes the redundant styles.css import from storybook preview since theme.css is self-contained. Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
…ctness rules - Replace cleancss with cp for theme.css build to prevent corruption of @theme and @Utility directives that cleancss doesn't understand - Enable better-tailwindcss correctness preset (no-conflicting-classes, no-unregistered-classes) for web packages to restore class validation lost during plugin migration Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
- Load eslint-plugin-tailwindcss via nativeRequire from storybook-react-native context where tailwindcss v3 is available, fixing 'resolveConfig' not found error from v4 - Remove redundant root eslint-plugin-tailwindcss dep since it needs v3 and root has v4 - Add TODO comments for focus-visible:outline redundancy when dropping v3 support Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
CI only built the v3 tailwind-preset before lint, but the RN eslint config also needs the twrnc-preset built to resolve custom class names via tailwind-intellisense.config.js. Also restores eslint-plugin-tailwindcss loading via nativeRequire from storybook-react-native context where tailwindcss v3 is available. Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
… selector - Add --font-size-*: initial and --font-weight-*: initial to @theme block to disable default Tailwind utilities (text-xs, font-thin, etc.), enforcing design system typography - Add explicit [data-theme='light'], .light selector block so light theme variables reset correctly when nested inside .dark ancestors for runtime theme switching Note: TextButton 'As Child' story has a pre-existing dark mode a11y contrast issue (#8b99ff link vs #ffffff surrounding text at 2.59:1) now surfaced by the @theme dark mode fix. Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
The twrnc-preset imports types from design-tokens, so the full build chain for lint is: design-tokens → tailwind-preset + twrnc-preset → lint. Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
…me.css Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Bump tailwind-merge from ^2.0.0 to ^3.0.0 for Tailwind CSS v4 support. Remove focus-visible:outline-none and focus-visible:outline from button variants — in v4, outline-2 implies outline-style: solid, making both redundant. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Static analysis test that verifies theme.css produces the same utility classes and CSS variables as the v3 design-system-tailwind-preset. Covers typography, colors, shadows, and bidirectional completeness (349 test cases). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Verifies .light and .dark blocks in theme.css match source light-theme-colors.css and dark-theme-colors.css — catches silent value divergence between duplicated color tokens. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Consumers now use @metamask/design-tokens/tailwind/theme.css for Tailwind v4 instead of the v3 preset. Also removes unused devDep from storybook-react. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Adds migration documentation to design-tokens (full setup guide for theme.css) and design-system-react (breaking changes for consumers: preset peer dep removal, tailwind-merge v3, focus outline v4 syntax). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
theme.css uses relative @import paths to brand-colors.css, typography.css, and shadow.css. The build now copies these to dist/css/ alongside dist/tailwind/theme.css so consumers installing from npm can resolve them. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Replaces the copy-based approach with postcss-import to inline brand-colors.css, typography.css, and shadow.css into dist/tailwind/theme.css. Consumers get a single self-contained file with zero relative @import dependencies. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add --color-*: initial and --box-shadow-*: initial to the @theme block. This clears Tailwind v4's entire default palette (slate, gray, blue, red, etc.) and shadow scale, enforcing design-token-only colors and shadows. Without this, utilities like bg-gray-500 or shadow-lg would resolve to Tailwind defaults instead of failing. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
import.meta.dirname is Node 20.11+ only, but engines allows ^18.18. On Node 18 it's undefined, causing path.resolve(undefined, ...) to throw and break all linting. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Tailwind v4 minifies hex values (#ffffff → #fff). The YIQ contrast function only handled 6/8-char hex, producing NaN for 3/4-char — white text was rendered on white backgrounds. Now expands shorthand hex before parsing. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Tailwind v4 uses --shadow-* (not --box-shadow-*) for shadow theme variables. Also inlines shadow size values directly since @theme can't resolve var() references to non-theme (:root) variables at build time. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Tailwind v4's shadow parser cannot process var() references in shadow values, causing custom @theme shadow definitions to be silently ignored. Bake the design token color (#0000001a) directly into shadow values so Tailwind generates correct utilities with --tw-shadow-color composition. - Replace var(--shadow-color, ...) with #0000001a in @theme shadow values - Remove unused @Utility shadow-default/primary/error (0 usage in design-system-react and extension, confirmed by codebase audit) - Remove !important that was on the dead @Utility directives - Remove unused Color story and shadow-primary/error story examples - Update tests to match new shadow value format
In Tailwind v4, the CSS-first @theme block needs --font-sans set explicitly for preflight to apply the correct font-family to <html>. Without this, elements not wrapped in a <Text> component (e.g. asChild ButtonBase) fall back to Tailwind's default system font stack instead of Geist.
- Reinstate focus-visible:outline-none, outline, and outline-2 strings on Button variants and ButtonHero (pre-a72c63ac parity) - Assert merged focus classes in tests (tailwind-merge v3 omits redundant outline) - Disable better-tailwindcss/no-conflicting-classes with scoped comments - Drop premature MIGRATION.md note about removing outline utilities Made-with: Cursor
- Restore outline-none before ring-0 with sort-classes disable (prior order, limit release diff) - Reword no-conflicting-classes disables: release diff, not Chromatic Made-with: Cursor
- Allowlist omitted v3-only shadow-default/primary/error utilities in parity script - Document shadow tokens in MIGRATION.md (--shadow-xs–lg, --color-shadow-*) - Add tsx devDependency for check:tailwind-theme-parity script Made-with: Cursor
- Restore Color story with TextColor on inverse backgrounds - Replace removed shadow-primary/error utilities with shadow-[var(--shadow-size-*)_var(--color-shadow-*)] Made-with: Cursor
f7b7c8c to
22624d1
Compare
📖 Storybook Preview |

Description
This PR migrates the design system to support Tailwind CSS v4 while maintaining backwards compatibility with v3. The migration adopts a dual-version strategy to accommodate different platform requirements:
Reason for Change
Tailwind CSS v4 introduces a new architecture with improved performance, better CSS-first configuration, and modern tooling. However, React Native packages (TWRNC) are not yet compatible with v4, requiring a dual-version approach.
Solution
Platform-Specific Tailwind Strategy:
React Web (
design-system-react):design-system-tailwind-preset(existing configuration)theme.cssindesign-tokenspackageReact Native (
design-system-react-native):Key Changes:
New Tailwind v4 Theme System:
packages/design-tokens/src/tailwind/theme.csswith design token integration@import 'tailwindcss',@source, and@themedirectivesStorybook Configuration:
apps/storybook-react) migrated to v4@tailwindcss/viteand@tailwindcss/postcsspluginsapps/storybook-react/tailwind.cssTypography Updates:
text-<variant>(e.g.,text-heading-lg)twMergeconfiguration for proper class mergingESLint Configuration:
eslint-plugin-better-tailwindcssfor v4 compatibilityeslint-plugin-tailwindcsswith relaxedno-custom-classnameMonorepo Tooling:
workspaceslevelBuild & Test Infrastructure:
@types/reactdependencies to resolve TypeScript compilation issuesdesign-system-twrnc-presettsconfig (jsx:reactinstead ofreact-native)Related issues
Fixes: (Add issue number if applicable)
Manual testing steps
Verify Web Storybook (Tailwind v4):
Verify React Native Storybook (Tailwind v3):
yarn storybook:ios # or yarn storybook:androidRun Parity Check:
Build All Packages:
Run Linting:
Screenshots/Recordings
N/A - This is a build tooling and configuration change with no visual differences.
Pre-merge author checklist
Pre-merge reviewer checklist
Technical Details
Package Changes
design-tokens:
theme.csswith Tailwind v4 theme configurationdesign-system-react:
better-tailwindcssfor v4design-system-react-native:
tailwindcsswith relaxed custom classesdesign-system-tailwind-preset:
design-system-twrnc-preset:
storybook-react:
Dependency Management
@types/reactto multiple packages for TypeScript compilationtwrnc/tailwindcssto enforce v3Known Limitations
Note
Medium Risk
Medium risk because it upgrades Tailwind and related tooling across the monorepo (including ESLint, PostCSS/Vite, and class merging), which can cause widespread styling/build/lint regressions despite limited runtime logic changes.
Overview
Introduces a Tailwind v4, CSS-first theming surface by adding
packages/design-tokens/src/tailwind/theme.css, exporting it as@metamask/design-tokens/tailwind/theme.css, and adding build + parity/test tooling (check-tailwind-theme-parity) to validate v3 preset vs v4 class coverage.Migrates
apps/storybook-reactto Tailwind v4 by removingtailwind.config.js, switchingtailwind.cssto@import 'tailwindcss'+@sourcescanning + theme import, and updating Vite/PostCSS to@tailwindcss/viteand@tailwindcss/postcss(plus Chromatic config). Web Tailwind linting switches toeslint-plugin-better-tailwindcsswith the new CSS entrypoint.Keeps React Native on Tailwind v3 by adding explicit v3
tailwindcssdeps/resolutions, ensuring CI builds the twrnc preset for class resolution, and adjusting repo tooling to allow mixed Tailwind versions (yarn.config.cjs,.depcheckrc.yml). Component/tests/stories are minimally tweaked for Tailwind v4/tailwind-mergev3 behavior (e.g., focus outline class expectations and shadow utilities).Written by Cursor Bugbot for commit 22624d1. This will update automatically on new commits. Configure here.