Style v2 with tailwind-variants/lite
The numbered v2 token scales (text-1…9, rounded-1…6, shadow-1…6) share a prefix with the color/utility namespaces, and tailwind-merge (which tailwind-variants runs by default) cannot tell a numeric text-3 from a color step, so it silently dropped the size whenever a text-<color> was present. Switch the v2 kit to the merge-free tailwind-variants/lite entrypoint (added in v3): upgrade tailwind-variants to ^3.2.2, import tv/VariantProps from tailwind-variants/lite in the typography primitives, and drop the per-call twMerge:false. The legacy v1 kit stays on tailwind-variants (merge intact). Document the rationale in ui.md and add a packages/ui/src/v2 ESLint guard that forbids tailwind-variants, tailwind-merge, and clsx. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -20,7 +20,7 @@ These rules are the **source of truth**. The legacy tree (`Atoms/`, `Molecules/`
|
|||||||
|------|------------|
|
|------|------------|
|
||||||
| Package | **`@probo/ui`** — v2 components under `src/v2`. Apps opt into v2 by importing the v2 theme (see [`v2-tokens.md`](v2-tokens.md)). |
|
| Package | **`@probo/ui`** — v2 components under `src/v2`. Apps opt into v2 by importing the v2 theme (see [`v2-tokens.md`](v2-tokens.md)). |
|
||||||
| Styling | **Tailwind v4** with the Radix-scale tokens (`bg-sand-3`, `text-sand-12`, `rounded-3`, `text-4`, …). |
|
| Styling | **Tailwind v4** with the Radix-scale tokens (`bg-sand-3`, `text-sand-12`, `rounded-3`, `text-4`, …). |
|
||||||
| Variants API | **`tailwind-variants`** only — `import { tv } from "tailwind-variants"`. |
|
| Variants API | **`tailwind-variants/lite`** only — `import { tv } from "tailwind-variants/lite"`. The `/lite` entrypoint ships **without `tailwind-merge`**, which is required: the numbered scales (`text-1…9`, `rounded-1…6`, `shadow-1…6`) collide with the color/utility namespaces and tailwind-merge would silently drop the scale class (e.g. `text-3` next to `text-sand-11`). The legacy v1 kit stays on `tailwind-variants` (with merge). |
|
||||||
| Class composition | **Do not use `clsx` or `tailwind-merge`.** All conditional styling goes through `tv` variants and slots. |
|
| Class composition | **Do not use `clsx` or `tailwind-merge`.** All conditional styling goes through `tv` variants and slots. |
|
||||||
| Headless primitives | **Base UI** (`@base-ui/react`). We **style** these primitives; we do not re-implement their behavior. |
|
| Headless primitives | **Base UI** (`@base-ui/react`). We **style** these primitives; we do not re-implement their behavior. |
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export function Dialog({ trigger, ref, children }: Props) {
|
|||||||
```tsx
|
```tsx
|
||||||
// Good — thin styling over Base UI; consumers use the lib's open/onOpenChange directly
|
// Good — thin styling over Base UI; consumers use the lib's open/onOpenChange directly
|
||||||
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
|
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
|
||||||
import { tv } from "tailwind-variants";
|
import { tv } from "tailwind-variants/lite";
|
||||||
|
|
||||||
const dialog = tv({
|
const dialog = tv({
|
||||||
slots: {
|
slots: {
|
||||||
@@ -111,7 +111,7 @@ For **compound / multi-slot** components, define `tv` in a **dedicated `variants
|
|||||||
```tsx
|
```tsx
|
||||||
// Bad — same file mixes tv() output with ad-hoc Tailwind / clsx on className
|
// Bad — same file mixes tv() output with ad-hoc Tailwind / clsx on className
|
||||||
import { clsx } from "clsx";
|
import { clsx } from "clsx";
|
||||||
import { tv } from "tailwind-variants";
|
import { tv } from "tailwind-variants/lite";
|
||||||
|
|
||||||
const row = tv({ base: "flex items-center gap-2" });
|
const row = tv({ base: "flex items-center gap-2" });
|
||||||
export function Row({ children }: { children: ReactNode }) {
|
export function Row({ children }: { children: ReactNode }) {
|
||||||
@@ -121,7 +121,7 @@ export function Row({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// Good — layout and look live in tv
|
// Good — layout and look live in tv
|
||||||
import { tv } from "tailwind-variants";
|
import { tv } from "tailwind-variants/lite";
|
||||||
|
|
||||||
const row = tv({
|
const row = tv({
|
||||||
base: "flex items-center gap-2 rounded-3 border border-sand-6",
|
base: "flex items-center gap-2 rounded-3 border border-sand-6",
|
||||||
@@ -133,7 +133,7 @@ export function Row({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// Good — optional styling toggles use tv variants, not extra className strings
|
// Good — optional styling toggles use tv variants, not extra className strings
|
||||||
import { tv } from "tailwind-variants";
|
import { tv } from "tailwind-variants/lite";
|
||||||
|
|
||||||
const row = tv({
|
const row = tv({
|
||||||
base: "flex items-center gap-2",
|
base: "flex items-center gap-2",
|
||||||
@@ -398,7 +398,7 @@ Model regions with `tv` `slots` named after the layout:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// ImageCard/variants.ts
|
// ImageCard/variants.ts
|
||||||
import { tv } from "tailwind-variants";
|
import { tv } from "tailwind-variants/lite";
|
||||||
|
|
||||||
export const imageCard = tv({
|
export const imageCard = tv({
|
||||||
slots: {
|
slots: {
|
||||||
|
|||||||
@@ -68,6 +68,34 @@ export default defineConfig([
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// The v2 kit styles with tailwind-variants/lite (no tailwind-merge): the
|
||||||
|
// numbered scales (text-1…9, rounded-1…6, shadow-1…6) collide with the
|
||||||
|
// color/utility namespaces and tailwind-merge would drop the scale class.
|
||||||
|
files: ["packages/ui/src/v2/**"],
|
||||||
|
rules: {
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
name: "tailwind-variants",
|
||||||
|
message:
|
||||||
|
"Import from tailwind-variants/lite (no tailwind-merge) in v2.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tailwind-merge",
|
||||||
|
message: "The v2 kit does not use tailwind-merge.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clsx",
|
||||||
|
message: "The v2 kit does not use clsx; style via tailwind-variants/lite.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
files: reactDirs,
|
files: reactDirs,
|
||||||
ignores: ["packages/ui/tailwind.config.js"],
|
ignores: ["packages/ui/tailwind.config.js"],
|
||||||
|
|||||||
27
package-lock.json
generated
27
package-lock.json
generated
@@ -18989,29 +18989,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwind-variants": {
|
"node_modules/tailwind-variants": {
|
||||||
"version": "1.0.0",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz",
|
||||||
"integrity": "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==",
|
"integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"tailwind-merge": "3.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.x",
|
"node": ">=16.x",
|
||||||
"pnpm": ">=7.x"
|
"pnpm": ">=7.x"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"tailwind-merge": ">=3.0.0",
|
||||||
"tailwindcss": "*"
|
"tailwindcss": "*"
|
||||||
}
|
},
|
||||||
},
|
"peerDependenciesMeta": {
|
||||||
"node_modules/tailwind-variants/node_modules/tailwind-merge": {
|
"tailwind-merge": {
|
||||||
"version": "3.0.2",
|
"optional": true
|
||||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
|
}
|
||||||
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/dcastil"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
@@ -21497,7 +21490,7 @@
|
|||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwind-variants": "^1.0.0",
|
"tailwind-variants": "^3.2.2",
|
||||||
"tailwindcss": "^4.3.1",
|
"tailwindcss": "^4.3.1",
|
||||||
"tw-animate-css": "^1.3.0",
|
"tw-animate-css": "^1.3.0",
|
||||||
"zustand": "^5.0.4"
|
"zustand": "^5.0.4"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwind-variants": "^1.0.0",
|
"tailwind-variants": "^3.2.2",
|
||||||
"tailwindcss": "^4.3.1",
|
"tailwindcss": "^4.3.1",
|
||||||
"tw-animate-css": "^1.3.0",
|
"tw-animate-css": "^1.3.0",
|
||||||
"zustand": "^5.0.4"
|
"zustand": "^5.0.4"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
import type { VariantProps } from "tailwind-variants";
|
import type { VariantProps } from "tailwind-variants/lite";
|
||||||
|
|
||||||
import { text } from "./variants";
|
import { text } from "./variants";
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
import type { VariantProps } from "tailwind-variants";
|
import type { VariantProps } from "tailwind-variants/lite";
|
||||||
|
|
||||||
import { textSkeleton } from "./variants";
|
import { textSkeleton } from "./variants";
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import { tv } from "tailwind-variants";
|
import { tv } from "tailwind-variants/lite";
|
||||||
|
|
||||||
// Numbered type scale (text-1 … text-9). Each utility carries its paired
|
// Numbered type scale (text-1 … text-9). Each utility carries its paired
|
||||||
// font-size, line-height, and letter-spacing from the v2 theme. Shared between
|
// font-size, line-height, and letter-spacing from the v2 theme. Shared between
|
||||||
|
|||||||
Reference in New Issue
Block a user