tailwind-to-style 5.0.0 → 5.1.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
@@ -71,11 +71,13 @@ const classes = cx('base', isActive && 'ring-2', { 'opacity-50': disabled });
71
71
 
72
72
  Want to try the library with real demos?
73
73
 
74
- - `examples/basic/` — runtime `tws()` examples for inline conversion and custom values.
74
+ - `examples/basic/` — runtime `tws()` examples, mixed CSS demo, and register API tests.
75
75
  - `examples/react-demo/` — full React showcase with components, variants, tokens, and theme switching.
76
76
  - `examples/twsx-classname-app/` — Vite-based runtime `tw()` v4 demo with variant and slots components.
77
+ - `examples/register-landing/` — landing page built with `register()` — zero Tailwind CLI, pure HTML + register calls.
78
+ - `examples/company-landing/` — single-file company landing page (Nexora) demonstrating `register.all()`, `register.group()`, `@keyframes`, and FOUC prevention.
77
79
 
78
- Run the demos by opening `examples/README.md` or using the commands below:
80
+ Run the demos:
79
81
 
80
82
  ```bash
81
83
  cd examples/react-demo
@@ -89,6 +91,18 @@ npm install
89
91
  npm run dev
90
92
  ```
91
93
 
94
+ ```bash
95
+ cd examples/register-landing
96
+ npm install
97
+ npm run dev
98
+ ```
99
+
100
+ ```bash
101
+ cd examples/company-landing
102
+ npm install
103
+ npm run dev
104
+ ```
105
+
92
106
  ---
93
107
 
94
108
  ## API Reference
@@ -398,3 +412,398 @@ Works with any framework or vanilla JS:
398
412
  ## License
399
413
 
400
414
  MIT © [Bigetion](https://github.com/Bigetion)
415
+
416
+ ---
417
+
418
+ ## Mixed Tailwind + Raw CSS in `tw()`
419
+
420
+ > New in v5 — what Tailwind alone can't do.
421
+
422
+ Every style config object in `tw()` now accepts **raw CSS properties** alongside Tailwind utility classes. Use `tw:` for Tailwind, and any standard CSS property (kebab-case or camelCase) for exact values Tailwind can't express.
423
+
424
+ ```js
425
+ import { tw } from 'tailwind-to-style';
426
+
427
+ // tw: handles Tailwind, raw CSS handles the rest
428
+ const label = tw({
429
+ tw: 'inline-flex items-center font-semibold',
430
+ 'font-size': '11px', // exact value — not in Tailwind scale
431
+ 'letter-spacing': '0.1em', // exact value
432
+ 'text-transform': 'uppercase',
433
+ color: 'var(--text-muted)', // CSS variable
434
+ });
435
+ ```
436
+
437
+ ### Key rules
438
+
439
+ | Key | Meaning |
440
+ |-----|---------|
441
+ | `tw` | Tailwind utility classes (preferred shorthand, replaces `_`) |
442
+ | `_` | Legacy alias for `tw` — still supported |
443
+ | kebab-case property | Raw CSS — `'font-size'`, `'border-radius'`, `'box-shadow'` |
444
+ | camelCase property | Raw CSS — `fontSize`, `borderRadius`, `boxShadow` |
445
+ | `--var-name` | CSS custom property |
446
+ | `hover`, `focus`, `dark`… | Tailwind pseudo shorthands |
447
+ | `sm`, `md`, `lg`… | Responsive breakpoint shorthands |
448
+ | `'&:hover'`, `'&::before'`… | Arbitrary nested CSS selectors |
449
+
450
+ ### Works everywhere
451
+
452
+ Mixed CSS works in all `tw()` modes — basic, variants, slots, and inside `register()`.
453
+
454
+ ```js
455
+ // In variants
456
+ const btn = tw({
457
+ name: 'btn',
458
+ base: {
459
+ tw: 'inline-flex items-center font-semibold rounded-lg transition-all',
460
+ 'font-family': 'inherit', // raw CSS
461
+ 'line-height': '1',
462
+ },
463
+ variants: {
464
+ size: {
465
+ sm: { tw: 'px-3 py-1.5', 'font-size': '12px' }, // mixed
466
+ md: { tw: 'px-5 py-2.5', 'font-size': '14px' },
467
+ lg: { tw: 'px-7 py-3.5', 'font-size': '16px' },
468
+ },
469
+ },
470
+ });
471
+
472
+ // In slots
473
+ const card = tw({
474
+ name: 'card',
475
+ slots: {
476
+ root: {
477
+ tw: 'rounded-2xl overflow-hidden',
478
+ 'box-shadow': '0 4px 24px rgba(0,0,0,.08)', // raw CSS
479
+ },
480
+ body: {
481
+ tw: 'px-6 py-4',
482
+ 'line-height': '1.6', // raw CSS
483
+ },
484
+ },
485
+ });
486
+ ```
487
+
488
+ ### Pseudo shorthands + raw CSS together
489
+
490
+ ```js
491
+ const input = tw({
492
+ tw: 'w-full px-4 py-2 border rounded-lg transition-all',
493
+ 'font-family': 'inherit',
494
+ 'font-size': '14px',
495
+ hover: 'border-gray-400', // pseudo shorthand
496
+ focus: 'ring-2 ring-blue-500',
497
+ dark: 'bg-gray-900 text-white', // media shorthand
498
+ md: 'text-base', // responsive shorthand
499
+ '&:disabled': { tw: 'opacity-50 cursor-not-allowed' }, // nested selector
500
+ });
501
+ ```
502
+
503
+ ---
504
+
505
+ ## `tailwind-to-style/register`
506
+
507
+ A semantic class registration API — Bootstrap-style clean HTML, Tailwind power under the hood.
508
+
509
+ ```js
510
+ import { register, cx, cn } from 'tailwind-to-style/register';
511
+ ```
512
+
513
+ ### Why `register`?
514
+
515
+ ```html
516
+ <!-- ❌ Tailwind: hard to read, duplicated everywhere -->
517
+ <button class="inline-flex items-center px-5 py-2.5 bg-indigo-600 text-white font-semibold rounded-lg hover:bg-indigo-700 transition-all">
518
+ Submit
519
+ </button>
520
+
521
+ <!-- ✅ register(): define once, clean HTML everywhere -->
522
+ <button class="btn btn-primary btn-md">Submit</button>
523
+ ```
524
+
525
+ ---
526
+
527
+ ### `register(className, config)`
528
+
529
+ Define a semantic CSS class. Injects CSS into the DOM automatically.
530
+
531
+ #### Simple form
532
+
533
+ ```js
534
+ register('btn', {
535
+ tw: 'px-5 py-2.5 rounded-lg font-semibold transition-all cursor-pointer',
536
+ 'font-family': 'inherit',
537
+ 'background-color': '#3b82f6',
538
+ color: '#fff',
539
+ '&:hover': {
540
+ 'background-color': '#2563eb',
541
+ transform: 'translateY(-1px)',
542
+ },
543
+ });
544
+
545
+ // <button class="btn">Click me</button>
546
+ ```
547
+
548
+ #### Complex form — `base` + `modifiers`
549
+
550
+ `modifiers` auto-generates `.className-key` for each entry:
551
+
552
+ ```js
553
+ register('btn', {
554
+ base: {
555
+ tw: 'inline-flex items-center font-semibold rounded-lg transition-all',
556
+ 'font-family': 'inherit',
557
+ 'line-height': '1',
558
+ },
559
+ modifiers: {
560
+ // colors → .btn-primary, .btn-danger
561
+ primary: { tw: 'bg-indigo-600 text-white hover:bg-indigo-700' },
562
+ danger: { tw: 'bg-red-600 text-white hover:bg-red-700' },
563
+
564
+ // sizes → .btn-sm, .btn-md, .btn-lg
565
+ sm: { tw: 'px-3 py-1.5', 'font-size': '12px' },
566
+ md: { tw: 'px-5 py-2.5', 'font-size': '14px' },
567
+ lg: { tw: 'px-7 py-3.5', 'font-size': '16px' },
568
+
569
+ // shapes → .btn-pill, .btn-square
570
+ pill: { 'border-radius': '9999px' },
571
+ square: { 'border-radius': '0' },
572
+ },
573
+ });
574
+
575
+ // Clean HTML — compose modifiers freely
576
+ // <button class="btn btn-primary btn-lg btn-pill">Get Started</button>
577
+ // <button class="btn btn-danger btn-sm">Delete</button>
578
+ ```
579
+
580
+ #### `extend` — inherit from another class
581
+
582
+ ```js
583
+ register('btn', { tw: 'px-4 py-2 rounded font-medium' });
584
+
585
+ // Inherits all btn styles, then adds its own
586
+ register('icon-btn', {
587
+ extend: 'btn',
588
+ tw: 'w-10 h-10 p-0 flex items-center justify-center',
589
+ });
590
+
591
+ // Multi-level extend
592
+ register('fab', {
593
+ extend: 'icon-btn', // inherits btn + icon-btn
594
+ tw: 'rounded-full shadow-lg',
595
+ 'background-color': '#6366f1',
596
+ color: '#fff',
597
+ });
598
+ ```
599
+
600
+ #### `@keyframes` support
601
+
602
+ ```js
603
+ register('@keyframes fadeUp', {
604
+ from: { opacity: '0', transform: 'translateY(24px)' },
605
+ to: { opacity: '1', transform: 'translateY(0)' },
606
+ });
607
+
608
+ register('@keyframes pulse', {
609
+ '0%, 100%': { opacity: '1' },
610
+ '50%': { opacity: '0.4' },
611
+ });
612
+ ```
613
+
614
+ #### Global selectors — `:root`, `*`, element tags
615
+
616
+ ```js
617
+ // CSS custom properties on :root
618
+ register(':root', {
619
+ '--brand': '#6366f1',
620
+ '--brand-dark': '#4f46e5',
621
+ '--radius': '10px',
622
+ });
623
+
624
+ // Universal reset
625
+ register('*', { 'box-sizing': 'border-box', margin: '0', padding: '0' });
626
+
627
+ // Element tag styling
628
+ register('body', {
629
+ tw: 'text-gray-900 antialiased',
630
+ 'font-family': "system-ui, -apple-system, sans-serif",
631
+ });
632
+ ```
633
+
634
+ ---
635
+
636
+ ### `register.group(baseName, components)`
637
+
638
+ Register multiple related classes at once. `root` key → `.baseName`, others → `.baseName-key`.
639
+
640
+ ```js
641
+ register.group('card', {
642
+ root: {
643
+ tw: 'rounded-2xl overflow-hidden bg-white',
644
+ 'box-shadow': '0 4px 24px rgba(0,0,0,.08)',
645
+ },
646
+ header: {
647
+ tw: 'px-6 py-4 border-b border-gray-100 font-semibold',
648
+ 'font-size': '16px',
649
+ },
650
+ body: { tw: 'px-6 py-4', 'line-height': '1.6' },
651
+ footer: { tw: 'px-6 py-4 border-t bg-gray-50 text-sm text-gray-500' },
652
+ });
653
+
654
+ // Generates: .card .card-header .card-body .card-footer
655
+ ```
656
+
657
+ ```html
658
+ <div class="card">
659
+ <div class="card-header">Title</div>
660
+ <div class="card-body">Content goes here.</div>
661
+ <div class="card-footer">Footer</div>
662
+ </div>
663
+ ```
664
+
665
+ ---
666
+
667
+ ### `register.all(map)`
668
+
669
+ Register multiple classes at once from a plain object — sugar for calling `register()` on each key. Each class still gets its own registry entry.
670
+
671
+ ```js
672
+ register.all({
673
+ ':root': { '--brand': '#6366f1', '--brand-dark': '#4f46e5' },
674
+ '*': { 'box-sizing': 'border-box', margin: '0', padding: '0' },
675
+ 'body': { tw: 'text-gray-900 antialiased' },
676
+ 'container': { tw: 'mx-auto w-full px-6', 'max-width': '1200px' },
677
+ 'section': { tw: 'py-24' },
678
+ 'heading-xl':{ tw: 'font-extrabold tracking-tight', 'font-size': 'clamp(2.5rem, 6vw, 4rem)' },
679
+ 'lead': { tw: 'text-lg leading-relaxed text-gray-500' },
680
+ });
681
+ ```
682
+
683
+ ---
684
+
685
+ ### `register.extractCSS()`
686
+
687
+ Extract all registered CSS as a string — useful for SSR.
688
+
689
+ ```js
690
+ // Server-side rendering
691
+ const html = renderApp();
692
+ const css = register.extractCSS();
693
+
694
+ res.send(`
695
+ <html>
696
+ <head><style>${css}</style></head>
697
+ <body>${html}</body>
698
+ </html>
699
+ `);
700
+ ```
701
+
702
+ ---
703
+
704
+ ### `register.reset()`
705
+
706
+ Clear all registered styles. Useful for testing.
707
+
708
+ ```js
709
+ register.reset();
710
+ ```
711
+
712
+ ---
713
+
714
+ ### `cx()` / `cn()`
715
+
716
+ Conditionally merge class names. `cn` is an alias of `cx` for shadcn/ui compatibility.
717
+
718
+ ```js
719
+ import { cx, cn } from 'tailwind-to-style/register';
720
+
721
+ cx('btn', isActive && 'btn-active', { 'btn-lg': isLarge })
722
+ // → "btn btn-active btn-lg"
723
+
724
+ cn('btn btn-primary', isLoading && 'opacity-50 cursor-not-allowed')
725
+ // → "btn btn-primary opacity-50 cursor-not-allowed"
726
+ ```
727
+
728
+ ---
729
+
730
+ ### FOUC Prevention
731
+
732
+ When using `register()` in the browser, add this to your HTML to prevent unstyled content flash:
733
+
734
+ ```html
735
+ <head>
736
+ <!-- Hide body until JS injects all styles -->
737
+ <style>body { opacity: 0 }</style>
738
+ <noscript><style>body { opacity: 1 }</style></noscript>
739
+ <script type="module" src="./main.js"></script>
740
+ </head>
741
+ ```
742
+
743
+ ```js
744
+ // main.js — add at the end, after all register() calls
745
+ requestAnimationFrame(() => {
746
+ document.body.style.cssText += ';transition:opacity 200ms ease;opacity:1';
747
+ });
748
+ ```
749
+
750
+ All `register()` calls are synchronous. By the time `requestAnimationFrame` fires, the browser has already parsed and applied the injected `<style>` tag — guaranteeing zero FOUC.
751
+
752
+ ---
753
+
754
+ ### Complete Example
755
+
756
+ ```js
757
+ import { register, cx } from 'tailwind-to-style/register';
758
+
759
+ // Tokens
760
+ register.all({
761
+ ':root': { '--brand': '#6366f1', '--brand-dark': '#4f46e5' },
762
+ '*': { 'box-sizing': 'border-box', margin: '0', padding: '0' },
763
+ 'body': { tw: 'text-gray-900 antialiased', 'font-family': 'system-ui, sans-serif' },
764
+ });
765
+
766
+ // Keyframes
767
+ register('@keyframes fadeIn', {
768
+ from: { opacity: '0', transform: 'translateY(8px)' },
769
+ to: { opacity: '1', transform: 'translateY(0)' },
770
+ });
771
+
772
+ // Components
773
+ register('btn', {
774
+ base: {
775
+ tw: 'inline-flex items-center font-semibold rounded-lg transition-all cursor-pointer border-0',
776
+ 'font-family': 'inherit',
777
+ 'line-height': '1',
778
+ },
779
+ modifiers: {
780
+ primary: { tw: 'text-white', 'background-color': 'var(--brand)' },
781
+ outline: { tw: 'bg-transparent border border-current', color: 'var(--brand)' },
782
+ sm: { tw: 'px-3 py-1.5 text-sm' },
783
+ md: { tw: 'px-5 py-2.5 text-sm' },
784
+ lg: { tw: 'px-7 py-3.5 text-base' },
785
+ pill: { 'border-radius': '9999px' },
786
+ },
787
+ });
788
+
789
+ register.group('card', {
790
+ root: { tw: 'bg-white rounded-2xl border border-gray-200', 'box-shadow': '0 2px 12px rgba(0,0,0,.07)' },
791
+ header: { tw: 'px-6 py-4 border-b font-semibold', 'font-size': '16px' },
792
+ body: { tw: 'px-6 py-4', 'line-height': '1.6' },
793
+ });
794
+
795
+ // Reveal page smoothly
796
+ requestAnimationFrame(() => {
797
+ document.body.style.cssText += ';transition:opacity 200ms;opacity:1';
798
+ });
799
+ ```
800
+
801
+ ```html
802
+ <button class="btn btn-primary btn-lg btn-pill">Get Started</button>
803
+ <button class="btn btn-outline btn-sm">Learn More</button>
804
+
805
+ <div class="card">
806
+ <div class="card-header">Welcome</div>
807
+ <div class="card-body">Build the future, faster.</div>
808
+ </div>
809
+ ```