@use-pico/cls
Version:
Type-safe, composable styling system for React, Vue, Svelte, and vanilla JS
1,278 lines (1,275 loc) • 55.5 kB
TypeScript
import * as tailwind_merge from 'tailwind-merge';
import { ClassNameValue } from 'tailwind-merge';
import * as react_jsx_runtime from 'react/jsx-runtime';
import { ReactNode } from 'react';
type ClassName = ClassNameValue;
type SlotContract = readonly string[];
type TokenContract = readonly string[];
type VariantContract = Record<string, readonly string[]>;
type ListOf<T> = [
T,
...T[]
];
type StringToBool<T extends string> = T extends "bool" ? boolean : T;
type MergeRecords<A extends Record<string, readonly any[]>, B extends Record<string, readonly any[]>> = {
[K in keyof A | keyof B]: K extends keyof B ? K extends keyof A ? [
...A[K],
...B[K]
] : B[K] : K extends keyof A ? A[K] : never;
};
type HasBaseInUseChain<Sub, Base> = Sub extends Base ? true : Sub extends {
"~use"?: infer U;
} ? HasBaseInUseChain<U, Base> : false;
type Contract<TTokenContract extends TokenContract, TSlotContract extends SlotContract, TVariantContract extends VariantContract, TUse extends Contract<any, any, any> | unknown = unknown> = {
tokens: TTokenContract;
slot: TSlotContract;
variant: TVariantContract;
"~use"?: TUse;
"~definition"?: Definition<any>;
};
type ContractEx<TTokenContract extends TokenContract, TSlotContract extends SlotContract, TVariantContract extends VariantContract, TBaseContract extends Contract<any, any, any>> = {
tokens: TTokenContract;
slot: TSlotContract;
variant: TVariantContract;
"~use"?: TBaseContract;
"~definition"?: Definition<any>;
};
type TokensOf<T extends Contract<any, any, any>> = T extends {
"~use"?: infer U;
} ? U extends Contract<any, any, any> ? T["tokens"][number] | TokensOf<U> : T["tokens"][number] : T["tokens"][number];
type TokensOfList<T extends Contract<any, any, any>> = ListOf<TokensOf<T>>;
type TokenDefinitionRequired<T extends Contract<any, any, any>> = {
[K in T["tokens"][number]]: What<T>;
};
type TokenDefinitionOptional<T extends Contract<any, any, any>> = Partial<Record<TokensOf<T>, What<T>>>;
type SlotsOf<T extends Contract<any, any, any>> = T extends {
"~use"?: infer U;
} ? U extends Contract<any, any, any> ? T["slot"][number] | SlotsOf<U> : T["slot"][number] : T["slot"][number];
type SlotMapping<T extends Contract<any, any, any>> = {
[K in SlotsOf<T>]?: What<T>;
};
type WhatConfigFn<T extends Contract<any, any, any>> = (props: WhatUtil<T>) => Partial<CreateConfig<T>>;
type ClsSlotFn<T extends Contract<any, any, any>> = (config?: WhatConfigFn<T>) => string;
type ClsSlots<T extends Contract<any, any, any>> = {
[K in SlotsOf<T>]: ClsSlotFn<T>;
};
type Variants<T extends Contract<any, any, any>> = T extends {
variant: infer V extends VariantContract;
"~use"?: infer U;
} ? U extends Contract<any, any, any> ? MergeRecords<Variants<U>, V> : V : {};
type VariantValueMapping<T extends Contract<any, any, any>> = {
[K in keyof Variants<T>]: StringToBool<Variants<T>[K][number]>;
};
type WhatClass = {
class: ClassName;
};
type WhatToken<T extends Contract<any, any, any>> = {
token: TokensOfList<T>;
};
type What<T extends Contract<any, any, any>> = WhatClass | WhatToken<T>;
interface RuleDefinition<T extends Contract<any, any, any>> {
override?: boolean;
match?: Partial<VariantValueMapping<T>>;
slot: SlotMapping<T>;
}
type MatchFn<TContract extends Contract<any, any, any>> = (match: RuleDefinition<TContract>["match"] | undefined, slot: SlotMapping<TContract>, override?: boolean) => RuleDefinition<TContract>;
type MatchSlotFn<TContract extends Contract<any, any, any>> = (slot: SlotMapping<TContract>, override?: boolean) => RuleDefinition<TContract>;
/**
* Core utility interface that provides type-safe styling helpers for CLS definitions.
*
* This interface is passed to definition functions and provides three main categories
* of utilities: `what` for creating styling values, `override` for destructive styling,
* and `def` for accumulative styling. Each category serves a specific purpose in
* the styling system.
*
* @template T - The contract type that defines the structure (tokens, slots, variants)
*
* @example
* ```typescript
* // Used in definition functions
* const ButtonCls = cls(contract, ({ what, override, def }) => ({
* token: def.token({
* "color.bg.primary": what.css(["bg-blue-600"])
* }),
* rules: [
* def.root({
* root: what.both(["px-4", "py-2"], ["color.bg.primary"])
* })
* ]
* }));
* ```
*/
interface WhatUtil<T extends Contract<any, any, any>> {
/**
* Styling value creation utilities for building type-safe styling configurations.
*
* The `what` object provides helpers for creating CSS classes, token references,
* variant values, and slot configurations. These utilities ensure type safety
* and proper structure for all styling values.
*/
what: {
/**
* Creates a pure CSS class styling value.
*
* Use this when you want to apply CSS classes directly without any token references.
* This is ideal for layout utilities, spacing, and other CSS-only styling.
*
* @param classes - CSS class names to apply
* @returns A WhatClass object containing the CSS classes
*
* @example
* ```typescript
* // Pure CSS classes for layout
* root: what.css(["inline-flex", "items-center", "rounded-md"])
*
* // Pure CSS classes for spacing
* root: what.css(["px-4", "py-2", "gap-2"])
*
* // Pure CSS classes for utilities
* root: what.css(["font-medium", "text-sm", "shadow-sm"])
* ```
*/
css(classes: ClassName): WhatClass;
/**
* Creates a token reference styling value.
*
* Use this when you want to reference design tokens defined in your contract.
* Tokens are resolved at runtime and can reference other tokens, creating
* a flexible design system.
*
* @param tokens - Array of token names to reference
* @returns A WhatToken object containing the token references
*
* @example
* ```typescript
* // Reference single token
* root: what.token(["color.bg.primary"])
*
* // Reference multiple tokens
* root: what.token(["color.bg.primary", "color.text.primary"])
*
* // Reference spacing tokens
* root: what.token(["spacing.padding.md", "spacing.radius.sm"])
* ```
*/
token(tokens: TokensOfList<T>): WhatToken<T>;
/**
* Creates a mixed styling value with both CSS classes and token references.
*
* Use this when you need both CSS classes (for layout/utilities) and design tokens
* (for theming) in the same styling configuration. This is the most common pattern.
*
* @param classes - CSS class names for layout and utilities
* @param tokens - Array of token names for theming
* @returns A What object containing both CSS classes and token references
*
* @example
* ```typescript
* // Mixed styling: layout + theming
* root: what.both(
* ["inline-flex", "items-center", "rounded-md"], // Layout
* ["color.bg.primary", "color.text.primary"] // Theming
* )
*
* // Mixed styling: utilities + design tokens
* root: what.both(
* ["font-medium", "text-sm", "shadow-sm"], // Utilities
* ["spacing.padding.md", "color.bg.default"] // Design tokens
* )
* ```
*/
both(classes: ClassName, tokens: TokensOfList<T>): What<T>;
/**
* Creates type-safe variant values for conditional styling.
*
* Use this to specify variant combinations that will be used in rule matching.
* The function ensures only valid variant values are used, providing compile-time
* type safety.
*
* @param variant - Partial variant mapping with type-safe values
* @returns A partial variant mapping for use in rules
*
* @example
* ```typescript
* // Single variant
* what.variant({ size: "lg" })
*
* // Multiple variants
* what.variant({ size: "lg", variant: "primary" })
*
* // Boolean variants
* what.variant({ disabled: true, loading: false })
*
* // Used in rules
* def.rule(
* what.variant({ size: "lg", variant: "primary" }),
* { root: what.css(["px-6", "py-3"]) }
* )
* ```
*/
variant(variant: Partial<VariantValueMapping<T>>): Partial<VariantValueMapping<T>>;
/**
* Creates slot-specific styling configurations.
*
* Use this to create slot mappings that can be applied to specific component parts.
* This is typically used in the `slot` property of configuration objects.
*
* @param slot - Slot mapping with styling for each slot
* @returns A slot mapping for use in configurations
*
* @example
* ```typescript
* // Slot configuration in create() method
* ButtonCls.create(({ what }) => ({
* slot: what.slot({
* root: what.css(["px-4", "py-2"]),
* icon: what.css(["mr-2", "w-4", "h-4"]),
* label: what.token(["color.text.primary"])
* })
* }))
* ```
*/
slot(slot: SlotMapping<T>): SlotMapping<T>;
};
/**
* Destructive styling utilities that replace previous styles.
*
* The `override` object provides helpers for creating styling rules that completely
* replace previous styles rather than adding to them. This is useful when you need
* to ensure clean styling without accumulation.
*/
override: {
/**
* Creates a root rule that replaces all previous styles for the specified slot.
*
* Use this when you want to completely replace the base styling for a slot.
* This is destructive - it will clear any previous styles and apply only
* the new ones.
*
* @param slot - Slot mapping with styling that will replace previous styles
* @param override - Optional override flag (defaults to true for override mode)
* @returns A rule definition for destructive styling
*
* @example
* ```typescript
* // Replace all root styles
* override.root({
* root: what.css(["px-6", "py-3", "bg-red-500"])
* })
*
* // Replace multiple slot styles
* override.root({
* root: what.css(["px-4", "py-2"]),
* icon: what.css(["w-5", "h-5"]),
* label: what.token(["color.text.error"])
* })
* ```
*/
root: MatchSlotFn<T>;
/**
* Creates a conditional rule that replaces styles when variants match.
*
* Use this when you want to completely replace styles for specific variant
* combinations. This is destructive - it will clear any previous styles
* and apply only the new ones when the condition is met.
*
* @param match - Variant condition that triggers the rule
* @param slot - Slot mapping with styling that will replace previous styles
* @param override - Optional override flag (defaults to true for override mode)
* @returns A rule definition for conditional destructive styling
*
* @example
* ```typescript
* // Replace styles for primary variant
* override.rule(
* what.variant({ variant: "primary" }),
* {
* root: what.token(["color.bg.primary", "color.text.primary"])
* }
* )
*
* // Replace styles for large primary variant
* override.rule(
* what.variant({ size: "lg", variant: "primary" }),
* {
* root: what.both(
* ["px-8", "py-4"],
* ["color.bg.primary", "color.text.primary"]
* )
* }
* )
* ```
*/
rule: MatchFn<T>;
/**
* Creates token overrides that replace existing token definitions.
*
* Use this when you want to completely replace token definitions rather than
* merge with them. This is destructive - it will clear any previous token
* definitions and apply only the new ones.
*
* @param token - Partial token definitions that will replace existing ones
* @returns Partial token definitions for destructive token styling
*
* @example
* ```typescript
* // Replace token definitions
* override.token({
* "color.bg.primary": what.css(["bg-indigo-600"]),
* "color.text.primary": what.css(["text-indigo-50"])
* })
* ```
*/
token(token: Partial<TokenDefinitionOptional<T>>): Partial<TokenDefinitionOptional<T>>;
};
/**
* Accumulative styling utilities that add to previous styles.
*
* The `def` object provides helpers for creating styling rules that add to
* previous styles rather than replacing them. This is the default behavior
* and is useful for building up styles incrementally.
*/
def: {
/**
* Creates a root rule that adds to the base styling for the specified slot.
*
* Use this when you want to add styling to the base configuration for a slot.
* This is accumulative - it will add to any previous styles rather than
* replacing them.
*
* @param slot - Slot mapping with styling that will be added to previous styles
* @param override - Optional override flag (defaults to false for append mode)
* @returns A rule definition for accumulative styling
*
* @example
* ```typescript
* // Add to base root styles
* def.root({
* root: what.both(
* ["inline-flex", "items-center", "rounded-md"],
* ["color.text.default", "color.bg.default"]
* )
* })
*
* // Add to multiple slots
* def.root({
* root: what.css(["px-4", "py-2"]),
* icon: what.css(["mr-2", "w-4", "h-4"]),
* label: what.css(["font-medium"])
* })
* ```
*/
root: MatchSlotFn<T>;
/**
* Creates a conditional rule that adds styles when variants match.
*
* Use this when you want to add styling for specific variant combinations.
* This is accumulative - it will add to any previous styles rather than
* replacing them when the condition is met.
*
* @param match - Variant condition that triggers the rule
* @param slot - Slot mapping with styling that will be added to previous styles
* @param override - Optional override flag (defaults to false for append mode)
* @returns A rule definition for conditional accumulative styling
*
* @example
* ```typescript
* // Add styles for large size
* def.rule(
* what.variant({ size: "lg" }),
* {
* root: what.css(["px-6", "py-3"])
* }
* )
*
* // Add styles for primary variant
* def.rule(
* what.variant({ variant: "primary" }),
* {
* root: what.token(["color.bg.primary", "color.text.primary"])
* }
* )
* ```
*/
rule: MatchFn<T>;
/**
* Creates token definitions that define the design system values.
*
* Use this to define the actual CSS values for your design tokens.
* These token definitions are used throughout the styling system
* and can be referenced by other tokens.
*
* @param token - Required token definitions for all declared tokens
* @returns Required token definitions for the design system
*
* @example
* ```typescript
* // Define all required tokens
* def.token({
* "color.text.default": what.css(["text-gray-900"]),
* "color.text.primary": what.css(["text-white"]),
* "color.bg.default": what.css(["bg-gray-100"]),
* "color.bg.primary": what.css(["bg-blue-600"]),
* "spacing.padding.md": what.css(["px-4", "py-2"])
* })
* ```
*/
token(token: TokenDefinitionRequired<T>): TokenDefinitionRequired<T>;
/**
* Creates default variant values for the component.
*
* Use this to specify the default values for each variant when no
* specific variant is provided. These defaults are used as fallbacks
* and can be overridden at runtime.
*
* @param defaults - Default values for each variant
* @returns Default variant mapping for the component
*
* @example
* ```typescript
* // Set default variants
* def.defaults({
* size: "md",
* variant: "default",
* disabled: false,
* loading: false
* })
* ```
*/
defaults(defaults: VariantValueMapping<T>): VariantValueMapping<T>;
};
}
/**
* Extended styling definition for CLS extension operations.
*
* This type is identical to `Definition` and is used specifically when extending
* existing CLS instances via the `extend()` method. It represents the complete
* styling definition that provides concrete values for an extended contract,
* ensuring that all declared tokens, rules, and defaults are properly defined.
*
* The key difference from `Definition` is the context in which it's used - it's
* returned by extension definition functions and works with `WhatUtil` to
* provide type safety for extended components.
*
* @template T - The contract type that defines the structure (tokens, slots, variants)
*
* @example
* ```typescript
* // Used in extend() operations with WhatUtil
* const PrimaryButtonCls = ButtonCls.extend(
* {
* tokens: ["color.bg.primary", "color.text.primary"],
* slot: ["root"],
* variant: {}
* },
* ({ what, def }) => ({ // Returns DefinitionEx with WhatUtil
* token: def.token({
* "color.bg.primary": what.css(["bg-blue-600"]),
* "color.text.primary": what.css(["text-white"])
* }),
* rules: [
* def.root({
* root: what.token(["color.bg.primary", "color.text.primary"])
* })
* ],
* defaults: {}
* })
* );
* ```
*/
type DefinitionEx<T extends Contract<any, any, any>> = {
/**
* Required token definitions for all declared tokens in the extended contract.
*
* This object contains all the design token definitions for the extended component.
* Unlike the regular `Definition` type, this enforces that all tokens declared
* in the extended contract must be defined, providing stronger type safety
* for extension operations.
*
* Each token name declared in the extended contract must have a corresponding
* definition that specifies the actual CSS classes or token references.
* These tokens can reference tokens from the parent contract and support
* recursive resolution.
*
* @example
* ```typescript
* // In extend() operation - all declared tokens must be defined
* token: def.token({
* // New tokens declared in extended contract
* "color.bg.primary": what.css(["bg-blue-600"]),
* "color.text.primary": what.css(["text-white"]),
*
* // Can reference parent tokens
* "button.primary": what.token([
* "color.bg.primary", // New token
* "color.text.primary", // New token
* "spacing.padding.md" // Parent token
* ])
* })
* ```
*/
token: TokenDefinitionRequired<T>;
/**
* Array of styling rules that define conditional styling for the extended component.
*
* Rules are processed in order and can either add to or replace previous styles
* from the parent component. Each rule specifies a condition (variant combination)
* and the styling to apply when that condition is met. Rules support both
* accumulative (def) and destructive (override) styling modes.
*
* Extended components can add new rules, override existing rules, or extend
* the styling behavior of the parent component.
*
* @example
* ```typescript
* rules: [
* // Override parent's root styles
* override.root({
* root: what.token(["color.bg.primary", "color.text.primary"])
* }),
*
* // Add new variant rules
* def.rule(
* what.variant({ theme: "dark" }),
* {
* root: what.token(["color.bg.dark", "color.text.dark"])
* }
* ),
*
* // Extend existing variant behavior
* def.rule(
* what.variant({ size: "lg" }),
* {
* root: what.css(["shadow-lg", "font-bold"])
* }
* )
* ]
* ```
*/
rules: RuleDefinition<T>[];
/**
* Default values for variants in the extended component.
*
* This object specifies the fallback values for each variant defined in the
* extended contract. These defaults can override parent defaults and are used
* when creating styled instances without explicit variant overrides.
* Extended components can provide new defaults or override inherited defaults.
*
* @example
* ```typescript
* defaults: def.defaults({
* // Override parent defaults
* variant: "primary", // Instead of parent's "default"
*
* // Keep parent defaults
* size: "md", // Same as parent
*
* // Add new variant defaults
* theme: "light",
* loading: false
* })
* ```
*
* @example
* ```typescript
* // Usage with extended defaults
* const slots = PrimaryButtonCls.create();
* // Uses extended defaults: variant="primary", size="md", theme="light"
*
* // Override extended defaults at runtime
* const slots = PrimaryButtonCls.create(({ what }) => ({
* variant: what.variant({ variant: "secondary", theme: "dark" })
* }));
* ```
*/
defaults: VariantValueMapping<T>;
};
/**
* Complete styling definition that provides concrete values for a CLS contract.
*
* This type represents the implementation of a CLS contract, containing all the
* styling values, rules, and defaults that determine how a component is actually
* styled. It's returned by definition functions and used internally by CLS instances
* to generate styled components.
*
* @template T - The contract type that defines the structure (tokens, slots, variants)
*
* @example
* ```typescript
* // Definition function returns this type
* const ButtonCls = cls(contract, ({ what, def }) => ({
* token: def.token({
* "color.bg.primary": what.css(["bg-blue-600"]),
* "color.text.primary": what.css(["text-white"])
* }),
* rules: [
* def.root({
* root: what.both(["px-4", "py-2"], ["color.bg.primary", "color.text.primary"])
* }),
* def.rule(
* what.variant({ size: "lg" }),
* { root: what.css(["px-6", "py-3"]) }
* )
* ],
* defaults: def.defaults({
* size: "md",
* variant: "default"
* })
* }));
* ```
*/
type Definition<T extends Contract<any, any, any>> = {
/**
* Token definitions that map token names to their CSS values.
*
* This object contains all the design token definitions for the component.
* Each token name (as declared in the contract) must have a corresponding
* definition that specifies the actual CSS classes or token references.
* These tokens can be referenced throughout the styling system and support
* recursive resolution (tokens can reference other tokens).
*
* @example
* ```typescript
* token: def.token({
* // Color tokens
* "color.text.default": what.css(["text-gray-900"]),
* "color.text.primary": what.css(["text-white"]),
* "color.bg.default": what.css(["bg-gray-100"]),
* "color.bg.primary": what.css(["bg-blue-600"]),
*
* // Spacing tokens
* "spacing.padding.sm": what.css(["px-2", "py-1"]),
* "spacing.padding.md": what.css(["px-4", "py-2"]),
* "spacing.padding.lg": what.css(["px-6", "py-3"]),
*
* // Composite tokens that reference other tokens
* "button.primary": what.token(["color.bg.primary", "color.text.primary"]),
* "button.default": what.token(["color.bg.default", "color.text.default"])
* })
* ```
*/
token: TokenDefinitionRequired<T>;
/**
* Array of styling rules that define conditional styling based on variants.
*
* Rules are processed in order and can either add to or replace previous styles
* depending on the `override` flag. Each rule specifies a condition (variant
* combination) and the styling to apply when that condition is met. Rules
* support both accumulative (def) and destructive (override) styling modes.
*
* @example
* ```typescript
* rules: [
* // Base styles (accumulative)
* def.root({
* root: what.both(
* ["inline-flex", "items-center", "rounded-md"],
* ["color.text.default", "color.bg.default"]
* ),
* icon: what.css(["mr-2", "w-4", "h-4"]),
* label: what.css(["font-medium"])
* }),
*
* // Size variants (accumulative - adds to base)
* def.rule(
* what.variant({ size: "sm" }),
* { root: what.css(["px-2", "py-1", "text-sm"]) }
* ),
* def.rule(
* what.variant({ size: "lg" }),
* { root: what.css(["px-6", "py-3", "text-lg"]) }
* ),
*
* // Variant styles (destructive - replaces colors)
* override.rule(
* what.variant({ variant: "primary" }),
* { root: what.token(["color.bg.primary", "color.text.primary"]) }
* ),
*
* // Complex conditional styling
* def.rule(
* what.variant({ size: "lg", variant: "primary" }),
* {
* root: what.css(["shadow-lg"]),
* icon: what.css(["w-5", "h-5"])
* }
* )
* ]
* ```
*/
rules: RuleDefinition<T>[];
/**
* Default values for variants when no specific variant is provided.
*
* This object specifies the fallback values for each variant defined in the
* contract. These defaults are used when creating styled instances without
* explicit variant overrides. Defaults can be overridden at runtime through
* the `create()` method or component props.
*
* @example
* ```typescript
* defaults: def.defaults({
* // String variants
* size: "md",
* variant: "default",
* theme: "light",
*
* // Boolean variants
* disabled: false,
* loading: false,
* fullWidth: false
* })
* ```
*
* @example
* ```typescript
* // Usage with defaults
* const slots = ButtonCls.create(); // Uses defaults: size="md", variant="default"
*
* // Override defaults at runtime
* const slots = ButtonCls.create(({ what }) => ({
* variant: what.variant({ size: "lg", variant: "primary" })
* }));
* ```
*/
defaults: VariantValueMapping<T>;
};
type CreateConfig<T extends Contract<any, any, any>> = {
variant?: Partial<VariantValueMapping<T>>;
slot?: SlotMapping<T>;
override?: SlotMapping<T>;
token?: Partial<TokenDefinitionOptional<T>>;
};
/**
* Standard component props interface that provides CLS integration capabilities.
*
* This helper type defines the standard structure for React component props that need
* CLS styling integration. It provides optional `tva` and `cls` properties while
* preserving all other props from the base type `P`.
*
* @template TCls - The CLS instance type for styling
* @template P - The base props type to extend (defaults to `unknown`)
*
* @example
* ```typescript
* // Basic usage in a component
* interface ButtonProps extends Component<typeof ButtonCls> {
* children: ReactNode;
* onClick?: () => void;
* }
*
* const Button: FC<ButtonProps> = ({ tva = ButtonCls, cls, children, onClick }) => {
* const slots = tva.create(cls);
* return (
* <button className={slots.root()} onClick={onClick}>
* {children}
* </button>
* );
* };
* ```
*
* @example
* ```typescript
* // With custom styling configuration
* const Button: FC<ButtonProps> = ({ tva = ButtonCls, cls, children, onClick }) => {
* const slots = tva.create(cls, ({ what }) => ({
* variant: what.variant({ size: "lg", variant: "primary" })
* }));
*
* return (
* <button className={slots.root()} onClick={onClick}>
* {children}
* </button>
* );
* };
* ```
*/
type Component<TCls extends Cls<any>, P = unknown> = {
/**
* The CLS instance to use for styling. If not provided, the component should
* use a default CLS instance. This allows components to be styled with different
* CLS instances while maintaining type safety.
*
* @example
* ```typescript
* // Use default styling
* <Button>Click me</Button>
*
* // Use extended CLS instance
* <Button tva={ButtonCls.use(PrimaryButtonCls)}>Click me</Button>
* ```
*/
tva?: TCls;
/**
* Optional styling configuration function that receives the `WhatUtil` object
* and returns partial configuration overrides. This allows for dynamic styling
* based on component state or props.
*
* @example
* ```typescript
* // Static configuration
* <Button cls={({ what }) => ({
* variant: what.variant({ size: "lg" })
* })}>Click me</Button>
*
* // Dynamic configuration based on props
* <Button cls={({ what }) => ({
* variant: what.variant({
* size: size,
* variant: isPrimary ? "primary" : "default"
* })
* })}>Click me</Button>
* ```
*/
cls?: (props: WhatUtil<TCls["contract"]>) => Partial<CreateConfig<TCls["contract"]>>;
} & Omit<P, "tva" | "cls">;
/**
* Extracts the slot functions type from a CLS instance for use in component props.
*
* This helper type takes a CLS instance and returns the type of its slot functions.
* Each slot function accepts an optional configuration and returns a CSS class string.
* This is commonly used in React components to provide type-safe access to styling slots.
*
* @template TCls - The CLS instance type
*
* @example
* ```typescript
* // Given a CLS with slots: ["root", "icon", "label"]
* interface ButtonProps {
* slots?: ComponentSlots<typeof ButtonCls>;
* }
*
* // Usage in component
* const Button: FC<ButtonProps> = ({ slots = ButtonCls.create() }) => {
* return (
* <button className={slots.root()}>
* <Icon className={slots.icon()} />
* <span className={slots.label()}>Click me</span>
* </button>
* );
* };
* ```
*
* @example
* ```typescript
* // With custom configuration
* const Button: FC<ButtonProps> = ({ slots = ButtonCls.create() }) => {
* const customSlots = slots(({ what }) => ({
* variant: what.variant({ size: "lg", variant: "primary" })
* }));
*
* return (
* <button className={customSlots.root()}>
* <Icon className={customSlots.icon()} />
* <span className={customSlots.label()}>Click me</span>
* </button>
* );
* };
* ```
*/
type ComponentSlots<TCls extends Cls<any>> = ClsSlots<TCls["contract"]>;
/**
* Extracts the value type for a specific variant from a CLS instance.
*
* This helper type takes a CLS instance and a variant name, then returns the appropriate
* value type for that variant. For boolean variants (defined with "bool" in the contract),
* it returns `boolean`. For all other variants, it returns the string literal type.
*
* @template TCls - The CLS instance type
* @template TVariant - The name of the variant to extract the type for
*
* @example
* ```typescript
* // Given a CLS with variants: { size: ["sm", "md", "lg"], disabled: ["bool"] }
* type ButtonSize = VariantOf<typeof ButtonCls, "size">; // "sm" | "md" | "lg"
* type ButtonDisabled = VariantOf<typeof ButtonCls, "disabled">; // boolean
* ```
*
* @example
* ```typescript
* // Usage in component props
* interface ButtonProps {
* size?: VariantOf<typeof ButtonCls, "size">;
* disabled?: VariantOf<typeof ButtonCls, "disabled">;
* }
* ```
*/
type VariantOf<TCls extends Cls<any>, TVariant extends keyof TCls["contract"]["variant"]> = StringToBool<TCls["contract"]["variant"][TVariant][number]>;
/**
* Main CLS instance interface that provides styling capabilities through contracts and definitions.
*
* This interface represents a complete CLS instance that combines a contract (structure)
* with a definition (styling values) to provide a type-safe styling system. It offers
* methods for creating styled instances, extending functionality, and managing inheritance.
*
* @template T - The contract type that defines the structure (tokens, slots, variants)
*
* @example
* ```typescript
* // Create a CLS instance
* const ButtonCls = cls(contract, definition);
*
* // Use the instance to create styled components
* const slots = ButtonCls.create();
* const customSlots = ButtonCls.create(({ what }) => ({
* variant: what.variant({ size: "lg", variant: "primary" })
* }));
* ```
*/
interface Cls<T extends Contract<any, any, any>> {
/**
* Creates a styled instance with optional configuration overrides.
*
* This method generates slot functions that can be called to get CSS class strings.
* It accepts optional user and internal configuration functions that can override
* the default styling behavior.
*
* @param userConfigFn - Optional user configuration function for runtime overrides
* @param internalConfigFn - Optional internal configuration function for component logic
* @returns An object with slot functions that return CSS class strings
*
* @example
* ```typescript
* // Basic usage - use default styling
* const slots = ButtonCls.create();
* const className = slots.root(); // "inline-flex items-center text-gray-900 bg-gray-100"
*
* // With user configuration
* const slots = ButtonCls.create(({ what }) => ({
* variant: what.variant({ size: "lg", variant: "primary" })
* }));
* const className = slots.root(); // "inline-flex items-center text-white bg-blue-600 px-6 py-3"
*
* // With both user and internal configuration
* const slots = ButtonCls.create(
* ({ what }) => ({ variant: what.variant({ size: "lg" }) }), // User config
* ({ what }) => ({ slot: { root: what.css(["internal-class"]) } }) // Internal config
* );
* ```
*/
create(userConfigFn?: WhatConfigFn<T>, internalConfigFn?: WhatConfigFn<T>): ClsSlots<T>;
/**
* Extends the current CLS instance with additional functionality.
*
* This method creates a new CLS instance that inherits from the current one,
* adding new tokens, slots, or variants while preserving the existing functionality.
* The new instance can override inherited values and add new styling rules.
*
* @template TTokenContract - Additional token contract
* @template TSlotContract - Additional slot contract
* @template TVariantContract - Additional variant contract
* @param contract - Extended contract with new tokens, slots, and variants
* @param definition - Function that receives WhatUtil and returns the extended definition
* @returns A new CLS instance with extended functionality
*
* @example
* ```typescript
* // Extend with new variants and tokens
* const PrimaryButtonCls = ButtonCls.extend(
* {
* tokens: ["color.bg.primary", "color.text.primary"],
* slot: ["root", "icon"],
* variant: {
* loading: ["bool"],
* size: ["sm", "md", "lg", "xl"]
* }
* },
* ({ what, def }) => ({
* token: def.token({
* "color.bg.primary": what.css(["bg-blue-600"]),
* "color.text.primary": what.css(["text-white"])
* }),
* rules: [
* def.rule(
* what.variant({ loading: true }),
* { root: what.css(["opacity-75", "cursor-not-allowed"]) }
* )
* ],
* defaults: def.defaults({ loading: false, size: "md" })
* })
* );
* ```
*/
extend<const TTokenContract extends TokenContract, const TSlotContract extends SlotContract, const TVariantContract extends VariantContract>(contract: Contract<TTokenContract, TSlotContract, TVariantContract, T>, definition: (props: WhatUtil<Contract<TTokenContract, TSlotContract, TVariantContract, T>>) => DefinitionEx<Contract<TTokenContract, TSlotContract, TVariantContract, T>>): Cls<ContractEx<TTokenContract, TSlotContract, TVariantContract, T>>;
/**
* Assigns a compatible CLS instance for type-safe inheritance.
*
* This method provides type-safe assignment of CLS instances that are derived
* from the current instance. It ensures that only compatible instances can be
* assigned, preventing runtime errors from incompatible styling systems.
*
* @template Sub - The sub-instance type that must be derived from the current instance
* @param sub - A CLS instance that must be derived from the current instance
* @returns The current CLS instance for method chaining
*
* @example
* ```typescript
* // Assign a compatible instance
* const ButtonGroup = ButtonCls.use(PrimaryButtonCls);
*
* // This would cause a TypeScript error if PrimaryButtonCls is not derived from ButtonCls
* // const InvalidGroup = ButtonCls.use(SomeOtherCls); // ❌ Type error
*
* // Use the assigned instance
* const slots = ButtonGroup.create();
* ```
*/
use<Sub extends Contract<any, any, any>>(sub: Cls<Sub> & {
contract: HasBaseInUseChain<Sub, T> extends true ? unknown : [
"❌ Not derived from Base contract",
{
sub: Sub;
base: T;
}
];
}): Cls<T>;
/**
* Creates a configuration function for use in component props.
*
* This method generates a configuration function that can be passed to components
* via the `cls` prop. It provides type-safe access to styling configuration
* while ensuring compatibility with the current CLS instance.
*
* @template Sub - The sub-contract type (defaults to the current contract)
* @param userConfigFn - Optional user configuration function
* @param internalConfigFn - Optional internal configuration function
* @returns A configuration function or undefined
*
* @example
* ```typescript
* // Create a configuration function for component props
* const configFn = ButtonCls.cls(({ what }) => ({
* variant: what.variant({ size: "lg", variant: "primary" })
* }));
*
* // Use in component
* <Button cls={configFn}>Click me</Button>
*
* // Or create inline
* <Button cls={ButtonCls.cls(({ what }) => ({
* variant: what.variant({ size: "lg" })
* }))}>Click me</Button>
* ```
*/
cls<Sub extends Contract<any, any, any> = T>(userConfigFn?: {
hack: WhatConfigFn<HasBaseInUseChain<Sub, T> extends true ? Sub : never>;
}["hack"], internalConfigFn?: {
hack: WhatConfigFn<HasBaseInUseChain<Sub, T> extends true ? Sub : never>;
}["hack"]): WhatConfigFn<T> | undefined;
/**
* The contract that defines the structure of this CLS instance.
*
* Contains the tokens, slots, and variants that define what can be styled
* and how the styling system is structured.
*
* @example
* ```typescript
* // Access contract information
* console.log(ButtonCls.contract.tokens); // ["color.text.default", "color.bg.default", ...]
* console.log(ButtonCls.contract.slot); // ["root", "label", "icon"]
* console.log(ButtonCls.contract.variant); // { size: ["sm", "md", "lg"], variant: ["default", "primary"] }
* ```
*/
contract: T;
/**
* The definition that provides concrete styling values for this CLS instance.
*
* Contains the token definitions, styling rules, and default values that
* determine how the component is actually styled.
*
* @example
* ```typescript
* // Access definition information
* console.log(ButtonCls.definition.token); // { "color.text.default": {...}, ... }
* console.log(ButtonCls.definition.rules); // Array of styling rules
* console.log(ButtonCls.definition.defaults); // { size: "md", variant: "default" }
* ```
*/
definition: Definition<T>;
}
declare function cls<const TTokenContract extends TokenContract, const TSlotContract extends SlotContract, const TVariantContract extends VariantContract, const TContract extends Contract<TTokenContract, TSlotContract, TVariantContract, any>>(contract: TContract, definitionFn: (props: WhatUtil<TContract>) => Definition<TContract>): Cls<TContract>;
declare namespace clx {
namespace Internal {
/**
* Just forward class name type to prevent direct dependency on providing package.
*/
type ClassName = ClassNameValue;
type SlotDef<TSlotKeys extends string> = Record<TSlotKeys, ClassName>;
type VariantMap<TSlot extends SlotDef<any>, TUse extends ClsFn<any, any, any> | unknown = unknown> = {
[K in string]: {
[V in string]: Partial<Record<keyof SlotEx<TSlot, TUse>, ClassName>>;
};
};
type VariantDef<TVariantKeys extends string, TSlot extends SlotDef<any>, TUse extends ClsFn<any, any, any> | unknown = unknown> = Record<TVariantKeys, Record<string, Partial<Record<keyof SlotEx<TSlot, TUse>, ClassName>>>>;
type VariantKeys<TVariant> = TVariant extends VariantDef<infer K, any, any> ? K : never;
type ValuesDef<TVariant extends Record<string, Record<string, any>>> = {
[K in keyof TVariant]?: keyof TVariant[K] extends "bool" ? boolean : keyof TVariant[K];
};
/**
* Compute slot from all slots (including uses - extensions).
*/
type SlotEx<TSlot extends SlotDef<any>, TUse extends (() => {
"~type": {
slot?: SlotDef<any>;
};
}) | unknown = unknown> = TUse extends () => {
"~type": {
slot?: infer S;
};
} ? TSlot & S : TSlot;
/**
* Compute variant from all variants (including uses - extensions).
*/
type VariantEx<TVariant extends Record<string, Record<string, any>>, TUse extends (() => {
"~type": {
variant?: Record<string, Record<string, any>>;
};
}) | unknown = unknown> = TUse extends () => {
"~type": {
variant?: infer V;
};
} ? TVariant & V : TVariant;
/**
* Compute default values from all variants (including uses - extensions).
*
* Current defaults are required, extensions are marked as optional.
*/
type DefaultsEx<TVariant extends Record<string, Record<string, any>>, TUse extends (() => {
"~type": {
variant?: Record<string, Record<string, any>>;
};
}) | unknown = unknown> = Required<ValuesDef<TVariant>> & (TUse extends () => {
"~type": {
variant?: infer V;
};
} ? ValuesDef<V & Record<string, Record<string, any>>> : {});
type SlotFn<TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> = (values?: ValuesDef<VariantEx<TVariant, TUse>>, cls?: ClassName) => string;
type Slots<TSlot extends SlotDef<any>, TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> = {
[K in keyof SlotEx<TSlot, TUse>]: SlotFn<TVariant, TUse>;
};
interface Config<TVariantEx extends Record<string, Record<string, any>>> {
/**
* Cumulated default values from all variants (including uses - extensions).
*/
defaults: ValuesDef<TVariantEx>;
/**
* Combined cumulated defaults & current values provided to `tva()`.
*/
values: ValuesDef<TVariantEx>;
}
type Type<TSlotEx extends SlotEx<any, TUse>, TVariantEx extends VariantEx<any, TUse>, TUse extends ClsFn<any, any, any> | unknown> = {
/**
* Extension type for this variant.
*/
use?: TUse;
/**
* Cumulated slots from all extensions.
*/
slot?: TSlotEx;
/**
* Cumulated variants from all extensions.
*/
variant?: TVariantEx;
};
type SlotCls<TSlot extends SlotDef<any>, TUse extends ClsFn<any, any, any> | unknown = unknown> = {
[K in keyof SlotEx<TSlot, TUse>]?: ClassName;
};
/**
* Output of the factory method.
*/
type ClsFn<TSlot extends SlotDef<any>, TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> = (variant?: ValuesDef<VariantEx<TVariant, TUse>>, cls?: SlotCls<TSlot, TUse>) => {
/**
* Individual slots for a component. Those slots are then
* used to compute individual class names.
*/
slots: Slots<TSlot, TVariant, TUse>;
/**
* Configuration used internally.
*
* This property does not havy any practical use in runtime.
*/
"~config": Config<VariantEx<TVariant, TUse>>;
/**
* Used for inheritance and type checking.
*
* This property does not havy any practical use in runtime.
*/
"~type": Type<SlotEx<TSlot, TUse>, VariantEx<TVariant, TUse>, TUse>;
};
/**
* Matching rules.
*/
interface Match<TSlot extends SlotDef<any>, TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> {
/**
* Conditions to match.
*
* All the provided values must match to apply the rule.
*/
if: ValuesDef<VariantEx<TVariant, TUse>>;
/**
* Classes to apply when all conditions are met.
*
* Keys are slot names.
*/
do: SlotCls<TSlot, TUse>;
}
}
}
declare namespace clx {
type Class = Internal.ClassName;
/**
* Variants configuration.
*/
interface Config<TSlot extends Internal.SlotDef<any>, TVariant extends Internal.VariantMap<TSlot, TUse>, TUse extends Internal.ClsFn<any, any, any> | unknown = unknown> {
/**
* Extension of the component.
*
* When provided, it also provides full type-checking from all previous
* extend variants.
*/
use?: TUse;
/**
* Define or override slots.
*
* Slot is a set of classes that are used to compute the final class name.
*
* By a convention and simpler use, even single-slot component must be placed here.
*/
slot: TSlot;
/**
* Define or override variants.
*
* Variants MUST be defined per slot. Each variant value maps to a record of slot -> class names.
*/
variant: TVariant;
/**
* Matching rules.
*
* Those are used to compute dynamic class names based on the current state (input values).
*/
match?: Internal.Match<TSlot, TVariant, TUse>[];
/**
* Default values.
*
* They're (cleverly) required to prevent surprises when using variants.
*/
defaults: Internal.DefaultsEx<TVariant, TUse>;
}
/**
* When used in components, this is a safe way how to extend component props.
*
* It omits `variant`, `tva`, and `cls` props from the parent props.
*/
type Props<TCls extends Internal.ClsFn<any, any, any>, P = unknown> = {
variant?: Internal.ValuesDef<Internal.VariantEx<ReturnType<TCls>["~type"]["variant"], TCls>>;
tva?: TCls;
cls?: {
[K in keyof Internal.SlotEx<ReturnType<TCls>["~type"]["slot"], TCls>]?: Class;
};
} & Omit<P, "variant" | "tva" | "cls">;
/**
* Extract `variant`, `tva`, and `cls` types from the given props.
*/
type Extract<TProps extends Props<any>> = Pick<TProps, "variant" | "tva" | "cls">;
/**
* Drop `cls` types from the gi