Skip to content

Tailwind v4#842

Draft
georgewrmarshall wants to merge 43 commits into
mainfrom
tailwind-v4
Draft

Tailwind v4#842
georgewrmarshall wants to merge 43 commits into
mainfrom
tailwind-v4

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented Oct 20, 2025

Copy link
Copy Markdown
Contributor

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):

    • Supports both Tailwind v3 and v4
    • v3 support via design-system-tailwind-preset (existing configuration)
    • v4 support via new theme.css in design-tokens package
    • Web Storybook migrated to v4 using CSS-first approach
  • React Native (design-system-react-native):

    • Tailwind v3 only (via TWRNC preset)
    • TWRNC library not yet compatible with v4
    • Maintains existing v3 workflow unchanged

Key Changes:

  1. New Tailwind v4 Theme System:

    • Created packages/design-tokens/src/tailwind/theme.css with design token integration
    • CSS-first configuration using @import 'tailwindcss', @source, and @theme directives
    • Automated parity check script validates v3/v4 class coverage
  2. Storybook Configuration:

    • Web Storybook (apps/storybook-react) migrated to v4
    • Uses @tailwindcss/vite and @tailwindcss/postcss plugins
    • New CSS entry point: apps/storybook-react/tailwind.css
  3. Typography Updates:

    • Simplified class names: text-<variant> (e.g., text-heading-lg)
    • Updated twMerge configuration for proper class merging
    • Comprehensive test updates for new class patterns
  4. ESLint Configuration:

    • Web packages: eslint-plugin-better-tailwindcss for v4 compatibility
    • React Native packages: eslint-plugin-tailwindcss with relaxed no-custom-classname
    • Platform-specific VSCode settings for appropriate Tailwind IntelliSense
  5. Monorepo Tooling:

    • Yarn hoisting limits set to workspaces level
    • Depcheck configuration allows Tailwind version inconsistencies across workspaces
    • React Native packages pinned to Tailwind v3 via resolutions
  6. Build & Test Infrastructure:

    • Added @types/react dependencies to resolve TypeScript compilation issues
    • Fixed design-system-twrnc-preset tsconfig (jsx: react instead of react-native)
    • Jest setup mocks for React Native platform constants
    • Disabled Watchman in package-level Jest configs

Related issues

Fixes: (Add issue number if applicable)

Manual testing steps

  1. Verify Web Storybook (Tailwind v4):

    yarn storybook
    • Verify all components render correctly
    • Check responsive utilities work
    • Verify design token classes apply properly
  2. Verify React Native Storybook (Tailwind v3):

    yarn storybook:ios
    # or
    yarn storybook:android
    • Verify mobile components render correctly
    • Check TWRNC styling works as expected
  3. Run Parity Check:

    yarn check:tailwind-theme-parity
    • Should show v3/v4 class coverage comparison
    • Verify no critical class losses
  4. Build All Packages:

    yarn build
    • Should complete without TypeScript errors
  5. Run Linting:

    yarn lint
    • Should pass with platform-specific Tailwind rules

Screenshots/Recordings

N/A - This is a build tooling and configuration change with no visual differences.

Pre-merge author checklist

  • I've followed MetaMask Contributor Docs
  • I've completed the PR template to the best of my ability
  • I've included tests if applicable
  • I've documented my code using JSDoc format if applicable
  • I've applied the right labels on the PR (see labeling guidelines). Not required for external contributors.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Technical Details

Package Changes

design-tokens:

  • New theme.css with Tailwind v4 theme configuration
  • Exports CSS file for consumption by web packages

design-system-react:

  • Updated to support both Tailwind v3 (preset) and v4 (theme.css)
  • Typography class simplification
  • ESLint plugin: better-tailwindcss for v4

design-system-react-native:

  • Remains on Tailwind v3 exclusively
  • ESLint plugin: tailwindcss with relaxed custom classes
  • Jest setup mocks for React Native platform

design-system-tailwind-preset:

  • Maintains v3 compatibility layer
  • Continues to work for consumers not yet migrated to v4

design-system-twrnc-preset:

  • Pinned to Tailwind v3 (TWRNC requirement)
  • TypeScript config fix (jsx: react)

storybook-react:

  • Migrated to Tailwind v4 with Vite/PostCSS plugins
  • New CSS entry point for theme

Dependency Management

  • Added @types/react to multiple packages for TypeScript compilation
  • Yarn resolution for twrnc/tailwindcss to enforce v3
  • Depcheck rules to allow intentional version mismatches

