The rules in this plugin lint Tailwind classes inside string literals.
To do that safely, the plugin must know which strings are expected to contain Tailwind classes. If it would lint every string literal in your codebase, it would produce many false positives and potentially unsafe fixes.
To configure this, you can provide an array of selectors that specify where the plugin should look for class strings and how to extract them.
The plugin already ships with defaults that support most popular tailwind utilities. You only need advanced configuration when:
- you use custom utilities/APIs not covered by defaults,
- you want to narrow down linting behavior,
- or you want to lint additional locations.
To extend defaults instead of replacing them, import and spread getDefaultSelectors() from eslint-plugin-better-tailwindcss/defaults.
You can find the default selectors in the defaults documentation.
Each selector targets one kind of source location and tells the plugin how to extract class strings from it.
The plugin supports four selector types: attribute, callee, variable, and tag.
Every selector can then match different types of string literals based on the provided match option.
- kind:
"attribute". - name: regular expression for attribute names.
- match
optional: selector matcher list.
When omitted, only direct string literals are collected.
type AttributeSelector = {
kind: "attribute";
name: string;
match?: SelectorMatcher[];
};- kind:
"callee". - name
optional: regular expression for callee names. - path
optional: regular expression for callee member paths likeclasses.push.
Whenpathis provided,nameis not required. - targetCall
optional: curried call target for example forfn()("my classes").
If a non-negative number is provided, the zero-based call index is used.
Negative numbers count from the end (-1is the last call).
When omitted, the first call in a curried chain is used. - targetArgument
optional: target specific call arguments.
If a non-negative number is provided, the zero-based argument index is used.
Negative numbers count from the end (-1is the last argument).
When omitted, all arguments of the selected call are checked. - match
optional: selector matcher list.
When omitted, only direct string literals are collected.
type CalleeSelector = {
kind: "callee";
match?: SelectorMatcher[];
name?: string;
path?: string;
targetArgument?: "all" | "first" | "last" | number;
targetCall?: "all" | "first" | "last" | number;
};- kind:
"variable". - name: regular expression for variable names.
Tip: The namedefaulttargets theexport default ...declaration. - match
optional: selector matcher list.
When omitted, only direct string literals are collected.
type VariableSelector = {
kind: "variable";
name: string;
match?: SelectorMatcher[];
};- kind:
"tag". - name:
optionalregular expression for tagged template names. - path
optional: regular expression for tagged template member paths liketwc.class.
Whenpathis provided,nameis not required. - match
optional: selector matcher list.
When omitted, only direct string literals are collected.
type TagSelector = {
kind: "tag";
name: string;
match?: SelectorMatcher[];
};- Names are treated as regular expressions.
- Reserved regex characters must be escaped.
- The regex must match the whole name (not a substring).
Matches all string literals that are not object keys or object values.
type SelectorStringMatcher = {
type: "strings";
};{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{ "type": "strings" }
]
}
]
}Matches:
tw(
"this will get linted",
{ className: "this will not get linted by this matcher" }
);Matches all object keys.
pathoptional: regular expression to narrow matching to specific object key paths See Path option details.
type SelectorObjectKeyMatcher = {
type: "objectKeys";
path?: string;
};{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{
"type": "objectKeys",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}Matches:
tw({
compoundVariants: [
{
className: "<- this key will get linted",
myVariant: "but this key will not get linted"
}
]
});Matches all object values.
pathoptional: regular expression to narrow matching to specific object value paths See Path option details.
type SelectorObjectValueMatcher = {
type: "objectValues";
path?: string;
};{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{
"type": "objectValues",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}Matches:
tw({
compoundVariants: [
{
className: "this value will get linted",
myVariant: "but this value will not get linted"
}
]
});Matches values returned from anonymous functions and applies nested matchers to those return values.
matchrequired: nested matcher array
The nestedmatcharray can includestrings,objectKeys, andobjectValuesmatchers.
type SelectorAnonymousFunctionReturnMatcher = {
match: (SelectorObjectKeyMatcher | SelectorObjectValueMatcher | SelectorStringMatcher)[];
type: "anonymousFunctionReturn";
};{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{
"type": "anonymousFunctionReturn",
"match": [
{ "type": "strings" },
{ "type": "objectKeys" },
{ "type": "objectValues" }
]
}
]
}
]
}Matches:
tw(() => "this will get linted with a nested string matcher");
tw(() => ({ className: "<- this key will get linted with a nested objectKeys matcher" }));
tw(() => ({ className: "this will get linted with nested objectValues matcher" }));The path option lets you narrow down objectKeys and objectValues matching to specific object paths.
This is especially useful for libraries like Class Variance Authority (cva), where class names appear in nested object structures.
path is a regex matched against the object path.
For example, the following matcher will only match object values for the compoundVariants.class key:
{
"selectors": [
{
"kind": "callee",
"name": "^cva$",
"match": [
{
"type": "objectValues",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}<img class={
cva("this will not get linted", {
compoundVariants: [
{
class: "but this will get linted",
myVariant: "and this will not get linted"
}
]
})
} />;The path reflects how the string is nested in the object:
- Dot notation for plain keys:
root.nested.values - Square brackets for arrays:
values[0] - Quoted brackets for special characters:
root["some-key"]
For example, the object path for value in the object below is root["nested-key"].values[0].value:
{
"root": {
"nested-key": {
"values": [
{
"value": "this will get linted"
}
]
}
}
}{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"targetCall": "last",
"targetArgument": "first"
}
]
}tw("keep", "ignore")("this will get linted", "this will not");{
"selectors": [
{
"kind": "callee",
"name": "^cva$",
"match": [
{
"type": "strings"
},
{
"type": "objectValues",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}<img class={
cva("this will get linted", {
compoundVariants: [
{
class: "and this will get linted",
myVariant: "but this will not get linted"
}
]
})
} />;You can match custom attributes by modifying your selectors configuration. Here is an example on how to match the values inside the Algolia classNames objects:
<SearchBox
classNames={{
form: "relative",
root: "p-3 shadow-sm"
}}
/>;// eslint.config.js
import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
import { getDefaultSelectors } from "eslint-plugin-better-tailwindcss/defaults";
import { SelectorKind } from "eslint-plugin-better-tailwindcss/types";
import { defineConfig } from "eslint/config";
export default defineConfig({
plugins: {
"better-tailwindcss": eslintPluginBetterTailwindcss
},
settings: {
"better-tailwindcss": {
entryPoint: "app/globals.css",
selectors: [
...getDefaultSelectors(), // preserve default selectors
{
kind: SelectorKind.Attribute,
match: [{ type: "objectValues" }],
name: "^classNames$"
}
]
}
}
});
// ...
{ "selectors": [ { "kind": "callee", "path": "^classes\\.push$", "match": [{ "type": "strings" }] } ] }