next-style 1.2.1 → 2.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 CHANGED
@@ -1,555 +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
- ---
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
- ## Package Information
9
+ ## Why next-style?
10
10
 
11
- - **Name:** next-style
12
- - **Version:** 1.2.1
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
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.
20
17
 
21
- ---
18
+ ## Quick Start
22
19
 
23
- ## Features
20
+ ### Installation
24
21
 
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
36
-
37
- ---
38
-
39
- ## Installation
40
-
41
- ``` bash
42
- npm install next-style
43
- # or
22
+ ```bash
44
23
  bun add next-style
45
24
  ```
46
25
 
47
- ---
26
+ ### Setup
48
27
 
49
- ## Peer Dependencies
28
+ #### 1. Configure PostCSS
50
29
 
51
- NextStyle relies on the following peer dependencies:
30
+ Create `postcss.config.js` in your project root:
52
31
 
53
- ``` txt
54
- react >= 18
55
- postcss ^8
56
- autoprefixer ^10
57
- ```
58
-
59
- Make sure they are installed in your project.
60
-
61
- ---
62
-
63
- ## Recommended Usage Pattern (Scoped)
64
-
65
- The **recommended and official pattern** is to scope styles per page or per component using destructuring.
66
-
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
40
+ If you already have `postcss.config.js` (e.g. with Tailwind), add next-style first:
76
41
 
77
- ---
78
-
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
- ### `resetStyle(): this as NextStyle`
52
+ > **Order matters** — next-style must come before other plugins.
119
53
 
120
- Reset default style
54
+ #### 2. Import styles in `globals.css`
121
55
 
122
- ``` ts
123
- const { css, ... } = new NextStyle().resetStyle()
56
+ ```css
57
+ @import "next-style";
58
+ /* your other imports/rules */
124
59
  ```
125
- or
126
- ``` ts
127
- const { resetStyle } = new NextStyle()
128
- resetStyle()
129
- ```
130
-
131
- ### `root(style): void`
132
60
 
133
- Create root style from a style object.
134
-
135
- ``` ts
136
- root({
137
- "--color-base": "#fff"
138
- })
139
- ```
61
+ The PostCSS plugin replaces this `@import` with the compiled CSS at build time.
140
62
 
141
- ### `css(style): string`
63
+ #### 3. Use in components
142
64
 
143
- Creates a class name from a style object.
65
+ ```tsx
66
+ import { css } from "next-style"
144
67
 
145
- ``` ts
146
- const className = css({
147
- color: "red",
148
- fontSize: "16px"
68
+ const title = css({
69
+ fontSize: "32px",
70
+ fontWeight: 500,
71
+ "@md": { fontSize: "40px" },
72
+ ":hover": { color: "#7F77DD" }
149
73
  })
150
- ```
151
-
152
- - Automatically converts camelCase → kebab-case
153
- - Deduplicates styles using hashing
154
- - Returns a stable class name
155
-
156
- ---
157
-
158
- ## Pseudo Selectors
159
-
160
- Supported pseudo keys:
161
-
162
- | Key | CSS Output |
163
- |----|-----------|
164
- | `_hover` | `:hover` |
165
- | `_focus` | `:focus` |
166
- | `_active` | `:active` |
167
74
 
168
- Example:
169
-
170
- ``` ts
171
- css({
172
- color: "black",
173
- _hover: {
174
- color: "red"
175
- }
176
- })
177
- ```
178
-
179
- ---
180
-
181
- ## Relation Selector API
182
-
183
- `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.
184
-
185
- This API is designed to be:
186
- - Declarative and readable
187
- - Type-safe (no string selectors)
188
- - Fully compatible with runtime CSS-in-JS
189
-
190
- ---
191
-
192
- ### Basic Usage
193
-
194
- ``` ts
195
- const { css, when } = new NextStyle()
196
-
197
- const container = css({})
198
- const button = css({})
199
-
200
- when(container)
201
- .hover()
202
- .adjacent(button, {
203
- backgroundColor: "red"
204
- })
205
- ```
206
- Equivalent CSS:
207
-
208
- ``` css
209
- .container:hover + .button {
210
- background-color: red;
75
+ export default function App() {
76
+ return <h1 className={title}>Hello World</h1>
211
77
  }
212
78
  ```
213
79
 
214
- ---
215
-
216
- ## Supported States
217
-
218
- You can define relationships based on the following pseudo states:
219
-
220
- - `hover()` → `:hover`
221
- - `focus()` → `:focus`
222
- - `active()` → `:active`
223
-
224
- Example:
80
+ ## Features
225
81
 
226
- ``` ts
227
- when(input)
228
- .focus()
229
- .sibling(label, {
230
- color: "blue"
231
- })
232
- ```
233
- 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
+ })
234
115
 