Known Limitations

  • React Native packages cannot upgrade to v4 until TWRNC supports it
  • Dual-version strategy adds slight complexity to dependency management
  • Some test failures in React Native are pre-existing (unrelated to this PR)

Note

Medium Risk
Medium risk due to broad tooling and dependency updates (Tailwind v4, new lint rules, build pipeline changes) that can affect styling output and CI across multiple packages, while also maintaining a dual v3/v4 setup for web vs React Native.

Overview
Adds Tailwind v4 support for web consumers by introducing design-tokens/src/tailwind/theme.css (built into dist/tailwind/theme.css) and wiring apps/storybook-react to use the CSS-first v4 flow via @tailwindcss/vite and @tailwindcss/postcss.

Adds safety rails for the migration: a new check:tailwind-theme-parity script in design-tokens (also exposed at repo root/Storybook) to compare v4 theme.css output against the v3 preset’s expected custom utilities, plus documentation (docs/tailwind-v4-pr-split-plan.md) outlining how to split the migration.

Updates repo tooling for dual Tailwind versions: switches web Tailwind linting to eslint-plugin-better-tailwindcss (entrypoint apps/storybook-react/tailwind.css) while keeping RN on eslint-plugin-tailwindcss with an absolute config path; CI lint setup now builds design-tokens, the v3 preset, and the twrnc preset; Yarn constraints allow inconsistent tailwindcss ranges and adds a twrnc/tailwindcss v3 resolution.

Adjusts component code/docs for new class semantics: bumps tailwind-merge to v3 and updates button focus-outline class expectations/order, simplifies Text variant class mappings to text-<variant>, updates shadow stories to use CSS variable-based shadows, and refreshes a few Storybook/examples/docs to match the new styling contracts.

Reviewed by Cursor Bugbot for commit 4b50dc2. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@socket-security

socket-security Bot commented Oct 20, 2025

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​react-native-get-random-values@​1.8.21001006877100
Updated@​babel/​preset-react@​7.27.1 ⏵ 7.28.51001007190100
Added@​tailwindcss/​vite@​4.1.141001007298100
Updated@​storybook/​react-vite@​10.3.1 ⏵ 10.3.599 +110073 +1100100
Updated@​babel/​preset-typescript@​7.27.1 ⏵ 7.28.51001007390100
Updated@​storybook/​addon-mcp@​0.4.1 ⏵ 0.4.2100 +110074100 +1100
Updated@​storybook/​addon-ondevice-actions@​10.3.1 ⏵ 10.3.210010076100 +1100
Updated@​storybook/​addon-vitest@​10.3.1 ⏵ 10.3.599 +110076100100
Updated@​storybook/​addon-ondevice-backgrounds@​10.3.1 ⏵ 10.3.210010077100 +1100
Updated@​storybook/​addon-a11y@​10.3.1 ⏵ 10.3.5100 +110078 +1100100
Updated@​babel/​core@​7.28.6 ⏵ 7.29.097 +110080 +192100
Updatedtsx@​4.21.0 ⏵ 4.20.6100 +11008185100
Addedpostcss-cli@​11.0.19910010082100
Updated@​storybook/​addon-ondevice-controls@​10.3.1 ⏵ 10.3.29910082 +1100 +1100
Updatedpostcss@​8.5.6 ⏵ 8.5.899998293100
Addedpostcss-import@​16.1.19910010084100
Updatedtailwindcss@​3.4.16 ⏵ 4.2.1100 +510084 -298100
Updatedtailwind-merge@​2.5.5 ⏵ 3.5.010010086 +196 +1100
Updatedtailwindcss@​3.4.16 ⏵ 3.4.199610087 +198100
Updated@​testing-library/​dom@​10.4.0 ⏵ 10.4.19910010087100
Updated@​testing-library/​react@​16.3.2 ⏵ 16.1.099 +110010087100
Updated@​storybook/​addon-ondevice-notes@​10.3.1 ⏵ 10.3.29310088 +1100 +1100
Updatedstorybook@​10.3.1 ⏵ 10.3.59810088 +1100100
Updatedeslint-plugin-tailwindcss@​3.18.0 ⏵ 3.18.29910010089 -2100
Updated@​gorhom/​bottom-sheet@​5.2.9 ⏵ 5.2.1098 +110093 +193 +4100
Addedeslint-plugin-better-tailwindcss@​3.7.109910010096100
Added@​tailwindcss/​postcss@​4.1.1410010010098100
Updated@​storybook/​react-native@​10.3.1 ⏵ 10.3.299 +1100100100 +1100
Updated@​storybook/​addon-docs@​10.3.1 ⏵ 10.3.599100100100100

