@archetypeai/ds-cli 0.3.7
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/LICENSE +21 -0
- package/README.md +123 -0
- package/bin.js +77 -0
- package/commands/add.js +42 -0
- package/commands/create.js +238 -0
- package/commands/init.js +199 -0
- package/files/AGENTS.md +63 -0
- package/files/CLAUDE.md +63 -0
- package/files/LICENSE +21 -0
- package/files/rules/accessibility.md +219 -0
- package/files/rules/charts.md +352 -0
- package/files/rules/components.md +267 -0
- package/files/rules/design-principles.md +56 -0
- package/files/rules/linting.md +31 -0
- package/files/rules/state.md +405 -0
- package/files/rules/styling.md +245 -0
- package/files/skills/apply-ds/SKILL.md +117 -0
- package/files/skills/apply-ds/scripts/setup.sh +271 -0
- package/files/skills/build-pattern/SKILL.md +202 -0
- package/files/skills/create-dashboard/SKILL.md +189 -0
- package/files/skills/deploy-worker/SKILL.md +231 -0
- package/files/skills/deploy-worker/references/wrangler-commands.md +327 -0
- package/files/skills/fix-accessibility/SKILL.md +184 -0
- package/files/skills/fix-metadata/SKILL.md +118 -0
- package/files/skills/fix-metadata/assets/favicon.ico +0 -0
- package/files/skills/setup-chart/SKILL.md +225 -0
- package/files/skills/setup-chart/data/embedding.csv +42 -0
- package/files/skills/setup-chart/data/timeseries.csv +173 -0
- package/files/skills/setup-chart/references/scatter-chart.md +229 -0
- package/files/skills/setup-chart/references/sensor-chart.md +156 -0
- package/lib/add-ds-config-codeagent.js +154 -0
- package/lib/add-ds-ui-svelte.js +93 -0
- package/lib/scaffold-ds-svelte-project.js +272 -0
- package/lib/use-package-manager.js +65 -0
- package/lib/use-shadcn-svelte-registry.js +26 -0
- package/lib/validate-url.js +31 -0
- package/package.json +34 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- '**/*.svelte'
|
|
4
|
+
- '**/*.svelte.js'
|
|
5
|
+
- '**/*.svelte.ts'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Svelte 5 State Management
|
|
9
|
+
|
|
10
|
+
Svelte 5 uses runes - special `$` prefixed primitives that control reactivity.
|
|
11
|
+
|
|
12
|
+
## $state - Reactive State
|
|
13
|
+
|
|
14
|
+
Declare reactive state that triggers UI updates when changed:
|
|
15
|
+
|
|
16
|
+
```svelte
|
|
17
|
+
<script>
|
|
18
|
+
let count = $state(0);
|
|
19
|
+
let user = $state({ name: 'Alice', age: 30 });
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<button onclick={() => count++}>
|
|
23
|
+
Clicked {count} times
|
|
24
|
+
</button>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Deep Reactivity
|
|
28
|
+
|
|
29
|
+
Objects and arrays become deeply reactive proxies:
|
|
30
|
+
|
|
31
|
+
```svelte
|
|
32
|
+
<script>
|
|
33
|
+
let todos = $state([{ done: false, text: 'Learn Svelte 5' }]);
|
|
34
|
+
|
|
35
|
+
// This triggers updates - the array is a proxy
|
|
36
|
+
todos.push({ done: false, text: 'Build app' });
|
|
37
|
+
|
|
38
|
+
// This also triggers updates
|
|
39
|
+
todos[0].done = true;
|
|
40
|
+
</script>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### $state.raw - Non-Proxied State
|
|
44
|
+
|
|
45
|
+
For large objects you don't plan to mutate, use `$state.raw` for better performance:
|
|
46
|
+
|
|
47
|
+
```svelte
|
|
48
|
+
<script>
|
|
49
|
+
// Must reassign entirely, can't mutate
|
|
50
|
+
let data = $state.raw({ items: largeArray });
|
|
51
|
+
|
|
52
|
+
// This won't trigger updates
|
|
53
|
+
data.items.push(newItem);
|
|
54
|
+
|
|
55
|
+
// This will - full reassignment
|
|
56
|
+
data = { items: [...data.items, newItem] };
|
|
57
|
+
</script>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### $state.snapshot - Get Plain Object
|
|
61
|
+
|
|
62
|
+
Get a non-reactive copy of a state proxy (useful for APIs that don't expect proxies):
|
|
63
|
+
|
|
64
|
+
```svelte
|
|
65
|
+
<script>
|
|
66
|
+
let state = $state({ count: 0 });
|
|
67
|
+
|
|
68
|
+
function save() {
|
|
69
|
+
// Pass plain object to external API
|
|
70
|
+
const snapshot = $state.snapshot(state);
|
|
71
|
+
localStorage.setItem('state', JSON.stringify(snapshot));
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Class Fields
|
|
77
|
+
|
|
78
|
+
Use $state in class fields:
|
|
79
|
+
|
|
80
|
+
```svelte
|
|
81
|
+
<script>
|
|
82
|
+
class Counter {
|
|
83
|
+
count = $state(0);
|
|
84
|
+
|
|
85
|
+
increment() {
|
|
86
|
+
this.count++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let counter = new Counter();
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## $derived - Computed Values
|
|
95
|
+
|
|
96
|
+
Declare values that automatically update when dependencies change:
|
|
97
|
+
|
|
98
|
+
```svelte
|
|
99
|
+
<script>
|
|
100
|
+
let count = $state(0);
|
|
101
|
+
let doubled = $derived(count * 2);
|
|
102
|
+
let quadrupled = $derived(doubled * 2);
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<p>{count} × 2 = {doubled}</p><p>{count} × 4 = {quadrupled}</p>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### $derived.by - Complex Derivations
|
|
109
|
+
|
|
110
|
+
For multi-line logic, use `$derived.by`:
|
|
111
|
+
|
|
112
|
+
```svelte
|
|
113
|
+
<script>
|
|
114
|
+
let items = $state([1, 2, 3, 4, 5]);
|
|
115
|
+
|
|
116
|
+
let stats = $derived.by(() => {
|
|
117
|
+
const sum = items.reduce((a, b) => a + b, 0);
|
|
118
|
+
const avg = sum / items.length;
|
|
119
|
+
const max = Math.max(...items);
|
|
120
|
+
return { sum, avg, max };
|
|
121
|
+
});
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<p>Sum: {stats.sum}, Avg: {stats.avg}, Max: {stats.max}</p>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Overriding Derived Values (Optimistic UI)
|
|
128
|
+
|
|
129
|
+
Derived values can be temporarily overridden:
|
|
130
|
+
|
|
131
|
+
```svelte
|
|
132
|
+
<script>
|
|
133
|
+
let { post } = $props();
|
|
134
|
+
let likes = $derived(post.likes);
|
|
135
|
+
|
|
136
|
+
async function like() {
|
|
137
|
+
likes++; // Optimistic update
|
|
138
|
+
try {
|
|
139
|
+
await api.likePost(post.id);
|
|
140
|
+
} catch {
|
|
141
|
+
likes--; // Rollback on failure
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
</script>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## $effect - Side Effects
|
|
148
|
+
|
|
149
|
+
Run code when reactive dependencies change:
|
|
150
|
+
|
|
151
|
+
```svelte
|
|
152
|
+
<script>
|
|
153
|
+
let count = $state(0);
|
|
154
|
+
|
|
155
|
+
$effect(() => {
|
|
156
|
+
console.log('Count changed to', count);
|
|
157
|
+
document.title = `Count: ${count}`;
|
|
158
|
+
});
|
|
159
|
+
</script>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Teardown / Cleanup
|
|
163
|
+
|
|
164
|
+
Return a function to clean up before re-running or on destroy:
|
|
165
|
+
|
|
166
|
+
```svelte
|
|
167
|
+
<script>
|
|
168
|
+
let interval = $state(1000);
|
|
169
|
+
|
|
170
|
+
$effect(() => {
|
|
171
|
+
const id = setInterval(() => {
|
|
172
|
+
console.log('tick');
|
|
173
|
+
}, interval);
|
|
174
|
+
|
|
175
|
+
// Cleanup: runs before re-run and on component destroy
|
|
176
|
+
return () => clearInterval(id);
|
|
177
|
+
});
|
|
178
|
+
</script>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### $effect.pre - Run Before DOM Updates
|
|
182
|
+
|
|
183
|
+
Rarely needed - runs before DOM updates:
|
|
184
|
+
|
|
185
|
+
```svelte
|
|
186
|
+
<script>
|
|
187
|
+
let messages = $state([]);
|
|
188
|
+
let container;
|
|
189
|
+
|
|
190
|
+
$effect.pre(() => {
|
|
191
|
+
// Check scroll position before DOM updates
|
|
192
|
+
messages.length; // Track this dependency
|
|
193
|
+
|
|
194
|
+
if (container && shouldAutoScroll(container)) {
|
|
195
|
+
// Will scroll after DOM updates
|
|
196
|
+
tick().then(() => container.scrollTo(0, container.scrollHeight));
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
</script>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### When NOT to Use $effect
|
|
203
|
+
|
|
204
|
+
**Don't sync state - use $derived instead:**
|
|
205
|
+
|
|
206
|
+
```svelte
|
|
207
|
+
<script>
|
|
208
|
+
let count = $state(0);
|
|
209
|
+
|
|
210
|
+
// BAD - don't do this
|
|
211
|
+
let doubled = $state(0);
|
|
212
|
+
$effect(() => {
|
|
213
|
+
doubled = count * 2;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// GOOD - use $derived
|
|
217
|
+
let doubled = $derived(count * 2);
|
|
218
|
+
</script>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Don't create infinite loops:**
|
|
222
|
+
|
|
223
|
+
```svelte
|
|
224
|
+
<script>
|
|
225
|
+
let count = $state(0);
|
|
226
|
+
|
|
227
|
+
// BAD - infinite loop! reads and writes count
|
|
228
|
+
$effect(() => {
|
|
229
|
+
count = count + 1;
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Use callbacks for linked values instead of effects:**
|
|
235
|
+
|
|
236
|
+
```svelte
|
|
237
|
+
<script>
|
|
238
|
+
let spent = $state(0);
|
|
239
|
+
let left = $derived(100 - spent);
|
|
240
|
+
|
|
241
|
+
// Update spent via callback, not effect
|
|
242
|
+
function updateLeft(newLeft) {
|
|
243
|
+
spent = 100 - newLeft;
|
|
244
|
+
}
|
|
245
|
+
</script>
|
|
246
|
+
|
|
247
|
+
<input type="range" bind:value={spent} max={100} />
|
|
248
|
+
<input type="range" value={left} oninput={(e) => updateLeft(+e.target.value)} max={100} />
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## $bindable - Two-Way Binding
|
|
252
|
+
|
|
253
|
+
Enable parent components to bind to a prop:
|
|
254
|
+
|
|
255
|
+
```svelte
|
|
256
|
+
<!-- FancyInput.svelte -->
|
|
257
|
+
<script>
|
|
258
|
+
let { value = $bindable(''), ...props } = $props();
|
|
259
|
+
</script>
|
|
260
|
+
|
|
261
|
+
<input bind:value {value} {...props} />
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```svelte
|
|
265
|
+
<!-- Parent.svelte -->
|
|
266
|
+
<script>
|
|
267
|
+
import FancyInput from './FancyInput.svelte';
|
|
268
|
+
let name = $state('');
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<FancyInput bind:value={name} /><p>Hello, {name}!</p>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
See [components.md](./components.md) for more on `$props()` and `$bindable()`.
|
|
275
|
+
|
|
276
|
+
## $inspect - Debugging (Dev Only)
|
|
277
|
+
|
|
278
|
+
Log when values change (removed in production builds):
|
|
279
|
+
|
|
280
|
+
```svelte
|
|
281
|
+
<script>
|
|
282
|
+
let count = $state(0);
|
|
283
|
+
let user = $state({ name: 'Alice' });
|
|
284
|
+
|
|
285
|
+
// Logs whenever count or user changes
|
|
286
|
+
$inspect(count, user);
|
|
287
|
+
</script>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Custom Inspection
|
|
291
|
+
|
|
292
|
+
```svelte
|
|
293
|
+
<script>
|
|
294
|
+
let count = $state(0);
|
|
295
|
+
|
|
296
|
+
$inspect(count).with((type, value) => {
|
|
297
|
+
if (type === 'update') {
|
|
298
|
+
debugger; // Break on changes
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
</script>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Trace Effect Dependencies
|
|
305
|
+
|
|
306
|
+
```svelte
|
|
307
|
+
<script>
|
|
308
|
+
$effect(() => {
|
|
309
|
+
$inspect.trace(); // Must be first statement
|
|
310
|
+
doSomethingComplex();
|
|
311
|
+
});
|
|
312
|
+
</script>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Common Gotchas
|
|
316
|
+
|
|
317
|
+
### Destructuring Breaks Reactivity
|
|
318
|
+
|
|
319
|
+
```svelte
|
|
320
|
+
<script>
|
|
321
|
+
let user = $state({ name: 'Alice', age: 30 });
|
|
322
|
+
|
|
323
|
+
// BAD - name and age are not reactive
|
|
324
|
+
let { name, age } = user;
|
|
325
|
+
|
|
326
|
+
// GOOD - access via object
|
|
327
|
+
// In template: {user.name}, {user.age}
|
|
328
|
+
</script>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Async Code Doesn't Track Dependencies
|
|
332
|
+
|
|
333
|
+
```svelte
|
|
334
|
+
<script>
|
|
335
|
+
let color = $state('red');
|
|
336
|
+
let size = $state(100);
|
|
337
|
+
|
|
338
|
+
$effect(() => {
|
|
339
|
+
// color is tracked (read synchronously)
|
|
340
|
+
console.log(color);
|
|
341
|
+
|
|
342
|
+
setTimeout(() => {
|
|
343
|
+
// size is NOT tracked (read async)
|
|
344
|
+
console.log(size);
|
|
345
|
+
}, 100);
|
|
346
|
+
});
|
|
347
|
+
</script>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Exporting Reassigned State
|
|
351
|
+
|
|
352
|
+
Can't directly export state that gets reassigned:
|
|
353
|
+
|
|
354
|
+
```javascript
|
|
355
|
+
// state.svelte.js
|
|
356
|
+
|
|
357
|
+
// BAD - won't work across modules
|
|
358
|
+
export let count = $state(0);
|
|
359
|
+
export function increment() {
|
|
360
|
+
count++;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// GOOD - wrap in object
|
|
364
|
+
export const counter = $state({ count: 0 });
|
|
365
|
+
export function increment() {
|
|
366
|
+
counter.count++;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// GOOD - use getters
|
|
370
|
+
let count = $state(0);
|
|
371
|
+
export function getCount() {
|
|
372
|
+
return count;
|
|
373
|
+
}
|
|
374
|
+
export function increment() {
|
|
375
|
+
count++;
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Effects Only Run in Browser
|
|
380
|
+
|
|
381
|
+
`$effect` doesn't run during SSR:
|
|
382
|
+
|
|
383
|
+
```svelte
|
|
384
|
+
<script>
|
|
385
|
+
$effect(() => {
|
|
386
|
+
// Safe to use browser APIs here
|
|
387
|
+
window.addEventListener('resize', handleResize);
|
|
388
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
389
|
+
});
|
|
390
|
+
</script>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Quick Reference
|
|
394
|
+
|
|
395
|
+
| Rune | Purpose | Example |
|
|
396
|
+
| ------------------- | ----------------- | ---------------------------------------- |
|
|
397
|
+
| `$state(value)` | Reactive state | `let count = $state(0)` |
|
|
398
|
+
| `$state.raw(value)` | Non-proxied state | `let data = $state.raw(bigObject)` |
|
|
399
|
+
| `$derived(expr)` | Computed value | `let doubled = $derived(count * 2)` |
|
|
400
|
+
| `$derived.by(fn)` | Complex computed | `$derived.by(() => { ... })` |
|
|
401
|
+
| `$effect(fn)` | Side effect | `$effect(() => { ... })` |
|
|
402
|
+
| `$effect.pre(fn)` | Pre-DOM effect | `$effect.pre(() => { ... })` |
|
|
403
|
+
| `$props()` | Component props | `let { foo } = $props()` |
|
|
404
|
+
| `$bindable()` | Two-way prop | `let { value = $bindable() } = $props()` |
|
|
405
|
+
| `$inspect(...)` | Debug logging | `$inspect(count)` |
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- '**/*.css'
|
|
4
|
+
- '**/*.svelte'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Styling Rules
|
|
8
|
+
|
|
9
|
+
## Semantic Token Reference
|
|
10
|
+
|
|
11
|
+
### Background & Foreground Colors
|
|
12
|
+
|
|
13
|
+
| Token | Usage |
|
|
14
|
+
| ------------------------- | ---------------------------- |
|
|
15
|
+
| `bg-background` | Page/app background |
|
|
16
|
+
| `text-foreground` | Primary text color |
|
|
17
|
+
| `bg-card` | Card backgrounds |
|
|
18
|
+
| `text-card-foreground` | Text on cards |
|
|
19
|
+
| `bg-popover` | Popover/dropdown backgrounds |
|
|
20
|
+
| `text-popover-foreground` | Text in popovers |
|
|
21
|
+
| `bg-muted` | Muted/subtle backgrounds |
|
|
22
|
+
| `text-muted-foreground` | Secondary/muted text |
|
|
23
|
+
|
|
24
|
+
### Interactive Colors
|
|
25
|
+
|
|
26
|
+
| Token | Usage |
|
|
27
|
+
| --------------------------- | ----------------------------- |
|
|
28
|
+
| `bg-primary` | Primary buttons, links |
|
|
29
|
+
| `text-primary-foreground` | Text on primary backgrounds |
|
|
30
|
+
| `bg-secondary` | Secondary buttons |
|
|
31
|
+
| `text-secondary-foreground` | Text on secondary backgrounds |
|
|
32
|
+
| `bg-accent` | Hover states, highlights |
|
|
33
|
+
| `text-accent-foreground` | Text on accent backgrounds |
|
|
34
|
+
| `bg-destructive` | Delete/error actions |
|
|
35
|
+
|
|
36
|
+
### Border & Input Colors
|
|
37
|
+
|
|
38
|
+
| Token | Usage |
|
|
39
|
+
| --------------- | -------------------- |
|
|
40
|
+
| `border-border` | Default border color |
|
|
41
|
+
| `border-input` | Input field borders |
|
|
42
|
+
| `ring-ring` | Focus ring color |
|
|
43
|
+
|
|
44
|
+
### Chart Colors
|
|
45
|
+
|
|
46
|
+
| Token | Usage |
|
|
47
|
+
| ----------- | -------------------------------- |
|
|
48
|
+
| `--chart-1` | First series color (purple) |
|
|
49
|
+
| `--chart-2` | Second series color (red-orange) |
|
|
50
|
+
| `--chart-3` | Third series color (green) |
|
|
51
|
+
| `--chart-4` | Fourth series color (yellow) |
|
|
52
|
+
| `--chart-5` | Fifth series color (coral) |
|
|
53
|
+
|
|
54
|
+
### ATAI Brand Colors
|
|
55
|
+
|
|
56
|
+
| Token | Usage |
|
|
57
|
+
| -------------------- | --------------------- |
|
|
58
|
+
| `bg-atai-neutral` | Brand blue accent |
|
|
59
|
+
| `text-atai-good` | Success/healthy state |
|
|
60
|
+
| `text-atai-warning` | Warning state |
|
|
61
|
+
| `text-atai-critical` | Critical/error state |
|
|
62
|
+
|
|
63
|
+
### Sidebar Colors
|
|
64
|
+
|
|
65
|
+
| Token | Usage |
|
|
66
|
+
| ------------------------- | -------------------- |
|
|
67
|
+
| `bg-sidebar` | Sidebar background |
|
|
68
|
+
| `text-sidebar-foreground` | Sidebar text |
|
|
69
|
+
| `bg-sidebar-accent` | Sidebar hover/active |
|
|
70
|
+
| `border-sidebar-border` | Sidebar borders |
|
|
71
|
+
|
|
72
|
+
## When to Use Semantic vs Standard Tailwind
|
|
73
|
+
|
|
74
|
+
### Use Semantic Tokens For:
|
|
75
|
+
|
|
76
|
+
```svelte
|
|
77
|
+
<!-- Themed colors that should change with light/dark mode -->
|
|
78
|
+
<div class="bg-background text-foreground">
|
|
79
|
+
<div class="bg-card border-border">
|
|
80
|
+
<button class="bg-primary text-primary-foreground">
|
|
81
|
+
<span class="text-muted-foreground">
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Use Standard Tailwind For:
|
|
85
|
+
|
|
86
|
+
```svelte
|
|
87
|
+
<!-- Spacing and sizing -->
|
|
88
|
+
<div class="p-4 gap-2 w-full h-screen">
|
|
89
|
+
|
|
90
|
+
<!-- Layout utilities -->
|
|
91
|
+
<div class="flex items-center justify-between">
|
|
92
|
+
<div class="grid grid-cols-2 gap-4">
|
|
93
|
+
<div class="absolute top-0 left-0">
|
|
94
|
+
|
|
95
|
+
<!-- One-off custom colors -->
|
|
96
|
+
<div class="bg-linear-to-r from-purple-500 to-pink-500">
|
|
97
|
+
<div class="text-amber-500"> <!-- specific non-themed color -->
|
|
98
|
+
|
|
99
|
+
<!-- Responsive breakpoints -->
|
|
100
|
+
<div class="sm:flex md:grid lg:hidden">
|
|
101
|
+
|
|
102
|
+
<!-- Animations and transitions -->
|
|
103
|
+
<div class="transition-all duration-200 ease-in-out">
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Dark Mode
|
|
107
|
+
|
|
108
|
+
Dark mode activates via `.dark` class on the root element:
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<html class="dark">
|
|
112
|
+
...
|
|
113
|
+
</html>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Semantic tokens automatically switch values in dark mode. No manual dark: prefixes needed for themed colors:
|
|
117
|
+
|
|
118
|
+
```svelte
|
|
119
|
+
<!-- This works in both light and dark mode automatically -->
|
|
120
|
+
<div class="bg-background text-foreground border-border">
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For custom dark mode overrides, use the `dark:` prefix:
|
|
124
|
+
|
|
125
|
+
```svelte
|
|
126
|
+
<div class="bg-white dark:bg-slate-900"> <!-- custom dark override -->
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## cn() Utility
|
|
130
|
+
|
|
131
|
+
Import and use for all class composition:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
import { cn } from '$lib/utils.js';
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```svelte
|
|
138
|
+
<div class={cn(
|
|
139
|
+
'base-classes here',
|
|
140
|
+
conditional && 'conditional-classes',
|
|
141
|
+
className
|
|
142
|
+
)}>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The utility:
|
|
146
|
+
|
|
147
|
+
- Uses `clsx` for conditional class joining
|
|
148
|
+
- Uses `tailwind-merge` to resolve conflicts (last wins)
|
|
149
|
+
|
|
150
|
+
Example conflict resolution:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
cn('p-4', 'p-2'); // → 'p-2' (later wins)
|
|
154
|
+
cn('bg-red-500', 'bg-background'); // → 'bg-background' (later wins)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## CSS Import Order
|
|
158
|
+
|
|
159
|
+
In your global CSS file:
|
|
160
|
+
|
|
161
|
+
```css
|
|
162
|
+
@import '@archetypeai/ds-lib-fonts-internal';
|
|
163
|
+
@import '@archetypeai/ds-lib-tokens/theme.css';
|
|
164
|
+
@import 'tailwindcss';
|
|
165
|
+
@import 'tw-animate-css'; /* optional, for animations */
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Order matters - tokens must come before Tailwind.
|
|
169
|
+
|
|
170
|
+
## Tailwind v4 Specifics
|
|
171
|
+
|
|
172
|
+
### @theme Directive
|
|
173
|
+
|
|
174
|
+
Custom theme values are defined with `@theme`:
|
|
175
|
+
|
|
176
|
+
```css
|
|
177
|
+
@theme {
|
|
178
|
+
--font-sans: 'PP Neue Montreal', system-ui, sans-serif;
|
|
179
|
+
--radius-md: var(--radius);
|
|
180
|
+
--spacing-md: 0.75rem;
|
|
181
|
+
--color-primary: var(--primary);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### CSS Variables in Classes
|
|
186
|
+
|
|
187
|
+
You can use CSS variables directly:
|
|
188
|
+
|
|
189
|
+
```svelte
|
|
190
|
+
<div style:--legend-color={seriesColor}>
|
|
191
|
+
<span class="bg-(--legend-color)"> </span>
|
|
192
|
+
</div>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### No @apply in Components
|
|
196
|
+
|
|
197
|
+
Prefer Tailwind classes directly in markup. Use @apply only in base layer CSS:
|
|
198
|
+
|
|
199
|
+
```css
|
|
200
|
+
/* In theme.css - OK */
|
|
201
|
+
@layer base {
|
|
202
|
+
body {
|
|
203
|
+
@apply bg-background text-foreground;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```svelte
|
|
209
|
+
<!-- In components - use classes directly -->
|
|
210
|
+
<div class="bg-background text-foreground">
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Common Patterns
|
|
214
|
+
|
|
215
|
+
### Focus States
|
|
216
|
+
|
|
217
|
+
```svelte
|
|
218
|
+
<button class="focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 outline-none">
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Disabled States
|
|
222
|
+
|
|
223
|
+
```svelte
|
|
224
|
+
<button class="disabled:pointer-events-none disabled:opacity-50">
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Data State Animations
|
|
228
|
+
|
|
229
|
+
```svelte
|
|
230
|
+
<div class="data-[state=open]:animate-in data-[state=closed]:animate-out">
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Responsive Design
|
|
234
|
+
|
|
235
|
+
```svelte
|
|
236
|
+
<div class="w-full max-w-[calc(100%-2rem)] sm:max-w-lg">
|
|
237
|
+
<div class="flex flex-col sm:flex-row">
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Icon Sizing
|
|
241
|
+
|
|
242
|
+
```svelte
|
|
243
|
+
<!-- Default icon sizing utility -->
|
|
244
|
+
<button class="[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
|
245
|
+
```
|