prettier-plugin-tailwind-align
v0.1.0
Published
Prettier plugin that sorts Tailwind CSS classes and aligns object properties — in one pass.
Maintainers
Readme
prettier-plugin-tailwind-align
One Prettier plugin. Two formatters. Zero conflicts.
Sorts Tailwind CSS utility classes and vertically aligns object properties — in a single pass.
Table of Contents
- Why this exists
- Installation
- Quick start
- Options
- What gets aligned
- Full example
- Migrating from two separate plugins
- Local development
- Compatibility
- License
Why this exists
Prettier only allows one plugin to own a given parser hook (e.g. parsers["typescript"]).
When prettier-plugin-tailwindcss and @huggingface/prettier-plugin-vertical-align are both
listed in "plugins", whichever comes last silently wins the hook — breaking the other one
with no error or warning.
The common workaround is a two-pass setup: run Tailwind sorting in one Prettier pass, alignment in another. This is fragile, slow, and ties your CI to a specific script order.
prettier-plugin-tailwind-align eliminates the problem entirely. It composes both behaviours
internally so Prettier sees exactly one plugin, runs one pass, and both Tailwind class sorting
and object alignment happen in the correct order — guaranteed.
Installation
# npm
npm install --save-dev prettier-plugin-tailwind-align
# pnpm
pnpm add -D prettier-plugin-tailwind-align
# yarn
yarn add -D prettier-plugin-tailwind-align
# bun
bun add --dev prettier-plugin-tailwind-alignPeer dependencies —
prettierandprettier-plugin-tailwindcssmust be installed separately (they are not bundled).npm install --save-dev prettier prettier-plugin-tailwindcss
Quick start
Add the plugin to your .prettierrc (or prettier.config.js / prettier.config.ts):
{
"plugins": ["prettier-plugin-tailwind-align"],
"tailwindStylesheet": "./app/globals.css",
"alignInGroups": "always"
}That's it. Remove any separate prettier-plugin-tailwindcss or
@huggingface/prettier-plugin-vertical-align entries — this plugin replaces both.
Options
All options from prettier-plugin-tailwindcss
are fully supported and passed through unchanged — tailwindConfig, tailwindStylesheet,
tailwindFunctions, tailwindAttributes, and so on.
In addition, the following option is provided:
alignInGroups
Controls how alignment groups are computed inside object literals, interfaces, type literals, and class bodies.
| Value | Default | Description |
|---|---|---|
| "never" | ✅ | Every alignable property in the same block shares one alignment column. |
| "always" | | A blank line starts a new independent group. Each group aligns to its own column. |
"never" — whole-block alignment
All properties align to the longest key in the entire block.
const palette = {
red: "#ef4444",
green: "#22c55e",
blue: "#3b82f6",
darkGreen: "#166534",
};"always" — group-level alignment
A blank line starts a new independent alignment group.
const palette = {
red: "#ef4444",
green: "#22c55e",
blue: "#3b82f6",
darkRed: "#991b1b",
darkGreen: "#166534",
darkBlue: "#1e3a8a",
};What gets aligned
| Node type | Aligned |
|---|---|
| Object literal properties — { key: value } | ✅ |
| TypeScript interface members | ✅ |
| TypeScript type literal members | ✅ |
| Class property definitions | ✅ |
| Shorthand properties — { x, y, z } | ❌ skipped |
| Method shorthands — { foo() {} } | ❌ skipped |
| Multi-line values (key and value on different lines) | ❌ skipped |
| Multiple properties sharing the same line | ❌ skipped |
Full example
Input
const config = {
host: "localhost",
port: 3000,
databaseName: "mydb",
ssl: false,
};
const theme = {
primary: "#3b82f6",
secondary: "#6366f1",
background: "#ffffff",
text: "#111827",
border: "#e5e7eb",
};
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
isAdmin: boolean;
}
type Dimensions = {
width: number;
height: number;
depth: number;
};
const palette = {
red: "#ef4444",
green: "#22c55e",
blue: "#3b82f6",
darkRed: "#991b1b",
darkGreen: "#166534",
darkBlue: "#1e3a8a",
};
export function Card({ title, body }: { title: string; body: string }) {
return (
<div className="p-4 text-sm font-medium bg-white rounded-lg shadow-md flex items-center gap-2">
<h2 className="text-xl leading-tight font-bold text-gray-900">{title}</h2>
<p className="mt-2 text-base text-gray-600 leading-relaxed">{body}</p>
</div>
);
}
const buttonVariants = {
base: "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500",
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-2 focus:ring-gray-400",
danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-2 focus:ring-red-500",
};
class ApiClient {
baseUrl: string = "https://api.example.com";
timeout: number = 5000;
retries: number = 3;
apiKey: string = "";
}Output — with alignInGroups: "always"
const config = {
host: "localhost",
port: 3000,
databaseName: "mydb",
ssl: false,
};
const theme = {
primary: "#3b82f6",
secondary: "#6366f1",
background: "#ffffff",
text: "#111827",
border: "#e5e7eb",
};
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
isAdmin: boolean;
}
type Dimensions = {
width: number;
height: number;
depth: number;
};
const palette = {
red: "#ef4444",
green: "#22c55e", // group 1 — aligns to "green:" width
blue: "#3b82f6",
darkRed: "#991b1b",
darkGreen: "#166534", // group 2 — aligns to "darkGreen:" width independently
darkBlue: "#1e3a8a",
};
// Tailwind classes sorted, object values aligned — in one pass
export function Card({ title, body }: { title: string; body: string }) {
return (
<div className="flex items-center gap-2 rounded-lg bg-white p-4 text-sm font-medium shadow-md">
<h2 className="text-xl leading-tight font-bold text-gray-900">{title}</h2>
<p className="mt-2 text-base leading-relaxed text-gray-600">{body}</p>
</div>
);
}
const buttonVariants = {
base: "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500",
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-2 focus:ring-gray-400",
danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-2 focus:ring-red-500",
};
class ApiClient {
baseUrl: string = "https://api.example.com";
timeout: number = 5000;
retries: number = 3;
apiKey: string = "";
}Migrating from two separate plugins
Before:
{
"plugins": [
"prettier-plugin-tailwindcss",
"@huggingface/prettier-plugin-vertical-align"
],
"tailwindStylesheet": "./app/globals.css",
"alignInGroups": "always"
}After:
{
"plugins": ["prettier-plugin-tailwind-align"],
"tailwindStylesheet": "./app/globals.css",
"alignInGroups": "always"
}Then uninstall the old plugins:
npm uninstall prettier-plugin-tailwindcss @huggingface/prettier-plugin-vertical-alignThe
alignInGroupsoption name is kept identical to@huggingface/prettier-plugin-vertical-align's option, so existing configs work without any value changes.
Local development
git clone https://github.com/w3Scribe/prettier-plugin-tailwind-align.git
cd prettier-plugin-tailwind-align
bun install
bun run build # compile src/ → dist/
bun run dev # watch mode
bun run typecheck # type-check without emitting
bun run test # run the test suiteCompatibility
| | Version |
|---|---|
| prettier | ^3.0 |
| prettier-plugin-tailwindcss | ^0.7 |
| Node.js | >=18 |
License
MIT © Sudhir Gadpayle