235
- ``` css
236
- input:focus ~ label {
237
- color: blue;
116
+ export function Button() {
117
+ return <button className={button}>Click me</button>
238
118
  }
239
119
  ```
240
120
 
241
- ---
242
-
243
- ## Supported Relationships
121
+ ### `global(styles: Record<string, CSSObject>): void`
244
122
 
245
- ### Adjacent Sibling (`+`)
123
+ Registers global CSS rules applied directly to selectors — no scoped class. Useful for resets, base typography, and third-party element overrides.
246
124
 
247
- Applies styles to the **immediately following sibling**.
248
-
249
- ``` ts
250
- when(div)
251
- .hover()
252
- .adjacent(p, {
253
- color: "red"
254
- })
255
- ```
256
- CSS equivalent:
125
+ ```tsx
126
+ import { global } from "next-style"
257
127
 
258
- ``` css
259
- div:hover + p {
260
- color: red;
261
- }
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
+ })
262
133
  ```
263
134
 
264
- ---
135
+ ## Examples
265
136
 
266
- ### General Sibling (`~`)
137
+ ### Responsive design
267
138
 
268
- Applies styles to **all following siblings**.
139
+ Breakpoints expand to standard `min-width` media queries and are sorted mobile-first automatically.
269
140
 
270
- ``` ts
271
- when(div)
272
- .hover()
273
- .sibling(p, {
274
- color: "red"
275
- })
276
- ```
277
- CSS equivalent:
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)` |
278
148
 
279
- ``` css
280
- div:hover ~ p {
281
- color: red;
282
- }
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
+ })
283
156
  ```
284
157
 
285
- ---
286
-
287
- ### Child (`>`)
288
-
289
- Applies styles to **direct children only**.
158
+ Arbitrary media queries are also supported:
290
159
 
291
- ``` ts
292
- when(card)
293
- .hover()
294
- .child(icon, {
295
- transform: "scale(1.1)"
296
- })
160
+ ```tsx
161
+ const sidebar = css({
162
+ display: "none",
163
+ "@media (min-width: 900px)": { display: "block" }
164
+ })
297
165
  ```
298
- CSS equivalent:
299
166
 
300
- ``` css
301
- card:hover > icon {
302
- transform: scale(1.1);
303
- }
304
- ```
167
+ ### Interactive states
305
168
 
306
- ---
307
-
308
- ### Descendant (space)
309
-
310
- Applies styles to **any nested element**.
311
-
312
- ``` ts
313
- when(menu)
314
- .hover()
315
- .descendant(item, {
316
- backgroundColor: "#eee"
317
- })
318
- ```
319
- CSS equivalent:
320
-
321
- ``` css
322
- menu:hover item {
323
- background-color: #eee;
324
- }
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
+ })
325
178
  ```
326
179
 
327
- ---
180
+ ### Keyframe animations
328
181
 
329
- ## Why Use `when()` Instead of `global()`?
182
+ Declare `@keyframes` inline alongside the style that uses them:
330
183
 
331
- 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
+ ```
332
195
 
