@aurora-ds/theme 3.4.0-dev → 3.5.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,150 +1,699 @@
1
1
  # Aurora Theme
2
2
 
3
- A performant, type-safe, and **fully customizable** CSS-in-JS theme library for React.
3
+ > A performant, type-safe and fully customizable **CSS-in-JS** theming library for React.
4
4
 
5
- ## Features
6
-
7
- - ⚡ **Performant** - LRU caching, static style deduplication
8
- - 🔒 **Type-safe** - Full TypeScript support with module augmentation
9
- - 🎨 **Flexible** - Define your own theme structure
10
- - 📦 **Lightweight** - No runtime dependencies besides React
5
+ [![npm version](https://img.shields.io/npm/v/@aurora-ds/theme)](https://www.npmjs.com/package/@aurora-ds/theme)
6
+ [![bundle size](https://img.shields.io/badge/bundle-~6.74KB_gz-brightgreen)](https://bundlephobia.com/package/@aurora-ds/theme)
7
+ [![license](https://img.shields.io/npm/l/@aurora-ds/theme)](./LICENSE)
11
8
 
9
+ ---
12
10
 
13
11
  ## Installation
14
12
 
15
13
  ```bash
16
14
  npm install @aurora-ds/theme
15
+ # or
16
+ yarn add @aurora-ds/theme
17
+ # or
18
+ pnpm add @aurora-ds/theme
17
19
  ```
18
20
 
21
+ **Peer dependencies:** `react >= 18`, `react-dom >= 18`
22
+
23
+ ---
24
+
19
25
  ## Quick Start
20
26
 
27
+ ### 1 — Define your theme
28
+
21
29
  ```typescript
22
- // theme.ts
30
+ // src/theme/index.ts
23
31
  import { createTheme } from '@aurora-ds/theme'
24
32
 
25
- // 1. Define your theme type
33
+ // Your theme type
26
34
  type MyTheme = {
27
35
  colors: {
28
36
  primary: string
29
- secondary: string
30
37
  background: string
31
38
  text: string
32
39
  }
33
- spacing: {
34
- sm: string
35
- md: string
36
- lg: string
37
- }
40
+ spacing: { sm: string; md: string; lg: string }
41
+ radius: { md: string }
42
+ breakpoints: { md: string; lg: string }
38
43
  }
39
- ```
40
- ```typescript
41
- // 2. Module augmentation (required for autocomplete)
44
+
45
+ // Register for global autocomplete (module augmentation)
42
46
  declare module '@aurora-ds/theme' {
43
47
  interface ThemeRegistry {
44
48
  theme: MyTheme
45
49
  }
46
50
  }
47
- ```
48
51
 
49
- ```typescript
50
- // 3. Create your themes (type is inferred automatically!)
52
+ // Create one or many themes — type is inferred automatically
51
53
  export const lightTheme = createTheme({
52
- colors: {
53
- primary: '#6366f1',
54
- secondary: '#64748b',
55
- background: '#ffffff',
56
- text: '#09090b',
57
- },
58
- spacing: {
59
- sm: '0.5rem',
60
- md: '1rem',
61
- lg: '1.5rem',
62
- },
54
+ colors: { primary: '#6366f1', background: '#ffffff', text: '#09090b' },
55
+ spacing: { sm: '0.5rem', md: '1rem', lg: '1.5rem' },
56
+ radius: { md: '0.375rem' },
57
+ breakpoints: { md: '768px', lg: '1024px' },
58
+ })
59
+
60
+ export const darkTheme = createTheme({
61
+ colors: { primary: '#818cf8', background: '#09090b', text: '#fafafa' },
62
+ spacing: { sm: '0.5rem', md: '1rem', lg: '1.5rem' },
63
+ radius: { md: '0.375rem' },
64
+ breakpoints: { md: '768px', lg: '1024px' },
63
65
  })
64
66
  ```
65
67
 
68
+ ### 2 — Wrap your app
69
+
66
70
  ```tsx
67
71
  // App.tsx
68
- import { ThemeProvider, createStyles } from '@aurora-ds/theme'
72
+ import { ThemeProvider } from '@aurora-ds/theme'
69
73
  import { lightTheme } from './theme'
70
74
 
71
- const styles = createStyles((theme) => ({
72
- container: {
73
- padding: theme.spacing.md, // ✅ Autocomplete!
74
- backgroundColor: theme.colors.background,
75
+ export function App() {
76
+ return (
77
+ <ThemeProvider theme={lightTheme}>
78
+ <Router />
79
+ </ThemeProvider>
80
+ )
81
+ }
82
+ ```
83
+
84
+ ### 3 — Style your components
85
+
86
+ ```typescript
87
+ // Card.styles.ts
88
+ import { createStyles } from '@aurora-ds/theme'
89
+
90
+ export const styles = createStyles((theme) => ({
91
+ root: {
92
+ backgroundColor: theme.colors.background, // ✅ Full autocomplete
75
93
  color: theme.colors.text,
94
+ padding: theme.spacing.md,
95
+ borderRadius: theme.radius.md,
96
+ ':hover': { opacity: 0.95 },
97
+ '@media (min-width: 768px)': { padding: theme.spacing.lg },
76
98
  },
99
+ title: {
100
+ fontSize: '1.25rem',
101
+ fontWeight: 600,
102
+ },
103
+ }), { id: 'card' })
104
+ ```
105
+
106
+ ```tsx
107
+ // Card.tsx
108
+ import { styles } from './Card.styles'
109
+
110
+ export function Card({ title, children }) {
111
+ return (
112
+ <div className={styles.root}>
113
+ <h2 className={styles.title}>{title}</h2>
114
+ {children}
115
+ </div>
116
+ )
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Core API
123
+
124
+ ### `createTheme(values)`
125
+
126
+ Creates a typed theme. Type is automatically inferred from your `ThemeRegistry` declaration.
127
+
128
+ ```typescript
129
+ import { createTheme } from '@aurora-ds/theme'
130
+
131
+ const myTheme = createTheme({
132
+ colors: { primary: '#6366f1', background: '#fff', text: '#000' },
133
+ spacing: { sm: '8px', md: '16px', lg: '24px' },
134
+ })
135
+ ```
136
+
137
+ ---
138
+
139
+ ### `ThemeProvider`
140
+
141
+ Injects theme tokens as CSS variables on `:root`. Theme changes update CSS variables instantly — **no component re-renders**.
142
+
143
+ ```tsx
144
+ import { ThemeProvider } from '@aurora-ds/theme'
145
+
146
+ <ThemeProvider theme={currentTheme}>
147
+ <App />
148
+ </ThemeProvider>
149
+ ```
150
+
151
+ #### Props
152
+
153
+ | Prop | Type | Default | Description |
154
+ |---|---|---|---|
155
+ | `theme` | `Theme` | required | The active theme |
156
+ | `disableTransitionsOnChange` | `boolean` | `true` | Disables CSS transitions during theme switch to prevent flashes |
157
+ | `transitionDuration` | `number` | — | Forces a smooth color transition (ms) instead of disabling transitions |
158
+
159
+ ```tsx
160
+ // Smooth 300ms transition on theme switch
161
+ <ThemeProvider theme={currentTheme} transitionDuration={300}>
162
+ <App />
163
+ </ThemeProvider>
164
+ ```
165
+
166
+ #### Switching themes
167
+
168
+ ```tsx
169
+ function App() {
170
+ const [theme, setTheme] = useState(lightTheme)
171
+ return (
172
+ <ThemeProvider theme={theme} transitionDuration={250}>
173
+ <button onClick={() => setTheme(t => t === lightTheme ? darkTheme : lightTheme)}>
174
+ Toggle dark mode
175
+ </button>
176
+ </ThemeProvider>
177
+ )
178
+ }
179
+ ```
180
+
181
+ Child components **never re-render** — only the CSS variables on `:root` change.
182
+
183
+ ---
184
+
185
+ ### `useTheme()`
186
+
187
+ Hook to access the current theme inside a component.
188
+
189
+ ```tsx
190
+ import { useTheme } from '@aurora-ds/theme'
191
+
192
+ function Avatar({ name }: { name: string }) {
193
+ const theme = useTheme()
194
+ return (
195
+ <div style={{ backgroundColor: theme.colors.primary, color: '#fff' }}>
196
+ {name[0]}
197
+ </div>
198
+ )
199
+ }
200
+ ```
201
+
202
+ > **Note:** throws if called outside a `ThemeProvider`.
203
+
204
+ ---
205
+
206
+ ### `createStyles`
207
+
208
+ The main CSS-in-JS API. Returns a map of stable class name strings.
209
+
210
+ ```typescript
211
+ import { createStyles } from '@aurora-ds/theme'
212
+
213
+ const styles = createStyles((theme) => ({
77
214
  button: {
78
215
  backgroundColor: theme.colors.primary,
79
- padding: theme.spacing.sm,
80
- ':hover': {
81
- opacity: 0.9
82
- }
83
- }
216
+ color: 'white',
217
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
218
+ borderRadius: theme.radius.md,
219
+ border: 'none',
220
+ cursor: 'pointer',
221
+
222
+ // Pseudo-classes
223
+ ':hover': { opacity: 0.9 },
224
+ ':focus-visible': { outline: `2px solid ${theme.colors.primary}` },
225
+ ':disabled': { opacity: 0.5, cursor: 'not-allowed' },
226
+
227
+ // Attribute / child selectors
228
+ '&[data-loading="true"]': { color: 'transparent' },
229
+ '& > svg': { marginRight: theme.spacing.sm },
230
+
231
+ // At-rules
232
+ '@media (max-width: 480px)': { width: '100%' },
233
+ '@container sidebar (min-width: 300px)': { flexDirection: 'row' },
234
+ '@supports (display: grid)': { display: 'grid' },
235
+ },
84
236
  }))
85
237
 
86
- function App() {
238
+ <button className={styles.button}>Click</button>
239
+ ```
240
+
241
+ #### Supported selector syntax
242
+
243
+ | Syntax | Example | Generated |
244
+ |---|---|---|
245
+ | Pseudo-class | `':hover'` | `.cls:hover { … }` |
246
+ | Pseudo-element | `':before'` | `.cls:before { … }` |
247
+ | `&` child | `'& > span'` | `.cls > span { … }` |
248
+ | `&` attribute | `'&[disabled]'` | `.cls[disabled] { … }` |
249
+ | Media query | `'@media (…)'` | `@media (…) { .cls { … } }` |
250
+ | Container query | `'@container (…)'` | `@container (…) { .cls { … } }` |
251
+ | Supports | `'@supports (…)'` | `@supports (…) { .cls { … } }` |
252
+
253
+ #### Dynamic styles
254
+
255
+ A style key can be a **function** — Aurora caches the result per unique argument combination.
256
+
257
+ ```typescript
258
+ const styles = createStyles(() => ({
259
+ item: (active: boolean) => ({
260
+ backgroundColor: active ? '#6366f1' : 'transparent',
261
+ color: active ? 'white' : 'inherit',
262
+ }),
263
+ }))
264
+
265
+ // Cached after the first call for each unique value
266
+ <li className={styles.item(isSelected)}>…</li>
267
+ ```
268
+
269
+ #### Explicit module id (recommended in production)
270
+
271
+ ```typescript
272
+ // Guarantees identical class names across SSR/CSR + between builds
273
+ export const styles = createStyles((theme) => ({
274
+ root: { padding: theme.spacing.md },
275
+ }), { id: 'card' })
276
+ // → always generates "card-root"
277
+ ```
278
+
279
+ ---
280
+
281
+ ### `createVariants`
282
+
283
+ Declare all your component variants in a single config and get a fully-typed className builder. Inspired by CVA / Stitches, built on top of `createStyles`.
284
+
285
+ ```typescript
286
+ // Button.styles.ts
287
+ import { createVariants } from '@aurora-ds/theme'
288
+
289
+ export const button = createVariants((theme) => ({
290
+ base: {
291
+ display: 'inline-flex',
292
+ alignItems: 'center',
293
+ gap: theme.spacing.sm,
294
+ border: 'none',
295
+ cursor: 'pointer',
296
+ fontWeight: 600,
297
+ borderRadius: theme.radius.md,
298
+ transition: 'all 0.2s ease',
299
+ ':disabled': { opacity: 0.5, cursor: 'not-allowed' },
300
+ ':hover:not(:disabled)': { transform: 'translateY(-1px)' },
301
+ },
302
+ variants: {
303
+ size: {
304
+ sm: { padding: `${theme.spacing.xs} ${theme.spacing.sm}`, fontSize: 12 },
305
+ md: { padding: `${theme.spacing.sm} ${theme.spacing.md}`, fontSize: 14 },
306
+ lg: { padding: `${theme.spacing.md} ${theme.spacing.lg}`, fontSize: 16 },
307
+ },
308
+ variant: {
309
+ primary: { backgroundColor: theme.colors.primary, color: 'white' },
310
+ secondary: { backgroundColor: theme.colors.secondary, color: theme.colors.text },
311
+ ghost: { backgroundColor: 'transparent', color: theme.colors.text },
312
+ danger: { backgroundColor: theme.colors.error, color: 'white' },
313
+ },
314
+ fullWidth: {
315
+ true: { width: '100%' },
316
+ },
317
+ },
318
+ defaultVariants: { size: 'md', variant: 'primary' },
319
+ // Applied only when ALL conditions match simultaneously
320
+ compoundVariants: [
321
+ { size: 'sm', variant: 'ghost', styles: { fontWeight: 400 } },
322
+ ],
323
+ }), { id: 'button' })
324
+ ```
325
+
326
+ ```tsx
327
+ // Button.tsx
328
+ import { button } from './Button.styles'
329
+
330
+ function Button({ size, variant, fullWidth, className, children, ...props }) {
87
331
  return (
88
- <ThemeProvider theme={lightTheme}>
89
- <div className={styles.container}>
90
- <button className={styles.button}>Click me</button>
91
- </div>
92
- </ThemeProvider>
332
+ <button
333
+ className={button({ size, variant, fullWidth: String(fullWidth) as 'true' }, className)}
334
+ {...props}
335
+ >
336
+ {children}
337
+ </button>
93
338
  )
94
339
  }
340
+
341
+ // All props are optional thanks to defaultVariants
342
+ <Button>Primary md</Button>
343
+ <Button size="lg" variant="ghost">Large ghost</Button>
344
+ <Button variant="danger" fullWidth>Delete account</Button>
95
345
  ```
96
346
 
97
- ## API
347
+ ---
98
348
 
99
- ### `createTheme(values)`
349
+ ### Responsive tokens
100
350
 
101
- Creates a theme. Type is inferred from `ThemeRegistry`.
351
+ Replace any CSS property value with an object keyed by breakpoint names:
102
352
 
103
353
  ```typescript
104
- const theme = createTheme({
105
- colors: { primary: '#007bff' },
106
- spacing: { sm: '8px' }
354
+ // theme.ts declare breakpoints
355
+ const myTheme = createTheme({
356
+ //
357
+ breakpoints: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px' },
107
358
  })
108
359
  ```
109
360
 
110
- ### `createStyles((theme) => styles)`
361
+ ```typescript
362
+ // Card.styles.ts
363
+ const styles = createStyles(() => ({
364
+ card: {
365
+ padding: { base: 8, md: 16, lg: 24 }, // → 8px then 16px @md then 24px @lg
366
+ fontSize: { base: 14, lg: 18 },
367
+ gridTemplateColumns: { base: '1fr', md: '1fr 1fr', lg: '1fr 1fr 1fr' },
368
+
369
+ // Mix freely with non-responsive values
370
+ display: 'grid',
371
+ gap: 16,
372
+ },
373
+ }))
374
+ ```
111
375
 
112
- Creates CSS classes from a style object.
376
+ - **`base`** unmediated rule (no media query, all screen sizes)
377
+ - Other keys must match a `theme.breakpoints` key
378
+ - Rules are emitted **mobile-first** in the order declared in `theme.breakpoints`
379
+ - `ThemeProvider` registers breakpoints automatically — no extra setup
380
+
381
+ ---
382
+
383
+ ### `cx`
384
+
385
+ Conditionally joins class names. A zero-dependency replacement for `clsx` / `classnames`.
386
+
387
+ ```tsx
388
+ import { cx } from '@aurora-ds/theme'
389
+
390
+ // Simple join
391
+ cx('foo', 'bar') // → "foo bar"
392
+
393
+ // Conditional — falsy values are ignored
394
+ cx(styles.base, isActive && styles.active) // → "base" or "base active"
395
+ cx(styles.root, isLarge && styles.lg, props.className)
396
+
397
+ // With createVariants
398
+ <button className={cx(button({ size: 'lg' }), props.className)} />
399
+ ```
400
+
401
+ ---
402
+
403
+ ### `globalStyles`
404
+
405
+ Injects global CSS rules. Supports the same nested syntax as `createStyles`.
113
406
 
114
407
  ```typescript
115
- const styles = createStyles((theme) => ({
116
- root: { color: theme.colors.primary }
408
+ import { globalStyles } from '@aurora-ds/theme'
409
+
410
+ // Call once at app bootstrap (e.g. in main.tsx)
411
+ globalStyles({
412
+ 'html, body': {
413
+ margin: 0,
414
+ padding: 0,
415
+ fontFamily: 'system-ui, -apple-system, sans-serif',
416
+ },
417
+ '*': { boxSizing: 'border-box' },
418
+ 'a': {
419
+ color: 'inherit',
420
+ ':hover': { textDecoration: 'underline' },
421
+ },
422
+ '@media (prefers-reduced-motion: reduce)': {
423
+ '*': { animation: 'none', transition: 'none' },
424
+ },
425
+ })
426
+ ```
427
+
428
+ ---
429
+
430
+ ### `keyframes`
431
+
432
+ Creates a `@keyframes` rule and returns the animation name. Deduplicates identical definitions.
433
+
434
+ ```typescript
435
+ import { createStyles, keyframes } from '@aurora-ds/theme'
436
+
437
+ const spin = keyframes({
438
+ from: { transform: 'rotate(0deg)' },
439
+ to: { transform: 'rotate(360deg)' },
440
+ })
441
+
442
+ const fadeIn = keyframes({
443
+ '0%': { opacity: 0, transform: 'translateY(8px)' },
444
+ '100%': { opacity: 1, transform: 'translateY(0)' },
445
+ })
446
+
447
+ const styles = createStyles(() => ({
448
+ spinner: {
449
+ animation: `${spin} 0.6s linear infinite`,
450
+ },
451
+ card: {
452
+ animation: `${fadeIn} 0.3s ease`,
453
+ },
117
454
  }))
118
- // styles.root → "root-abc123"
119
455
  ```
120
456
 
121
- ### `useTheme()`
457
+ ---
122
458
 
123
- Hook to access the current theme in components.
459
+ ### `fontFace`
460
+
461
+ Injects a `@font-face` rule and returns the font family name. Safe to call multiple times.
124
462
 
125
463
  ```typescript
126
- function Component() {
127
- const theme = useTheme()
128
- return <div style={{ color: theme.colors.primary }} />
464
+ import { fontFace } from '@aurora-ds/theme'
465
+
466
+ const inter = fontFace({
467
+ fontFamily: 'Inter',
468
+ src: 'url(/fonts/inter-variable.woff2) format("woff2")',
469
+ fontWeight: '100 900', // variable font range
470
+ fontDisplay: 'swap',
471
+ })
472
+
473
+ const styles = createStyles(() => ({
474
+ body: { fontFamily: inter }, // → 'Inter'
475
+ }))
476
+ ```
477
+
478
+ | Option | Type | Default |
479
+ |---|---|---|
480
+ | `fontFamily` | `string` | required |
481
+ | `src` | `string` | required |
482
+ | `fontStyle` | `'normal'` \| `'italic'` \| `'oblique'` | `'normal'` |
483
+ | `fontWeight` | `number` \| `string` | `400` |
484
+ | `fontDisplay` | `'auto'` \| `'block'` \| `'swap'` \| `'fallback'` \| `'optional'` | `'swap'` |
485
+ | `unicodeRange` | `string` | — |
486
+
487
+ ---
488
+
489
+ ### CSS Variables helpers
490
+
491
+ #### `cssVar(path, fallback?)`
492
+
493
+ Returns a CSS variable reference string for any theme path.
494
+
495
+ ```typescript
496
+ import { cssVar } from '@aurora-ds/theme'
497
+
498
+ cssVar('colors.primary') // → "var(--theme-colors-primary)"
499
+ cssVar('spacing.md', '1rem') // → "var(--theme-spacing-md, 1rem)"
500
+ ```
501
+
502
+ #### `cssVariables(variables, options?)`
503
+
504
+ Creates CSS variable references from an object and optionally injects them on `:root`.
505
+
506
+ ```typescript
507
+ import { cssVariables } from '@aurora-ds/theme'
508
+
509
+ const vars = cssVariables(
510
+ { primaryRgb: '99 102 241', shadowStrength: 0.1 },
511
+ { prefix: 'app', inject: true }
512
+ )
513
+
514
+ vars.primaryRgb // → "var(--app-primary-rgb)"
515
+ vars.shadowStrength // → "var(--app-shadow-strength)"
516
+ ```
517
+
518
+ #### `injectCssVariables(theme, prefix?)`
519
+
520
+ Manually injects all theme tokens as CSS variables. Already called automatically by `ThemeProvider` — useful for non-React contexts or testing.
521
+
522
+ ```typescript
523
+ import { injectCssVariables } from '@aurora-ds/theme'
524
+
525
+ injectCssVariables(myTheme, 'theme')
526
+ // → injects --theme-colors-primary, --theme-spacing-md, etc. on :root
527
+ ```
528
+
529
+ ---
530
+
531
+ ### SSR utilities
532
+
533
+ Aurora collects all CSS rules server-side in a buffer. Flush them into your HTML before hydration.
534
+
535
+ ```typescript
536
+ import {
537
+ getSSRStyleTag,
538
+ clearSSRRules,
539
+ } from '@aurora-ds/theme'
540
+
541
+ // In your server render:
542
+ const html = renderToString(<App />)
543
+ const styleTag = getSSRStyleTag()
544
+ // → '<style id="aurora-styles">…all collected rules…</style>'
545
+
546
+ clearSSRRules() // Reset buffer between requests
547
+
548
+ res.send(`<!DOCTYPE html>
549
+ <html>
550
+ <head>${styleTag}</head>
551
+ <body><div id="root">${html}</div></body>
552
+ </html>`)
553
+ ```
554
+
555
+ | Function | Description |
556
+ |---|---|
557
+ | `getSSRStyles()` | Returns all collected CSS as a string |
558
+ | `getSSRStyleTag()` | Returns a ready-to-inject `<style>` tag |
559
+ | `getSSRRulesArray()` | Returns rules as an array of strings |
560
+ | `clearSSRRules()` | Resets the SSR buffer (call between requests) |
561
+
562
+ ---
563
+
564
+ ## Best practices
565
+
566
+ ### Name your files `*.styles.ts`
567
+
568
+ Aurora uses this convention to generate stable development IDs and assign a dedicated `<style>` tag per module — makes HMR clean and DevTools readable.
569
+
570
+ ### Always add an `id` in production
571
+
572
+ ```typescript
573
+ // ✅ Guarantees stable class names across builds and SSR/CSR boundaries
574
+ export const styles = createStyles((theme) => ({ … }), { id: 'card' })
575
+ export const button = createVariants((theme) => ({ … }), { id: 'button' })
576
+ ```
577
+
578
+ ### Call `createStyles` at module top-level
579
+
580
+ ```typescript
581
+ // ✅ Called once when the module loads
582
+ const styles = createStyles(...)
583
+
584
+ function MyComponent() {
585
+ return <div className={styles.root} />
586
+ }
587
+
588
+ // ❌ Called on every render — don't do this
589
+ function MyComponent() {
590
+ const styles = createStyles(...)
591
+ return <div className={styles.root} />
129
592
  }
130
593
  ```
131
594
 
132
- ### `ThemeProvider`
595
+ ---
133
596
 
134
- Provides the theme to your app.
597
+ ## Complete example
598
+
599
+ ```typescript
600
+ // main.tsx
601
+ import { globalStyles } from '@aurora-ds/theme'
602
+
603
+ globalStyles({
604
+ 'html, body': { margin: 0, padding: 0, fontFamily: 'system-ui, sans-serif' },
605
+ '*': { boxSizing: 'border-box' },
606
+ })
607
+ ```
608
+
609
+ ```typescript
610
+ // Button.styles.ts
611
+ import { createVariants, keyframes } from '@aurora-ds/theme'
612
+
613
+ const spin = keyframes({ from: { transform: 'rotate(0deg)' }, to: { transform: 'rotate(360deg)' } })
614
+
615
+ export const button = createVariants((theme) => ({
616
+ base: {
617
+ display: 'inline-flex',
618
+ alignItems: 'center',
619
+ gap: theme.spacing.sm,
620
+ border: 'none',
621
+ cursor: 'pointer',
622
+ fontWeight: 600,
623
+ borderRadius: theme.radius.md,
624
+ // Responsive padding
625
+ padding: { base: `${theme.spacing.xs} ${theme.spacing.sm}`, md: `${theme.spacing.sm} ${theme.spacing.md}` },
626
+ ':disabled': { opacity: 0.5, cursor: 'not-allowed' },
627
+ ':hover:not(:disabled)': { transform: 'translateY(-1px)' },
628
+ },
629
+ variants: {
630
+ variant: {
631
+ primary: { backgroundColor: theme.colors.primary, color: 'white' },
632
+ ghost: { backgroundColor: 'transparent', color: theme.colors.text },
633
+ },
634
+ loading: { true: { color: 'transparent', position: 'relative' } },
635
+ },
636
+ defaultVariants: { variant: 'primary' },
637
+ }), { id: 'button' })
638
+
639
+ export const spinnerClass = `border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:${spin} 0.6s linear infinite;width:1em;height:1em;position:absolute`
640
+ ```
135
641
 
136
642
  ```tsx
137
- <ThemeProvider theme={myTheme}>
138
- <App />
139
- </ThemeProvider>
643
+ // Button.tsx
644
+ import { cx } from '@aurora-ds/theme'
645
+ import { button } from './Button.styles'
646
+
647
+ function Button({ variant, loading, disabled, className, children, ...props }) {
648
+ return (
649
+ <button
650
+ className={cx(
651
+ button({ variant, loading: loading ? 'true' : undefined }),
652
+ className
653
+ )}
654
+ disabled={disabled || loading}
655
+ {...props}
656
+ >
657
+ {children}
658
+ </button>
659
+ )
660
+ }
140
661
  ```
141
662
 
142
663
  ---
143
664
 
665
+ ## API Reference
666
+
667
+ | Export | Type | Description |
668
+ |---|---|---|
669
+ | `createTheme` | `(values) => Theme` | Creates a typed theme |
670
+ | `ThemeProvider` | Component | Injects theme as CSS vars + React context |
671
+ | `useTheme` | Hook | Returns the current theme |
672
+ | `createStyles` | `(fn, opts?) => classes` | CSS-in-JS class factory |
673
+ | `createVariants` | `(config, opts?) => fn` | CVA-style variant builder |
674
+ | `cx` | `(...args) => string` | Conditional class name joiner |
675
+ | `globalStyles` | `(rules) => void` | Injects global CSS |
676
+ | `keyframes` | `(frames) => name` | Creates `@keyframes` rule |
677
+ | `fontFace` | `(options) => family` | Creates `@font-face` rule |
678
+ | `cssVar` | `(path, fallback?) => string` | Returns `var(--theme-…)` reference |
679
+ | `cssVariables` | `(vars, opts?) => refs` | Creates CSS variable references |
680
+ | `injectCssVariables` | `(theme, prefix?) => void` | Injects theme as CSS vars on `:root` |
681
+ | `getSSRStyles` | `() => string` | SSR: collected CSS string |
682
+ | `getSSRStyleTag` | `() => string` | SSR: ready `<style>` tag string |
683
+ | `getSSRRulesArray` | `() => string[]` | SSR: rules as array |
684
+ | `clearSSRRules` | `() => void` | SSR: reset buffer between requests |
685
+ | `colors` | object | Built-in color scales (19 palettes × 12 shades) |
686
+ | `ThemeRegistry` | interface | Module augmentation entry point |
687
+ | `StyleWithPseudos` | type | Style object with pseudo/at-rule support |
688
+ | `ResponsiveValue<T>` | type | `T \| { base?: T, [bp: string]: T }` |
689
+ | `FontFaceOptions` | type | Options for `fontFace()` |
690
+ | `VariantProps<V>` | type | Inferred variant props from `createVariants` |
691
+
692
+ ---
144
693
 
145
694
  ## Color Scales
146
695
 
147
- Aurora provides **color scales** with **12 shades each** (25, 50, 100-900, 950).
696
+ Aurora ships built-in color scales, each with **12 shades** (25, 50, 100900, 950).
148
697
 
149
698
  ```typescript
150
699
  import { colors } from '@aurora-ds/theme'
@@ -156,17 +705,12 @@ colors.white // '#ffffff'
156
705
  colors.black // '#000000'
157
706
  ```
158
707
 
159
- ### Complete Color Scales Reference
160
-
161
- All color scales with their hex values (25-950 shades):
162
-
163
- #### Neutrals
708
+ Available scales: `gray`, `slate`, `stone`, `red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`.
164
709
 
165
710
  | Scale | 25 | 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
166
- |-------|----|----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
711
+ |---|---|---|---|---|---|---|---|---|---|---|---|---|
167
712
  | **gray** | ![](https://img.shields.io/badge/%20-fcfcfc?style=flat-square) | ![](https://img.shields.io/badge/%20-fafafa?style=flat-square) | ![](https://img.shields.io/badge/%20-f4f4f5?style=flat-square) | ![](https://img.shields.io/badge/%20-e4e4e7?style=flat-square) | ![](https://img.shields.io/badge/%20-d4d4d8?style=flat-square) | ![](https://img.shields.io/badge/%20-a1a1aa?style=flat-square) | ![](https://img.shields.io/badge/%20-71717a?style=flat-square) | ![](https://img.shields.io/badge/%20-52525b?style=flat-square) | ![](https://img.shields.io/badge/%20-3f3f46?style=flat-square) | ![](https://img.shields.io/badge/%20-27272a?style=flat-square) | ![](https://img.shields.io/badge/%20-18181b?style=flat-square) | ![](https://img.shields.io/badge/%20-09090b?style=flat-square) |
168
713
  | **slate** | ![](https://img.shields.io/badge/%20-fcfcfd?style=flat-square) | ![](https://img.shields.io/badge/%20-f8fafc?style=flat-square) | ![](https://img.shields.io/badge/%20-f1f5f9?style=flat-square) | ![](https://img.shields.io/badge/%20-e2e8f0?style=flat-square) | ![](https://img.shields.io/badge/%20-cbd5e1?style=flat-square) | ![](https://img.shields.io/badge/%20-94a3b8?style=flat-square) | ![](https://img.shields.io/badge/%20-64748b?style=flat-square) | ![](https://img.shields.io/badge/%20-475569?style=flat-square) | ![](https://img.shields.io/badge/%20-334155?style=flat-square) | ![](https://img.shields.io/badge/%20-1e293b?style=flat-square) | ![](https://img.shields.io/badge/%20-0f172a?style=flat-square) | ![](https://img.shields.io/badge/%20-020617?style=flat-square) |
169
- | **stone** | ![](https://img.shields.io/badge/%20-fcfcfb?style=flat-square) | ![](https://img.shields.io/badge/%20-fafaf9?style=flat-square) | ![](https://img.shields.io/badge/%20-f5f5f4?style=flat-square) | ![](https://img.shields.io/badge/%20-e7e5e4?style=flat-square) | ![](https://img.shields.io/badge/%20-d6d3d1?style=flat-square) | ![](https://img.shields.io/badge/%20-a8a29e?style=flat-square) | ![](https://img.shields.io/badge/%20-78716c?style=flat-square) | ![](https://img.shields.io/badge/%20-57534e?style=flat-square) | ![](https://img.shields.io/badge/%20-44403c?style=flat-square) | ![](https://img.shields.io/badge/%20-292524?style=flat-square) | ![](https://img.shields.io/badge/%20-1c1917?style=flat-square) | ![](https://img.shields.io/badge/%20-0c0a09?style=flat-square) |
170
714
  | **red** | ![](https://img.shields.io/badge/%20-fffbfb?style=flat-square) | ![](https://img.shields.io/badge/%20-fef2f2?style=flat-square) | ![](https://img.shields.io/badge/%20-fee2e2?style=flat-square) | ![](https://img.shields.io/badge/%20-fecaca?style=flat-square) | ![](https://img.shields.io/badge/%20-fca5a5?style=flat-square) | ![](https://img.shields.io/badge/%20-f87171?style=flat-square) | ![](https://img.shields.io/badge/%20-ef4444?style=flat-square) | ![](https://img.shields.io/badge/%20-dc2626?style=flat-square) | ![](https://img.shields.io/badge/%20-b91c1c?style=flat-square) | ![](https://img.shields.io/badge/%20-991b1b?style=flat-square) | ![](https://img.shields.io/badge/%20-7f1d1d?style=flat-square) | ![](https://img.shields.io/badge/%20-450a0a?style=flat-square) |
171
715
  | **orange** | ![](https://img.shields.io/badge/%20-fffcfa?style=flat-square) | ![](https://img.shields.io/badge/%20-fff7ed?style=flat-square) | ![](https://img.shields.io/badge/%20-ffedd5?style=flat-square) | ![](https://img.shields.io/badge/%20-fed7aa?style=flat-square) | ![](https://img.shields.io/badge/%20-fdba74?style=flat-square) | ![](https://img.shields.io/badge/%20-fb923c?style=flat-square) | ![](https://img.shields.io/badge/%20-f97316?style=flat-square) | ![](https://img.shields.io/badge/%20-ea580c?style=flat-square) | ![](https://img.shields.io/badge/%20-c2410c?style=flat-square) | ![](https://img.shields.io/badge/%20-9a3412?style=flat-square) | ![](https://img.shields.io/badge/%20-7c2d12?style=flat-square) | ![](https://img.shields.io/badge/%20-431407?style=flat-square) |
172
716
  | **amber** | ![](https://img.shields.io/badge/%20-fffdfb?style=flat-square) | ![](https://img.shields.io/badge/%20-fffbeb?style=flat-square) | ![](https://img.shields.io/badge/%20-fef3c7?style=flat-square) | ![](https://img.shields.io/badge/%20-fde68a?style=flat-square) | ![](https://img.shields.io/badge/%20-fcd34d?style=flat-square) | ![](https://img.shields.io/badge/%20-fbbf24?style=flat-square) | ![](https://img.shields.io/badge/%20-f59e0b?style=flat-square) | ![](https://img.shields.io/badge/%20-d97706?style=flat-square) | ![](https://img.shields.io/badge/%20-b45309?style=flat-square) | ![](https://img.shields.io/badge/%20-92400e?style=flat-square) | ![](https://img.shields.io/badge/%20-78350f?style=flat-square) | ![](https://img.shields.io/badge/%20-451a03?style=flat-square) |
@@ -184,11 +728,28 @@ All color scales with their hex values (25-950 shades):
184
728
  | **pink** | ![](https://img.shields.io/badge/%20-fef5f9?style=flat-square) | ![](https://img.shields.io/badge/%20-fdf2f8?style=flat-square) | ![](https://img.shields.io/badge/%20-fce7f3?style=flat-square) | ![](https://img.shields.io/badge/%20-fbcfe8?style=flat-square) | ![](https://img.shields.io/badge/%20-f9a8d4?style=flat-square) | ![](https://img.shields.io/badge/%20-f472b6?style=flat-square) | ![](https://img.shields.io/badge/%20-ec4899?style=flat-square) | ![](https://img.shields.io/badge/%20-db2777?style=flat-square) | ![](https://img.shields.io/badge/%20-be185d?style=flat-square) | ![](https://img.shields.io/badge/%20-9d174d?style=flat-square) | ![](https://img.shields.io/badge/%20-831843?style=flat-square) | ![](https://img.shields.io/badge/%20-500724?style=flat-square) |
185
729
  | **rose** | ![](https://img.shields.io/badge/%20-fff5f6?style=flat-square) | ![](https://img.shields.io/badge/%20-fff1f2?style=flat-square) | ![](https://img.shields.io/badge/%20-ffe4e6?style=flat-square) | ![](https://img.shields.io/badge/%20-fecdd3?style=flat-square) | ![](https://img.shields.io/badge/%20-fda4af?style=flat-square) | ![](https://img.shields.io/badge/%20-fb7185?style=flat-square) | ![](https://img.shields.io/badge/%20-f43f5e?style=flat-square) | ![](https://img.shields.io/badge/%20-e11d48?style=flat-square) | ![](https://img.shields.io/badge/%20-be123c?style=flat-square) | ![](https://img.shields.io/badge/%20-9f1239?style=flat-square) | ![](https://img.shields.io/badge/%20-881337?style=flat-square) | ![](https://img.shields.io/badge/%20-4c0519?style=flat-square) |
186
730
 
731
+ ---
732
+
733
+ ## TypeScript & autocomplete
734
+
735
+ Aurora uses **module augmentation** — declare your theme type once, get autocomplete everywhere.
736
+
737
+ ```typescript
738
+ // src/theme.ts
739
+ declare module '@aurora-ds/theme' {
740
+ interface ThemeRegistry {
741
+ theme: typeof myTheme // inferred from createTheme()
742
+ }
743
+ }
187
744
  ```
188
- ```
745
+
746
+ After this:
747
+ - `createStyles((theme) => ...)` — `theme` is fully typed ✅
748
+ - `useTheme()` — returns your exact theme type ✅
749
+ - `createTheme(...)` — validates structure at compile time ✅
189
750
 
190
751
  ---
191
752
 
192
753
  ## License
193
754
 
194
- MIT
755
+ MIT © [Lilian MARZET](https://github.com/LilianMrzt)