prettier-plugin-tv-groups
v0.0.2
Published
Prettier plugin: group tailwind-variants tv() base classes by modifier buckets
Maintainers
Readme
prettier-plugin-tv-groups
Prettier 3 plugin for tailwind-variants: it reorders the base field of tv({ ... }) (and other names you configure) into one string per modifier group (plain utilities, hover:, dark:, responsive prefixes, aria-, data-, etc.). The order of groups follows a fixed list inside the plugin; within each string, classes keep a stable order so a tool like prettier-plugin-tailwindcss can sort them afterward.
Example
Before — dozens of classes in arbitrary order, all in one base string (typical copy-paste / merge mess):
const button = tv({
base: "dark:hover:bg-slate-800 aria-busy:pointer-events-none md:flex data-[state=open]:animate-in focus-visible:ring-2 lg:inline-flex hover:bg-primary/90 border-transparent disabled:opacity-50 dark:text-white shrink-0 active:scale-95 px-4 py-2 rounded-full size-10 border-2 focus-visible:outline-none shadow-sm text-sm font-medium transition-colors disabled:pointer-events-none dark:bg-slate-900 focus-visible:ring-offset-2",
variants: {},
});After Prettier runs with this plugin — base becomes an array of strings: plain layout/typography together, then hover:, focus-visible:, active:, disabled:, aria-, data-, breakpoints (md:, lg:), dark:, in a fixed group order decided by the plugin:
const button = tv({
base: [
"border-transparent shrink-0 px-4 py-2 rounded-full size-10 border-2 shadow-sm text-sm font-medium transition-colors",
"hover:bg-primary/90",
"focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-offset-2",
"active:scale-95",
"disabled:opacity-50 disabled:pointer-events-none",
"aria-busy:pointer-events-none",
"data-[state=open]:animate-in",
"md:flex lg:inline-flex",
"dark:hover:bg-slate-800 dark:text-white dark:bg-slate-900",
],
variants: {},
});You can see at a glance which bucket a class belongs to; diffs stay localized to one array element when you tweak e.g. only dark mode. If you also use prettier-plugin-tailwindcss, it can sort names inside each string in the same format pass. A base that stays one literal is unchanged until you format with this plugin.
Requirements
- Node.js >= 18
- Prettier 3.8.1 (see
peerDependenciesin this package)
Install
npm add -D prettier-plugin-tv-groupsPin Prettier to the same major line your project already uses; this repo is tested with Prettier 3.8.1.prettier-plugin-tailwindcss is an optional peer — add it when you want Tailwind class sorting in the same format run.
Which files are handled
The plugin only hooks Prettier’s typescript and babel-ts parsers. In practice that is mainly .ts / .tsx, and .jsx when Prettier uses babel-ts. Plain .js often uses the babel parser; this package does not wrap that parser, so tv() grouping may not run unless you force a compatible parser (for example via overrides in your Prettier config).
Configure Prettier
Standalone (no Tailwind sorting) — .prettierrc or prettier.config with JSON:
{
"plugins": ["prettier-plugin-tv-groups"]
}Option: tvGroupsFunctionNames
Array of local callee names to treat like tv. Default: ["tv"].
- Only simple calls
name({ base: ... })are handled (Identifiercallee, notfoo.tv()). - If you use a single alias and no
tv, set e.g.["createVariants"]— thentvis not transformed unless you include it in the array. []disables grouping for this plugin.
Example:
{
"plugins": ["prettier-plugin-tv-groups"],
"tvGroupsFunctionNames": ["tv", "createVariants"]
}With prettier-plugin-tailwindcss
prettier-plugin-tailwindcss is expected to be the last plugin in plugins. If you list prettier-plugin-tv-groups as a separate entry, Prettier may end up never running this plugin’s parsers, or you break Tailwind’s “last plugin” rule — so the supported approach is to attach tv-groups onto the Tailwind plugin.
Use a JavaScript config (for example prettier.config.mjs) and import attachTvGroupsToTailwind from the subpath prettier-plugin-tv-groups/attach-to-tailwind. Pass the namespace import of Tailwind’s plugin (import * as tailwindPlugin from "prettier-plugin-tailwindcss"), not a default export.
import * as tailwindPlugin from "prettier-plugin-tailwindcss";
import { attachTvGroupsToTailwind } from "prettier-plugin-tv-groups/attach-to-tailwind";
export default {
plugins: [attachTvGroupsToTailwind(tailwindPlugin)],
tailwindFunctions: ["tv"],
};Add every name from tvGroupsFunctionNames to tailwindFunctions if you want class sorting inside those calls too.
If you use @ianvs/prettier-plugin-sort-imports, keep it before the combined Tailwind + tv-groups entry so import sorting still runs first.
The default export (prettier-plugin-tv-groups alone) is still useful for tests or when Tailwind is not loaded; it tries to delegate to Tailwind’s typescript / babel-ts parsers when that package is installed, but with both plugins listed separately, ordering is fragile — prefer attachTvGroupsToTailwind for day-to-day projects.
What gets rewritten
- First argument to the call must be an object literal with a
baseproperty. basemay be a string literal or an array of string literals only (no template literals with${}, spreads, or arbitrary expressions).
Renamed imports are not inferred: you must add the local name via tvGroupsFunctionNames.
Development
npm test
npm run lintLicense
Licensed under the MIT License.
