tailwind-to-style 3.3.0 → 4.0.1
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 +222 -1109
- package/dist/animations/index.cjs +9391 -0
- package/dist/animations/index.d.ts +58 -0
- package/dist/animations/index.esm.js +9385 -0
- package/dist/animations/index.esm.js.map +1 -0
- package/dist/className/index.cjs +2241 -4181
- package/dist/className/index.esm.js +2241 -4181
- package/dist/className/index.esm.js.map +1 -1
- package/dist/core/tws.cjs +136 -114
- package/dist/core/tws.cjs.map +1 -0
- package/dist/core/tws.esm.js +136 -114
- package/dist/core/tws.esm.js.map +1 -1
- package/dist/core/twsx.cjs +1971 -3970
- package/dist/core/twsx.esm.js +1971 -3970
- package/dist/core/twsx.esm.js.map +1 -1
- package/dist/core/twsxVariants.cjs +1997 -3986
- package/dist/core/twsxVariants.esm.js +1997 -3986
- package/dist/core/twsxVariants.esm.js.map +1 -1
- package/dist/cx.cjs +2 -2
- package/dist/cx.cjs.map +1 -0
- package/dist/cx.esm.js +2 -2
- package/dist/index.cjs +5253 -9252
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.esm.js +5251 -9201
- 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 +10177 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.ts +69 -0
- package/dist/react/index.esm.js +10173 -0
- package/dist/react/index.esm.js.map +1 -0
- package/dist/styled/index.cjs +9094 -0
- package/dist/styled/index.cjs.map +1 -0
- package/dist/styled/index.d.ts +17 -0
- package/dist/styled/index.esm.js +9087 -0
- package/dist/styled/index.esm.js.map +1 -0
- package/dist/tokens/index.cjs +359 -0
- package/dist/tokens/index.d.ts +33 -0
- package/dist/tokens/index.esm.js +355 -0
- package/dist/tokens/index.esm.js.map +1 -0
- package/dist/utils/index.cjs +219 -270
- package/dist/utils/index.esm.js +219 -270
- package/dist/utils/index.esm.js.map +1 -1
- package/llms.txt +481 -0
- package/package.json +38 -24
- package/types/animations/index.d.ts +58 -0
- package/types/index.d.ts +4 -1
- package/types/react/index.d.ts +69 -0
- package/types/tokens/index.d.ts +33 -0
- package/types/v4.d.ts +191 -0
package/llms.txt
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# tailwind-to-style
|
|
2
|
+
|
|
3
|
+
> Zero-build runtime Tailwind CSS engine. Convert utility classes to real CSS at runtime — with variants, slots, design tokens, and React bindings.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install tailwind-to-style
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
tailwind-to-style replaces the Tailwind CSS build step entirely. You write Tailwind utility classes as strings, and the library generates and injects real CSS into the DOM at runtime. It supports responsive breakpoints (md:, lg:), pseudo-classes (hover:, focus:), arbitrary values, variants, slots, design tokens, and SSR.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## API Reference
|
|
18
|
+
|
|
19
|
+
### `tw()` — The main function (4 modes)
|
|
20
|
+
|
|
21
|
+
#### Mode 1: Atomic classes
|
|
22
|
+
Pass a string of Tailwind utilities. Returns auto-generated class names. CSS is injected into the DOM automatically, including @media queries and pseudo-class rules.
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { tw } from 'tailwind-to-style';
|
|
26
|
+
|
|
27
|
+
tw('flex items-center gap-4 hover:bg-gray-100 md:hidden')
|
|
28
|
+
// Returns: "tw-flex tw-items-center tw-gap-4 tw-hover-bg-gray-100 tw-md-hidden"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use with `className={}` in React. Supports all responsive prefixes (sm:, md:, lg:, xl:, 2xl:) and pseudo-classes (hover:, focus:, active:, disabled:, focus-within:, etc.).
|
|
32
|
+
|
|
33
|
+
#### Mode 2: Named class (preferred for static styles)
|
|
34
|
+
Two string arguments: name + Tailwind classes. Returns the name as the class. This is the preferred shorthand for defining reusable static styles.
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
tw('sidebar', 'w-64 h-screen bg-white border-r border-gray-200')
|
|
38
|
+
// Returns: "sidebar"
|
|
39
|
+
// Injects: .sidebar { width: 16rem; height: 100vh; background: #fff; ... }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### Mode 3: Variants (returns a selector function)
|
|
43
|
+
Object config with `name`, `base`, `variants`, optional `compoundVariants` and `defaultVariants`. Returns a function you call with variant selections.
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const button = tw({
|
|
47
|
+
name: 'btn',
|
|
48
|
+
base: 'px-4 py-2 rounded-lg font-medium transition-all cursor-pointer',
|
|
49
|
+
variants: {
|
|
50
|
+
color: {
|
|
51
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
|
52
|
+
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
|
|
53
|
+
danger: 'bg-red-600 text-white hover:bg-red-700',
|
|
54
|
+
ghost: 'bg-transparent text-gray-700 hover:bg-gray-100',
|
|
55
|
+
},
|
|
56
|
+
size: {
|
|
57
|
+
sm: 'text-sm px-3 py-1.5',
|
|
58
|
+
md: 'text-base px-4 py-2',
|
|
59
|
+
lg: 'text-lg px-6 py-3',
|
|
60
|
+
},
|
|
61
|
+
fullWidth: {
|
|
62
|
+
true: 'w-full',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
compoundVariants: [
|
|
66
|
+
{ color: 'primary', size: 'lg', class: 'shadow-lg shadow-blue-500/25' },
|
|
67
|
+
{ color: 'danger', size: 'lg', class: 'shadow-lg shadow-red-500/25' },
|
|
68
|
+
],
|
|
69
|
+
defaultVariants: { color: 'primary', size: 'md' },
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
button({ color: 'danger', size: 'lg' })
|
|
73
|
+
// Returns: "btn btn--color-danger btn--size-lg"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Mode 4: Slots (multi-part components)
|
|
77
|
+
Object config with `name`, `slots`, and `variants` where each variant value is an object mapping slot names to classes.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const card = tw({
|
|
81
|
+
name: 'card',
|
|
82
|
+
slots: {
|
|
83
|
+
root: 'bg-white rounded-xl border overflow-hidden',
|
|
84
|
+
header: 'border-b flex items-center justify-between',
|
|
85
|
+
title: 'font-semibold text-gray-900',
|
|
86
|
+
body: 'text-gray-700',
|
|
87
|
+
footer: 'border-t bg-gray-50',
|
|
88
|
+
},
|
|
89
|
+
variants: {
|
|
90
|
+
variant: {
|
|
91
|
+
default: {
|
|
92
|
+
root: 'border-gray-200 shadow-sm',
|
|
93
|
+
header: 'border-gray-100',
|
|
94
|
+
footer: 'border-gray-100',
|
|
95
|
+
},
|
|
96
|
+
elevated: {
|
|
97
|
+
root: 'border-gray-200 shadow-lg hover:shadow-xl',
|
|
98
|
+
header: 'border-gray-100',
|
|
99
|
+
footer: 'border-gray-100',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
size: {
|
|
103
|
+
sm: { header: 'px-4 py-3', title: 'text-sm', body: 'px-4 py-3 text-sm', footer: 'px-4 py-3' },
|
|
104
|
+
md: { header: 'px-6 py-4', title: 'text-base', body: 'px-6 py-4', footer: 'px-6 py-4' },
|
|
105
|
+
lg: { header: 'px-8 py-5', title: 'text-lg', body: 'px-8 py-5', footer: 'px-8 py-5' },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
defaultVariants: { variant: 'default', size: 'md' },
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const slots = card({ variant: 'elevated', size: 'lg' })
|
|
112
|
+
// Returns: { root: "card__root card__root--variant-elevated card__root--size-lg", ... }
|
|
113
|
+
// Use: <div className={slots.root}> ... <h3 className={slots.title}> ...
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### `tws()` — Inline style converter
|
|
119
|
+
|
|
120
|
+
Converts Tailwind classes to CSS string or JS style object. Does NOT support responsive or pseudo-class variants (those require `tw()`).
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { tws } from 'tailwind-to-style';
|
|
124
|
+
|
|
125
|
+
// CSS string
|
|
126
|
+
tws('bg-blue-500 text-white p-4 rounded-lg')
|
|
127
|
+
// → "background-color: rgb(59,130,246); color: #fff; padding: 1rem; border-radius: 0.5rem;"
|
|
128
|
+
|
|
129
|
+
// JS object (pass true as second arg)
|
|
130
|
+
tws('flex items-center gap-4', true)
|
|
131
|
+
// → { display: 'flex', alignItems: 'center', gap: '1rem' }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### `cx()` — Conditional class merging
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
import { cx } from 'tailwind-to-style';
|
|
140
|
+
|
|
141
|
+
cx('base-class', isActive && 'ring-2 ring-blue-500', { 'opacity-50': disabled })
|
|
142
|
+
cx(button({ color: 'primary' }), disabled && tw('opacity-50 pointer-events-none'), className)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### Design Tokens
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
import { createTheme, activateTheme, token, tokenRegistry } from 'tailwind-to-style/tokens';
|
|
151
|
+
|
|
152
|
+
createTheme({
|
|
153
|
+
colors: { primary: '#2563eb', surface: '#ffffff', text: '#0f172a', border: '#e2e8f0' },
|
|
154
|
+
}, { name: 'light' });
|
|
155
|
+
|
|
156
|
+
createTheme({
|
|
157
|
+
colors: { primary: '#60a5fa', surface: '#1e293b', text: '#f1f5f9', border: '#334155' },
|
|
158
|
+
}, { name: 'dark' });
|
|
159
|
+
|
|
160
|
+
// Reference in tw() via arbitrary value syntax
|
|
161
|
+
tw('themed-btn', `bg-[${token('colors.primary')}] text-white px-4 py-2 rounded-lg`)
|
|
162
|
+
// token('colors.primary') → "var(--tws-colors-primary)"
|
|
163
|
+
|
|
164
|
+
// Switch themes at runtime
|
|
165
|
+
activateTheme('dark')
|
|
166
|
+
|
|
167
|
+
// Programmatic access
|
|
168
|
+
tokenRegistry.get('colors.primary')
|
|
169
|
+
tokenRegistry.set('colors.primary', '#1d4ed8')
|
|
170
|
+
tokenRegistry.subscribe((tokens) => { /* react to changes */ })
|
|
171
|
+
tokenRegistry.toCSS() // → ":root { --tws-colors-primary: ...; }"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### React Bindings
|
|
177
|
+
|
|
178
|
+
```jsx
|
|
179
|
+
import { ThemeProvider, useTheme, styled } from 'tailwind-to-style/react';
|
|
180
|
+
|
|
181
|
+
<ThemeProvider theme={lightTheme} name="light">
|
|
182
|
+
<App />
|
|
183
|
+
</ThemeProvider>
|
|
184
|
+
|
|
185
|
+
// Inside components:
|
|
186
|
+
const { tokens, setTheme } = useTheme();
|
|
187
|
+
|
|
188
|
+
// styled() — creates React components with variant props
|
|
189
|
+
const Button = styled('button', {
|
|
190
|
+
name: 'btn',
|
|
191
|
+
base: 'px-4 py-2 rounded-lg font-medium',
|
|
192
|
+
variants: {
|
|
193
|
+
color: { primary: 'bg-blue-600 text-white', secondary: 'bg-gray-200 text-gray-800' },
|
|
194
|
+
},
|
|
195
|
+
defaultVariants: { color: 'primary' },
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
<Button color="primary" onClick={handleClick}>Click</Button>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Animations
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
import { animate, defineAnimation } from 'tailwind-to-style/animations';
|
|
207
|
+
|
|
208
|
+
element.className = animate('fadeIn', { duration: '300ms', delay: '100ms' });
|
|
209
|
+
// Built-in: fadeIn, fadeOut, slideInUp, slideInDown, slideInLeft, slideInRight,
|
|
210
|
+
// scaleIn, scaleOut, bounce, shake, pulse, spin, ping
|
|
211
|
+
|
|
212
|
+
defineAnimation('wiggle', {
|
|
213
|
+
keyframes: [
|
|
214
|
+
{ transform: 'rotate(0deg)' },
|
|
215
|
+
{ transform: 'rotate(-3deg)' },
|
|
216
|
+
{ transform: 'rotate(3deg)' },
|
|
217
|
+
{ transform: 'rotate(0deg)' },
|
|
218
|
+
],
|
|
219
|
+
duration: '300ms',
|
|
220
|
+
easing: 'ease-in-out',
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
### SSR (Server-Side Rendering)
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
import { tw, createSSRCollector } from 'tailwind-to-style';
|
|
230
|
+
|
|
231
|
+
const collector = createSSRCollector();
|
|
232
|
+
const html = renderToString(<App />);
|
|
233
|
+
const css = collector.extract();
|
|
234
|
+
// Inject css into <style> in <head> of server-rendered HTML
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Import Paths
|
|
240
|
+
|
|
241
|
+
| Path | Exports |
|
|
242
|
+
|------|---------|
|
|
243
|
+
| `tailwind-to-style` | `tw`, `tws`, `cx` |
|
|
244
|
+
| `tailwind-to-style/react` | `ThemeProvider`, `useTheme`, `styled`, `useTws` |
|
|
245
|
+
| `tailwind-to-style/tokens` | `createTheme`, `activateTheme`, `token`, `tokenRegistry` |
|
|
246
|
+
| `tailwind-to-style/animations` | `animate`, `defineAnimation`, `getAnimationNames` |
|
|
247
|
+
| `tailwind-to-style/cx` | `cx` (standalone, ~300B) |
|
|
248
|
+
| `tailwind-to-style/tws` | `tws` (standalone subset) |
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Real-World Component Patterns
|
|
253
|
+
|
|
254
|
+
These patterns are extracted from the library's production demo app.
|
|
255
|
+
|
|
256
|
+
### Pattern: Simple component with Mode 2 static classes
|
|
257
|
+
|
|
258
|
+
```jsx
|
|
259
|
+
import { tw, cx } from 'tailwind-to-style';
|
|
260
|
+
|
|
261
|
+
const accordionRoot = tw('accordion', 'rounded-lg border border-gray-200 overflow-hidden divide-y divide-gray-200');
|
|
262
|
+
const accordionItem = tw('accordion-item', 'bg-white');
|
|
263
|
+
const accordionTrigger = tw('accordion-trigger', 'flex items-center justify-between w-full px-4 py-3 text-left text-sm font-medium text-gray-900 cursor-pointer select-none hover:bg-gray-50 transition-colors');
|
|
264
|
+
const accordionContent = tw('accordion-content', 'px-4 pb-3 text-sm text-gray-600');
|
|
265
|
+
|
|
266
|
+
export function Accordion({ items, className }) {
|
|
267
|
+
return (
|
|
268
|
+
<div className={cx(accordionRoot, className)}>
|
|
269
|
+
{items.map((item, i) => (
|
|
270
|
+
<div className={accordionItem}>
|
|
271
|
+
<button className={accordionTrigger}>{item.title}</button>
|
|
272
|
+
<div className={accordionContent}>{item.content}</div>
|
|
273
|
+
</div>
|
|
274
|
+
))}
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Pattern: Component with variants
|
|
281
|
+
|
|
282
|
+
```jsx
|
|
283
|
+
const inputField = tw({
|
|
284
|
+
name: 'input',
|
|
285
|
+
base: 'block w-full rounded-lg border bg-white text-gray-900 transition-all duration-200 focus:outline-none focus:ring-2',
|
|
286
|
+
variants: {
|
|
287
|
+
size: {
|
|
288
|
+
sm: 'text-sm px-3 py-1.5',
|
|
289
|
+
md: 'text-base px-3.5 py-2.5',
|
|
290
|
+
lg: 'text-lg px-4 py-3',
|
|
291
|
+
},
|
|
292
|
+
state: {
|
|
293
|
+
default: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500/20',
|
|
294
|
+
error: 'border-red-400 focus:border-red-500 focus:ring-red-500/20',
|
|
295
|
+
success: 'border-emerald-400 focus:border-emerald-500 focus:ring-emerald-500/20',
|
|
296
|
+
},
|
|
297
|
+
disabled: {
|
|
298
|
+
true: 'opacity-50 cursor-not-allowed bg-gray-50',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
defaultVariants: { size: 'md', state: 'default' },
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Usage — filter undefined props before passing:
|
|
305
|
+
const variantProps = { state: error ? 'error' : 'default' };
|
|
306
|
+
if (size !== undefined) variantProps.size = size;
|
|
307
|
+
if (disabled) variantProps.disabled = true;
|
|
308
|
+
|
|
309
|
+
<input className={inputField(variantProps)} />
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Pattern: Component with compoundVariants
|
|
313
|
+
|
|
314
|
+
```jsx
|
|
315
|
+
const tab = tw({
|
|
316
|
+
name: 'tab',
|
|
317
|
+
base: 'inline-flex items-center justify-center font-medium cursor-pointer transition-all duration-200',
|
|
318
|
+
variants: {
|
|
319
|
+
variant: {
|
|
320
|
+
underline: 'px-4 py-2.5 text-sm border-b-2 -mb-px',
|
|
321
|
+
pills: 'px-3 py-1.5 text-sm rounded-md',
|
|
322
|
+
},
|
|
323
|
+
active: { true: '', false: '' },
|
|
324
|
+
},
|
|
325
|
+
compoundVariants: [
|
|
326
|
+
{ variant: 'underline', active: true, class: 'border-blue-500 text-blue-600' },
|
|
327
|
+
{ variant: 'underline', active: false, class: 'border-transparent text-gray-500 hover:text-gray-700' },
|
|
328
|
+
{ variant: 'pills', active: true, class: 'bg-white text-gray-900 shadow-sm' },
|
|
329
|
+
{ variant: 'pills', active: false, class: 'text-gray-500 hover:text-gray-700' },
|
|
330
|
+
],
|
|
331
|
+
defaultVariants: { variant: 'underline', active: false },
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
tab({ variant: 'pills', active: true })
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Pattern: Multi-part component with slots
|
|
338
|
+
|
|
339
|
+
```jsx
|
|
340
|
+
const dialog = tw({
|
|
341
|
+
name: 'dialog',
|
|
342
|
+
slots: {
|
|
343
|
+
overlay: 'fixed inset-0 z-50 flex items-center justify-center p-4',
|
|
344
|
+
backdrop: 'absolute inset-0 bg-black/50',
|
|
345
|
+
content: 'relative bg-white rounded-xl shadow-xl w-full max-h-[85vh] overflow-y-auto',
|
|
346
|
+
header: 'border-b border-gray-100 flex items-center justify-between',
|
|
347
|
+
title: 'font-semibold text-gray-900',
|
|
348
|
+
body: '',
|
|
349
|
+
footer: 'border-t border-gray-100 flex justify-end gap-2',
|
|
350
|
+
},
|
|
351
|
+
variants: {
|
|
352
|
+
size: {
|
|
353
|
+
sm: { content: 'max-w-sm', header: 'px-4 py-3', body: 'px-4 py-3', footer: 'px-4 py-3' },
|
|
354
|
+
md: { content: 'max-w-md', header: 'px-6 py-4', body: 'px-6 py-4', footer: 'px-6 py-4' },
|
|
355
|
+
lg: { content: 'max-w-2xl', header: 'px-8 py-5', body: 'px-8 py-5', footer: 'px-8 py-5' },
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
defaultVariants: { size: 'md' },
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Usage:
|
|
362
|
+
const slots = dialog({ size: 'lg' });
|
|
363
|
+
<div className={slots.overlay}>
|
|
364
|
+
<div className={slots.backdrop} onClick={onClose} />
|
|
365
|
+
<div className={slots.content}>
|
|
366
|
+
<div className={slots.header}><h2 className={slots.title}>{title}</h2></div>
|
|
367
|
+
<div className={slots.body}>{children}</div>
|
|
368
|
+
<div className={slots.footer}>{footer}</div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Pattern: Toggle with color maps (combining tw() and inline styles)
|
|
374
|
+
|
|
375
|
+
When a component has many possible colors (not fixed variants), use a color map with inline styles for the dynamic color, and tw() for structural styling:
|
|
376
|
+
|
|
377
|
+
```jsx
|
|
378
|
+
const track = tw({
|
|
379
|
+
name: 'toggle-track',
|
|
380
|
+
base: 'relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200',
|
|
381
|
+
variants: {
|
|
382
|
+
size: { sm: 'w-8 h-4', md: 'w-11 h-6', lg: 'w-14 h-7' },
|
|
383
|
+
checked: { true: '', false: 'bg-gray-200' },
|
|
384
|
+
disabled: { true: 'opacity-50 cursor-not-allowed' },
|
|
385
|
+
},
|
|
386
|
+
defaultVariants: { size: 'md', checked: false },
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const checkedColors = {
|
|
390
|
+
blue: '#3b82f6', green: '#10b981', red: '#ef4444', purple: '#8b5cf6',
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Usage: tw() handles layout/structure, inline style handles dynamic color
|
|
394
|
+
<button
|
|
395
|
+
className={track({ size, checked, disabled })}
|
|
396
|
+
style={checked ? { backgroundColor: checkedColors[color] } : undefined}
|
|
397
|
+
/>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Pattern: Table with structural tw() + value maps for density/variant
|
|
401
|
+
|
|
402
|
+
For data-driven variations where utilities don't cleanly map (padding values, complex conditional styles), define value maps as plain objects and apply via inline style:
|
|
403
|
+
|
|
404
|
+
```jsx
|
|
405
|
+
const wrapperCls = tw('tbl-wrapper', 'overflow-x-auto rounded-lg');
|
|
406
|
+
const tableCls = tw('tbl-table', 'w-full text-sm text-left');
|
|
407
|
+
const thBaseCls = tw('tbl-th', 'text-xs font-semibold text-gray-600 uppercase tracking-wider');
|
|
408
|
+
const tdBaseCls = tw('tbl-td', 'text-gray-700 border-b border-gray-100');
|
|
409
|
+
|
|
410
|
+
const DENSITY_PADDING = {
|
|
411
|
+
compact: { padding: '4px 12px' },
|
|
412
|
+
default: { padding: '12px 16px' },
|
|
413
|
+
comfortable: { padding: '20px 24px' },
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// Usage:
|
|
417
|
+
<th className={thBaseCls} style={DENSITY_PADDING[density]}>{col.label}</th>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Pattern: Responsive layout using tw()
|
|
421
|
+
|
|
422
|
+
```jsx
|
|
423
|
+
const layout = tw('app-layout', 'flex h-screen overflow-hidden bg-gray-50');
|
|
424
|
+
const sidebar = tw('app-sidebar', 'hidden md:flex w-56 shrink-0 flex-col bg-white border-r border-gray-200 h-full overflow-y-auto');
|
|
425
|
+
const hamburger = tw('app-hamburger', 'flex md:hidden items-center p-1 rounded-md text-gray-700 hover:bg-gray-100 bg-transparent border-none cursor-pointer');
|
|
426
|
+
const content = tw('app-content', 'flex-1 overflow-y-auto overflow-x-hidden p-4 md:p-8 min-w-0');
|
|
427
|
+
|
|
428
|
+
// Sidebar hidden on mobile, shown on md+. Hamburger visible on mobile only.
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Pattern: Design tokens with live theme switching
|
|
432
|
+
|
|
433
|
+
```jsx
|
|
434
|
+
import { createTheme, token } from 'tailwind-to-style/tokens';
|
|
435
|
+
import { ThemeProvider, useTheme } from 'tailwind-to-style/react';
|
|
436
|
+
|
|
437
|
+
const THEMES = {
|
|
438
|
+
blue: { colors: { primary: '#2563eb', primaryLight: '#dbeafe', surface: '#ffffff' } },
|
|
439
|
+
emerald: { colors: { primary: '#059669', primaryLight: '#d1fae5', surface: '#f0fdf4' } },
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Apply theme
|
|
443
|
+
createTheme(structuredClone(THEMES.blue), { name: 'blue' });
|
|
444
|
+
|
|
445
|
+
// Reference in component styles (uses CSS variables — updates automatically when theme changes)
|
|
446
|
+
const themedBtn = tw({
|
|
447
|
+
name: 'theme-btn',
|
|
448
|
+
base: 'px-4 py-2 rounded-lg font-medium transition-all cursor-pointer',
|
|
449
|
+
variants: {
|
|
450
|
+
intent: {
|
|
451
|
+
primary: `bg-[${token('colors.primary')}] text-white hover:opacity-90`,
|
|
452
|
+
surface: `bg-[${token('colors.surface')}] text-[${token('colors.text')}] border border-[${token('colors.border')}]`,
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
defaultVariants: { intent: 'primary' },
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Best Practices
|
|
462
|
+
|
|
463
|
+
1. **Use `tw('name', 'classes')` for static styles.** It's the shortest, cleanest form.
|
|
464
|
+
2. **Use Mode 3 (object with base + variants) only when you need runtime variant selection.**
|
|
465
|
+
3. **Use `className={tw(...)}` for all responsive and interactive styles.** Never `style={tws('md:...')}` — responsive/pseudo-class does not work in inline styles.
|
|
466
|
+
4. **Use `cx()` to merge conditional classes.** Not string concatenation.
|
|
467
|
+
5. **Filter undefined variant props** before passing to a variant function — pass only defined keys so defaultVariants apply correctly.
|
|
468
|
+
6. **For dynamic colors not in a fixed variant set**, use a color map + inline style for the color, and tw() for everything structural.
|
|
469
|
+
7. **This library replaces Tailwind CSS entirely.** Do not install both.
|
|
470
|
+
8. **Always clone theme objects** when passing to `createTheme()` in React effects (use `structuredClone()`).
|
|
471
|
+
9. **Arbitrary values work:** `tw('box', 'w-[200px] h-[50vh] bg-[#ff6600] text-[1.2rem]')`
|
|
472
|
+
10. **All Tailwind v3 utilities are supported.** Most v4 utilities also supported.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Links
|
|
477
|
+
|
|
478
|
+
- npm: https://www.npmjs.com/package/tailwind-to-style
|
|
479
|
+
- GitHub: https://github.com/Bigetion/tailwind-to-style
|
|
480
|
+
- Live Demo: https://n86xr8.csb.app/
|
|
481
|
+
- License: MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tailwind-to-style",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
|
+
"description": "Zero-build runtime Tailwind CSS engine. Convert utility classes to real CSS with variants, slots, tokens, and React bindings. SSR-ready, tree-shakeable.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.esm.js",
|
|
@@ -15,26 +15,26 @@
|
|
|
15
15
|
"import": "./dist/index.esm.js",
|
|
16
16
|
"require": "./dist/index.cjs"
|
|
17
17
|
},
|
|
18
|
+
"./react": {
|
|
19
|
+
"types": "./dist/react/index.d.ts",
|
|
20
|
+
"import": "./dist/react/index.esm.js",
|
|
21
|
+
"require": "./dist/react/index.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./tokens": {
|
|
24
|
+
"types": "./dist/tokens/index.d.ts",
|
|
25
|
+
"import": "./dist/tokens/index.esm.js",
|
|
26
|
+
"require": "./dist/tokens/index.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./animations": {
|
|
29
|
+
"types": "./dist/animations/index.d.ts",
|
|
30
|
+
"import": "./dist/animations/index.esm.js",
|
|
31
|
+
"require": "./dist/animations/index.cjs"
|
|
32
|
+
},
|
|
18
33
|
"./tws": {
|
|
19
34
|
"types": "./dist/core/tws.d.ts",
|
|
20
35
|
"import": "./dist/core/tws.esm.js",
|
|
21
36
|
"require": "./dist/core/tws.cjs"
|
|
22
37
|
},
|
|
23
|
-
"./twsx": {
|
|
24
|
-
"types": "./dist/core/twsx.d.ts",
|
|
25
|
-
"import": "./dist/core/twsx.esm.js",
|
|
26
|
-
"require": "./dist/core/twsx.cjs"
|
|
27
|
-
},
|
|
28
|
-
"./twsx-variants": {
|
|
29
|
-
"types": "./dist/core/twsxVariants.d.ts",
|
|
30
|
-
"import": "./dist/core/twsxVariants.esm.js",
|
|
31
|
-
"require": "./dist/core/twsxVariants.cjs"
|
|
32
|
-
},
|
|
33
|
-
"./utils": {
|
|
34
|
-
"types": "./dist/utils/index.d.ts",
|
|
35
|
-
"import": "./dist/utils/index.esm.js",
|
|
36
|
-
"require": "./dist/utils/index.cjs"
|
|
37
|
-
},
|
|
38
38
|
"./cx": {
|
|
39
39
|
"types": "./dist/cx.d.ts",
|
|
40
40
|
"import": "./dist/cx.esm.js",
|
|
@@ -48,11 +48,16 @@
|
|
|
48
48
|
"./preflight.css": "./preflight.css",
|
|
49
49
|
"./package.json": "./package.json"
|
|
50
50
|
},
|
|
51
|
-
"sideEffects":
|
|
51
|
+
"sideEffects": [
|
|
52
|
+
"*.css",
|
|
53
|
+
"dist/*.js",
|
|
54
|
+
"dist/**/*.js"
|
|
55
|
+
],
|
|
52
56
|
"files": [
|
|
53
57
|
"dist",
|
|
54
58
|
"types",
|
|
55
59
|
"preflight.css",
|
|
60
|
+
"llms.txt",
|
|
56
61
|
"README.md",
|
|
57
62
|
"LICENSE"
|
|
58
63
|
],
|
|
@@ -82,19 +87,20 @@
|
|
|
82
87
|
"tailwind",
|
|
83
88
|
"tailwindcss",
|
|
84
89
|
"css",
|
|
85
|
-
"stylesheet",
|
|
86
|
-
"inline-styles",
|
|
87
|
-
"runtime-css",
|
|
88
90
|
"css-in-js",
|
|
91
|
+
"runtime-css",
|
|
89
92
|
"zero-build",
|
|
93
|
+
"variants",
|
|
94
|
+
"slots",
|
|
95
|
+
"design-tokens",
|
|
96
|
+
"styled-components",
|
|
90
97
|
"ssr",
|
|
91
98
|
"server-side-rendering",
|
|
92
99
|
"tree-shakeable",
|
|
93
|
-
"variants",
|
|
94
|
-
"style-converter",
|
|
95
100
|
"react",
|
|
96
101
|
"vue",
|
|
97
|
-
"svelte"
|
|
102
|
+
"svelte",
|
|
103
|
+
"vanilla-js"
|
|
98
104
|
],
|
|
99
105
|
"author": "Bigetion",
|
|
100
106
|
"license": "MIT",
|
|
@@ -106,6 +112,14 @@
|
|
|
106
112
|
"url": "https://github.com/Bigetion/tailwind-to-style/issues"
|
|
107
113
|
},
|
|
108
114
|
"homepage": "https://github.com/Bigetion/tailwind-to-style#readme",
|
|
115
|
+
"peerDependencies": {
|
|
116
|
+
"react": ">=17.0.0"
|
|
117
|
+
},
|
|
118
|
+
"peerDependenciesMeta": {
|
|
119
|
+
"react": {
|
|
120
|
+
"optional": true
|
|
121
|
+
}
|
|
122
|
+
},
|
|
109
123
|
"devDependencies": {
|
|
110
124
|
"@babel/core": "^7.27.4",
|
|
111
125
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Type definitions for tailwind-to-style/animations
|
|
2
|
+
|
|
3
|
+
export type AnimationPresetName =
|
|
4
|
+
| 'fadeIn' | 'fadeOut'
|
|
5
|
+
| 'slideInUp' | 'slideInDown' | 'slideInLeft' | 'slideInRight'
|
|
6
|
+
| 'scaleIn' | 'scaleOut'
|
|
7
|
+
| 'bounce' | 'shake' | 'pulse' | 'spin' | 'ping';
|
|
8
|
+
|
|
9
|
+
export interface AnimationKeyframe {
|
|
10
|
+
[property: string]: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AnimationConfig {
|
|
14
|
+
keyframes: AnimationKeyframe[];
|
|
15
|
+
duration?: string;
|
|
16
|
+
easing?: string;
|
|
17
|
+
delay?: string;
|
|
18
|
+
iterations?: string | number;
|
|
19
|
+
fillMode?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AnimateOptions {
|
|
23
|
+
duration?: string;
|
|
24
|
+
easing?: string;
|
|
25
|
+
delay?: string;
|
|
26
|
+
iterations?: string | number;
|
|
27
|
+
fillMode?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Apply a named animation, returning a className with the animation injected.
|
|
32
|
+
*/
|
|
33
|
+
export declare function animate(name: AnimationPresetName | string, options?: AnimateOptions): string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Define a custom animation preset.
|
|
37
|
+
*/
|
|
38
|
+
export declare function defineAnimation(name: string, config: AnimationConfig): void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get all available animation names (built-in + custom).
|
|
42
|
+
*/
|
|
43
|
+
export declare function getAnimationNames(): string[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get an animation preset config by name.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getPreset(name: string): AnimationConfig | undefined;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Clear all injected keyframes and custom animations.
|
|
52
|
+
*/
|
|
53
|
+
export declare function clearAnimations(): void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Built-in animation presets.
|
|
57
|
+
*/
|
|
58
|
+
export declare const ANIMATION_PRESETS: Record<AnimationPresetName, AnimationConfig>;
|
package/types/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
// Type definitions for tailwind-to-style
|
|
1
|
+
// Type definitions for tailwind-to-style v4
|
|
2
2
|
// Project: https://github.com/Bigetion/tailwind-to-style
|
|
3
3
|
// Definitions by: Bigetion
|
|
4
4
|
|
|
5
|
+
// v4 unified API re-exports
|
|
6
|
+
export { tw, tws, cx, createSSRCollector } from './v4';
|
|
7
|
+
|
|
5
8
|
// ============================================================================
|
|
6
9
|
// Environment Detection
|
|
7
10
|
// ============================================================================
|