UNPKG

@use-pico/cls

Version:

Type-safe, composable styling system for React, Vue, Svelte, and vanilla JS

1,437 lines (1,214 loc) โ€ข 71 kB
# CLS - Type-Safe Styling System ๐Ÿš€ > **The styling system that finally makes sense** - because we're tired of CSS chaos! ๐ŸŽจโœจ [![npm version](https://badge.fury.io/js/@use-pico%2Fcls.svg)](https://badge.fury.io/js/@use-pico%2Fcls) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ## ๐Ÿ“š Table of Contents - [๐ŸŽฏ What is CLS?](#-what-is-cls) - [๐Ÿ›  Installation](#-installation) - [๐Ÿš€ Quick Start](#-quick-start) - [โœจ Core Features](#-core-features) - [๐Ÿ† Quality & Reliability](#-quality--reliability) - [๐Ÿ—๏ธ Contract Builder API](#๏ธ-contract-builder-api) - [๐Ÿš€ Basic Button Example](#-basic-button-example) - [๐ŸŽฏ Using Your Button](#-using-your-button) - [๐Ÿ”ง Contract Builder Methods](#-contract-builder-methods) - [๐ŸŽจ Definition Builder Methods](#-definition-builder-methods) - [๐Ÿง  Smart Type Safety](#-smart-type-safety) - [๐ŸŽจ Design Tokens](#-design-tokens) - [๐ŸŽฏ Type-Safety Magic](#-type-safety-magic) - [๐Ÿ“– Basic Concepts](#-basic-concepts) - [๐Ÿง  Mental Model](#-mental-model) - [๐Ÿท๏ธ Tokens](#-tokens) - [๐ŸŽช Slots](#-slots) - [๐ŸŽญ Variants](#-variants) - [๐ŸŽฏ Rules](#-rules) - [๐Ÿงฌ Inheritance](#-inheritance) - [โš›๏ธ React Integration](#๏ธ-react-integration) - [๐ŸŽฏ Advanced Features](#-advanced-features) - [๐Ÿš€ Performance](#-performance) - [๐ŸŽฏ When to Use CLS](#-when-to-use-cls) - [๐Ÿ” Comparison with Other Solutions](#-comparison-with-other-solutions) - [๐Ÿ†š Real-World Comparison Examples](#-real-world-comparison-examples) - [๐Ÿ”— Community & Support](#-community--support) - [๐Ÿค– LLM Compatibility](#-llm-compatibility) ## ๐ŸŽฏ What is CLS? **CLS** stands for **Class List System** (because we're creative with acronyms, obviously! ๐Ÿ˜„). Think of it as that **smart friend** who shows up to a construction site with a perfectly organized toolbox instead of just throwing random tools in a bag ๐Ÿงฐ. While other styling libraries are like *"here's a hammer, good luck building a house"*, CLS is like *"here's a blueprint, here are the materials, and here's how they all work together โ€“ oh, and everything is type-safe because we're not savages"* ๐Ÿ˜Ž. ### Why CLS? Because we're tired of: - ๐ŸŽจ **Styling chaos** - hunting down inconsistent colors across codebases - ๐Ÿ› **Runtime surprises** - styling errors that should've been caught at compile time - ๐Ÿ”„ **Multiple sources of truth** - maintaining CSS variables AND TypeScript types - ๐Ÿ”’ **Vendor lock-in** - being stuck with heavy, opinionated styling solutions ## ๐Ÿ›  Installation ```bash npm install @use-pico/cls # or bun add @use-pico/cls # or yarn add @use-pico/cls ``` ## ๐Ÿš€ Quick Start Let's build a button component from scratch! This example shows the **Contract Builder API** - the recommended way to create CLS instances: ```typescript import { contract } from '@use-pico/cls'; // Step 1: Create a contract (define what can be styled) const ButtonCls = contract() .tokens(["color.bg.primary", "color.text.primary"]) // Design tokens .slots(["root", "label"]) // Component parts .variant("size", ["sm", "md", "lg"]) // Size variants .bool("disabled") // Boolean variant .def() // Start definition phase .token({ // Define token values "color.bg.primary": { class: ["bg-blue-600"] }, "color.text.primary": { class: ["text-white"] }, }) .root({ // Base styling root: { token: ["color.bg.primary", "color.text.primary"], class: ["px-4", "py-2", "rounded", "font-medium"], }, label: { class: ["select-none"] }, }) .rule({ size: "lg" }, { root: { class: ["px-6", "py-3"] } }) // Conditional rules .rule({ disabled: true }, { root: { class: ["opacity-50", "cursor-not-allowed"] } }) .defaults({ size: "md", disabled: false }) // Default values .cls(); // Create CLS instance // Step 2: Use it! (variadic tweak parameters, undefined values cleaned up) const slots = ButtonCls.create({ variant: { size: "lg" } }); console.log(slots.root()); // "bg-blue-600 text-white px-4 py-2 rounded font-medium px-6 py-3" // Individual slots also support tweaks! const customSlots = ButtonCls.create(); console.log(customSlots.root({ variant: { size: "sm" } })); // "bg-blue-600 text-white px-3 py-1 text-sm" ``` ### With React (because who doesn't love React? ๐Ÿ’™) ```tsx import { useCls } from '@use-pico/cls'; function MyButton({ size = "md", disabled = false, tweak }) { const { slots, variant } = useCls(ButtonCls, { variant: { size, disabled } }, // Component props (lower precedence) tweak // User tweak (highest precedence) ); return ( <button className={slots.root()}> <span className={slots.label()}>Click me</span> </button> ); } ``` ## โœจ Core Features - **๐Ÿ”’ Type Safety First** - Catch styling errors at compile time (no more midnight debugging sessions! ๐ŸŒ™) - **๐Ÿงฉ Composable** - Build design systems with inheritance and composition - **โšก Performance** - Lazy evaluation, smart caching, and minimal runtime overhead - **๐ŸŒ Framework Agnostic** - Works with React, Vue, Svelte, vanilla JS, or any framework - **๐ŸŽจ Design System Ready** - Tokens, variants, and slots for scalable styling - **๐ŸŽฏ Granular Control** - Individual slots support tweaks for fine-grained customization - **๐Ÿ›  Developer Experience** - Excellent IDE support and intuitive API - **๐Ÿ›ก๏ธ Battle-Tested** - Over 300 comprehensive tests ensuring rock-solid behavior and preventing regressions ## ๐Ÿ† Quality & Reliability CLS isn't just another styling library - it's a **heavily polished, battle-tested solution** that's ready for production use. ### โœจ Heavily Polished & Production-Ready CLS has been refined through *countless iterations* to achieve **maximum convenience** and developer experience. Every piece has been *carefully crafted* and polished to perfection. While the project powers several **larger applications**, its primary development is driven by *side projects* โ€“ ensuring the library maintains the **highest quality standards*. **Production-ready stability** is guaranteed with **zero breaking changes** going forward. When we added the powerful Contract Builder API, it was built *on top of* the main `cls()` function without affecting the overall API - ensuring existing code continues to work perfectly while new features enhance the experience. **Your investment in CLS is safe** - we're committed to maintaining backward compatibility and stable APIs. ### ๐Ÿงช Extensively Tested CLS is *thoroughly tested* with comprehensive test suites covering all features, edge cases, and integration scenarios. The test coverage deeply encompasses all **three main APIs** of CLS: the **Contract Builder API** (fluent contract and definition building), the **low-level `cls()` function** (direct contract and definition usage), and the **React integration** (hooks, context, and component patterns). **Every bug that has been found gets a new test** to ensure it never happens again - creating a robust safety net that grows stronger with each discovery. This rigorous testing approach means you can trust CLS to work reliably in production environments. ## ๐Ÿ—๏ธ Contract Builder API The **Contract Builder** is CLS's main API - it's fluent, type-safe, and incredibly powerful! Think of it as your styling toolkit that guides you every step of the way. ### ๐Ÿš€ Basic Button Example ```typescript import { contract } from '@use-pico/cls'; const ButtonCls = contract() .slots(["root", "icon", "label"]) // Define component parts .variant("size", ["sm", "md", "lg"]) // Define size options .variant("tone", ["default", "primary"]) // Define tone options .bool("disabled") // Add boolean variant .def() // Start defining styles .root({ // Base styles (no conditions) root: { class: ["inline-flex", "items-center", "gap-2"] }, icon: { class: ["w-4", "h-4"] }, label: { class: ["font-medium"] } }) .match("size", "sm", { // Size-specific styles root: { class: ["px-3", "py-1", "text-sm"] } }) .match("size", "md", { root: { class: ["px-4", "py-2", "text-base"] } }) .match("size", "lg", { root: { class: ["px-6", "py-3", "text-lg"] } }) .match("tone", "primary", { // Tone-specific styles root: { class: ["bg-blue-600", "text-white"] } }) .match("disabled", true, { // Boolean variant root: { class: ["opacity-50", "cursor-not-allowed"] } }) .defaults({ // Default values size: "md", tone: "default", disabled: false }) .cls(); // Create the CLS instance ``` ### ๐ŸŽฏ Using Your Button ```typescript // Create styled slots and variants (variadic tweak parameters) const { slots, variant } = ButtonCls.create({ variant: { size: "lg", tone: "primary", disabled: false } }); // Use in your component <button className={slots.root()}> <Icon className={slots.icon()} /> <span className={slots.label()}>Click me!</span> </button> // Access resolved variants for component logic console.log(variant.size); // "lg" console.log(variant.tone); // "primary" console.log(variant.disabled); // false ``` ### ๐Ÿ”ง Contract Builder Methods **Define the structure** (used before `.def()`): | Method | Purpose | Example | |--------|---------|---------| | `.tokens()` | Define multiple tokens | `.tokens(["color.primary", "spacing.md"])` | | `.token()` | Define single token | `.token("color.primary")` | | `.slots()` | Define component parts | `.slots(["root", "icon", "label"])` | | `.slot()` | Add single slot | `.slot("badge")` | | `.variant()` | Add string/enum variants | `.variant("size", ["sm", "md", "lg"])` | | `.variants()` | Define multiple variants | `.variants({ size: ["sm", "md"], tone: ["default", "primary"] })` | | `.bool()` | Add boolean variants | `.bool("disabled")` | ### ๐ŸŽจ Definition Builder Methods **Define the styling** (used after `.def()`): | Method | Purpose | Example | |--------|---------|---------| | `.root()` | Base styles (no conditions) | `.root({ root: { class: ["flex"] } })` | | `.rule()` | Complex condition matching | `.rule({ size: "lg", disabled: true }, { root: { class: ["opacity-50"] } })` | | `.match()` | Single variant matching | `.match("size", "lg", { root: { class: ["px-6"] } })` | | `.switch()` | Boolean variant helper | `.switch("disabled", { root: { class: ["opacity-50"] } }, { root: { class: ["opacity-100"] } })` | | `.tokens.rule()` | Token conditional rules | `.tokens.rule({ tone: "primary" }, { "color.bg": { class: ["bg-blue-600"] } })` | | `.tokens.match()` | Single variant token matching | `.tokens.match("tone", "primary", { "color.bg": { class: ["bg-blue-600"] } })` | | `.tokens.switch()` | Boolean variant token helper | `.tokens.switch("disabled", { "color.bg": { class: ["bg-gray-300"] } }, { "color.bg": { class: ["bg-blue-600"] } })` | | `.defaults()` | Set default variant values | `.defaults({ size: "md", disabled: false })` | | `.cls()` | Create final CLS instance | `.cls()` | ### ๐Ÿง  Smart Type Safety CLS enforces correctness at every step: ```typescript const ButtonCls = contract() .variant("size", ["sm", "md", "lg"]) .bool("disabled") .def() .match("size", "xl", { root: { class: ["px-8"] } }) // โŒ TypeScript error! "xl" not in variants .match("loading", true, { root: { class: ["animate-spin"] } }) // โŒ TypeScript error! "loading" not defined .cls(); ``` ### ๐ŸŽจ Design Tokens Create reusable design values: ```typescript const ThemeCls = contract() .token({ "color.bg.primary": { class: ["bg-blue-600"] }, "color.text.primary": { class: ["text-white"] }, "spacing.padding.md": { class: ["px-4", "py-2"] } }) .def() .root({ root: { class: ["font-sans"] } }) .cls(); // Use tokens in other components const ButtonCls = contract(ThemeCls.contract) .def() .root({ root: { token: ["color.bg.primary", "color.text.primary", "spacing.padding.md"] } }) .cls(); ``` ## ๐Ÿ“– Basic Concepts ### ๐Ÿง  Mental Model CLS is built on **two powerful pillars**: **contract-first design** and **heavy typechecking**. Think of it like building a house โ€“ you start with solid blueprints, then everything flows naturally! ๐Ÿ—๏ธ #### Contract = Structure (What can be styled?) ```typescript // Contract defines WHAT can be styled using Contract Builder API const MyComponentCls = contract() .tokens(["color.text", "color.bg"]) // What design tokens exist? .slots(["root", "label"]) // What parts can be styled? .variant("size", ["sm", "md", "lg"]) // What size variations are possible? .variant("variant", ["default", "primary"]) // What style variations are possible? .build(); // This creates the contract structure ``` #### Definition = Values (How is it styled?) ```typescript // Definition defines HOW it's styled using Definition Builder API const MyComponentCls = contract() .tokens(["color.text", "color.bg"]) .slots(["root", "label"]) .variant("size", ["sm", "md", "lg"]) .variant("variant", ["default", "primary"]) .def() // Start definition phase .token({ // Define token values "color.text.default": { class: ["text-gray-900"] }, "color.text.primary": { class: ["text-white"] }, "color.bg.default": { class: ["bg-gray-100"] }, "color.bg.primary": { class: ["bg-blue-600"] } }) .root({ // Base styles (no conditions) root: { class: ["inline-flex", "items-center"] } }) .match("size", "lg", { // Size-specific styles root: { class: ["px-6", "py-3"] } }) .match("variant", "primary", { // Variant-specific styles root: { token: ["color.text.primary", "color.bg.primary"] } }) .defaults({ size: "md", variant: "default" }) // Default values .cls(); // This creates the complete CLS instance ``` > **๐Ÿ’ก Key Insight**: Think of **Contract** as the "interface" and **Definition** as the "implementation" ### ๐Ÿท๏ธ Tokens Tokens are **named design values** that can be referenced throughout your components: ```typescript const ButtonCls = contract() .tokens(["color.bg.primary", "color.text.primary", "spacing.padding.md"]) // Define tokens .slots(["root"]) .def() .token({ // Define token values "color.bg.primary": { class: ["bg-blue-600"] }, "color.text.primary": { class: ["text-white"] }, "spacing.padding.md": { class: ["px-4", "py-2"] } }) .root({ // Reference tokens in slots root: { token: ["color.bg.primary", "color.text.primary"] } }) .cls(); ``` ### ๐ŸŽฏ Token Rules Tokens support **conditional rules** just like slots! Use `.tokens.rule()` to apply different token values based on variants: ```typescript const ButtonCls = contract() .tokens(["color.bg", "color.text"]) .slots(["root"]) .variant("tone", ["default", "primary", "danger"]) .bool("disabled") .def() .token({ // Base token values "color.bg": { class: ["bg-gray-100"] }, "color.text": { class: ["text-gray-900"] } }) .tokens.rule({ tone: "primary" }, { // Primary tone tokens "color.bg": { class: ["bg-blue-600"] }, "color.text": { class: ["text-white"] } }) .tokens.rule({ tone: "danger" }, { // Danger tone tokens "color.bg": { class: ["bg-red-600"] }, "color.text": { class: ["text-white"] } }) .tokens.rule({ disabled: true }, { // Disabled state tokens "color.bg": { class: ["bg-gray-300"] }, "color.text": { class: ["text-gray-500"] } }) .root({ root: { token: ["color.bg", "color.text"] } }) .cls(); ``` **๐ŸŽจ Token Rule Helpers:** ```typescript // Match specific variant combinations .tokens.match("tone", "primary", { "color.bg": { class: ["bg-blue-600"] } }) // Boolean variant switch (generates two rules) .tokens.switch("disabled", { "color.bg": { class: ["bg-gray-300"] } }, // when disabled: true { "color.bg": { class: ["bg-blue-600"] } } // when disabled: false ) // Override previous token rules .tokens.rule({ tone: "primary" }, { "color.bg": { class: ["bg-purple-600"] } }, true) // override: true ``` ### ๐ŸŽช Slots Slots represent **named parts** of a component that can receive independent styling: ```typescript const ButtonCls = contract() .slots(["root", "icon", "label", "badge"]) // Define component parts .def() .root({ // Style each slot independently root: { class: ["flex", "items-center"] }, icon: { class: ["w-4", "h-4"] }, label: { class: ["font-medium"] }, badge: { class: ["ml-2", "px-1", "rounded"] } }) .cls(); ``` ### ๐ŸŽญ Variants Variants are **configurable properties** that control component appearance: ```typescript const ButtonCls = contract() .variant("size", ["sm", "md", "lg"]) // String variants .variant("tone", ["default", "primary", "danger"]) .bool("disabled") // Boolean variants .bool("loading") .slots(["root"]) .def() .match("size", "lg", { // Use variants in rules root: { class: ["px-6", "py-3"] } }) .match("disabled", true, { root: { class: ["opacity-50"] } }) .cls(); ``` ### ๐ŸŽฏ Rules Rules define **conditional styling** based on variant combinations: ```typescript const ButtonCls = contract() .variant("size", ["sm", "md", "lg"]) .variant("tone", ["default", "primary"]) .bool("disabled") .slots(["root"]) .def() // Simple rule .match("size", "lg", { root: { class: ["px-6", "py-3"] } }) // Complex rule with multiple conditions .rule({ size: "lg", tone: "primary" }, { root: { class: ["px-8", "py-4"], token: ["color.bg.primary"] } }) // Boolean rules .match("disabled", true, { root: { class: ["opacity-50", "cursor-not-allowed"] } }) .cls(); ``` ### ๐ŸŽฏ Type-Safety Magic โœจ CLS's contract builder is **incredibly clever** - it tracks what you've defined in your contract and **forces you to implement everything properly**. Here's how it works: #### **Token Enforcement** ๐Ÿท๏ธ ```typescript // โœ… Define tokens in contract const ButtonCls = contract() .tokens(["color.bg.primary", "color.text.primary"]) // โ† Declare tokens .def() .token({ // โ† MUST implement all declared tokens "color.bg.primary": { class: ["bg-blue-600"] }, "color.text.primary": { class: ["text-white"] }, // Missing "color.bg.primary"? TypeScript will SCREAM! ๐Ÿšจ }) .cls(); // โœ… Only works when all tokens are implemented ``` **What happens if you miss a token?** ```typescript // โŒ This will cause a TypeScript error! const BadButtonCls = contract() .tokens(["color.bg.primary", "color.text.primary"]) .def() .token({ "color.bg.primary": { class: ["bg-blue-600"] }, // Missing "color.text.primary" - TypeScript error! }) .cls(); // Error: "Tokens are defined on a contract, but you've not called token() definition method" ``` #### **Default Enforcement** โš™๏ธ ```typescript // โœ… Define variants in contract const ButtonCls = contract() .variant("size", ["sm", "md", "lg"]) // โ† Declare variants .def() .defaults({ // โ† MUST provide defaults for all variants size: "md", // Missing size default? TypeScript will SCREAM! ๐Ÿšจ }) .cls(); // โœ… Only works when all defaults are provided ``` **What happens if you miss a default?** ```typescript // โŒ This will cause a TypeScript error! const BadButtonCls = contract() .variant("size", ["sm", "md", "lg"]) .def() // Missing .defaults() call - TypeScript error! .cls(); // Error: "Variants are defined on a contract, but you've not called defaults() definition method" ``` #### **Slot Flexibility** ๐ŸŽช ```typescript // โœ… Define slots in contract const ButtonCls = contract() .slots(["root", "label"]) // โ† Declare available slots .def() .root({ // โ† Style only the slots you need root: { class: ["px-4", "py-2"] }, label: { class: ["font-medium"] }, // You're free to style any or all declared slots! ๐ŸŽจ }) .cls(); ``` **Flexibility at its finest:** ```typescript // โœ… Style only some slots const MinimalButton = contract() .slots(["root", "label", "icon"]) .def() .root({ root: { class: ["px-4", "py-2"] }, // label and icon are optional - no TypeScript errors! }) .cls(); // โœ… Style slots in different rules const DynamicButton = contract() .slots(["root", "label", "icon"]) .variant("size", ["sm", "md"]) .def() .root({ root: { class: ["px-4", "py-2"] }, }) .rule({ size: "lg" }, { icon: { class: ["w-6", "h-6"] }, // Add icon styling only for large buttons }) .cls(); ``` #### **Variant Type Safety** ๐ŸŽญ ```typescript // โœ… TypeScript knows exactly what variants are valid const ButtonCls = contract() .variant("size", ["sm", "md", "lg"]) .bool("disabled") .def() .rule({ size: "lg" }, { root: { class: ["px-6"] } }) // โœ… Valid .rule({ size: "xl" }, { root: { class: ["px-8"] } }) // โŒ TypeScript error! "xl" not in ["sm", "md", "lg"] .rule({ disabled: true }, { root: { class: ["opacity-50"] } }) // โœ… Valid .cls(); ``` ### **The Magic Behind the Scenes** ๐Ÿง™โ€โ™‚๏ธ CLS uses **advanced TypeScript features** to enforce correctness: 1. **Conditional Types** - Checks if tokens/variants exist in contract 2. **Template Literal Types** - Creates precise error messages 3. **Generic Constraints** - Ensures type safety throughout the chain 4. **Type-Level Flags** - Tracks completion state (hasToken, hasDefaults) ```typescript // This is what CLS does under the hood (simplified): type Builder<TContract, TState> = // If contract has tokens but you haven't called .token() Check.If<Token.Has<TContract>, TState["hasToken"]> extends false ? { cls(error: "Tokens are defined but you've not called token()"): never } : {} // If contract has variants but you haven't called .defaults() & Check.If<Variant.With<TContract>, TState["hasDefaults"]> extends false ? { cls(error: "Variants are defined but you've not called defaults()"): never } : {} // Only when everything is properly implemented & { cls(): Cls.Type<TContract> } ``` ### **Why This Matters** ๐ŸŽฏ - **๐Ÿ› No More Runtime Surprises** - Catch errors at compile time, not in production - **๐Ÿง  Better Developer Experience** - TypeScript guides you to correct usage - **๐Ÿ”’ Guaranteed Completeness** - Can't forget to implement required parts - **๐Ÿ“ Self-Documenting** - Contract serves as both interface and documentation - **๐Ÿš€ Refactoring Safety** - Change contract, TypeScript tells you what to update ### Basic Contract Builder Methods ```typescript import { contract } from '@use-pico/cls'; const ButtonCls = contract() .tokens(["color.bg.primary", "color.text.primary"]) // Add tokens .slots(["root", "label"]) // Add slots .variant("size", ["sm", "md", "lg"]) // Add string variants .bool("disabled") // Add boolean variants .def() // Start definition phase .token({ // Define token values "color.bg.primary": { class: ["bg-blue-600"] }, "color.text.primary": { class: ["text-white"] }, }) .root({ // Define base styling root: { class: ["px-4", "py-2", "rounded"] }, label: { class: ["font-medium"] }, }) .rule({ size: "lg" }, { root: { class: ["px-6", "py-3"] } }) // Add rules .defaults({ size: "md", disabled: false }) // Set defaults .cls(); // Create instance ``` ### Low-Level API - Advanced Usage > **โš ๏ธ Advanced Usage Warning:** The low-level `cls()` function is available but **quite complicated** and should generally be avoided in favor of the Contract Builder API. It's included here for completeness, but most developers should stick with the `contract()` approach. #### **1. Low-Level `cls()` Function** ```typescript import { cls } from '@use-pico/cls'; // Call cls() with everything embedded for maximum type safety const ButtonCls = cls( // Contract (structure) - embedded inline { tokens: ["color.bg.primary"], slot: ["root"], variant: { size: ["sm", "md"] } }, // Definition (styling) - embedded inline { token: { "color.bg.primary": { class: ["bg-blue-600"] } }, rules: [ { match: {}, // No conditions (root rule) slot: { root: { class: ["px-4", "py-2"], token: ["color.bg.primary"] } } } ], defaults: { size: "md" } } ); ``` #### **2. Runtime Overrides (Single Tweak)** ```typescript // โœ… Runtime overrides using single tweak object (undefined values are cleaned up) const { slots, variant } = ButtonCls.create({ variant: { size: "lg" }, slot: { root: { class: ["shadow-lg"] } }, token: { "color.bg.primary": { class: ["bg-indigo-600"] } // Override token value } }); ``` ## ๐Ÿงฌ Inheritance CLS makes inheritance **simple and type-safe**: ```typescript // Parent contract const BaseButtonCls = contract() .tokens(["color.bg", "color.text"]) .slots(["root"]) .variant("size", ["sm", "md", "lg"]) .def() .token({ "color.bg": { class: ["bg-gray-100"] }, "color.text": { class: ["text-gray-900"] }, }) .root({ root: { token: ["color.bg", "color.text"], class: ["border", "rounded"] } }) .defaults({ size: "md" }) .cls(); // Child contract inheriting from parent const PrimaryButtonCls = contract(BaseButtonCls.contract) .tokens(["color.bg.primary"]) // Add new tokens .bool("disabled") // Add new variants .def() .token({ "color.bg.primary": { class: ["bg-blue-600"] }, "color.bg": { class: ["bg-white"] }, // Override parent token }) .rule({ disabled: true }, { root: { class: ["opacity-50"] } }) .defaults({ size: "lg", disabled: false }) // Override parent defaults .cls(); ``` ## โš›๏ธ React Integration CLS provides **first-class React integration** with hooks, context, and HOCs. Here's a complete, real-world example: ### Complete Button Component Example ```tsx import { type Cls, useCls, withCls } from '@use-pico/cls'; import type { ButtonHTMLAttributes, FC, Ref } from 'react'; // Available type exports for advanced usage: // import type { Utils } from '@use-pico/cls'; // Utility type helpers // 1. Define your CLS instance (simplified version) const ButtonCls = contract() .slots(["wrapper", "root"]) .bool("disabled") .variant("size", ["sm", "md", "lg"]) .variant("tone", ["primary", "secondary", "danger"]) .def() .root({ wrapper: { class: ["Button-wrapper"] }, root: { class: ["Button-root", "flex", "items-center", "gap-2", "transition-all"], token: ["scale.default", "border.default"] } }) .rule({ size: "sm" }, { root: { token: ["size.sm"] } }) .rule({ size: "md" }, { root: { token: ["size.md"] } }) .rule({ size: "lg" }, { root: { token: ["size.lg"] } }) .rule({ tone: "primary" }, { root: { token: ["tone.primary"] } }) .rule({ tone: "secondary" }, { root: { token: ["tone.secondary"] } }) .rule({ tone: "danger" }, { root: { token: ["tone.danger"] } }) .rule({ disabled: true }, { wrapper: { class: ["cursor-not-allowed"] }, root: { token: ["disabled"] } }) .defaults({ size: "md", tone: "primary", disabled: false }) .cls(); // 2. Export the type and namespace for TypeScript integration export type ButtonCls = typeof ButtonCls; export namespace ButtonCls { export type Props<P = unknown> = Cls.Props<ButtonCls, P>; } // 3. Define component props with CLS integration export namespace Button { export interface Props extends ButtonCls.Props<ButtonHTMLAttributes<HTMLButtonElement>> { wrapperRef?: Ref<HTMLDivElement>; buttonRef?: Ref<HTMLButtonElement>; loading?: boolean; // CLS variant props with proper typing size?: Cls.VariantOf<ButtonCls, "size">; tone?: Cls.VariantOf<ButtonCls, "tone">; } } // 4. Base component with full CLS integration export const BaseButton: FC<Button.Props> = ({ wrapperRef, buttonRef, loading, size, tone, cls = ButtonCls, // Allow CLS override tweak, // User customization disabled, children, ...props }) => { // 5. useCls with internal and user configs const { slots, variant } = useCls(cls, { // Internal config (lower precedence) variant: { disabled: disabled || loading, size, tone, }, }, tweak // User tweak (highest precedence) ); // `variant` contains the resolved variant values from both component props and user tweaks // This gives you access to the final resolved state for component logic if needed // console.log(variant.size); // "lg" (from props or tweak override) // console.log(variant.disabled); // true/false (component logic or tweak override) // console.log(variant.tone); // "primary" (from props or tweak override) return ( <div ref={wrapperRef} className={slots.wrapper()} > <button ref={buttonRef} className={slots.root()} disabled={disabled || loading} {...props} > {loading && <span>โณ</span>} {children} </button> </div> ); }; // 6. Export with withCls HOC for external CLS access export const Button = withCls(BaseButton, ButtonCls); ``` ### Key React Integration Features #### **1. Props Extension Pattern** ๐Ÿ”— ```tsx // Extend CLS props with native HTML attributes export interface Props extends ButtonCls.Props<ButtonHTMLAttributes<HTMLButtonElement>> { // Custom props loading?: boolean; // CLS variants with proper typing size?: Cls.VariantOf<ButtonCls, "size">; tone?: Cls.VariantOf<ButtonCls, "tone">; } ``` #### **2. Nested Array Support in Tweak Prop** ๐ŸŽฏ The `tweak` prop in `Cls.Props` supports **nested arrays** for complex tweak combinations: ```tsx // Single tweak object tweak?: { variant: { size: "lg" }, slot: { root: { class: ["custom"] } } } // Array of tweaks (last takes precedence) tweak?: [ { variant: { size: "sm" } }, { slot: { root: { class: ["override"] } } } ] // Nested arrays with undefined values (automatically filtered) tweak?: [ { variant: { size: "md" } }, [ undefined, { slot: { root: { class: ["nested"] } } }, undefined ], { variant: { tone: "primary" } } ] // Deep nesting (up to 10 levels supported) tweak?: [ { variant: { size: "lg" } }, [ [ { slot: { root: { class: ["deep-nested"] } } } ] ] ] ``` **Key Features:** - **๐Ÿ”„ Automatic Flattening**: Nested arrays are automatically flattened up to 10 levels deep - **๐Ÿงน Undefined Filtering**: `undefined` values are automatically removed - **๐Ÿ“ˆ Precedence Order**: Later tweaks override earlier ones (left to right) - **๐ŸŽฏ Type Safety**: Full TypeScript support for all nested structures **Practical Example - Complex Tweak Composition:** ```tsx const MyButton = ({ size, tone, disabled, loading, userTweak }) => { const { slots, variant } = useCls(ButtonCls, { // Complex nested tweak structure tweak: [ // Base component logic { variant: { size, tone, disabled: disabled || loading } }, // Conditional styling based on state loading ? { slot: { root: { class: ["loading"] } } } : undefined, // Nested array for complex overrides [ { slot: { root: { class: ["base-override"] } } }, disabled ? { slot: { root: { class: ["disabled-override"] } } } : undefined, ], // User customization (highest precedence) userTweak ] }); return <button className={slots.root()}>Button</button>; }; ``` #### **3. useCls with Multiple Tweaks** โš™๏ธ ```tsx const { slots, variant } = useCls( cls, // CLS instance (can be overridden) { // Internal config (component logic) - LOWEST PRECEDENCE variant: { disabled: disabled || loading, // Component-controlled logic }, }, { // Props config (from component props) - MEDIUM PRECEDENCE variant: { size, tone } }, tweak // User config (from tweak prop) - HIGHEST PRECEDENCE ); ``` > **๐Ÿ’ก Precedence Rule:** Tweaks are processed in order, with **last tweak taking precedence** over earlier ones. All tweaks are cleaned up (undefined values removed), so you can safely pass partial objects without affecting the final result. #### **3. withCls HOC** ๐ŸŽญ ```tsx // Enables external CLS access export const Button = withCls(BaseButton, ButtonCls); // Now you can access: // Button.cls - The full CLS instance // Button.contract - The contract (structure definition) // Button.definition - The definition (styling values) // Button["~slots"] - Internal slots kit (advanced usage) ``` **Advanced withCls Usage:** ```tsx // Access contract for extending const CustomButton = contract(Button.contract) .variant("style", ["outline", "ghost"]) .def() .rule({ style: "outline" }, { root: { class: ["border-2"] } }) .cls(); // Access definition for inspection console.log(Button.definition.defaults); // { size: "md", tone: "primary" } console.log(Button.definition.rules); // All styling rules ``` ### Advanced Usage Examples #### **Tweak Precedence Pattern** ๐ŸŽฏ ```tsx // Complete example showing tweak precedence: user > props > component interface ButtonProps { size?: Cls.VariantOf<ButtonCls, "size">; tone?: Cls.VariantOf<ButtonCls, "tone">; disabled?: boolean; loading?: boolean; tweak?: Cls.Props<ButtonCls>["tweak"]; // User customization } const Button: FC<ButtonProps> = ({ size, tone, disabled, loading, tweak, // User tweak - HIGHEST PRECEDENCE children }) => { const { slots, variant } = useCls(ButtonCls, { // 1. Component logic (lowest precedence) variant: { disabled: disabled || loading } }, { // 2. Props from component (medium precedence) variant: { size, tone } }, tweak // 3. User tweak (highest precedence) ); return ( <button className={slots.root()}> {variant.loading ? "Loading..." : children} </button> ); }; // Usage examples: <Button size="md" tone="primary" tweak={{ variant: { size: "lg" } }} // User overrides size to "lg" > Button Text </Button> // Result: size="lg" (user tweak wins), tone="primary" (from props) ``` #### **Custom CLS Instance** ```tsx // Create custom button variant const CustomButtonCls = contract(ButtonCls.contract) .variant("style", ["outline", "ghost"]) .def() .rule({ style: "outline" }, { root: { class: ["border-2", "bg-transparent"] } }) .rule({ style: "ghost" }, { root: { class: ["bg-transparent", "hover:bg-gray-100"] } }) .cls(); // Use custom CLS with type safety <Button cls={ButtonCls.use(CustomButtonCls)} style="outline" tone="danger"> Custom Button </Button> ``` > **๐ŸŽฏ Type Safety Trick:** `ButtonCls.use(CustomButtonCls)` ensures **type compatibility** - `CustomButtonCls` *must* extend `ButtonCls` or TypeScript will error! This prevents incompatible CLS instances from being passed. #### **Runtime Customization** ```tsx // User customization via tweak prop <Button size="lg" tone="primary" tweak={{ slot: what.slot({ root: { class: ["shadow-lg", "hover:shadow-xl"] } }), variant: { tone: "secondary" } // Override tone })} > Customized Button </Button> ``` #### **Tweak Utility for Advanced Merging** ```tsx // Manual tweak merging for advanced scenarios const MyButton = ({ userTweak, size, tone, disabled }) => { const finalTweak = ButtonCls.tweak( { variant: { disabled } }, // Component logic (lowest precedence) { variant: { size, tone } }, // Props (medium precedence) userTweak // User customization (highest precedence) ); const { slots, variant } = ButtonCls.create(finalTweak); return <button className={slots.root()}>Button</button>; }; // Alternative: Direct usage (simplest) const SimpleButton = ({ userTweak }) => { const { slots, variant } = ButtonCls.create(userTweak); return <button className={slots.root()}>Button</button>; }; ``` #### **Context Integration** ```tsx import { TokenContext, VariantProvider } from '@use-pico/cls'; const App = () => ( <TokenContext value={ThemeCls}> <div> {/* All buttons inherit theme tokens */} <Button tone="primary">Themed Button</Button> <Button tone="secondary">Another Themed Button</Button> {/* Scope variant overrides to a subtree with automatic type inference */} <VariantProvider cls={ButtonCls} variant={{ size: "lg" }}> <div> {/* All buttons in this subtree will be large */} <Button tone="primary">Large Themed Button</Button> <Button tone="secondary">Another Large Themed Button</Button> {/* Nested VariantProvider with inheritance - merges parent variants */} <VariantProvider cls={ButtonCls} variant={{ tone: "danger" }} inherit> <div> {/* These buttons will be large AND danger (inherited size + new tone) */} <Button>Large Danger Button</Button> <Button>Another Large Danger Button</Button> </div> </VariantProvider> </div> </VariantProvider> </div> </TokenContext> ); ``` > **๐Ÿ—๏ธ Design System Compatibility:** If you have an external Design System (e.g., `DesignCls` from which `ThemeCls` extends), use `DesignCls.use(ThemeCls)` in the provider to ensure **design system compatibility**. This guarantees everything matches and runs as expected across your entire application. ### Available React Hooks #### **useCls** - The Main Hook ๐ŸŽฏ ```tsx const { slots, variant } = useCls(ButtonCls, ...tweaks); ``` **`useCls` is the main hook** for CLS in React components. It returns both **slots** and **variants** for maximum flexibility: ```tsx // Simple usage - single tweak (automatically subscribes to TokenContext and VariantContext) const { slots, variant } = useCls(ButtonCls, tweak); // Multiple tweaks with precedence (last takes precedence) const { slots, variant } = useCls(ButtonCls, internalTweak, // Lowest precedence propsTweak, // Medium precedence userTweak // Highest precedence ); // Use slots for styling <button className={slots.root()}>Button</button> // Individual slots also support tweaks! <button className={slots.root( { variant: { size: "lg" } }, // Override size for this specific slot { slot: { root: { class: ["shadow-lg"] } } } // Add custom classes )}>Button</button> // Use variants for component logic console.log(variant.size); // "lg" console.log(variant.disabled); // true/false console.log(variant.tone); // resolved from theme or props ``` **Key Features:** - โœ… **Automatically connected to TokenContext** - Global theme inheritance works seamlessly - โœ… **Automatically connected to VariantContext** - Scoped variant overrides work automatically - โœ… **Performance optimized** - Minimal overhead for common use cases - โœ… **Type-safe** - Full TypeScript support with proper inference - โœ… **Complete API** - Both slots and variants in one hook - โœ… **Single source of truth** - Access resolved variant values when needed **When to Use Variants:** - ๐ŸŽฏ **Component logic** that needs to access resolved variant values - ๐ŸŽฏ **Conditional rendering** based on variant combinations - ๐ŸŽฏ **Debugging** - Inspect what variants are actually applied - ๐ŸŽฏ **Analytics** - Track which variant combinations users interact with > **๐Ÿ’ก Pro Tip:** This is the hook you'll use **100% of the time**. It provides everything you need - slots for styling and variants for component logic - all in one convenient, type-safe package! #### **useTokenContext** - Access Token Context ๐Ÿ”— ```tsx const context = useTokenContext<ButtonCls>(); ``` **Low-level hook** for accessing the current CLS context: ```tsx // Access current theme const themeCls = useTokenContext(); // With type safety const buttonTheme = useTokenContext<ButtonCls>(); ``` **When to Use:** - ๐ŸŽฏ **Custom hooks** that need theme access - ๐ŸŽฏ **Theme switching logic** - ๐ŸŽฏ **Advanced composition patterns** > **๐Ÿ”ง Advanced Usage:** This is a low-level hook. Most components should use `useCls` which automatically connects to context. #### **useVariantContext** - Access Variant Context ๐ŸŽ›๏ธ ```tsx const variantContext = useVariantContext(); ``` **Hook** for accessing the current VariantContext values: ```tsx // Access current variant context const variants = useVariantContext(); ``` **When to Use:** - ๐ŸŽฏ **Custom hooks** that need variant access - ๐ŸŽฏ **Conditional logic** based on variant values - ๐ŸŽฏ **Advanced composition patterns** > **๐Ÿ”ง Advanced Usage:** This is a low-level hook. Most components should use `useCls` which automatically integrates with VariantContext. #### **VariantProvider** - Scoped Variant Context ๐ŸŽ›๏ธ **React component** for providing type-safe variant overrides to a subtree: ```tsx <VariantProvider cls={ButtonCls} variant={{ size: "lg" }} inherit> <YourComponents /> </VariantProvider> ``` **Props:** - **`cls`** - CLS instance (used for type inference) - **`variant`** - Variant object with overrides (not whole tweak) - **`inherit`** - Whether to merge with parent VariantContext (default: `false`) **Inheritance Behavior:** - **`inherit={false}`** (default) - Replaces parent variants completely - **`inherit={true}`** - Merges with parent variants, lower VariantProvider takes precedence ```tsx // Parent provides size: "lg" <VariantProvider cls={ButtonCls} variant={{ size: "lg" }}> {/* Child replaces parent completely */} <VariantProvider cls={ButtonCls} variant={{ tone: "danger" }}> {/* Result: { tone: "danger" } - size is lost */} </VariantProvider> {/* Child inherits and merges with parent */} <VariantProvider cls={ButtonCls} variant={{ tone: "danger" }} inherit> {/* Result: { size: "lg", tone: "danger" } - both preserved */} </VariantProvider> {/* Reset all variants from parent - provide empty variants and disable inheritance */} <VariantProvider cls={ButtonCls} variant={{}}> {/* Result: {} - no variants from parent or child */} </VariantProvider> </VariantProvider> ``` **When to Use:** - ๐ŸŽฏ **Scoped variant overrides** for specific UI sections - ๐ŸŽฏ **Nested theming** with inheritance - ๐ŸŽฏ **Conditional styling** based on variant context - ๐ŸŽฏ **Resetting variants** - Use `variant={{}}` to clear all parent variants **Common Patterns:** ```tsx // Override specific variants <VariantProvider cls={ButtonCls} variant={{ size: "lg" }}> <Button>Large Button</Button> </VariantProvider> // Inherit and extend parent variants <VariantProvider cls={ButtonCls} variant={{ tone: "danger" }} inherit> <Button>Danger Button with inherited size</Button> </VariantProvider> // Reset all variants (clear parent context) <VariantProvider cls={ButtonCls} variant={{}}> <Button>Button with default variants only</Button> </VariantProvider> ``` #### **wrap** - Type-Safe VariantProvider Factory ๐Ÿญ **Utility function** for creating type-safe `VariantProvider` components: ```tsx import { wrap } from '@use-pico/cls'; // Create a type-safe VariantProvider factory for a specific CLS const ButtonWrapper = wrap(ButtonCls); // Use it without needing to pass cls prop <ButtonWrapper.VariantProvider variant={{ size: "lg" }}> <YourComponents /> </ButtonWrapper.VariantProvider> ``` **Benefits:** - ๐ŸŽฏ **Type safety** - VariantProvider is pre-configured with the correct CLS type - ๐ŸŽฏ **Cleaner syntax** - No need to pass `cls` prop every time - ๐ŸŽฏ **Reusable** - Create once, use everywhere with the same CLS **When to Use:** - ๐ŸŽฏ **Component libraries** - Pre-configure VariantProviders for your components - ๐ŸŽฏ **Design systems** - Create typed providers for consistent usage - ๐ŸŽฏ **Large applications** - Reduce boilerplate when using the same CLS repeatedly #### **useClsMemo** - Memoized CLS Hook ๐Ÿš€ ```tsx const { slots, variant } = useClsMemo(ButtonCls, tweaks, deps); ``` **Performance-optimized version** of `useCls` that memoizes both slots and variants using `useMemo`: ```tsx // Simple usage - single tweak with memoization const { slots, variant } = useClsMemo(ButtonCls, tweak, [tweak]); // Array of tweaks with memoization const MyButton = ({ size, tone, disabled, loading, tweak }) => { const { slots, variant } = useClsMemo( ButtonCls, [ // Array of tweaks (last takes precedence) { // Component logic - LOWEST PRECEDENCE variant: { disabled: disabled || loading // Component-controlled logic } }, { // Props from component - MEDIUM PRECEDENCE variant: { size, tone } }, tweak // User customization from props - HIGHEST PRECEDENCE ], [size, tone, disabled, loading, tweak] // Only recompute when these change ); // Use slots for styling // Use variants for component logic console.log(variant.size); // "lg" console.log(variant.disabled); // true/false console.log(variant.tone); // resolved from theme or props return <button className={slots.root()}>Button</button>; }; ``` **When to Use `useClsMemo`:** - ๐ŸŽฏ **Performance-critical components** with stable props - ๐ŸŽฏ **Large component trees** where memoization prevents cascading re-renders - ๐ŸŽฏ **Components with expensive CLS computations** - ๐ŸŽฏ **Analytics or logging** based on variant combinations - ๐ŸŽฏ **Conditional rendering** based on resolved variant states > **โš ๏ธ Important Dependency Note:** `useClsMemo` is **dependency-driven**. If a user provides a dynamic `tweak` prop to a component, the memoized hook won't automatically detect changes in the tweak function's behavior. You must include all relevant dependencies in the `deps` array, or the hook will use stale values. For dynamic user tweaks, consider using the non-memoized version (`useCls`) instead. **Practical Example - Performance Optimization:** ```tsx // Performance-critical button with memoization const OptimizedButton = ({ size, tone, disabled, loading, tweak }) => { const { slots, variant } = useClsMemo( ButtonCls, [ { // Internal config (lower precedence) variant: { size, tone, disabled: disabled || loading } }, tweak // User tweak (highest precedence) ], [size, tone, disabled, loading, tweak] // All dependencies included ); return ( <button className={slots.root()}> {variant.loading ? "Loading..." : "Click me"} </button> ); }; ``` ## ๐ŸŽฏ Advanced Features ### Runtime Overrides ```typescript // Override tokens at creation time (variadic tweak parameters) const { slots, variant } = ButtonCls.create({ variant: { size: "lg" }, token: { // Runtime override of token values "color.bg.primary": { class: ["bg-indigo-600"] } } }); // Multiple tweaks with precedence const { slots, variant } = ButtonCls.create( internalTweak, // Lowest precedence propsTweak, // Medium precedence userTweak // Highest precedence ); ``` ### Individual Slot Tweaks ```typescript // Individual slots support variadic tweaks too! const { slots, variant } = ButtonCls.create(); // Basic slot usage <button className={slots.root()}>Button</button> // Slot with variant override <button className={slots.root({ variant: { size: "lg" } })}>Large Button</button> // Slot with multiple tweaks <button className={slots.root( { variant: { size: "lg" } }, // Override size { slot: { root: { class: ["shadow-lg"] } } } // Add custom classes )}>Custom Button</button> // Slot with token override <button className={slots.root({ token: { "color.bg.primary": { class: ["bg-purple-600"] } } })}>Purple Button</button> ``` ### Override and Clear Flags ```typescript // Use the override flag to explicitly override previous tweaks const { slots, variant } = ButtonCls.create( { variant: { size: "md" } }, // This will be overridden { override: true, // Override flag variant: { size: "lg" } // This will override the previous size } ); // Use the clear flag to completely reset and start fresh const { slots, variant } = ButtonCls.create( { variant: { size: "md" } }, // This will be completely ignored { slot: { root: { class: ["px-4"] } } }, // This will be completely ignored { clear: true, // Clear flag - kills all previous tweaks variant: { size: "lg" } // Only this will be applied } ); // Override flag works with slots too <button className={slots.root( { slot: { root: { class: ["px-4"] } } }, // This will be overridden { override: true, // Override flag slot: { root: { class: ["px-8"] } } // This will override the previous padding } )}>Button</button> // Clear flag works with slots too <button className={slots.root( { slot: { root: { class: ["px-4"] } } }, // This will be completely ignored { slot: { root: { class: ["py-2"] } } }, // This will be completely ignored { clear: true, // Clear flag - kills all previous tweaks slot: { root: { class: ["px-8", "py-4"] } } // Only this will