Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ npx vite-plugin-useclassy init

`init` patches Vite and your CSS engine, plus VS Code IntelliSense for Tailwind. Run it from the app root (the folder with `package.json` and `vite.config.*`).

| Option | Default | Notes |
| ---------------- | ------------ | ---------------------------------------------------------------------------------- |
| `--language` | `'vue'` | `'vue'` \| `'react'` \| `'blade'` \| `'svelte'` |
| `--engine` | auto-detect | `'tailwind'` \| `'unocss'`; Tailwind wins if both are installed |
| `--with-skills` | `false` | Agent skill, Cursor rules, and `AGENTS.md` |
| `--with-claude` | `false` | Also copy to `.claude/skills/` (requires `--with-skills`) |
| `--force` | `false` | Overwrite locally edited skill files |
| `--dry-run` | `false` | Print planned edits |
| Option | Default | Notes |
| --------------- | ----------- | --------------------------------------------------------------- |
| `--language` | `'vue'` | `'vue'` \| `'react'` \| `'blade'` \| `'svelte'` |
| `--engine` | auto-detect | `'tailwind'` \| `'unocss'`; Tailwind wins if both are installed |
| `--with-skills` | `false` | Agent skill, Cursor rules, and `AGENTS.md` |
| `--with-claude` | `false` | Also copy to `.claude/skills/` (requires `--with-skills`) |
| `--force` | `false` | Overwrite locally edited skill files |
| `--dry-run` | `false` | Print planned edits |

If detection fails, follow the [manual setup](#vite) below.

Expand All @@ -53,11 +53,19 @@ If detection fails, follow the [manual setup](#vite) below.
<button
className="px-4 py-2 rounded"
className:hover={isActive ? 'bg-blue-500 text-white' : 'bg-gray-200'}
className:@md="px-6"
className:group-hover/item="bg-red-500"
className:[&>*]="mt-2"
className:data-[state=open]="block"
/>
```

Expressions with no string literals (`className:hover={hoverClasses}`) are left alone. Import types with `import 'vite-plugin-useclassy/react'` (or `ClassyProps`). React 18/19 is an optional peer, only needed for those helpers.

Quoted modifier values may use `"` or `'`. Prefer `"` in docs and new code.

`className:@md`, `className:group-hover/item`, and arbitrary variants like `className:[&>*]` / `className:data-[state=open]` use the same attribute spelling as Vue. UseClassy rewrites them before the JSX/HTML parser runs (bracket-aware so `=` inside `[…]` stays part of the name). Put UseClassy before `@vitejs/plugin-react`. TypeScript and some linters may still flag the source the same way they already flag chained modifiers.

**Svelte.** Quoted modifiers transform; native directives do not. Put UseClassy before `@sveltejs/vite-plugin-svelte`.

```svelte
Expand All @@ -70,7 +78,7 @@ Expressions with no string literals (`className:hover={hoverClasses}`) are left

`class:sm:hover="underline"` emits `sm:hover:underline` only, the same composition as Tailwind / UnoCSS, not the individual `sm:` and `hover:` pieces.

Modifier names may include letters, digits, `_`, `-`, `:`, `/` (`group-hover/item`), and `@` (`@md`). Arbitrary variants (`[&>*]`, `data-[state=open]`) cannot be attribute names, so leave those on the base class. In React JSX, `/` is invalid in an attribute name, so named groups stay on `className`.
Modifier names may include letters, digits, `_`, `-`, `:`, `/` (`group-hover/item`), `@` (`@md`), and arbitrary variants with `[…]` (`[&>*]`, `data-[state=open]`). UseClassy parses modifier names with bracket depth so an `=` inside `[…]` is not treated as the attribute separator. React uses the same modifier attributes as Vue; UseClassy rewrites them before JSX/HTML parse.

## Vite

Expand Down Expand Up @@ -144,7 +152,7 @@ UseClassy is variant-first (`class:hover="bg-red"`), not [Attributify](https://u

```json
{
"tailwindCSS.classAttributes": ["class", "class:[\\w:/@-]*", "className", "className:[\\w:/@-]*"]
"tailwindCSS.classAttributes": ["class", "class:[\\w:/@\\[\\]\\-=&*>.]*", "className", "className:[\\w:/@\\[\\]\\-=&*>.]*"]
}
```

Expand Down
22 changes: 20 additions & 2 deletions demos/react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ function App() {
</h1>
<p className="max-w-xl text-sm text-zinc-400">
Smoke coverage for quoted modifiers, conditionals, comparison
operands, nested braces, and chained modifiers. Chained attributes
match Tailwind variant composition (`sm:hover:underline` only).
operands, nested braces, chained modifiers,{" "}
<code className="text-zinc-200">@md</code>, and named groups.
</p>
</header>

Expand Down Expand Up @@ -184,6 +184,24 @@ function App() {
</p>
</Case>

<Case
title="@md, named groups, arbitrary variants"
detail="className:@md, className:group-hover/item, className:[&>*], className:data-[state=open] — bracket-aware rewrite before JSX parse."
>
<div className="group/item @container rounded-lg border border-zinc-700 bg-zinc-900 p-4">
<div
className="rounded px-4 py-3 text-zinc-100 transition"
data-state={isActive ? "open" : "closed"}
className:@md="p-6 text-base"
className:group-hover/item="bg-red-500 text-white"
className:[&>*]="mt-2"
className:data-[state=open]="ring-2 ring-emerald-400"
>
<span>Hover · @md · toggle isActive for data-state</span>
</div>
</div>
</Case>

{/* Nested modifiers */}
<Case
title="Nested modifiers"
Expand Down
Loading
Loading