Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No internal styles rule proposal #311

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/rules/no-internal-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Forbid passing styles affecting component internals (no-internal-style)

Tailwind recommends reusing styles by creating components using your favorite front-end framework (e.g. React). Such components almost always need to expose a way to set classes so that users can specify layout styles (e.g. margins, size, positioning). For example, in React, `<Button className="mt-5" ... />`. However, exposing the "put any style you want" string sometimes leads to developers customizing the internal appearance of the component, not only affecting their external layout. For example, the following overrides internal component styling which should really be controlled using a `variant`/`color` property instead of specified through `className`: `<Button className="bg-sky-500 text-semibold" ... />`.

This rule aims to restrict usage of internal properties in components. Unfortunately, detecting if a component should support internal style overrides or just external ones is not possible in the general case, so this rule differentiates these two based on the property name. For example, `class` and `className` would allow internal styles, and `classes` would not. This is configurable.

**By default this rule is turned `off`, if you want to use it set it to `warn` or `error`.**

## Rule Details

Examples of **incorrect** code for this rule:

```jsx
<Component classes="p-4">Padding is an internal concern</Component>
```

```jsx
<Component classes="flex flex-row">
Display is an internal concern as well
</Component>
```

Examples of **correct** code for this rule:

```jsx
<Component classes="m-4">Margin is an external concern</Component>
```

```jsx
<Component className="p-4">`className` still allows internal styles</Component>
```

### Options

```js
...
"tailwindcss/no-internal-style": [<enabled>, {
"externalClassRegex": <string>,
"externalCallees": Array<string>,
"skipClassAttribute": <boolean>,
"tags": Array<string>,
}]
...
```

### `externalCallees` (default: `[]`)