View full report

@socket-security

socket-security Bot commented Oct 20, 2025

Copy link
Copy Markdown

Caution

MetaMask internal reviewing guidelines:

  • Do not ignore-all
  • Each alert has instructions on how to review if you don't know what it means. If lost, ask your Security Liaison or the supply-chain group
  • Copy-paste ignore lines for specific packages or a group of one kind with a note on what research you did to deem it safe.
    @SocketSecurity ignore npm/PACKAGE@VERSION
Action Severity Alert  (click "▶" to expand/collapse)
Block High
Obfuscated code: npm @storybook/addon-ondevice-notes is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: apps/storybook-react-native/package.jsonnpm/@storybook/addon-ondevice-notes@10.3.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@storybook/addon-ondevice-notes@10.3.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Block Low
Publisher changed: npm @expo/devcert is now published by kudochien instead of philpl

New Author: kudochien

Previous Author: philpl

From: ?npm/expo@52.0.49npm/@expo/devcert@1.2.1

ℹ Read more on: This package | This alert | What is new author?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Scrutinize new collaborator additions to packages because they now have the ability to publish code into your dependency tree. Packages should avoid frequent or unnecessary additions or changes to publishing rights.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@expo/devcert@1.2.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Block Low
Publisher changed: npm slugify is now published by joshuakgoldberg instead of trott

New Author: joshuakgoldberg

Previous Author: trott

From: ?npm/expo@52.0.49npm/expo-asset@11.0.5npm/slugify@1.6.9

ℹ Read more on: This package | This alert | What is new author?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Scrutinize new collaborator additions to packages because they now have the ability to publish code into your dependency tree. Packages should avoid frequent or unnecessary additions or changes to publishing rights.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/slugify@1.6.9. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm @babel/core is 100.0% likely to have a medium risk anomaly

Notes: The examined code is a standard, benign helper for constructing and wrapping configuration items from descriptors within Babel’s tooling. There is no evidence of data leakage, exfiltration, backdoors, or other malicious activity in this fragment. The combination of immutability, brand-based identity, and non-enumerable descriptor storage indicates a well-scoped internal utility rather than anything suspicious.

Confidence: 1.00

Severity: 0.60

From: apps/storybook-react-native/package.jsonnpm/@babel/core@7.29.0

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@babel/core@7.29.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm esbuild is 100.0% likely to have a medium risk anomaly

