Skip to content

feat: add Tailwind class name sorting (#552) - #795

Open
todor-a wants to merge 13 commits into
dprint:mainfrom
todor-a:issue-552-tailwind-class-sorting
Open

todor-a wants to merge 13 commits into
dprint:mainfrom
todor-a:issue-552-tailwind-class-sorting

Conversation

@todor-a

@todor-a todor-a commented May 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #552. Adds opt-in Tailwind class sorting aligned with prettier-plugin-tailwindcss.


Transparency note: This PR was prepared with AI assistance (Claude).


Problem

Tailwind users often rely on Prettier's Tailwind plugin to keep utility classes in a stable recommended order.

const value = <div className="p-4 flex bg-red-500 text-white" />;

Currently dprint-plugin-typescript preserves the input order, so projects that use dprint cannot opt into that Tailwind ordering for JSX class attributes or common class helper calls.

Fix

Add jsx.sortClassNames with a new tailwind value. The default remains maintain.

When enabled, the formatter sorts Tailwind class lists in:

  • JSX class and className string attributes
  • string literals, object keys, and simple template literals inside className={...} expression containers
  • configured helper calls via jsx.sortClassNames.functions, for example cn, classnames, or tw
  • configured tagged template literals via the same function list

The sorter follows the same broad behavior as prettier-plugin-tailwindcss for the covered cases: custom classes stay first, whitespace is collapsed, duplicate classes are removed, and variants sort after base utilities.

Behavior

input config after
<div className="p-4 flex" /> default unchanged
<div className="p-4 flex" /> jsx.sortClassNames: tailwind <div className="flex p-4" />
className={cn("p-4 flex")} jsx.sortClassNames: tailwind string inside expression sorted
cn("p-4 flex") jsx.sortClassNames.functions: ["cn"] cn("flex p-4")
tw`p-4 flex` jsx.sortClassNames.functions: ["tw"] tw`flex p-4`

Spec and docs updates

New JSX attribute specs cover:

  • default no-op behavior
  • Tailwind ordering for class and className
  • public prettier-plugin-tailwindcss examples
  • custom class ordering
  • whitespace collapse and duplicate removal
  • className={...} expression containers
  • configured function calls and tagged templates

Docs updates cover:

  • README configuration example
  • generated schema descriptions for jsx.sortClassNames and jsx.sortClassNames.functions

Cross-referenced against prettier-plugin-tailwindcss docs/source and Oxc's Tailwind sorting tests.

cargo test passes.

@todor-a todor-a changed the title [codex] Add Tailwind class name sorting feat: add Tailwind class name sorting (#552) May 28, 2026
@todor-a
todor-a marked this pull request as ready for review May 28, 2026 16:48
@todor-a
todor-a force-pushed the issue-552-tailwind-class-sorting branch from 4e467f7 to 76131f3 Compare August 24, 2026 06:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Interpolated templates can change semantics, helper-only configuration does not work as documented, and several common Tailwind classes are ordered incorrectly.

3 open findings
What changed in this PR

Adds opt-in Tailwind class sorting for JSX attributes, configured helpers, and tagged templates.

Changes:

  • Adds Tailwind class parsing and ordering.
  • Adds configuration, builder, and schema support.
  • Adds formatter specifications for supported contexts.
File Description
src/​generation/​sorting/​tailwind_class_names.rs Implements class ordering.
src/​generation/​sorting/​mod.rs Exposes the sorter.
src/​generation/​generate.rs Integrates sorting into generation.
src/​configuration/​types.rs Defines configuration types.
src/​configuration/​resolve_config.rs Resolves new options.
src/​configuration/​builder.rs Adds builder methods.
deployment/​schema.json Documents configuration schema.
tests/​specs/​jsx/​JsxAttribute/​JsxAttribute_SortClassNames_Default.txt Tests default behavior.
tests/​specs/​jsx/​JsxAttribute/​JsxAttribute_SortClassNames_Tailwind.txt Tests JSX attributes.
tests/​specs/​jsx/​JsxAttribute/​JsxAttribute_SortClassNames_TailwindExpressions.txt Tests expression containers.
tests/​specs/​jsx/​JsxAttribute/​JsxAttribute_SortClassNames_TailwindFunctions.txt Tests configured helpers.
tests/​specs/​jsx/​JsxAttribute/​JsxAttribute_SortClassNames_TailwindFunctionsDefault.txt Tests helper defaults.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/generation/generate.rs Outdated
Comment thread src/tailwind/tables.rs Outdated
Comment thread src/generation/sorting/tailwind_class_names.rs Outdated
Moves the sorter into a self-contained `tailwind` module and patterns its
handling of whitespace, duplicates, unknown classes and template literals
off prettier-plugin-tailwindcss, porting that plugin's tests.

- keep the whitespace that separates concatenated strings
- sort the text around template literal expressions without moving the
  classes that touch an expression
- don't sort the other attributes of an element in a class attribute
- handle the important modifier and sort unknown variants after known ones
- borrow the text when it's already sorted and avoid per class allocations
Replaces the hand-written tables with ones generated from Tailwind CSS 4.3.3
by scripts/generate_tailwind_tables.ts and ports Tailwind's comparisons of
utilities, variants and breakpoints, so that classes sharing a prefix sort by
what they set (ex. `text-sm` before `text-red-500`).

The script also generates class lists sorted by Tailwind, which a test checks
the Rust code sorts the same way.
…ames

Adds config for what the sorter can't know without a project's CSS:

- `jsx.sortClassNames.tailwind.prefix`
- `jsx.sortClassNames.tailwind.theme` for theme variables, which orders custom
  breakpoints by size and values by the kind of variable they're for
- `jsx.sortClassNames.tailwind.variants` for custom variants
- `jsx.sortClassNames.tailwind.utilities` for custom utilities, which are
  ordered by the CSS properties they set

Also aligns more with prettier-plugin-tailwindcss by adding
`jsx.sortClassNames.attributes`, `jsx.sortClassNames.preserveWhitespace` and
`jsx.sortClassNames.preserveDuplicates`, sorting every string in a class
attribute's expression, and treating a class with an unknown variant as an
unknown class.

The script now also generates class lists sorted by Tailwind for a project
that customizes it, which a test checks the Rust code sorts the same way.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/generation/generate.rs Outdated
Comment thread deployment/schema.json Outdated
Comment thread src/generation/generate.rs Outdated
Comment thread src/tailwind/mod.rs Outdated
Comment thread deployment/schema.json
Comment on lines +63 to +64
"jsx.sortClassNames": {
"description": "How to sort JSX class attribute values.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not doing this one. The configuration section was removed from the README on purpose and the PR description predates that; the options are documented in the schema.

Also handles quotes and escapes in arbitrary values, only treats ASCII
whitespace as separating classes at template literal boundaries, and
looks at the text beside a template literal expression when deciding if
a string's whitespace can be removed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Whitespace-only string results are discarded, and large custom utility definitions can be sorted using truncated property data.

1 open finding
4 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Whitespace-only strings are not collapsed when transformed

src/​tailwind/​mod.rs:100

This returns a different string as Cow::Borrowed, but gen_string_literal treats every borrowed result as unchanged and discards it (src/generation/generate.rs:4250-4253). Consequently a whitespace-only string such as className={" "} remains unchanged instead of collapsing to one space. Return an owned value whenever the output differs from text.

Medium severity Property lists over 32 entries are silently truncated

src/​tailwind/​mod.rs:496

The public utilities option accepts an unbounded property list, but this fixed buffer silently drops every distinct recognized property after the first 32. Two large custom utilities can therefore receive the same truncated sort key and fall back to class-name order even when Tailwind orders them by a later property. Keep the complete property-order vector (or validate and reject oversized lists) rather than truncating it.

🧠 Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Sorting strings at unseparated concatenation boundaries can change which runtime class contains the adjacent expression.

2 open findings

🧠 Review effort: Balanced

Comment thread src/generation/generate.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Template-expression boundary sorting can change runtime class names, and one configuration diagnostic is inaccurate.

2 open findings
1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Low severity Mention numeric theme values in diagnostic guidance

src/​configuration/​resolve_config.rs:387

This diagnostic says only strings are accepted even though the immediately preceding branch also accepts numeric theme values. Invalid input therefore gets misleading guidance; mention both supported forms.

🧠 Review effort: Balanced

Comment thread src/generation/generate.rs
@dsherret
dsherret requested a balanced review from Copilot October 9, 2026 19:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Candidate validation currently misclassifies unsupported utility modifiers and unknown breakpoint values as valid Tailwind classes.

1 open finding
1 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Reject unsupported slash modifiers during utility validation

src/​tailwind/​mod.rs:439

Unsupported slash modifiers are treated as valid for every utility because lookup falls back to utility_without_modifier. For example, Tailwind rejects p-4/foo, so it should remain an unknown class and sort before flex; this code assigns it the padding position and leaves flex p-4/foo. Preserve modifier-support metadata from Tailwind (or otherwise validate candidates) before dropping the suffix.

Medium severity Validate configured breakpoint values for functional variants

src/​tailwind/​mod.rs:634

Value-ordered functional variants are considered known even when their value is not a configured/default breakpoint. For example, without --breakpoint-foo, Tailwind treats max-foo:flex as unknown and keeps it before p-4, while this path recognizes max and sorts it as a real variant. Validate breakpoint/container values while still permitting bracketed arbitrary values.

🧠 Review effort: Balanced

The generated tables now have the values each utility accepts, what may
follow the slash after it, and what the value of each variant may be, so
classes like p-banana, p-4/foo, and max-foo:flex are left as unknown.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Logical-expression boundaries and malformed variant parsing can produce incorrect class ordering.

4 open findings

🧠 Review effort: Balanced

Comment thread src/generation/generate.rs Outdated
Comment thread src/tailwind/mod.rs Outdated
Comment thread src/tailwind/mod.rs
@dsherret
dsherret requested a balanced review from Copilot October 9, 2026 20:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dsherret
dsherret requested a balanced review from Copilot October 10, 2026 16:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@todor-a

todor-a commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@dsherret
dsherret requested a balanced review from Copilot October 14, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tailwind class sorting

3 participants