Skip to content

enforce-consistent-line-wrapping: opt-in fix for JSX attribute strings #216

Closed

Description

@followdarko

Summary

enforce-consistent-line-wrapping never fixes a string literal. The docs give the reason: a string literal can't safely span lines, and turning it into a multiline template is a judgment call. That holds for a JS string, but not for a JSX attribute string, which can span lines as it is:

<div
  className="
    flex items-center justify-between rounded-md bg-bg-elevated px-3 py-1
    text-sm text-fg-secondary shadow-md
  "
/>

This is valid JSX, and the class attribute treats the newlines as ordinary whitespace. Nothing gets converted, so there's no template to decide on. I'd like to ask whether an opt-in fix for this case would be welcome. If so, I'm happy to implement it.

Why

Proposal

Opt-in, so nothing changes by default. For example:

"tailwindcss/enforce-consistent-line-wrapping": ["error", {
  "printWidth": 90,
  "wrapLines": "overWidth",
  "wrapStrings": "jsx"   // default: "never"
}]
  • Applies only to a Literal that is the direct value of a JSX attribute (className="…"), never to a JS string (cn("…"), const classes = "…"). Those keep today's warn-only behaviour, since fixing them would need a string → template conversion.
  • Reuses the rewrapTemplateToWidth / rewrapOverBudgetLinesToWidth layout, and escapes nothing new. A JSX string can't contain its own quote, and class lists never do.
  • classesPerLine could follow the same switch.

Questions

  1. Is the "judgment call" in the docs about strings in general, or mainly about converting a JS string into a template? If it's the latter, JSX attributes seem to fall outside it.
  2. Option name and shape: wrapStrings: "never" | "jsx", a boolean, or something else?

Activity

  1. added a commit that references this issue on Oct 9, 2026
  2. sergioazoc commented on Oct 9, 2026

    @sergioazoc
    Owner

    Out in 1.15.0 (#224) as wrapStrings: "jsx".

    You were right about the "judgment call": it was about turning a JS string into a template, and a JSX attribute's own string doesn't need that, its newlines are just whitespace in a class attribute. So with wrapStrings: "jsx" the rule wraps className="…" in place, same block layout as templates, with the closing quote at the attribute line's indentation. Both wrapLines and classesPerLine apply, and JS strings (className={"…"}, cn("…"), variables) keep reporting without a fix.

    Two details before you turn it on in that codebase with 328 of them. A CRLF file gets CRLF breaks (the value keeps the file's \r\n, so it's measured and written back with the right ending). And if oxfmt formats with sortTailwindcss, set preserveWhitespace: true, otherwise it collapses the block back to one line and the two keep undoing each other.

    Docs: https://oxlint-tailwindcss.pages.dev/rules/enforce-consistent-line-wrapping#wrapstrings

  3. dl-web-dev commented on Oct 9, 2026

    @dl-web-dev

    Just curious, are there plans to also include className={"…"}, cn("…"), variables ? Want to fully migrate to this plugin but really like the way the readability of the wrapped strings from eslint-plugin-better-tailwindcss in both cn and className={"…"}

  4. sergioazoc commented on Oct 10, 2026

    @sergioazoc
    Owner

    Not yet, but it's a small step from wrapStrings: "jsx", so I'll add it as an opt-in, wrapStrings: "all". It works like better-tailwindcss: when a JS string has to wrap, it becomes a template literal with the same block layout. That covers cn("…"), className={"…"}, ternary and && branches, arrays, cva/tv values and variables. It never touches an object key (cn({ "…": cond })), or a string holding a backslash, a backtick or ${.

    It stays opt-in because it changes the string's value. The newlines and indentation are just whitespace in a class list, but they matter if a variable matched by variablePatterns is used for something else. I'll post here when it's out.

  5. sergioazoc commented on Oct 10, 2026

    @sergioazoc
    Owner

    Out in 1.16.0 (#232) as wrapStrings: "all". A JS string that has to wrap (cn("…"), className={"…"}, arrays, cva/tv values, ternary and && branches, variables) becomes a template literal with the same block layout as your JSX strings, and className="…" is still wrapped in place.

    If oxfmt sorts your classes, set preserveWhitespace: true in sortTailwindcss. Otherwise it collapses the template back to one line and the two keep undoing each other, same as with the JSX strings.

    Docs: https://oxlint-tailwindcss.pages.dev/rules/enforce-consistent-line-wrapping#wrapstrings

  6. dl-web-dev commented on Oct 12, 2026

    @dl-web-dev

    13. Out in 1.16.0 (#232) as wrapStrings: "all". A JS string that has to wrap (cn("…"), className={"…"}, arrays, cva/tv values, ternary and && branches, variables) becomes a template literal with the same block layout as your JSX strings, and className="…" is still wrapped in place.
    If oxfmt sorts your classes, set preserveWhitespace: true in sortTailwindcss. Otherwise it collapses the template back to one line and the two keep undoing each other, same as with the JSX strings.
    Docs: https://oxlint-tailwindcss.pages.dev/rules/enforce-consistent-line-wrapping#wrapstrings

    Much appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions