tailwind-to-style 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # tailwind-to-style
2
2
 
3
3
  [📦 View on npm](https://www.npmjs.com/package/tailwind-to-style)
4
+ | [🌐 Landing Page](https://bigetion.github.io/tailwind-to-style/landing.html)
5
+ | [🛝 Playground](https://bigetion.github.io/tailwind-to-style/sandbox.html)
4
6
 
5
7
  [![npm version](https://img.shields.io/npm/v/tailwind-to-style.svg)](https://www.npmjs.com/package/tailwind-to-style)
6
8
  [![Build Status](https://github.com/Bigetion/tailwind-to-style/workflows/CI%2FCD/badge.svg)](https://github.com/Bigetion/tailwind-to-style/actions)
@@ -22,6 +24,8 @@
22
24
  - [`tws()` — Tailwind to Inline Styles](#tws--tailwind-to-inline-styles)
23
25
  - [`twsx()` — CSS-in-JS Engine](#twsx--css-in-js-engine)
24
26
  - [`twsxVariants()` — Component Variant System](#twsxvariants--component-variant-system)
27
+ - [`twsxClassName()` — Unified CSS-in-JS](#twsxclassname--unified-css-in-js)
28
+ - [`tw()` — Atomic CSS Classes](#tw--atomic-css-classes)
25
29
  - [`cx()` — Conditional Class Builder](#cx--conditional-class-builder)
26
30
  - [Configuration & Plugins](#configuration--plugins)
27
31
  - [`configure()` — Custom Theme](#configure--custom-theme)
@@ -50,6 +54,8 @@
50
54
  | **Full Tailwind Support** | All utilities, responsive, pseudo-states, arbitrary values |
51
55
  | **SCSS-like Nesting** | `twsx()` for complex nested selector-based styles |
52
56
  | **Variant System** | Type-safe component variants like CVA/tailwind-variants |
57
+ | **Unified CSS-in-JS** | `twsxClassName()` — one API for basic/variants/slots |
58
+ | **Atomic CSS Classes** | `tw()` — reusable classes with hover/responsive support |
53
59
  | **Conditional Classes** | Built-in `cx()` utility (like clsx/classnames) |
54
60
  | **SSR Support** | Server-side rendering with `startSSR()`/`stopSSR()` |
55
61
  | **@css Directive** | Inject raw CSS for vendor-specific or complex properties |
@@ -179,6 +185,8 @@ tws('p-0.5 m-1.5 gap-2.5')
179
185
 
180
186
  Generates real CSS from Tailwind classes with full selector support, SCSS-like nesting, and auto-injects a `<style>` tag into the DOM.
181
187
 
188
+ > **HMR-safe** — each `twsx()` call owns a stable slot in the injected style tag keyed by its top-level selectors. When you edit styles during development, the old slot is **replaced** (not appended), so changes are reflected immediately without a hard refresh.
189
+
182
190
  ```javascript
183
191
  import { twsx } from 'tailwind-to-style'
184
192
 
@@ -385,6 +393,303 @@ btnClass({ 'opacity-50': disabled })
385
393
 
386
394
  ---
387
395
 
396
+ ### `twsxClassName()` — Unified CSS-in-JS
397
+
398
+ The complete CSS-in-JS solution with automatic mode detection. Generates scoped class names with auto-injected CSS from Tailwind classes.
399
+
400
+ ```javascript
401
+ import { twsxClassName, tw } from 'tailwind-to-style'
402
+ ```
403
+
404
+ **Basic Mode** — Simple className generation:
405
+
406
+ ```javascript
407
+ // Returns: "btn-a1b2c3d4" (or "btn" if hash: false)
408
+ const button = twsxClassName({
409
+ name: 'btn',
410
+ _: 'px-4 py-2 bg-blue-500 text-white rounded-lg',
411
+ hover: 'bg-blue-600',
412
+ focus: 'ring-2 ring-blue-500',
413
+ active: 'bg-blue-700',
414
+ dark: 'bg-blue-400',
415
+ })
416
+
417
+ // Usage
418
+ <button class="${button}">Click me</button>
419
+ ```
420
+
421
+ **Variants Mode** — Component variants like CVA/tailwind-variants:
422
+
423
+ ```javascript
424
+ const button = twsxClassName({
425
+ name: 'btn',
426
+ base: 'px-4 py-2 font-medium rounded-lg transition-colors',
427
+ variants: {
428
+ variant: {
429
+ primary: 'bg-blue-500 text-white hover:bg-blue-600',
430
+ secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
431
+ outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50',
432
+ },
433
+ size: {
434
+ sm: 'text-sm px-3 py-1.5',
435
+ md: 'text-base px-4 py-2',
436
+ lg: 'text-lg px-6 py-3',
437
+ },
438
+ disabled: {
439
+ true: 'opacity-50 cursor-not-allowed',
440
+ false: 'cursor-pointer',
441
+ }
442
+ },
443
+ compoundVariants: [
444
+ { variant: 'primary', size: 'lg', class: 'shadow-lg' },
445
+ ],
446
+ defaultVariants: {
447
+ variant: 'primary',
448
+ size: 'md',
449
+ disabled: false,
450
+ },
451
+ })
452
+
453
+ // Usage
454
+ <button class="${button()}">Default</button>
455
+ <button class="${button({ variant: 'secondary', size: 'lg' })}">Large Secondary</button>
456
+ <button class="${button({ disabled: true })}">Disabled</button>
457
+ ```
458
+
459
+ **Slots Mode** — Multi-part components:
460
+
461
+ ```javascript
462
+ const card = twsxClassName({
463
+ name: 'card',
464
+ slots: {
465
+ root: 'bg-white rounded-xl shadow-lg overflow-hidden',
466
+ header: 'px-6 py-4 border-b border-gray-200',
467
+ title: 'text-xl font-bold text-gray-900',
468
+ body: 'px-6 py-4',
469
+ footer: 'px-6 py-4 border-t border-gray-200',
470
+ },
471
+ variants: {
472
+ variant: {
473
+ elevated: { root: 'shadow-2xl' },
474
+ outlined: { root: 'shadow-none border-2 border-gray-200' },
475
+ },
476
+ },
477
+ })
478
+
479
+ // Usage
480
+ const classes = card({ variant: 'elevated' })
481
+ <div class="${classes.root}">
482
+ <div class="${classes.header}">
483
+ <h2 class="${classes.title}">Card Title</h2>
484
+ </div>
485
+ <div class="${classes.body}">Content here</div>
486
+ <div class="${classes.footer}">Footer</div>
487
+ </div>
488
+ ```
489
+
490
+ **Namespace Methods:**
491
+
492
+ | Category | Method | Description |
493
+ |----------|--------|-------------|
494
+ | **Config** | `config(options)` | Set global options (prefix, hash, hashLength) |
495
+ | | `getConfig()` | Get current configuration |
496
+ | **Tokens** | `defineTokens(tokens)` | Define design tokens |
497
+ | | `getTokens()` | Get all defined tokens |
498
+ | | `setToken(path, value)` | Set a single token value |
499
+ | **Themes** | `createTheme(name, tokens)` | Create a named theme |
500
+ | | `setTheme(name)` | Activate a theme |
501
+ | | `getTheme()` | Get active theme name |
502
+ | | `getThemes()` | Get all defined themes |
503
+ | **Extend** | `extend(base, extension)` | Extend an existing config |
504
+ | | `compose(...configs)` | Compose multiple configs |
505
+ | | `merge(...classes)` | Merge class values (like cx) |
506
+ | **Animation** | `defineAnimation(name, config)` | Register custom animation |
507
+ | | `getAnimations()` | Get all registered animations |
508
+ | **SSR** | `getCSS(className)` | Get CSS for a className |
509
+ | | `getAllCSS()` | Get all generated CSS |
510
+ | | `extractCSS()` | Extract as `<style>` tag |
511
+ | **Cache** | `clearCache()` | Clear all caches |
512
+ | | `getCacheStats()` | Get cache statistics |
513
+ | **Utility** | `tw(classes)` | Atomic CSS class generator |
514
+
515
+ ```javascript
516
+ // ═══════════════════════════════════════════════════════════════
517
+ // Configuration
518
+ // ═══════════════════════════════════════════════════════════════
519
+
520
+ // Global configuration
521
+ twsxClassName.config({ prefix: 'my-app', hash: false, hashLength: 8 })
522
+ twsxClassName.getConfig() // → { prefix: 'my-app', hash: false, ... }
523
+
524
+ // ═══════════════════════════════════════════════════════════════
525
+ // Design Tokens
526
+ // ═══════════════════════════════════════════════════════════════
527
+
528
+ // Define tokens
529
+ twsxClassName.defineTokens({
530
+ colors: { primary: '#3b82f6', danger: '#ef4444' },
531
+ spacing: { sm: '0.5rem', md: '1rem' },
532
+ })
533
+
534
+ // Get all tokens
535
+ twsxClassName.getTokens() // → { colors: {...}, spacing: {...} }
536
+
537
+ // Set a single token
538
+ twsxClassName.setToken('colors.success', '#10b981')
539
+
540
+ // Use tokens in styles (use $path.to.token syntax)
541
+ const btn = twsxClassName({ _: 'bg-$colors.primary p-$spacing.md' })
542
+
543
+ // ═══════════════════════════════════════════════════════════════
544
+ // Theme System
545
+ // ═══════════════════════════════════════════════════════════════
546
+
547
+ // Create named themes
548
+ twsxClassName.createTheme('dark', { background: '#1f2937', text: '#f9fafb' })
549
+ twsxClassName.createTheme('light', { background: '#ffffff', text: '#111827' })
550
+
551
+ // Switch themes
552
+ twsxClassName.setTheme('dark')
553
+
554
+ // Get current/all themes
555
+ twsxClassName.getTheme() // → 'dark'
556
+ twsxClassName.getThemes() // → { dark: {...}, light: {...} }
557
+
558
+ // ═══════════════════════════════════════════════════════════════
559
+ // Extend & Compose
560
+ // ═══════════════════════════════════════════════════════════════
561
+
562
+ // Extend existing config
563
+ const iconButton = twsxClassName.extend(button, {
564
+ base: 'p-0 aspect-square',
565
+ variants: { size: { sm: 'w-8 h-8', md: 'w-10 h-10' } },
566
+ })
567
+
568
+ // Compose multiple configs
569
+ const combined = twsxClassName.compose(baseStyles, variants, overrides)
570
+
571
+ // Merge classes (like cx)
572
+ twsxClassName.merge('p-4', isActive && 'bg-blue-500', { 'opacity-50': disabled })
573
+
574
+ // ═══════════════════════════════════════════════════════════════
575
+ // Animations
576
+ // ═══════════════════════════════════════════════════════════════
577
+
578
+ // Define custom animation preset
579
+ twsxClassName.defineAnimation('myFade', {
580
+ keyframes: { '0%': { opacity: 0 }, '100%': { opacity: 1 } },
581
+ duration: '300ms',
582
+ timing: 'ease-out',
583
+ })
584
+
585
+ // Get all registered animations
586
+ twsxClassName.getAnimations() // → { myFade: {...}, ... }
587
+
588
+ // Use in config
589
+ const modal = twsxClassName({
590
+ _: 'fixed inset-0',
591
+ animation: 'myFade', // or built-in: 'fadeIn', 'slideUp', etc.
592
+ })
593
+
594
+ // ═══════════════════════════════════════════════════════════════
595
+ // SSR Support
596
+ // ═══════════════════════════════════════════════════════════════
597
+
598
+ // Get CSS for specific className
599
+ twsxClassName.getCSS('btn-a1b2c3d4')
600
+
601
+ // Get all generated CSS
602
+ twsxClassName.getAllCSS()
603
+
604
+ // Extract as style tag (for SSR)
605
+ const styleTag = twsxClassName.extractCSS()
606
+ // → '<style data-twsx-classname>...</style>'
607
+
608
+ // ═══════════════════════════════════════════════════════════════
609
+ // Cache Management
610
+ // ═══════════════════════════════════════════════════════════════
611
+
612
+ // Clear all caches
613
+ twsxClassName.clearCache()
614
+
615
+ // Get cache statistics
616
+ twsxClassName.getCacheStats()
617
+ // → { classNameCacheSize: 42, cssCacheSize: 38, styleRegistrySize: 15 }
618
+ ```
619
+
620
+ ---
621
+
622
+ ### `tw()` — Atomic CSS Classes
623
+
624
+ Generates reusable atomic CSS classes from Tailwind utilities. Unlike `tws()` which returns inline styles, `tw()` returns class names with auto-injected CSS — supporting pseudo-classes and responsive breakpoints.
625
+
626
+ ```javascript
627
+ import { tw } from 'tailwind-to-style'
628
+ // or: import { twsxClassName } from '...' → twsxClassName.tw(...)
629
+ ```
630
+
631
+ **Basic Usage:**
632
+
633
+ ```javascript
634
+ // Returns: "tw-flex tw-gap-3 tw-items-center"
635
+ tw('flex gap-3 items-center')
636
+
637
+ // Usage
638
+ <div class="${tw('flex gap-3 items-center')}">
639
+ <span>Item 1</span>
640
+ <span>Item 2</span>
641
+ </div>
642
+ ```
643
+
644
+ **With Pseudo-classes:**
645
+
646
+ ```javascript
647
+ // Returns: "tw-bg-gray-100 tw-hover-bg-gray-200 tw-focus-ring-2"
648
+ tw('bg-gray-100 hover:bg-gray-200 focus:ring-2')
649
+
650
+ // Unlike tws(), these actually work!
651
+ <div class="${tw('opacity-0 hover:opacity-100 transition-opacity')}">
652
+ Hover to reveal
653
+ </div>
654
+ ```
655
+
656
+ **With Responsive Breakpoints:**
657
+
658
+ ```javascript
659
+ // Returns: "tw-flex tw-flex-col tw-md-flex-row tw-lg-gap-8"
660
+ tw('flex flex-col md:flex-row lg:gap-8')
661
+
662
+ <div class="${tw('grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4')}">
663
+ <!-- Responsive grid -->
664
+ </div>
665
+ ```
666
+
667
+ **Combining with twsxClassName:**
668
+
669
+ ```javascript
670
+ const button = twsxClassName({
671
+ name: 'btn',
672
+ base: 'px-4 py-2 rounded-lg',
673
+ variants: { color: { primary: 'bg-blue-500', danger: 'bg-red-500' } },
674
+ })
675
+
676
+ // Use tw() for layout, twsxClassName for components
677
+ <div class="${tw('flex gap-3 flex-wrap')}">
678
+ <button class="${button({ color: 'primary' })}">Save</button>
679
+ <button class="${button({ color: 'danger' })}">Delete</button>
680
+ </div>
681
+ ```
682
+
683
+ **When to use which:**
684
+
685
+ | Function | Use Case | Example |
686
+ |----------|----------|---------|
687
+ | `tws()` | Quick inline styles, no pseudo | `style="${tws('p-4 bg-blue')}"` |
688
+ | `tw()` | Reusable layout utilities | `class="${tw('flex gap-3 hover:...')}"` |
689
+ | `twsxClassName` | Components with variants | `class="${button({ size: 'lg' })}"` |
690
+
691
+ ---
692
+
388
693
  ## Configuration & Plugins
389
694
 
390
695
  ### `configure()` — Custom Theme
@@ -502,12 +807,49 @@ const fullHtml = `
502
807
  | `IS_BROWSER` | `true` in browser environment |
503
808
  | `IS_SERVER` | `true` in Node.js/server environment |
504
809
 
810
+ ### Advanced SSR Collector
811
+
812
+ For more control over SSR CSS collection:
813
+
814
+ ```javascript
815
+ import { createSSRCollector } from 'tailwind-to-style'
816
+
817
+ // Create collector with options
818
+ const ssr = createSSRCollector({
819
+ dedupe: true, // Remove duplicate rules
820
+ minify: true, // Minify output CSS
821
+ sort: true, // Sort by specificity
822
+ })
823
+
824
+ // Render app
825
+ const html = renderToString(<App />)
826
+
827
+ // Extract CSS
828
+ const css = ssr.extractRaw() // Raw CSS string
829
+ const styleTag = ssr.extract({ id: 'ssr-css' }) // <style id="ssr-css">...</style>
830
+
831
+ // Or extract critical CSS (above-the-fold)
832
+ const { critical, rest, stats } = ssr.extractCritical({ maxSize: 14000 })
833
+ // critical: CSS under 14KB limit
834
+ // rest: remaining CSS to lazy-load
835
+ // stats: { criticalSize, criticalCount, totalCount }
836
+
837
+ // Utilities
838
+ ssr.peek() // Preview CSS without stopping
839
+ ssr.count // Number of rules collected
840
+ ssr.uniqueCount // Unique rules (after dedupe)
841
+ ssr.clear(true) // Clear and restart collecting
842
+ ssr.getStats() // { ruleCount, uniqueCount, totalSize, minifiedSize }
843
+ ```
844
+
505
845
  ---
506
846
 
507
847
  ## Animation System
508
848
 
509
849
  ### Built-in CSS Animations
510
850
 
851
+ Tailwind animation utilities work out of the box:
852
+
511
853
  ```javascript
512
854
  tws('animate-spin') // → animation: spin 1s linear infinite
513
855
  tws('animate-bounce') // → animation: bounce 1s infinite
@@ -515,32 +857,104 @@ tws('animate-pulse') // → animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) i
515
857
  tws('animate-ping') // → animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite
516
858
  ```
517
859
 
518
- ### Web Animations API
860
+ ### Programmatic Animations (Web Animations API)
861
+
862
+ For interactive animations with full control:
863
+
864
+ ```javascript
865
+ import { animate, chain, stagger, parallel, transition } from 'tailwind-to-style'
866
+
867
+ // Single element animation
868
+ const ctrl = animate(element, 'fadeIn', { duration: 300 })
869
+ ctrl.pause() // Pause
870
+ ctrl.play() // Resume
871
+ ctrl.reverse() // Reverse
872
+ ctrl.cancel() // Cancel
873
+ await ctrl.finished // Wait for completion
874
+
875
+ // Sequential animations
876
+ await chain(element, ['fadeIn', 'slideUp', 'pulse'])
877
+
878
+ // With custom options per step
879
+ await chain(element, [
880
+ 'fadeIn',
881
+ { name: 'slideUp', delay: 200 },
882
+ { name: 'pulse', options: { iterations: 2 } }
883
+ ])
884
+
885
+ // Staggered animations (lists, grids)
886
+ stagger('.card', 'fadeIn', {
887
+ delay: 50, // 50ms between each
888
+ from: 'start', // 'start' | 'end' | 'center' | 'random'
889
+ onAllComplete: () => console.log('Done!')
890
+ })
891
+
892
+ // Parallel animations on multiple elements
893
+ await parallel([el1, el2, el3], 'fadeIn')
894
+
895
+ // Transition between states
896
+ transition(element,
897
+ { opacity: 0, transform: 'scale(0.9)' }, // from
898
+ { opacity: 1, transform: 'scale(1)' }, // to
899
+ { duration: 300, easing: 'ease-out' }
900
+ )
901
+ ```
902
+
903
+ ### Animation Presets
519
904
 
520
905
  ```javascript
521
- import { applyWebAnimation } from 'tailwind-to-style'
906
+ import { ANIMATION_PRESETS, EASING } from 'tailwind-to-style'
522
907
 
523
- // Apply a named animation to a DOM element
524
- applyWebAnimation(element, 'fadeIn')
525
- applyWebAnimation(element, 'slideUp')
908
+ // Available presets
909
+ // fadeIn, fadeOut, slideUp, slideDown, slideLeft, slideRight,
910
+ // zoomIn, zoomOut, bounce, shake, pulse, spin, flipX, flipY,
911
+ // enterScale, exitScale, wiggle, heartbeat
912
+
913
+ // Easing presets
914
+ // linear, ease, easeIn, easeOut, easeInOut,
915
+ // spring, springLight, springMedium, springHeavy,
916
+ // smooth, smoothIn, smoothOut, bounce, elastic
526
917
  ```
527
918
 
528
- ### Inline Animations
919
+ ### Custom Keyframes
529
920
 
530
921
  ```javascript
531
- import { applyInlineAnimation, animateElement, chainAnimations, staggerAnimations } from 'tailwind-to-style'
922
+ import { createKeyframes, clearKeyframes, registerPreset } from 'tailwind-to-style'
923
+
924
+ // Create custom keyframes (auto-injects CSS)
925
+ const animationValue = createKeyframes('myFade', {
926
+ '0%': { opacity: 0, transform: 'translateY(-10px)' },
927
+ '100%': { opacity: 1, transform: 'translateY(0)' }
928
+ }, { duration: 400, easing: 'ease-out' })
929
+ // → "myFade 400ms ease-out forwards"
930
+
931
+ // Register as reusable preset
932
+ registerPreset('myFade', {
933
+ keyframes: [{ opacity: 0 }, { opacity: 1 }],
934
+ options: { duration: 400 }
935
+ })
532
936
 
533
- // Single animation
534
- applyInlineAnimation(element, 'fadeIn')
937
+ // Use it
938
+ animate(element, 'myFade')
535
939
 
536
- // Programmatic animation
537
- animateElement(element, { opacity: [0, 1] }, { duration: 300 })
940
+ // Cleanup
941
+ clearKeyframes()
942
+ ```
538
943
 
539
- // Sequential chain
540
- chainAnimations(element, ['fadeIn', 'slideUp', 'bounceIn'])
944
+ ### Animation Utilities
541
945
 
542
- // Staggered across multiple elements
543
- staggerAnimations('.card', 'fadeIn', { delay: 100 })
946
+ ```javascript
947
+ import {
948
+ cancelAllAnimations,
949
+ getActiveAnimationCount,
950
+ isAnimationSupported,
951
+ getPresetNames
952
+ } from 'tailwind-to-style'
953
+
954
+ cancelAllAnimations() // Stop all running animations
955
+ getActiveAnimationCount() // Number of active animations
956
+ isAnimationSupported() // Check Web Animations API support
957
+ getPresetNames() // List all available preset names
544
958
  ```
545
959
 
546
960
  ---
@@ -551,26 +965,81 @@ Import only what you need to reduce bundle size by **50-70%**:
551
965
 
552
966
  ```javascript
553
967
  // Individual imports (recommended for production)
554
- import { tws } from 'tailwind-to-style/tws' // ~3KB
555
- import { twsx } from 'tailwind-to-style/twsx' // ~6KB
556
- import { twsxVariants } from 'tailwind-to-style/twsx-variants' // ~6KB
557
- import { cx } from 'tailwind-to-style/cx' // <1KB
968
+ import { tws } from 'tailwind-to-style/tws' // ~3KB
969
+ import { twsx } from 'tailwind-to-style/twsx' // ~6KB
970
+ import { twsxVariants } from 'tailwind-to-style/twsx-variants' // ~6KB
971
+ import { twsxClassName, tw } from 'tailwind-to-style/classname' // ~8KB
972
+ import { cx } from 'tailwind-to-style/cx' // <1KB
558
973
 
559
974
  // Full import (everything)
560
- import { tws, twsx, twsxVariants, cx } from 'tailwind-to-style' // ~12KB
975
+ import { tws, twsx, twsxVariants, twsxClassName, cx } from 'tailwind-to-style' // ~15KB
561
976
  ```
562
977
 
563
978
  | Import Path | Includes | Size (minified) |
564
979
  |---|---|---|
565
- | `tailwind-to-style` | Everything | ~12KB |
980
+ | `tailwind-to-style` | Everything | ~15KB |
566
981
  | `tailwind-to-style/tws` | `tws()` only | ~3KB |
567
982
  | `tailwind-to-style/twsx` | `twsx()` | ~6KB |
568
983
  | `tailwind-to-style/twsx-variants` | `twsxVariants()` | ~6KB |
984
+ | `tailwind-to-style/classname` | `twsxClassName()`, `tw()` | ~8KB |
569
985
  | `tailwind-to-style/cx` | `cx()` | <1KB |
570
986
  | `tailwind-to-style/utils` | Logger, LRUCache, error handler | ~2KB |
571
987
 
572
988
  All sub-paths provide ESM + CJS bundles with TypeScript type definitions.
573
989
 
990
+ ### Utility Exports
991
+
992
+ ```javascript
993
+ import {
994
+ // Debounced versions (for rapid updates)
995
+ debouncedTws, // Debounced tws() - 50ms delay
996
+ debouncedTwsx, // Debounced twsx() - 100ms delay
997
+
998
+ // Performance utilities
999
+ performanceUtils,
1000
+
1001
+ // Error handling
1002
+ TwsError,
1003
+ onError,
1004
+ handleError,
1005
+
1006
+ // Cache utilities
1007
+ LRUCache,
1008
+ getTailwindCache,
1009
+ resetTailwindCache,
1010
+
1011
+ // Logging
1012
+ logger,
1013
+ Logger,
1014
+ } from 'tailwind-to-style'
1015
+
1016
+ // Debounced functions - useful for user input
1017
+ const handleInput = (value) => {
1018
+ debouncedTws(`w-[${value}px]`, true) // Won't spam during typing
1019
+ }
1020
+
1021
+ // Error handling
1022
+ const unsubscribe = onError((error) => {
1023
+ console.error('TWS Error:', error.message, error.context)
1024
+ sendToErrorTracking(error)
1025
+ })
1026
+
1027
+ // Custom error
1028
+ const error = handleError(new Error('Invalid class'), { className: 'invalid' })
1029
+ // → TwsError with context and timestamp
1030
+
1031
+ // LRU Cache (bounded Map)
1032
+ const cache = new LRUCache(1000) // Max 1000 items
1033
+ cache.set('key', 'value')
1034
+ cache.get('key') // → 'value'
1035
+ cache.has('key') // → true
1036
+ cache.size // → 1
1037
+
1038
+ // Logger
1039
+ logger.setLevel('debug') // 'debug' | 'info' | 'warn' | 'error' | 'silent'
1040
+ logger.debug('Processing class:', className)
1041
+ ```
1042
+
574
1043
  ---
575
1044
 
576
1045
  ## Preflight CSS
@@ -621,10 +1090,41 @@ function App() {
621
1090
 
622
1091
  ### Vue
623
1092
 
1093
+ ```vue
1094
+ <script setup>
1095
+ import 'tailwind-to-style/preflight.css'
1096
+ import { tws, twsx } from 'tailwind-to-style'
1097
+
1098
+ // twsx() at the top level of <script setup> is HMR-safe.
1099
+ // When you edit the classes, Vite's HMR re-runs this block and
1100
+ // the old CSS slot is replaced automatically — no hard refresh needed.
1101
+ twsx({
1102
+ 'html': 'bg-gray-100 min-h-screen flex items-center justify-center',
1103
+ '.card': [
1104
+ 'bg-white p-5 border border-gray-300 rounded-xl shadow-md',
1105
+ {
1106
+ '&:hover': 'shadow-xl',
1107
+ '> .title': 'text-xl font-bold text-gray-900 mb-3',
1108
+ '> .body': 'text-sm text-gray-700',
1109
+ },
1110
+ ],
1111
+ })
1112
+ </script>
1113
+
1114
+ <template>
1115
+ <div class="card">
1116
+ <div class="title">Card Title</div>
1117
+ <p class="body">Styled with twsx — hot reload works out of the box.</p>
1118
+ </div>
1119
+ </template>
1120
+ ```
1121
+
1122
+ For simple inline styles, use `tws()` with the reactive system:
1123
+
624
1124
  ```vue
625
1125
  <script setup>
626
1126
  import { tws } from 'tailwind-to-style'
627
- const btnStyle = tws('bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600', true)
1127
+ const btnStyle = tws('bg-blue-500 text-white px-4 py-2 rounded-lg', true)
628
1128
  </script>
629
1129
 
630
1130
  <template>
@@ -670,7 +1170,7 @@ v3.2.0 includes major performance optimizations:
670
1170
  - **Pre-compiled regex** — compiled once at module load, reused for every call
671
1171
  - **Multi-level LRU caching** — class resolution, CSS generation, config lookups
672
1172
  - **Bounded caches** — Maps capped at 5,000 entries, Sets at 10,000 to prevent memory leaks
673
- - **`sheet.insertRule()` injection** — avoids full stylesheet reparsing on each call
1173
+ - **Slot-based CSS injection** — each `twsx()` call owns a named slot; updates rebuild the tag instead of appending, preventing HMR accumulation
674
1174
  - **FNV-1a hashing** — 100x faster than `JSON.stringify` for cache keys
675
1175
 
676
1176
  ```