333
- - Clear intent and better readability
334
- - No need to manually concatenate class names
335
- - Safer refactoring (class tokens, not strings)
336
- - IDE autocomplete and JSDoc support
196
+ ### Container queries
337
197
 
338
- Comparison:
198
+ ```tsx
199
+ const card = css({
200
+ fontSize: "14px",
201
+ "@container sidebar (min-width: 300px)": { fontSize: "16px" }
202
+ })
203
+ ```
339
204
 
340
- ``` ts
341
- // global selector
342
- global(`.${a}:hover + .${b}`, { color: "red" })
205
+ ### CSS variables
343
206
 
344
- // relation API
345
- 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
+ })
346
214
  ```
347
215
 
348
- ---
216
+ ## TypeScript
349
217
 
350
- ## 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.
351
219
 
352
- - `when()` works with class names generated by `css()`
353
- - Relation rules are injected via the same internal pipeline as `global()`
354
- - Media queries and nested styles are fully supported inside relation styles
220
+ ```tsx
221
+ import { css, type CSSObject } from "next-style"
355
222
 
356
- ---
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
+ }
357
229
 
358
- ## Example With Media Query
359
-
360
- ``` ts
361
- when(container)
362
- .hover()
363
- .sibling(text, {
364
- color: "red",
365
- _md: {
366
- color: "blue"
367
- }
368
- })
230
+ const title = css(myStyles)
369
231
  ```
370
232
 
371
- ---
372
-
373
- This API is intentionally minimal and composable, allowing you to express complex UI relationships without leaking CSS selector syntax into your application code.
374
-
375
- ---
233
+ ### Exported types
376
234
 
377
- ## Responsive Media Queries
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 |
378
239
 
379
- Built-in breakpoints:
240
+ ## Advanced: `createTransformer`
380
241
 
381
- | Key | Media Query |
382
- |----|-------------|
383
- | `_sm` | `(min-width: 640px)` |
384
- | `_md` | `(min-width: 768px)` |
385
- | `_lg` | `(min-width: 1024px)` |
386
- | `_xl` | `(min-width: 1280px)` |
387
- | `_xxl` | `(min-width: 1536px)` |
242
+ For SWC/Babel transforms and test harnesses that need an isolated collector independent of the shared runtime instance:
388
243
 
389
- Example:
244
+ ```ts
245
+ import { createTransformer } from "next-style"
390
246
 
391
- ``` ts
392
- css({
393
- fontSize: "14px",
394
- _lg: {
395
- fontSize: "18px"
396
- }
397
- })
247
+ const { collector, transformCssCall } = createTransformer()
248
+ const className = transformCssCall({ color: "red" }) // "ns-abc123"
249
+ const css = collector.getAllStyles() // ".ns-abc123 { color: red; }"
398
250
  ```
399
251
 
400
- Media queries can be nested and merged automatically.
401
-
402
- ---
252
+ ## Development
403
253
 
404
- ## Global Styles
254
+ ```bash
255
+ # Install dependencies
256
+ bun install
405
257
 
406
- ### `global(selector, style)`
258
+ # Build
259
+ bun run build
407
260
 
408
- Apply styles globally without generating a class.
261
+ # Watch for changes
262
+ bun run dev
409
263
 
410
- ``` ts
411
- const { global, StyleProvider } = new NextStyle("global")
264
+ # Type-check only
265
+ bunx tsc --noEmit
412
266
 
413
- global("body", {
414
- margin: 0,
415
- fontFamily: "system-ui"
416
- })
267
+ # Lint
268
+ bun run lint
417
269
 
