cvx
The cvx function resolves values from a structured variant configuration. It can compose class-name strings, merge native style objects, or return arrays of strings. Variant selections can use defaultVariants and be overridden by user input.
Syntax
function cvx<T extends cvxKeys>(keys: cvxRecord<T>): (variants?: cvxResult<T>) => cvxReturn<T>;cvx({
assign: "", // optional
variants: {}, // required
defaultVariants: {} // optional
});How cvx works
-
Merge selections:
Values passed throughvariantsare merged withdefaultVariants, when present. -
Resolve configured values:
For each variant key,cvxfinds the value configured for the merged selection. Variant keys such astrue,false, numeric strings,null, andundefinedcontinue to accept their corresponding primitive inputs. -
Return native values:
When every resolved value is a string, they are combined into one space-separated string andassignis prepended. When every value is an object, they are merged into one object. When every value is a string array, they are flattened into astring[]. A configuration that mixes these value kinds returns their inferred union; at runtime,cvxreturns the first resolved value for a mixed selection.
Example Usage
import { cvx, type cvxVariant, type cvxVariants } from "xuxi";
const classes = cvx({
// assign values that is definitely returned
assign: "bg-muted rounded-sm px-2 border flex items-center justify-center",
variants: {
variant: {
light: "font-light",
bold: "font-bold",
semibold: "font-semibold"
},
effect: {
italic: "font-italic"
},
color: {
blue: "text-blue-600",
green: "text-green-700",
red: "text-red-500",
purple: "text-purple-500"
},
size: {
sm: "h-4",
md: "h-6",
lg: "h-10",
xl: "h-14"
}
},
// determine the variance value by default
defaultVariants: {
variant: "bold",
color: "blue",
size: "lg"
}
});type VariantsType = cvxVariants<typeof classes>;
// Output:
type VariantsType = {
variant?: "bold" | "light" | "semibold" | undefined;
effect?: "italic" | undefined;
color?: "blue" | "green" | "red" | "purple" | undefined;
size?: "sm" | "md" | "lg" | "xl" | undefined;
};type Color = cvxVariant<typeof classes, "color">;
// Output:
type Color = "blue" | "green" | "red" | "purple";Dynamic values
Variant values may be strings, objects, or arrays of strings. TypeScript infers the return type from every configured value.
const stylesFont = cvx({
variants: {
size: {
small: { fontSize: 14, lineHeight: 20, fontWeight: 500 },
smallBold: { fontSize: 14, lineHeight: 20, fontWeight: 700 },
default: { fontSize: 16, lineHeight: 24, fontWeight: 500 }
}
},
defaultVariants: { size: "default" }
});
stylesFont();
// { fontSize: number, lineHeight: number, fontWeight: number }
const buttonType = cvx({
variants: {
type: {
button: "type=button",
submit: "type=submit",
reset: "type=reset"
}
},
defaultVariants: {
type: "button"
}
});
type ButtonTypeVariant = cvxVariants<typeof buttonType>;
// result
type ButtonTypeVariant = {
type?: "button" | "submit" | "reset" | undefined;
};
const fontSizeVariant = cvx({
variants: {
size: {
small: { fontSize: 14, lineHeight: 20, fontWeight: 500 },
smallBold: { fontSize: 14, lineHeight: 20, fontWeight: 700 },
default: { fontSize: 16, lineHeight: 24, fontWeight: 500 }
}
}
});
type fontSizeVariant = cvxVariants<typeof fontSizeVariant>;
type SizeVariant = cvxVariant<typeof fontSizeVariant, "size">;
type SizeVariant = "small" | "smallBold" | "default";
const dinamycVariant = cvx({
variants: {
size: {
var: ["200"],
small: "small",
practice: "practice",
default: [1, 7, 9]
},
fontSize: {
all: ["300", "400", "500", "600", "700"],
thin: "300",
normal: "400",
medium: "600",
semibold: "700"
}
},
defaultVariants: {
size: "small",
fontSize: "medium"
}
});
console.log(dinamycVariant());
// 'small 600'
console.log(dinamycVariant({ fontSize: "all", size: "small" }));
// 'small'
console.log(dinamycVariant({ fontSize: "all", size: "var" }));
// [ '200', '300', '400', '500', '600', '700' ] (array value)When object shapes differ, the return type preserves that difference:
const stylesNative = cvx({
variants: {
variant: {
"text-small": { fontSize: 14, lineHeight: 20, fontWeight: 500 },
"with-transform": { fontSize: 14, lineHeight: 20, fontWeight: 700, transform: "", transformOrigin: "" }
}
}
});
type NativeStyle = ReturnType<typeof stylesNative>;
// { fontSize: number; lineHeight: number; fontWeight: number }
// | { fontSize: number; lineHeight: number; fontWeight: number; transform: string; transformOrigin: string }Mixed configured values produce a union. Array entries must all be strings.
const variantDinamis = cvx({
variants: {
size: {
small: "small",
smallBold: { fontSize: 14, lineHeight: 20, fontWeight: 700 },
default: ["1", "2", "3"]
}
}
});
type DynamicValue = ReturnType<typeof variantDinamis>;
// string | { fontSize: number; lineHeight: number; fontWeight: number } | string[]Cvx Types
- Example
const xx = cvx({
assign: "CLASS",
variants: {
state: {
true: "IS_TRUE",
false: "IS_FALSE",
Infinity: "IS_INFINITY",
NaN: "IS_NAN",
null: "IS_NULL",
undefined: "IS_UNDEFINED"
},
default: {
true: "DEFAULT_TRUE",
false: "DEFAULT_FALSE"
}
}
});- Validated Result:
console.log(xx({ state: true })); // ✅ (valid)
console.log(xx({ state: false })); // ✅ (valid)
console.log(xx({ state: Infinity })); // ✅ (valid)
console.log(xx({ state: null })); // ✅ (valid)
console.log(xx({ state: NaN })); // ✅ (valid)
console.log(xx({ state: undefined })); // ✅ (valid)
console.log(xx({ state: "random" as any })); // ❌ (not found!)- Validation Types
type xxVariantsKeys = keyof x.cvxVariants<typeof xx>;
// type xxVariantsKeys = "default" | "state";
type xxVariants = x.cvxVariants<typeof xx>;
// type xxVariants = {
// default?: boolean | undefined;
// state?: number | boolean | null | undefined;
// };
type stateVariants = cvxVariants<typeof xx>["state"];
// type stateVariants = number | boolean | null | undefinedor with ocx()
const getVariants = x.ocx({
root: rootVariants,
description: decsVariants,
action: actVariants,
dismiss: dissVariants
});
type getVariantsKeys = keyof x.cvxResult<typeof getVariants>;
// type getVariantsKeys = "root" | "description" | "dismiss" | "action";Advantages
- Flexibility:
Supports class names, native objects, and string arrays. - Consistency:
Keeps variant selection and inferred return values in one clearly defined structure. - Efficiency:
Minimizes duplication of class logic in code.
IntelliSense
If you are using the vscode editor, enable autocomplete for the tailwindcss class using the following command:
- Install the
Tailwind CSS IntelliSenseVisual Studio Code extension - Add to your
settings.json:
"tailwindCSS.experimental.classRegex": [
["cvx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cvx\\(([^)]*)\\)", "(?:'|\"|`)([^'\"`]*)(?:'|\"|`)"],
["assign:\\s*['\"`]([^'\"`]*?)['\"`]", "(?:'|\"|`)([^'\"`\\]]*|\\[[^\\]]+\\])(?:'|\"|`)"],
["assign:\\s*['\"`]([^'\"`]*?)['\"`]", "(?:^|\\s+)([\\w-:\\[\\].()#\\/%]+)(?=\\s+|$)"],
["variants:\\s*\\{([^}]*?)\\}", "(?:'|\"|`)([^'\"`\\]]*|\\[[^\\]]+\\])(?:'|\"|`)"],
["variants:\\s*\\{[^}]*?['\"`\\w]+:\\s*['\"`]([^'\"`]*)['\"`]", "(?:^|\\s+)([\\w-:\\[\\].()#\\/%]+)(?=\\s+|$)"],
],cva uses the first argument as a constant that will be distributed throughout the variance, in cvx this argument is moved to the assign parameter. cvx does not or has not passed the class and className parameters.