# Advanced Configuration
The [rules](../../README.md#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](#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](../../README.md#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](../api/defaults.md).
## Selectors
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.
### Type
### `attribute`
- **kind**: `"attribute"`.
- **name**: regular expression for attribute names.
- **match** `optional`: [selector matcher](#selector-matcher-types) list.
When omitted, only direct string literals are collected.
```ts
type AttributeSelector = {
kind: "attribute";
name: string;
match?: SelectorMatcher[];
};
```
### `callee`
- **kind**: `"callee"`.
- **name** `optional`: regular expression for callee names.
- **path** `optional`: regular expression for callee member paths like `classes.push`.
When `path` is provided, `name` is not required.
- **targetCall** `optional`: curried call target for example for `fn()("my classes")`.
If a non-negative number is provided, the zero-based call index is used.
Negative numbers count from the end (`-1` is 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 (`-1` is the last argument).
When omitted, all arguments of the selected call are checked.
- **match** `optional`: [selector matcher](#selector-matcher-types) list.
When omitted, only direct string literals are collected.
```ts
type CalleeSelector = {
kind: "callee";
match?: SelectorMatcher[];
name?: string;
path?: string;
targetArgument?: "all" | "first" | "last" | number;
targetCall?: "all" | "first" | "last" | number;
};
```
### `variable`
- **kind**: `"variable"`.
- **name**: regular expression for variable names.
Tip: The name `default` targets the `export default ...` declaration.
- **match** `optional`: [selector matcher](#selector-matcher-types) list.
When omitted, only direct string literals are collected.
```ts
type VariableSelector = {
kind: "variable";
name: string;
match?: SelectorMatcher[];
};
```
### `tag`
- **kind**: `"tag"`.
- **name**: `optional` regular expression for tagged template names.
- **path** `optional`: regular expression for tagged template member paths like `twc.class`.
When `path` is provided, `name` is not required.
- **match** `optional`: [selector matcher](#selector-matcher-types) list.
When omitted, only direct string literals are collected.
```ts
type TagSelector = {
kind: "tag";
name: string;
match?: SelectorMatcher[];
};
```
### How selector matching works
- Names are treated as regular expressions.
- Reserved regex characters must be escaped.
- The regex must match the whole name (not a substring).
```jsonc
{
"selectors": [
{
"kind": "callee",
"path": "^classes\\.push$",
"match": [{ "type": "strings" }]
}
]
}
```
### Matchers
#### Selector matcher types
##### `strings`
Matches all string literals that are not object keys or object values.
```ts
type SelectorStringMatcher = {
type: "strings";
};
```
```json
{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{ "type": "strings" }
]
}
]
}
```
Matches:
```tsx
tw(
"this will get linted",
{ className: "this will not get linted by this matcher" }
);
```
##### `objectKeys`
Matches all object keys.
- `path` `optional`: regular expression to narrow matching to specific object key paths
See [Path option details](#path-option-details).
```ts
type SelectorObjectKeyMatcher = {
type: "objectKeys";
path?: string;
};
```
```json
{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{
"type": "objectKeys",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}
```
Matches:
```tsx
tw({
compoundVariants: [
{
className: "<- this key will get linted",
myVariant: "but this key will not get linted"
}
]
});
```
##### `objectValues`
Matches all object values.
- `path` `optional`: regular expression to narrow matching to specific object value paths
See [Path option details](#path-option-details).
```ts
type SelectorObjectValueMatcher = {
type: "objectValues";
path?: string;
};
```
```json
{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{
"type": "objectValues",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}
```
Matches:
```tsx
tw({
compoundVariants: [
{
className: "this value will get linted",
myVariant: "but this value will not get linted"
}
]
});
```
##### `anonymousFunctionReturn`
Matches values returned from anonymous functions and applies nested matchers to those return values.
- `match` `required`: nested matcher array
The nested `match` array can include `strings`, `objectKeys`, and `objectValues` matchers.
```ts
type SelectorAnonymousFunctionReturnMatcher = {
match: (SelectorObjectKeyMatcher | SelectorObjectValueMatcher | SelectorStringMatcher)[];
type: "anonymousFunctionReturn";
};
```
```json
{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"match": [
{
"type": "anonymousFunctionReturn",
"match": [
{ "type": "strings" },
{ "type": "objectKeys" },
{ "type": "objectValues" }
]
}
]
}
]
}
```
Matches:
```tsx
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" }));
```
##### Path option details
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)](https://cva.style/docs/getting-started/installation#intellisense), 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:
```json
{
"selectors": [
{
"kind": "callee",
"name": "^cva$",
"match": [
{
"type": "objectValues",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}
```
```tsx
;
```
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`:
```json
{
"root": {
"nested-key": {
"values": [
{
"value": "this will get linted"
}
]
}
}
}
```
### Examples
#### Example: lint only the first argument of the last curried call
```jsonc
{
"selectors": [
{
"kind": "callee",
"name": "^tw$",
"targetCall": "last",
"targetArgument": "first"
}
]
}
```
```tsx
tw("keep", "ignore")("this will get linted", "this will not");
```
#### Example: lint `cva` strings + specific nested values
```jsonc
{
"selectors": [
{
"kind": "callee",
"name": "^cva$",
"match": [
{
"type": "strings"
},
{
"type": "objectValues",
"path": "^compoundVariants\\[\\d+\\]\\.(?:className|class)$"
}
]
}
]
}
```
```tsx
;
```
#### Full example: custom Algolia attribute selector
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:
```tsx
;
```
```js
// 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$"
}
]
}
}
});
// ...
```