chore: implement tailwind eslint#760
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
📖 Storybook Preview |
📖 Storybook Preview |
a9b5105 to
bbc840e
Compare
| // Tailwind CSS | ||
| plugins: ['prettier-plugin-tailwindcss'], | ||
| tailwindFunctions: ['tw', 'twMerge'], // Support twrnc and twMerge template literals and utility functions | ||
| tailwindAttributes: ['twClassName'], // Support twClassName prop | ||
|
|
||
| // Use Prettier's configuration overrides to handle multiple Tailwind configs | ||
| // This allows different files to use different Tailwind configurations | ||
| // See: https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/335#issuecomment-2801686781 | ||
| overrides: [ | ||
| { | ||
| // React web components and stories use the Storybook React Tailwind config | ||
| files: [ | ||
| './packages/design-system-react/src/**', | ||
| './apps/storybook-react/stories/**', | ||
| './packages/design-tokens/stories/**', | ||
| ], | ||
| options: { | ||
| tailwindConfig: './apps/storybook-react/tailwind.config.js', | ||
| }, | ||
| }, | ||
| { | ||
| // React Native components and stories use the TWRNC preset Tailwind config | ||
| files: [ | ||
| './packages/design-system-react-native/src/**', | ||
| './apps/storybook-react-native/stories/**', | ||
| ], | ||
| options: { | ||
| tailwindConfig: | ||
| './apps/storybook-react-native/tailwind-intellisense.config.js', | ||
| }, | ||
| }, | ||
| ], |
There was a problem hiding this comment.
Removing all Tailwind Prettier-related configurations from the Prettier config, as we're no longer using the Prettier Tailwind plugin. The ESLint plugin now handles class ordering and enforces additional formatting.
📖 Storybook Preview |
| TWCLASSMAP_AVATARBASE_SIZE_DIMENSION[AvatarBaseSize.Md], | ||
| ].join(' '); | ||
| const customClasses = `${baseClasses} extra-class`; | ||
| const customClasses = `${baseClasses} bg-default`; |
There was a problem hiding this comment.
Because we are enforcing no non-tailwind classnames we have to update tests with mock clasnames to valid tailwind classnames
| import { promises as fs } from 'fs'; | ||
| import * as path from 'path'; | ||
| /* eslint-enable import-x/no-nodejs-modules */ | ||
| import postcss from 'postcss'; | ||
| import { parse } from 'postcss'; |
| }) => ( | ||
| <div | ||
| className={`grid h-[100px] place-content-center rounded bg-default text-center ${className}`} | ||
| className={`grid h-24 place-content-center rounded bg-default text-center ${className}`} |
There was a problem hiding this comment.
Even though we are allowing arbitrary values we should try to use the spacing and size system provided by tailwind as much as possible to reduce the amount of CSS that is generated
bbc840e to
d41f041
Compare
📖 Storybook Preview |
d41f041 to
d20188c
Compare
📖 Storybook Preview |
d20188c to
0bc9e12
Compare
📖 Storybook Preview |
0bc9e12 to
236469d
Compare
📖 Storybook Preview |
| // This removes all default Tailwind font sizes and weights. | ||
| // We want to rely on the design system font sizes and enforce use of the Text component | ||
| fontSize: {}, | ||
| fontWeight: {}, |
There was a problem hiding this comment.
We want to enforce the usage of only the preset classnames for font weights e.g. font-regular, font-medium, font-bold. The twMerge utility was still allowing for all default tailwind font weight names and allowing for variant font weights which we've removed.
| white: '#ffffff', | ||
| }, | ||
| fontSize: {}, // This removes all default Tailwind font sizes. We want to rely on the design system font sizes and enforce use of the Text component | ||
| extend: {}, |
There was a problem hiding this comment.
Removing extend as I don't think it's needed
| // Keep essential semantic colors, remove default palette colors. | ||
| // We want to rely on the colors provided by @metamask-previews/design-system-tailwind-preset |
There was a problem hiding this comment.
Adding a line break here to make comments easier to read
| <Box twClassName="w-full bg-default py-4"> | ||
| {/* Header */} | ||
| <Box twClassName="border-b border-muted px-4 py-4"> | ||
| <Box twClassName="border-b border-muted p-4"> |
There was a problem hiding this comment.
enforces-shorthand
| decorators: [ | ||
| (Story) => ( | ||
| <Box className="m-[-1rem]"> | ||
| <Box className="-m-4"> |
There was a problem hiding this comment.
no-unnecessary-arbitrary-value
| <Box className="mx-auto w-full bg-default md:max-w-xl md:rounded-3xl md:py-4"> | ||
| {/* Header */} | ||
| <Box className="border-color-border-muted px-4 py-4 md:px-8"> | ||
| <Box className="border-b border-muted p-4 md:px-8"> |
There was a problem hiding this comment.
no-custom-classname this rule is one of the most important IMO it catches invalid tailwind classnames. border-color-border-muted was invalid but it was not being picked up.
| settings: { | ||
| tailwindcss: { | ||
| callees: ['tw', 'twMerge', 'twClassName'], | ||
| config: 'apps/storybook-react/tailwind.config.js', | ||
| classRegex: '^class(Name)?$', | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
We should have good coverage for both React Native and React libraries, but a few points to note:
- We're still using the React Tailwind config for linting, so some classes not supported by
twrncmay not be flagged. This will be addressed in Improve Tailwind IntelliSense for React Native Development #737) - Classnames in our
*constant.tsfiles aren't being caught by the plugin. I've tried several regex approaches without success and have opened an issue feat: Add Tailwind class validation for constant files #763 - The
tw`` template literal syntax in twrnc won't work with the ESLint Tailwind plugin because the plugin is primarily designed to validate class names in static string contexts (likeclassNameortwClassNameprops). When using template literals with thetwtag, the class names are processed at runtime and potentially transformed into React Native style objects, making it harder for ESLint's static analysis to reliably extract and validate the class names. This is why the plugin'sclassRegex` pattern works well with props but struggles with template literal tags, as it can't properly parse the dynamic nature of tagged template literals in its AST traversal. twrnc will take care of the invalid classnames but we won't get the other eslint rule benefits
| @@ -1,3 +1,4 @@ | |||
| /* eslint-disable tailwindcss/no-contradicting-classname */ | |||
There was a problem hiding this comment.
We are actively testing how twMerge utility handles contradictory classnames so we want to disable the eslint rule for this test file
236469d to
04da97e
Compare
📖 Storybook Preview |
04da97e to
e4afa9b
Compare
| describe('font weight conflicts', () => { | ||
| it('should handle standard Tailwind font weight overrides', () => { | ||
| const result = twMerge('font-bold font-normal'); | ||
| expect(result).toBe('font-normal'); | ||
| }); | ||
|
|
||
| it('should handle custom typography font weight overrides', () => { | ||
| const result = twMerge('font-s-body-md font-l-heading-lg'); | ||
| expect(result).toBe('font-l-heading-lg'); | ||
| const result = twMerge('font-bold font-medium'); | ||
| expect(result).toBe('font-medium'); | ||
| }); | ||
|
|
||
| it('should handle mixed standard and custom font weight overrides', () => { | ||
| const result = twMerge('font-s-body-md font-bold'); | ||
| expect(result).toBe('font-bold'); | ||
| const result = twMerge('font-bold font-regular'); | ||
| expect(result).toBe('font-regular'); |
There was a problem hiding this comment.
Simplifying tests because we now only support 3 font weight classnames that are provided by the tailwind preset package: font-regular(custom), font-medium and font-bold.
describe('font weight conflicts', () => {
it('should handle standard Tailwind font weight overrides', () => {
const result = twMerge('font-bold font-medium');
expect(result).toBe('font-medium');
});
it('should handle mixed standard and custom font weight overrides', () => {
const result = twMerge('font-bold font-regular');
expect(result).toBe('font-regular');
});
});
📖 Storybook Preview |
e4afa9b to
734cbb2
Compare
| 'font-extralight', | ||
| 'font-light', | ||
| 'font-normal', | ||
| 'font-regular', |
There was a problem hiding this comment.
font-regular is the only custom font weight classname that we support
| // Custom typography classes | ||
| { | ||
| font: variantClassGroups, | ||
| }, |
There was a problem hiding this comment.
We have removed variant based font weight classnames. e.g. font-s-display-lg so these are no longer needed
📖 Storybook Preview |
734cbb2 to
6cfb53b
Compare
📖 Storybook Preview |
6cfb53b to
842e179
Compare
📖 Storybook Preview |
📖 Storybook Preview |
| - 'eslint-plugin-*' | ||
| - 'jest-silent-reporter' | ||
| - 'prettier-plugin-packagejson' | ||
| - 'prettier-plugin-tailwindcss' |
There was a problem hiding this comment.
removed all instances of prettier-plugin-tailwindcss
| - **ESLint Integration**: Use `eslint-plugin-tailwindcss` for Tailwind class validation and ordering | ||
| - **Consistent Ordering**: Maintain consistent class ordering through ESLint rules | ||
| - **Class Validation**: ESLint enforces proper class usage, prevents contradictory classes, and encourages shorthand utilities |
There was a problem hiding this comment.
Updating docs to reference eslint tailwind plugin instead of prettier-plugin-tailwindcss
baa20c2 to
1da1750
Compare
📖 Storybook Preview |

Description
This PR improves the linting and code quality of our Tailwind CSS usage by replacing Prettier-based formatting with comprehensive ESLint rules. The changes enforce Tailwind CSS best practices, catch common errors, and ensure consistent utility class usage across the codebase.
Key improvements:
prettier-plugin-tailwindcsstoeslint-plugin-tailwindcssfor better error detection and enforcementclassnames-order: Enforces consistent class orderingenforces-shorthand: Requires shorthand utilities (e.g.,size-4instead ofh-4 w-4)no-custom-classname: Catches invalid/custom class namesno-contradicting-classname: Prevents conflicting utility classesno-arbitrary-value: Originally tried to enforce this to prevent static colors e.g. bg-[#000] but we legitimately need it for sizes, animations, etc. We can look at using our own eslint-plugin-design-tokens for this like portfolio does.h-full w-fullwithsize-fullshorthandpx-4 py-4top-4shorthandWhy this change:
Related issues
Fixes: #738
Manual testing steps
yarn lintto verify ESLint rules are working correctlybg-invalid-color) and confirm ESLint catches themh-4 w-4) and verify ESLint suggests shorthand (size-4)flex hidden)Screenshots/Recordings
Before
After
React
after.react.eslint720.mov
React native
after.react.native.storybook.lint720.mov
twMerge
twmerge.eslint.mov
Pre-merge author checklist
Pre-merge reviewer checklist