Notes: On its face this package.json looks like the legitimate esbuild package layout: it runs a local install.js during postinstall and defines many optional platform-specific packages. The main risk is that the uninspected install.js may perform arbitrary, privileged actions (download remote binaries, execute code, add hooks, exfiltrate data, etc.). You should audit the contents of install.js (and any code it downloads or executes) before trusting installation in sensitive environments. The package.json itself does not contain obvious malicious indicators (no http:// deps, no non-registry dependency URLs, no overrides), but the postinstall hook makes it potentially risky.

Confidence: 1.00

Severity: 0.60

From: ?npm/storybook@10.3.5npm/esbuild@0.27.2

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/esbuild@0.27.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm resolve is 100.0% likely to have a medium risk anomaly

Notes: This manifest uses a non-registry, relative-path dependency ('resolve': '../../../') which is a significant supply-chain risk because it allows arbitrary local code to be pulled in and executed without registry protections. Combined with the 'lerna bootstrap' postinstall script (which can trigger other lifecycle scripts across the monorepo), this setup increases the chance of untrusted code execution and other malicious behavior. Inspect the target of the relative path, all bootstrap-linked packages, and any lifecycle scripts before running npm install in an untrusted environment.

Confidence: 1.00

Severity: 0.60

From: ?npm/tailwindcss@3.4.19npm/expo@52.0.49npm/@storybook/react-vite@10.3.5npm/@storybook/addon-ondevice-controls@10.3.2npm/@storybook/react-native@10.3.2npm/depcheck@1.4.7npm/@babel/preset-env@7.26.0npm/eslint-plugin-import-x@4.6.1npm/@react-native/babel-preset@0.76.9npm/postcss-import@16.1.1npm/jest@29.7.0npm/metro-react-native-babel-preset@0.76.9npm/resolve@1.22.12

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/resolve@1.22.12. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm tailwindcss is 100.0% likely to have a medium risk anomaly

Notes: The code is a targeted, low-risk utility for locating a config file referenced by a PostCSS @config at-rule. It enforces single usage, requires a quoted path, resolves relative to the source file, and ensures the file exists before returning the path or null. No malicious behavior or external data leakage is evident in this fragment, though error messages could be toned for production usage.

Confidence: 1.00

Severity: 0.60

From: apps/storybook-react-native/package.jsonnpm/tailwindcss@3.4.19

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/tailwindcss@3.4.19. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm tailwindcss is 100.0% likely to have a medium risk anomaly

Notes: The code appears to be a legitimate PostCSS/Tailwind nesting integration that handles Tailwind-like at-rules and converts them to standard CSS constructs for downstream processing. The dynamic plugin loading and private API usage are the primary non-ideal aspects but are documented and controlled within the plugin design. Overall, the security risk is moderate due to potential arbitrary code execution from dynamic requires if misused, but there is no evidence of data exfiltration or malicious payloads in this fragment.

Confidence: 1.00

Severity: 0.60

From: apps/storybook-react-native/package.jsonnpm/tailwindcss@3.4.19

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/tailwindcss@3.4.19. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm tar is 100.0% likely to have a medium risk anomaly

Notes: This module acts as a standard tar extraction wrapper using synchronous and asynchronous code paths. There is no evident malicious activity within this fragment. Security risk hinges on the behavior of the Unpack/UnpackSync implementation and how tar entries are written to disk (e.g., path traversal). No hardcoded secrets or network calls are present here. Recommend ensuring tar extraction handles path traversal and destination path sanitization in Unpack, and consider validating opt.file presence and type before streaming.

Confidence: 1.00

Severity: 0.60

From: ?npm/tailwindcss@3.4.19npm/postcss-cli@11.0.1npm/clean-css-cli@5.6.3npm/vitest@3.2.4npm/playwright@1.54.1npm/@jest/globals@29.7.0npm/babel-jest@29.7.0npm/jest@29.7.0npm/vite@6.3.6npm/tsx@4.20.6npm/@tailwindcss/vite@4.1.14npm/@tailwindcss/postcss@4.1.14npm/tar@7.5.1

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/tar@7.5.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm tsx is 100.0% likely to have a medium risk anomaly

Notes: This fragment appears to be a bundler-generated bootstrap/initialization piece that imports many modules and executes an initialization function (r). No explicit malicious activity is evident within this fragment itself, but the risk stems from side effects of the imported modules on load. A careful review of the implementations of the imported modules (especially those exporting r and those performing initialization, build-time, or network/file operations) is recommended to rule out hidden telemetry, backdoors, or undesired side effects.

Confidence: 1.00

Severity: 0.60

From: package.jsonnpm/tsx@4.20.6

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/tsx@4.20.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm undici is 100.0% likely to have a medium risk anomaly

Notes: The analyzed code appears to implement a standard in-memory cache batch operation flow (put/delete) with careful handling of response bodies by buffering and storing bytes for caching. No signs of malware, data exfiltration, backdoors, or obfuscated behavior were found. The primary security considerations relate to memory usage from buffering potentially large response bodies and ensuring robust validation within batch operations to prevent cache state corruption. Overall risk is moderate, driven by in-memory data handling rather than external communication.

Confidence: 1.00

Severity: 0.60

From: ?npm/expo@52.0.49npm/undici@6.25.0

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/undici@6.25.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm undici is 100.0% likely to have a medium risk anomaly

Notes: The analyzed code implements a conventional HTTP/WebSocket-like upgrade handler with proper input validation, abort signal integration, and asynchronous callback management. It does not exhibit malicious activity such as data exfiltration or backdoors. The deliberate onHeaders error path is consistent with protocol expectations to reject non-upgrade responses. Overall security risk remains low to moderate, contingent on integration context, but no indicators of malware or obfuscation are detected in this fragment.

Confidence: 1.00

Severity: 0.60

From: ?npm/expo@52.0.49npm/undici@6.25.0

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/undici@6.25.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm undici is 100.0% likely to have a medium risk anomaly

Notes: The code is a focused error-handling helper for HTTP responses that safely parses small payloads to include in an error object. It includes protective measures (chunk limits, controlled parsing, microtask-based callbacks) but uses unusual, brittle content-type checks and suppresses stack traces for debugging concealment. There is no evidence of malicious activity, data exfiltration, or backdoors within this fragment. The main risk is potential silent data loss if payloads exceed the chunk limit or mismatched content-type handling leads to missing payloads, but this is a functional trade-off rather than malicious. Suggested improvements include robust content-type parsing, clearer error signaling when payload is truncated, and optional logging to aid debugging without exposing stack traces in production.

Confidence: 1.00

Severity: 0.60

From: ?npm/expo@52.0.49npm/undici@6.25.0

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/undici@6.25.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm undici is 100.0% likely to have a medium risk anomaly

Notes: The script performs an in-place, lossy re-encoding of a local file from UTF-8 to Latin-1 and rewrites it without backups or validation. This is unsafe due to potential data loss and code corruption, and could be exploited to tamper with source files in a supply chain. It does not exhibit active malware behavior, but its destructive nature warrants removal or strict safeguards (backups, explicit intent, error handling).

Confidence: 1.00

Severity: 0.60

From: ?npm/expo@52.0.49npm/undici@6.25.0

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/undici@6.25.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Potential code anomaly (AI signal): npm yaml is 100.0% likely to have a medium risk anomaly

Notes: The analyzed code is a standard YAML stringify module with robust tag resolution, anchor handling, and formatting controls. It correctly delegates to appropriate stringify logic and handles edge cases like circular aliases and unresolved tags with explicit errors. Overall security posture is conservative and typical for a serialization library; no malicious activity detected.

Confidence: 1.00

Severity: 0.60

From: ?npm/@metamask/create-release-branch@4.2.0npm/postcss-cli@11.0.1npm/yaml@2.8.2

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/yaml@2.8.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Ignoring alerts on:

  • @emnapi/core@1.5.0
  • @emnapi/runtime@1.5.0
  • @eslint/css-tree@3.6.6
  • @tailwindcss/oxide@4.1.14
  • @tailwindcss/oxide-wasm32-wasi@4.1.14
  • @tybys/wasm-util@0.10.1
  • postcss-import@16.1.1
  • synckit@0.11.11
  • jiti@2.6.1

View full report

@github-actions

github-actions Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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);
+  }
+}