418
- global("a", {
419
- color: "inherit",
420
- _hover: {
421
- textDecoration: "underline"
422
- }
423
- })
270
+ # Format
271
+ bun run format
424
272
  ```
425
273
 
426
- ---
427
-
428
- ## Animations
429
-
430
- ### `keyframes(frames): string`
274
+ ### Project structure
431
275
 
432
- Creates a `@keyframes` rule and returns its name.
433
-
434
- ``` ts
435
- const fadeIn = keyframes({
436
- from: { opacity: 0 },
437
- to: { opacity: 1 }
438
- })
439
-
440
- css({
441
- animation: `${fadeIn} 300ms ease-in`
442
- })
443
276
  ```
444
-
445
- ---
446
-
447
- ## Fonts
448
-
449
- ### `fontFace(font)`
450
-
451
- Registers a `@font-face` rule.
452
-
453
- ``` ts
454
- fontFace({
455
- fontFamily: "MyFont",
456
- src: "url(/fonts/myfont.woff2)",
457
- fontWeight: 400,
458
- fontStyle: "normal",
459
- fontDisplay: "swap"
460
- })
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
461
282
  ```
462
283
 
463
- ---
464
-
465
- ## Rendering Styles
466
-
467
- ### `<StyleProvider />`
284
+ ## Performance
468
285
 
469
- Injects all generated CSS into a `<style>` tag.
470
-
471
- ``` tsx
472
- <>
473
- <StyleProvider />
474
- <App />
475
- </>
476
- ```
286
+ - **Bundle size** ~2 KB minified + gzipped
287
+ - **Runtime cost** — 0 bytes (styles extracted at build time)
288
+ - **Build overhead** — negligible
477
289
 
478
- - Returns `null` if no styles exist
479
- - Should be rendered **once per scope**
290
+ ## Browser support
480
291
 
481
- ---
292
+ All modern browsers (Chrome, Firefox, Safari, Edge).
482
293
 
483
- ### `toTextCss(): string | null`
294
+ ## Troubleshooting
484
295
 
485
- Returns all generated CSS as a string.
296
+ ### Styles not appearing
486
297
 
487
- Useful for:
488
- - Server-side rendering (SSR)
489
- - Manual injection
490
- - 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
491
302
 
492
- ``` ts
493
- const cssText = toTextCss()
494
- ```
303
+ ### Using alongside Tailwind / Autoprefixer
495
304
 
496
- ---
305
+ next-style must be listed **first** in the plugins object:
497
306
 
498
- ## Component Scoped Example
499
-
500
- Reusable, self-contained component.
501
-
502
- ``` tsx
503
- import { NextStyle } from "next-style"
504
-
505
- export function Card({ title, children }) {
506
- const { css, StyleProvider } = new NextStyle("card")
507
-
508
- const root = css({
509
- padding: "16px",
510
- borderRadius: "12px",
511
- backgroundColor: "#fff",
512
- boxShadow: "0 10px 25px rgba(0,0,0,.1)"
513
- })
514
-
515
- const heading = css({
516
- fontSize: "18px",
517
- fontWeight: 600,
518
- marginBottom: "8px"
519
- })
520
-
521
- return (
522
- <>
523
- <StyleProvider />
524
- <div className={root}>
525
- <div className={heading}>{title}</div>
526
- {children}
527
- </div>
528
- </>
529
- )
307
+ ```js
308
+ export default {
309
+ plugins: {
310
+ "next-style/plugin": {}, // ← first
311
+ tailwindcss: {},
312
+ autoprefixer: {}
313
+ }
530
314
  }
531
315
  ```
532
316
 
533
- ---
534
-
535
- ## Best Practices
536
-
537
- - Create **one NextStyle instance per page or component**
538
- - Do **not** share instances globally
539
- - Render `StyleProvider` only once per scope
540
- - Use meaningful prefixes (`home`, `card`, `profile`)
541
-
542
- ---
543
-
544
- ## Design Intentions
317
+ ## License
545
318
 
546
- - No descendant selectors (`& > div`)
547
- - No arbitrary selector nesting
548
- - Predictable output over expressiveness
549
- - Optimized for runtime and SSR safety
319
+ MIT © [TiwPhiraphan](https://github.com/TiwPhiraphan)
550
320
 
551
321
  ---
552
322
 
553
- ## License
554
-
555
- MIT © kingslimes
323
+ **Made with ❤️ for Next.js developers**