next-style 1.2.0 → 2.0.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,532 +1,323 @@
1
- # NextStyle
1
+ # Next Style
2
2
 
3
- A lightweight **runtime CSS-in-JS engine** for React with deterministic class names, nested pseudo selectors, media queries, global styles, keyframes, and font-face support.
3
+ > **Zero-Runtime CSS-in-JS** for Next.js with Turbopack support
4
4
 
5
- Designed for **page-scoped and component-scoped usage** without build-time tooling.
5
+ A lightweight CSS-in-JS library that extracts styles at build time, resulting in zero runtime overhead. Write styles in JavaScript with full TypeScript support while shipping only pure CSS.
6
6
 
7
- ---
8
-
9
- ## Package Information
10
-
11
- - **Name:** next-style
12
- - **Version:** 1.1.5
13
- - **License:** MIT
14
- - **Author:** kingslimes
15
- https://github.com/kingslimes
16
- - **Repository:**
17
- https://github.com/kingslimes/next-style
18
- - **Issue Tracker:**
19
- https://github.com/kingslimes/next-style/issues
20
-
21
- ---
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
22
8
 
23
- ## Features
9
+ ## Why next-style?
24
10
 
25
- - Object-based styling (TypeScript friendly)
26
- - Deterministic class names (same style same class)
27
- - Pseudo selectors (`:hover`, `:focus`, `:active`)
28
- - Responsive media queries (`sm` `xxl`)
29
- - Global styles
30
- - `@keyframes` support
31
- - `@font-face` support
32
- - Built-in PostCSS + Autoprefixer
33
- - Zero DOM dependency
34
- - Tree-shakeable (`sideEffects: false`)
35
- - Copy–paste friendly API
11
+ - **Zero Runtime** All style extraction happens at build time. Ship pure CSS, not JavaScript.
12
+ - **Turbopack Ready** Optimized for Next.js 15+ and Turbopack without additional configuration.
13
+ - **Type Safe** — Full TypeScript support powered by [csstype](https://github.com/frenic/csstype) for intelligent autocomplete on every CSS property and value.
14
+ - **Automatic Deduplication** Identical style objects always produce the same class name.
15
+ - **Responsive First** — Built-in shorthand breakpoints (`@sm`, `@md`, `@lg`, `@xl`, `@2xl`), sorted mobile-first automatically.
16
+ - **Developer Experience** — Simple API. Just `css({})` and go.
36
17
 
37
- ---
18
+ ## Quick Start
38
19
 
39
- ## Installation
20
+ ### Installation
40
21
 
41
- ``` bash
42
- npm install next-style
43
- # or
22
+ ```bash
44
23
  bun add next-style
45
24
  ```
46
25
 
47
- ---
48
-
49
- ## Peer Dependencies
50
-
51
- NextStyle relies on the following peer dependencies:
52
-
53
- ``` txt
54
- react >= 18
55
- postcss ^8
56
- autoprefixer ^10
57
- ```
58
-
59
- Make sure they are installed in your project.
60
-
61
- ---
26
+ ### Setup
62
27
 
63
- ## Recommended Usage Pattern (Scoped)
28
+ #### 1. Configure PostCSS
64
29
 
65
- The **recommended and official pattern** is to scope styles per page or per component using destructuring.
30
+ Create `postcss.config.js` in your project root:
66
31
 
67
- ``` ts
68
- const { css, StyleProvider } = new NextStyle("home")
32
+ ```js
33
+ export default {
34
+ plugins: {
35
+ "next-style/plugin": {}
36
+ }
37
+ }
69
38
  ```
70
39
 
71
- Why this works well:
72
- - Clear scope ownership
73
- - No global side effects
74
- - Easy to copy and reuse
75
- - Matches React’s mental model
76
-
77
- ---
40
+ If you already have `postcss.config.js` (e.g. with Tailwind), add next-style first:
78
41
 
79
- ## Basic Example (Page Scoped)
80
-
81
- ``` tsx
82
- import { NextStyle } from "next-style"
83
-
84
- export default function HomePage() {
85
- const { css, StyleProvider } = new NextStyle("home")
86
-
87
- const title = css({
88
- fontSize: "32px",
89
- fontWeight: 700,
90
- marginBottom: "16px"
91
- })
92
-
93
- const button = css({
94
- padding: "10px 20px",
95
- borderRadius: "8px",
96
- backgroundColor: "#2563eb",
97
- color: "#fff",
98
-
99
- _hover: {
100
- backgroundColor: "#1d4ed8"
101
- }
102
- })
103
-
104
- return (
105
- <>
106
- <StyleProvider />
107
- <h1 className={title}>Home</h1>
108
- <button className={button}>Click me</button>
109
- </>
110
- )
42
+ ```js
43
+ export default {
44
+ plugins: {
45
+ "next-style/plugin": {},
46
+ tailwindcss: {},
47
+ autoprefixer: {}
48
+ }
111
49
  }
112
50
  ```
113
51
 
114
- ---
115
-
116
- ## Styling API
117
-
118
- ### `css(style): string`
52
+ > **Order matters** — next-style must come before other plugins.
119
53
 
120
- Creates a class name from a style object.
54
+ #### 2. Import styles in `globals.css`
121
55
 
122
- ``` ts
123
- const className = css({
124
- color: "red",
125
- fontSize: "16px"
126
- })
56
+ ```css
57
+ @import "next-style";
58
+ /* your other imports/rules */
127
59
  ```
128
60
 
129
- - Automatically converts camelCase kebab-case
130
- - Deduplicates styles using hashing
131
- - Returns a stable class name
61
+ The PostCSS plugin replaces this `@import` with the compiled CSS at build time.
132
62
 
133
- ---
63
+ #### 3. Use in components
134
64
 
135
- ## Pseudo Selectors
65
+ ```tsx
66
+ import { css } from "next-style"
136
67
 
137
- Supported pseudo keys:
138
-
139
- | Key | CSS Output |
140
- |----|-----------|
141
- | `_hover` | `:hover` |
142
- | `_focus` | `:focus` |
143
- | `_active` | `:active` |
144
-
145
- Example:
146
-
147
- ``` ts
148
- css({
149
- color: "black",
150
- _hover: {
151
- color: "red"
152
- }
68
+ const title = css({
69
+ fontSize: "32px",
70
+ fontWeight: 500,
71
+ "@md": { fontSize: "40px" },
72
+ ":hover": { color: "#7F77DD" }
153
73
  })
154
- ```
155
-
156
- ---
157
-
158
- ## Relation Selector API
159
74
 
160
- `next-style` provides a **relation-based selector API** that allows you to define styles based on the state of one element affecting another element, without manually writing complex CSS selectors.
161
-
162
- This API is designed to be:
163
- - Declarative and readable
164
- - Type-safe (no string selectors)
165
- - Fully compatible with runtime CSS-in-JS
166
-
167
- ---
168
-
169
- ### Basic Usage
170
-
171
- ``` ts
172
- const { css, when } = new NextStyle()
173
-
174
- const container = css({})
175
- const button = css({})
176
-
177
- when(container)
178
- .hover()
179
- .adjacent(button, {
180
- backgroundColor: "red"
181
- })
182
- ```
183
- Equivalent CSS:
184
-
185
- ``` css
186
- .container:hover + .button {
187
- background-color: red;
75
+ export default function App() {
76
+ return <h1 className={title}>Hello World</h1>
188
77
  }
189
78
  ```
190
79
 
191
- ---
192
-
193
- ## Supported States
194
-
195
- You can define relationships based on the following pseudo states:
196
-
197
- - `hover()` → `:hover`
198
- - `focus()` → `:focus`
199
- - `active()` → `:active`
200
-
201
- Example:
80
+ ## Features
202
81
 
203
- ``` ts
204
- when(input)
205
- .focus()
206
- .sibling(label, {
207
- color: "blue"
208
- })
209
- ```
210
- Equivalent CSS:
82
+ | Feature | Status | Details |
83
+ |---------|--------|---------|
84
+ | Zero Runtime | ✅ | Styles extracted at build time |
85
+ | Turbopack | ✅ | Native support for Next.js 15+ |
86
+ | Type Safety | ✅ | Powered by csstype — full property + value autocomplete |
87
+ | Responsive Breakpoints | ✅ | `@sm` `@md` `@lg` `@xl` `@2xl` |
88
+ | Arbitrary Media Queries | ✅ | `'@media (min-width: 900px)'` |
89
+ | Pseudo-classes | ✅ | `:hover` `:focus` `:active` `:disabled` `:focus-visible` … |
90
+ | Pseudo-elements | ✅ | `::before` `::after` `::first-letter` … |
91
+ | Keyframe Animations | ✅ | `'@keyframes name'` inline with the style object |
92
+ | Container Queries | ✅ | `'@container sidebar (min-width: 300px)'` |
93
+ | `@supports` | ✅ | `'@supports (display: grid)'` |
94
+ | `@layer` | ✅ | `'@layer utilities'` |
95
+ | CSS Variables | ✅ | `var(--token)` as values |
96
+ | Deduplication | ✅ | Same object → same class, always |
97
+ | Global Styles | ✅ | `global()` for resets and base rules |
98
+
99
+ ## API
100
+
101
+ ### `css(styles: CSSObject): string`
102
+
103
+ Converts a style object into a unique, stable class name. Identical objects always return the same class (deduplication). Styles are collected at build time — zero cost at runtime.
104
+
105
+ ```tsx
106
+ const button = css({
107
+ padding: "8px 16px",
108
+ borderRadius: "6px",
109
+ backgroundColor: "#7F77DD",
110
+ color: "#fff",
111
+ cursor: "pointer",
112
+ ":hover": { backgroundColor: "#534AB7" },
113
+ "@md": { padding: "10px 20px" }
114
+ })
211
115
 
212
- ``` css
213
- input:focus ~ label {
214
- color: blue;
116
+ export function Button() {
117
+ return <button className={button}>Click me</button>
215
118
  }
216
119
  ```
217
120
 
218
- ---
219
-
220
- ## Supported Relationships
121
+ ### `global(styles: Record<string, CSSObject>): void`
221
122
 
222
- ### Adjacent Sibling (`+`)
123
+ Registers global CSS rules applied directly to selectors — no scoped class. Useful for resets, base typography, and third-party element overrides.
223
124
 
224
- Applies styles to the **immediately following sibling**.
125
+ ```tsx
126
+ import { global } from "next-style"
225
127
 
226
- ``` ts
227
- when(div)
228
- .hover()
229
- .adjacent(p, {
230
- color: "red"
231
- })
128
+ global({
129
+ "*": { boxSizing: "border-box", margin: "0" },
130
+ "body": { fontFamily: "Inter, sans-serif", lineHeight: "1.6" },
131
+ "h1, h2, h3": { fontWeight: 500, lineHeight: "1.2" }
132
+ })
232
133
  ```
233
- CSS equivalent:
234
134
 
235
- ``` css
236
- div:hover + p {
237
- color: red;
238
- }
239
- ```
135
+ ## Examples
240
136
 
241
- ---
137
+ ### Responsive design
242
138
 
243
- ### General Sibling (`~`)
139
+ Breakpoints expand to standard `min-width` media queries and are sorted mobile-first automatically.
244
140
 
245
- Applies styles to **all following siblings**.
141
+ | Shorthand | Expands to |
142
+ |-----------|-----------|
143
+ | `@sm` | `@media (min-width: 640px)` |
144
+ | `@md` | `@media (min-width: 768px)` |
145
+ | `@lg` | `@media (min-width: 1024px)` |
146
+ | `@xl` | `@media (min-width: 1280px)` |
147
+ | `@2xl` | `@media (min-width: 1536px)` |
246
148
 
247
- ``` ts
248
- when(div)
249
- .hover()
250
- .sibling(p, {
251
- color: "red"
252
- })
253
- ```
254
- CSS equivalent:
255
-
256
- ``` css
257
- div:hover ~ p {
258
- color: red;
259
- }
149
+ ```tsx
150
+ const container = css({
151
+ width: "100%",
152
+ padding: "16px",
153
+ "@md": { width: "768px", padding: "24px" },
154
+ "@lg": { width: "1024px", padding: "32px" }
155
+ })
260
156
  ```
261
157
 
262
- ---
263
-
264
- ### Child (`>`)
265
-
266
- Applies styles to **direct children only**.
158
+ Arbitrary media queries are also supported:
267
159
 
268
- ``` ts
269
- when(card)
270
- .hover()
271
- .child(icon, {
272
- transform: "scale(1.1)"
273
- })
274
- ```
275
- CSS equivalent:
276
-
277
- ``` css
278
- card:hover > icon {
279
- transform: scale(1.1);
280
- }
160
+ ```tsx
161
+ const sidebar = css({
162
+ display: "none",
163
+ "@media (min-width: 900px)": { display: "block" }
164
+ })
281
165
  ```
282
166
 
283
- ---
284
-
285
- ### Descendant (space)
167
+ ### Interactive states
286
168
 
287
- Applies styles to **any nested element**.
288
-
289
- ``` ts
290
- when(menu)
291
- .hover()
292
- .descendant(item, {
293
- backgroundColor: "#eee"
294
- })
295
- ```
296
- CSS equivalent:
297
-
298
- ``` css
299
- menu:hover item {
300
- background-color: #eee;
301
- }
169
+ ```tsx
170
+ const link = css({
171
+ color: "#3b82f6",
172
+ textDecoration: "none",
173
+ transition: "color 0.2s",
174
+ ":hover": { color: "#1e40af" },
175
+ ":focus-visible": { outline: "2px solid #3b82f6", outlineOffset: "2px" },
176
+ ":active": { color: "#1e3a8a" }
177
+ })
302
178
  ```
303
179
 
304
- ---
180
+ ### Keyframe animations
305
181
 
306
- ## Why Use `when()` Instead of `global()`?
182
+ Declare `@keyframes` inline alongside the style that uses them:
307
183
 
308
- While `global()` allows full control over raw selectors, `when()` provides:
184
+ ```tsx
185
+ const spinner = css({
186
+ animationName: "spin",
187
+ animationDuration: "1s",
188
+ animationTimingFunction: "linear",
189
+ animationIterationCount: "infinite",
190
+ "@keyframes spin": {
191
+ to: { transform: "rotate(360deg)" }
192
+ }
193
+ })
194
+ ```
309
195
 
310
- - Clear intent and better readability
311
- - No need to manually concatenate class names
312
- - Safer refactoring (class tokens, not strings)
313
- - IDE autocomplete and JSDoc support
196
+ ### Container queries
314
197
 
315
- Comparison:
198
+ ```tsx
199
+ const card = css({
200
+ fontSize: "14px",
201
+ "@container sidebar (min-width: 300px)": { fontSize: "16px" }
202
+ })
203
+ ```
316
204
 
317
- ``` ts
318
- // global selector
319
- global(`.${a}:hover + .${b}`, { color: "red" })
205
+ ### CSS variables
320
206
 
321
- // relation API
322
- when(a).hover().adjacent(b, { color: "red" })
207
+ ```tsx
208
+ const card = css({
209
+ backgroundColor: "var(--bg-primary)",
210
+ color: "var(--text-primary)",
211
+ padding: "var(--spacing-4)",
212
+ borderRadius: "var(--radius-lg)"
213
+ })
323
214
  ```
324
215
 
325
- ---
216
+ ## TypeScript
326
217
 
327
- ## Notes
218
+ All CSS properties and values are fully typed via [csstype](https://github.com/frenic/csstype). Typos in property names are caught at compile time and your editor will autocomplete valid values.
328
219
 
329
- - `when()` works with class names generated by `css()`
330
- - Relation rules are injected via the same internal pipeline as `global()`
331
- - Media queries and nested styles are fully supported inside relation styles
220
+ ```tsx
221
+ import { css, type CSSObject } from "next-style"
332
222
 
333
- ---
223
+ const myStyles: CSSObject = {
224
+ fontSize: "16px", // ✅ typed
225
+ colour: "red", // ❌ compile error — unknown property
226
+ "@md": { fontSize: "20px" },
227
+ ":hover": { opacity: 0.8 }
228
+ }
334
229
 
335
- ## Example With Media Query
336
-
337
- ``` ts
338
- when(container)
339
- .hover()
340
- .sibling(text, {
341
- color: "red",
342
- _md: {
343
- color: "blue"
344
- }
345
- })
230
+ const title = css(myStyles)
346
231
  ```
347
232
 
348
- ---
349
-
350
- This API is intentionally minimal and composable, allowing you to express complex UI relationships without leaking CSS selector syntax into your application code.
233
+ ### Exported types
351
234
 
352
- ---
235
+ | Type | Description |
236
+ |------|-------------|
237
+ | `CSSObject` | Style object accepted by `css()` and `global()` |
238
+ | `CSSProperties` | CSS properties only (no at-rules or pseudos) — backed by csstype |
353
239
 
354
- ## Responsive Media Queries
240
+ ## Advanced: `createTransformer`
355
241
 
356
- Built-in breakpoints:
242
+ For SWC/Babel transforms and test harnesses that need an isolated collector independent of the shared runtime instance:
357
243
 
358
- | Key | Media Query |
359
- |----|-------------|
360
- | `_sm` | `(min-width: 640px)` |
361
- | `_md` | `(min-width: 768px)` |
362
- | `_lg` | `(min-width: 1024px)` |
363
- | `_xl` | `(min-width: 1280px)` |
364
- | `_xxl` | `(min-width: 1536px)` |
244
+ ```ts
245
+ import { createTransformer } from "next-style"
365
246
 
366
- Example:
367
-
368
- ``` ts
369
- css({
370
- fontSize: "14px",
371
- _lg: {
372
- fontSize: "18px"
373
- }
374
- })
247
+ const { collector, transformCssCall } = createTransformer()
248
+ const className = transformCssCall({ color: "red" }) // "ns-abc123"
249
+ const css = collector.getAllStyles() // ".ns-abc123 { color: red; }"
375
250
  ```
376
251
 
377
- Media queries can be nested and merged automatically.
252
+ ## Development
378
253
 
379
- ---
254
+ ```bash
255
+ # Install dependencies
256
+ bun install
380
257
 
381
- ## Global Styles
258
+ # Build
259
+ bun run build
382
260
 
383
- ### `global(selector, style)`
261
+ # Watch for changes
262
+ bun run dev
384
263
 
385
- Apply styles globally without generating a class.
264
+ # Type-check only
265
+ bunx tsc --noEmit
386
266
 
387
- ``` ts
388
- const { global, StyleProvider } = new NextStyle("global")
267
+ # Lint
268
+ bun run lint
389
269
 
390
- global("body", {
391
- margin: 0,
392
- fontFamily: "system-ui"
393
- })
394
-
395
- global("a", {
396
- color: "inherit",
397
- _hover: {
398
- textDecoration: "underline"
399
- }
400
- })
270
+ # Format
271
+ bun run format
401
272
  ```
402
273
 
403
- ---
404
-
405
- ## Animations
406
-
407
- ### `keyframes(frames): string`
408
-
409
- Creates a `@keyframes` rule and returns its name.
410
-
411
- ``` ts
412
- const fadeIn = keyframes({
413
- from: { opacity: 0 },
414
- to: { opacity: 1 }
415
- })
274
+ ### Project structure
416
275
 
417
- css({
418
- animation: `${fadeIn} 300ms ease-in`
419
- })
420
276
  ```
421
-
422
- ---
423
-
424
- ## Fonts
425
-
426
- ### `fontFace(font)`
427
-
428
- Registers a `@font-face` rule.
429
-
430
- ``` ts
431
- fontFace({
432
- fontFamily: "MyFont",
433
- src: "url(/fonts/myfont.woff2)",
434
- fontWeight: 400,
435
- fontStyle: "normal",
436
- fontDisplay: "swap"
437
- })
277
+ src/
278
+ ├── runtime/ # css() · global() · CSSObject type
279
+ ├── postcss-plugin/ # @import "next-style" → compiled CSS
280
+ ├── compiler/ # StyleCollector · createTransformer
281
+ └── utils/ # camelToKebab · generateClassHash · BREAKPOINTS
438
282
  ```
439
283
 
440
- ---
441
-
442
- ## Rendering Styles
284
+ ## Performance
443
285
 
444
- ### `<StyleProvider />`
286
+ - **Bundle size** — ~2 KB minified + gzipped
287
+ - **Runtime cost** — 0 bytes (styles extracted at build time)
288
+ - **Build overhead** — negligible
445
289
 
446
- Injects all generated CSS into a `<style>` tag.
290
+ ## Browser support
447
291
 
448
- ``` tsx
449
- <>
450
- <StyleProvider />
451
- <App />
452
- </>
453
- ```
454
-
455
- - Returns `null` if no styles exist
456
- - Should be rendered **once per scope**
457
-
458
- ---
292
+ All modern browsers (Chrome, Firefox, Safari, Edge).
459
293
 
460
- ### `toTextCss(): string | null`
294
+ ## Troubleshooting
461
295
 
462
- Returns all generated CSS as a string.
296
+ ### Styles not appearing
463
297
 
464
- Useful for:
465
- - Server-side rendering (SSR)
466
- - Manual injection
467
- - Debugging
298
+ 1. Confirm `postcss.config.js` includes `"next-style/plugin": {}`
299
+ 2. Confirm `@import "next-style";` is present in `globals.css`
300
+ 3. Restart the dev server — PostCSS config changes require a restart
301
+ 4. Clear the Next.js cache: `rm -rf .next` then restart
468
302
 
469
- ``` ts
470
- const cssText = toTextCss()
471
- ```
303
+ ### Using alongside Tailwind / Autoprefixer
472
304
 
473
- ---
305
+ next-style must be listed **first** in the plugins object:
474
306
 
475
- ## Component Scoped Example
476
-
477
- Reusable, self-contained component.
478
-
479
- ``` tsx
480
- import { NextStyle } from "next-style"
481
-
482
- export function Card({ title, children }) {
483
- const { css, StyleProvider } = new NextStyle("card")
484
-
485
- const root = css({
486
- padding: "16px",
487
- borderRadius: "12px",
488
- backgroundColor: "#fff",
489
- boxShadow: "0 10px 25px rgba(0,0,0,.1)"
490
- })
491
-
492
- const heading = css({
493
- fontSize: "18px",
494
- fontWeight: 600,
495
- marginBottom: "8px"
496
- })
497
-
498
- return (
499
- <>
500
- <StyleProvider />
501
- <div className={root}>
502
- <div className={heading}>{title}</div>
503
- {children}
504
- </div>
505
- </>
506
- )
307
+ ```js
308
+ export default {
309
+ plugins: {
310
+ "next-style/plugin": {}, // ← first
311
+ tailwindcss: {},
312
+ autoprefixer: {}
313
+ }
507
314
  }
508
315
  ```
509
316
 
510
- ---
511
-
512
- ## Best Practices
513
-
514
- - Create **one NextStyle instance per page or component**
515
- - Do **not** share instances globally
516
- - Render `StyleProvider` only once per scope
517
- - Use meaningful prefixes (`home`, `card`, `profile`)
518
-
519
- ---
520
-
521
- ## Design Intentions
317
+ ## License
522
318
 
523
- - No descendant selectors (`& > div`)
524
- - No arbitrary selector nesting
525
- - Predictable output over expressiveness
526
- - Optimized for runtime and SSR safety
319
+ MIT © [TiwPhiraphan](https://github.com/TiwPhiraphan)
527
320
 
528
321
  ---
529
322
 
530
- ## License
531
-
532
- MIT © kingslimes
323
+ **Made with ❤️ for Next.js developers**