If you use some utility library like [@netlify/classnames-template-literals](https://github.com/netlify/classnames-template-literals), you can add its name to the list to make sure it gets parsed by this rule.

You'll need to differentiate between the function you use for internal and external styles, for example by aliasing it with a different name:

```js
export const externcss = classnames;

// Config
"tailwindcss/no-internal-style": ["error", {
"externalClassRegex": "^classes$",
"externalCallees": ["externcss"]
}]

// Code
<Button classes={externcss(..., 'pt-2')}>...</Button>
```

### `ignoredKeys` (default: `["compoundVariants", "defaultVariants"]`)

Using libraries like `cva`, some of its object keys are not meant to contain classnames in its value(s).
You can specify which key(s) won't be parsed by the plugin using this setting.
For example, `cva` has `compoundVariants` and `defaultVariants`.
NB: As `compoundVariants` can have classnames inside its `class` property, you can also use a callee to make sure this inner part gets parsed while its parent is ignored.

### `skipClassAttribute` (default: `false`)

Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.

### `tags` (default: `[]`)

Optional, if you are using tagged templates, you should provide the tags in this array.

### `externalClassRegex` (default: `"^class(Name)?$"`)

Optional, can be used to support custom attributes
40 changes: 40 additions & 0 deletions lib/config/groups.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,44 @@ module.exports.groups = [
{
type: 'Columns',
members: 'columns\\-(?<value>${columns})',
internal: true,
},
{
type: 'Break After',
members: 'break\\-after\\-(?<value>auto|avoid|all|avoid\\-page|page|left|right|column)',
internal: true,
},
{
type: 'Break Before',
members: 'break\\-before\\-(?<value>auto|avoid|all|avoid\\-page|page|left|right|column)',
internal: true,
},
{
type: 'Break Inside',
members: 'break\\-inside\\-(?<value>auto|avoid|avoid\\-page|avoid\\-column)',
internal: true,
},
{
type: 'Box Decoration Break',
members: 'box\\-decoration\\-(?<value>clone|slice)',
internal: true,
},
{
type: 'Deprecated Box Decoration Break',
members: 'decoration\\-(?<value>clone|slice)',
deprecated: true,
internal: true,
},
{
type: 'Box Sizing',
members: 'box\\-(?<value>border|content)',
internal: true,
},
{
type: 'Display',
members:
'block|flex|grid|flow\\-root|contents|hidden|inline(\\-(block|flex|table|grid))?|table\\-(column|footer|header|row)\\-group|table(\\-(caption|row|cell|column))?|list\\-item',
internal: true,
},
{
type: 'Floats',
Expand All @@ -88,17 +96,21 @@ module.exports.groups = [
{
type: 'Isolation',
members: '(isolate|isolation\\-auto)',
internal: true,
},
{
type: 'Object Fit',
members: 'object\\-(?<value>contain|cover|fill|none|scale\\-down)',
internal: true,
},
{
type: 'Object Position',
members: 'object\\-(?<value>${objectPosition})',
internal: true,
},
{
type: 'Overflow',
internal: true,
members: [
{
type: 'overflow',
Expand All @@ -122,6 +134,7 @@ module.exports.groups = [
},
{
type: 'Overscroll Behavior',
internal: true,
members: [
{
type: 'overscroll',
Expand Down Expand Up @@ -214,10 +227,12 @@ module.exports.groups = [
{
type: 'Flex Direction',
members: 'flex\\-(row|col)(\\-reverse)?',
internal: true,
},
{
type: 'Flex Wrap',
members: 'flex\\-(wrap(\\-reverse)?|nowrap)',
internal: true,
},
{
type: 'Flex',
Expand Down Expand Up @@ -248,6 +263,7 @@ module.exports.groups = [
{
type: 'Grid Template Columns',
members: 'grid\\-cols\\-(?<value>${gridTemplateColumns})',
internal: true,
},
{
type: 'Grid Column Start / End',
Expand All @@ -269,6 +285,7 @@ module.exports.groups = [
{
type: 'Grid Template Rows',
members: 'grid\\-rows\\-(?<value>${gridTemplateRows})',
internal: true,
},
{
type: 'Grid Row Start / End',
Expand All @@ -290,17 +307,21 @@ module.exports.groups = [
{
type: 'Grid Auto Flow',
members: 'grid\\-flow\\-(dense|(row|col)(\\-dense)?)',
internal: true,
},
{
type: 'Grid Auto Columns',
members: 'auto\\-cols\\-(?<value>${gridAutoColumns})',
internal: true,
},
{
type: 'Grid Auto Rows',
members: 'auto\\-rows\\-(?<value>${gridAutoRows})',
internal: true,
},
{
type: 'Gap',
internal: true,
members: [
{
type: 'gap',
Expand All @@ -325,10 +346,12 @@ module.exports.groups = [
{
type: 'Justify Content',
members: 'justify\\-(start|end|center|between|around|evenly)',
internal: true,
},
{
type: 'Justify Items',
members: 'justify\\-items\\-(start|end|center|stretch)',
internal: true,
},
{
type: 'Justify Self',
Expand All @@ -337,10 +360,12 @@ module.exports.groups = [
{
type: 'Align Content',
members: 'content\\-(center|start|end|between|around|evenly|baseline)',
internal: true,
},
{
type: 'Align Items',
members: 'items\\-(start|end|center|baseline|stretch)',
internal: true,
},
{
type: 'Align Self',
Expand All @@ -349,10 +374,12 @@ module.exports.groups = [
{
type: 'Place Content',
members: 'place\\-content\\-(center|start|end|between|around|evenly|stretch|baseline)',
internal: true,
},
{
type: 'Place Items',
members: 'place\\-items\\-(start|end|center|stretch|baseline)',
internal: true,
},
{
type: 'Place Self',
Expand All @@ -365,6 +392,7 @@ module.exports.groups = [
members: [
{
type: 'Padding',
internal: true,
members: [
{
type: 'p',
Expand Down Expand Up @@ -459,6 +487,7 @@ module.exports.groups = [
},
{
type: 'Space Between',
internal: true,
members: [
{
type: 'space-y',
Expand Down Expand Up @@ -515,6 +544,7 @@ module.exports.groups = [
},
{
type: 'Typography',
internal: true,
members: [
{
type: 'Font Family',
Expand Down Expand Up @@ -626,6 +656,7 @@ module.exports.groups = [
},
{
type: 'Backgrounds',
internal: true,
members: [
{
type: 'Background Image URL',
Expand Down Expand Up @@ -854,6 +885,7 @@ module.exports.groups = [
},
{
type: 'Divide Width',
internal: true,
members: [
{
type: 'divide-y',
Expand All @@ -875,26 +907,32 @@ module.exports.groups = [
},
{
type: 'Divide Color',
internal: true,
members: 'divide\\-(?<value>${divideColor})',
},
{
type: 'Divide Style',
internal: true,
members: 'divide\\-(solid|dashed|dotted|double|none)',
},
{
type: 'Outline Width',
internal: true,
members: 'outline\\-(?<value>${outlineWidth})',
},
{
type: 'Outline Color',
internal: true,
members: 'outline\\-(?<value>${outlineColor})',
},
{
type: 'Outline Style',
internal: true,
members: 'outline(\\-(none|dashed|dotted|double|hidden))?',
},
{
type: 'Outline Offset',
internal: true,
members:
'(outline\\-offset\\-(?<value>${outlineOffset})|\\-outline\\-offset\\-(?<negativeValue>${-outlineOffset}))',
},
Expand Down Expand Up @@ -1047,6 +1085,7 @@ module.exports.groups = [
},
{
type: 'Tables',
internal: true,
members: [
{
type: 'Border Collapse',
Expand Down Expand Up @@ -1406,6 +1445,7 @@ module.exports.groups = [
},
{
type: 'Line Clamp',
internal: true,
members: 'line\\-clamp\\-(none|(?<value>${lineClamp}))',
},
],
Expand Down
2 changes: 2 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
'no-arbitrary-value': require(base + 'no-arbitrary-value'),
'no-contradicting-classname': require(base + 'no-contradicting-classname'),
'no-custom-classname': require(base + 'no-custom-classname'),
'no-internal-style': require(base + 'no-internal-style'),
},
configs: {
recommended: {
Expand All @@ -36,6 +37,7 @@ module.exports = {
'tailwindcss/no-arbitrary-value': 'off',
'tailwindcss/no-custom-classname': 'warn',
'tailwindcss/no-contradicting-classname': 'error',
'tailwindcss/no-internal-style': 'off',
},
},
},
Expand Down
Loading