Comment thread packages/design-system-react/src/components/Text/Text.constants.ts
Comment thread packages/design-system-react/src/utils/tw-merge.test.ts

@georgewrmarshall georgewrmarshall left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments. I'm not sure we need to change anything in the design-system-tailwind-preset package

Comment thread apps/storybook-react/scripts/check-tailwind-theme-parity.mjs Outdated
Comment thread apps/storybook-react/package.json
Comment thread packages/design-system-react-native/jest.setup.js
Comment thread packages/design-system-react/src/components/AvatarBase/AvatarBase.test.tsx Outdated
Comment thread packages/design-system-twrnc-preset/tsconfig.build.json Outdated
Comment thread .yarnrc.yml Outdated
Comment thread eslint.config.mjs Outdated
Comment thread jest.config.packages.js Outdated
Comment thread package.json Outdated
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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",

Comment thread packages/design-tokens/package.json Outdated
Comment thread eslint.config.mjs
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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;

Comment thread eslint.config.mjs Outdated
Comment thread packages/design-tokens/src/tailwind/theme.css
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 .light CSS selector breaks theme switching
    • Added explicit [data-theme='light'], .light block in theme.css reapplying light theme variables so .light can override .dark in nested contexts.

Create PR

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 {

Comment thread packages/design-tokens/src/tailwind/theme.css
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

Comment thread eslint.config.mjs
Comment thread apps/storybook-react/.storybook/preview.tsx
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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 {

Comment thread packages/design-tokens/src/tailwind/theme.css
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments

Comment thread eslint.config.mjs Outdated
Comment thread packages/design-tokens/scripts/check-tailwind-theme-parity.ts
Comment thread packages/design-tokens/src/tailwind/theme.test.ts
Comment thread .github/workflows/lint-build-test.yml

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 outline class breaks focus visibility for v3 consumers
    • Re-added focus-visible:outline to Primary, Secondary, and Tertiary buttons to restore focus outline style in Tailwind v3 while remaining harmless in v4.

Create PR

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',

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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

Comment thread packages/design-system-react/package.json
@georgewrmarshall

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/design-system-react": "0.11.0-preview.fa05289",
  "@metamask-previews/design-system-react-native": "0.11.0-preview.fa05289",
  "@metamask-previews/design-system-shared": "0.4.0-preview.fa05289",
  "@metamask-previews/design-system-tailwind-preset": "0.6.1-preview.fa05289",
  "@metamask-previews/design-system-twrnc-preset": "0.3.0-preview.fa05289",
  "@metamask-previews/design-tokens": "8.2.2-preview.fa05289"
}

@georgewrmarshall

georgewrmarshall commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

@SocketSecurity ignore npm/@tailwindcss/oxide@4.1.14
@SocketSecurity ignore npm/@tailwindcss/oxide-wasm32-wasi@4.1.14

@SocketSecurity ignore npm/@emnapi/core@1.5.0
@SocketSecurity ignore npm/@emnapi/runtime@1.5.0
@SocketSecurity ignore npm/@tybys/wasm-util@0.10.1

@SocketSecurity ignore npm/postcss-import@16.1.1
@SocketSecurity ignore npm/@eslint/css-tree@3.6.6
@SocketSecurity ignore npm/jiti@2.6.1
@SocketSecurity ignore npm/synckit@0.11.11

Tailwind v4 core packages — first-party @tailwindcss/* packages. The oxide compiler uses shell access and network to download platform-specific native binaries at install time (standard pattern for Rust-based tools like SWC, esbuild, Lightning CSS). The WASM fallback variant uses fetch to load WASM modules when native binaries aren't available.

WASM runtime dependencies — transitive deps of @tailwindcss/oxide-wasm32-wasi providing the N-API/WASM bridge. The globalThis.fetch usage is for loading WASM modules at runtime, not external network calls. Socket's own AI analysis confirms "no evidence of malicious behavior" for both.

ESLint plugin dependencies — transitive deps of eslint-plugin-better-tailwindcss. postcss-import publisher change: romainmenke is a long-standing PostCSS org maintainer (postcss-plugins lead). jiti is the standard unjs TypeScript JIT loader. synckit is a worker thread sync utility. @eslint/css-tree is the official ESLint CSS parser. All widely used, Socket AI confirms no malicious behavior.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

georgewrmarshall and others added 27 commits May 4, 2026 19:39
- 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>
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>
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'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

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: twMerge config missing new typography class names
    • Updated twMerge’s variantClassGroups to include the new simplified typography tokens (e.g., display-lg, body-md) so text-* classes deduplicate correctly while keeping legacy tokens.

Create PR

Or push these changes by commenting:

@cursor push 389f68f56b
Preview (389f68f56b)
diff --git a/packages/design-system-react/src/utils/tw-merge.ts b/packages/design-system-react/src/utils/tw-merge.ts
--- a/packages/design-system-react/src/utils/tw-merge.ts
+++ b/packages/design-system-react/src/utils/tw-merge.ts
@@ -1,6 +1,9 @@
 import { extendTailwindMerge } from 'tailwind-merge';
 
+// Register both legacy (s-*/l-*) and new simplified typography tokens so
+// twMerge can properly resolve conflicts regardless of which format is used.
 const variantClassGroups = [
+  // Legacy tokens (kept for backward compatibility)
   's-display-lg',
   's-display-md',
   's-heading-lg',
@@ -29,6 +32,21 @@
   'l-button-label-md',
   'l-button-label-lg',
   'l-amount-display-lg',
+  // New simplified tokens (current Text variant class names)
+  'display-lg',
+  'display-md',
+  'heading-lg',
+  'heading-md',
+  'heading-sm',
+  'body-lg',
+  'body-md',
+  'body-sm',
+  'body-xs',
+  'page-heading',
+  'section-heading',
+  'button-label-md',
+  'button-label-lg',
+  'amount-display-lg',
 ];
 
 /**

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 4b50dc2. Configure here.

[TextVariant.SectionHeading]: 'text-section-heading',
[TextVariant.ButtonLabelMd]: 'text-button-label-md',
[TextVariant.ButtonLabelLg]: 'text-button-label-lg',
[TextVariant.AmountDisplayLg]: 'text-amount-display-lg',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

twMerge config missing new typography class names

High Severity

TWCLASSMAP_TEXT_VARIANT_FONTSTYLE was updated to emit simplified class names like text-display-lg, text-body-md, etc., but variantClassGroups in tw-merge.ts still only contains the old prefixed names (s-display-lg, l-display-lg, etc.). twMerge therefore cannot recognize the new typography classes as belonging to the font-size group, so conflicting typography classes won't be deduplicated when consumers override via className.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4b50dc2. Configure here.

@georgewrmarshall georgewrmarshall self-assigned this May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant