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 +411 -2
- package/dist/animations/index.cjs +288 -167
- package/dist/animations/index.esm.js +288 -167
- package/dist/animations/index.esm.js.map +1 -1
- package/dist/className/index.cjs +288 -167
- package/dist/className/index.esm.js +288 -167
- package/dist/className/index.esm.js.map +1 -1
- package/dist/core/tws.cjs +1 -1
- package/dist/core/tws.esm.js +1 -1
- package/dist/cx.cjs +1 -1
- package/dist/cx.esm.js +1 -1
- package/dist/index.cjs +288 -167
- package/dist/index.d.ts +8 -15
- package/dist/index.esm.js +288 -167
- package/dist/index.esm.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/react/index.cjs +288 -167
- package/dist/react/index.esm.js +288 -167
- package/dist/react/index.esm.js.map +1 -1
- package/dist/register/index.cjs +8313 -0
- package/dist/register/index.d.ts +229 -0
- package/dist/register/index.esm.js +8307 -0
- package/dist/register/index.esm.js.map +1 -0
- package/dist/runtime/index.cjs +429 -0
- package/dist/runtime/index.d.ts +15 -0
- package/dist/runtime/index.esm.js +425 -0
- package/dist/runtime/index.esm.js.map +1 -0
- package/dist/tokens/index.cjs +1 -1
- package/dist/tokens/index.esm.js +1 -1
- package/dist/v4.d.ts +347 -0
- package/package.json +6 -1
- package/types/index.d.ts +8 -15
- package/types/register.d.ts +229 -0
- package/types/v4.d.ts +159 -3
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
|
|
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
|
|
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
|
+
```
|