@wippy-fe/theme 0.0.26 → 0.0.28
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/THEMING.md +904 -492
- package/package.json +1 -1
package/THEMING.md
CHANGED
|
@@ -1,492 +1,904 @@
|
|
|
1
|
-
# Wippy Theming
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
-
|
|
328
|
-
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
|
408
|
-
|
|
409
|
-
|
|
|
410
|
-
|
|
|
411
|
-
|
|
|
412
|
-
|
|
|
413
|
-
|
|
|
414
|
-
|
|
|
415
|
-
|
|
|
416
|
-
|
|
|
417
|
-
|
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
|
432
|
-
|
|
|
433
|
-
|
|
|
434
|
-
|
|
|
435
|
-
|
|
|
436
|
-
|
|
|
437
|
-
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
|
451
|
-
|
|
452
|
-
|
|
|
453
|
-
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
|
460
|
-
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
1
|
+
# Wippy Theming Guide
|
|
2
|
+
|
|
3
|
+
Authoritative guide for theming Wippy apps. Covers when to theme at the **facade** (host-wide), when to theme at the **page** (per-iframe), and the full CSS-variable / Tailwind / PrimeVue reference.
|
|
4
|
+
|
|
5
|
+
> **Mirrored at three locations.** Same content, three places — keep them in sync.
|
|
6
|
+
> - This file: `app-template/frontend/docs/theming.md` (in-repo, primary editing surface)
|
|
7
|
+
> - npm package: `@wippy-fe/theme/THEMING.md` (shipped with the theme package — `node_modules/@wippy-fe/theme/THEMING.md` after install)
|
|
8
|
+
> - Wippy KB: `wippy.frontend` → "Wippy Theming Guide" (semantic search via the `wippy-kb` MCP)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of contents
|
|
13
|
+
|
|
14
|
+
- [Three levels of theming — quick start](#three-levels-of-theming--quick-start)
|
|
15
|
+
- [Level 1 — Basic theming (accent only, via facade)](#level-1--basic-theming-accent-only-via-facade)
|
|
16
|
+
- [Level 2 — Full theming (palette + custom CSS, via facade)](#level-2--full-theming-palette--custom-css-via-facade)
|
|
17
|
+
- [Level 3 — Per-page theme override (configOverrides / runtime)](#level-3--per-page-theme-override-configoverrides--runtime)
|
|
18
|
+
- [Reference — CSS variables](#reference--css-variables)
|
|
19
|
+
- [Reference — Tailwind utility classes](#reference--tailwind-utility-classes)
|
|
20
|
+
- [Reference — Dark mode](#reference--dark-mode)
|
|
21
|
+
- [Reference — PrimeVue styling (opt-in)](#reference--primevue-styling-opt-in)
|
|
22
|
+
- [Reference — Choosing custom colors (luminosity, WCAG)](#reference--choosing-custom-colors-luminosity-wcag)
|
|
23
|
+
- [Reference — Host UI customization (`--wippy-host-*` + BEM classes)](#reference--host-ui-customization---wippy-host---bem-classes)
|
|
24
|
+
- [Related docs](#related-docs)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Three levels of theming — quick start
|
|
29
|
+
|
|
30
|
+
Almost every "I want to restyle this" task fits one of three levels. Pick the lightest one that solves the problem; never reach for a heavier level "just in case".
|
|
31
|
+
|
|
32
|
+
| Level | Where it lives | Reaches | Use when |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| **1 — Basic** | `wippy/facade` parameter `css_variables` (a few `--p-*` overrides) | Host UI **and** all child iframes | You want a brand accent color and otherwise want stock theme |
|
|
35
|
+
| **2 — Full** | `wippy/facade` parameters `css_variables` + `custom_css` (and the scoped variants) | Host UI **and** all child iframes (or scoped to one or the other) | You're shipping a product with bespoke visual identity — full palette, custom components, BEM overrides |
|
|
36
|
+
| **3 — Per-page** | A specific page's `package.json` (`wippy.configOverrides.customization.cssVariables` / `customCSS`) **or** runtime `window.__WIPPY_CONFIG_OVERRIDES__` | Just that one page (the iframe it loads in) | A demo page, an A/B variant, an experiment, a one-off embed — different look from the rest of the app |
|
|
37
|
+
|
|
38
|
+
> **Anti-pattern alert.** Don't theme by editing your child app's `src/styles.css` to override `--p-surface-0..950` or other tokens. That CSS is loaded inside the child iframe AFTER the host injects the real theme — it shadows host theming, never reaches the host UI itself, and creates per-iframe visual drift. Audit any project that has a multi-hundred-line `styles.css` in a child app. The right place is the facade (Levels 1–2) or `wippy.configOverrides` (Level 3).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Level 1 — Basic theming (accent only, via facade)
|
|
43
|
+
|
|
44
|
+
**You want:** brand-color buttons, brand-color highlights, otherwise stock theme.
|
|
45
|
+
|
|
46
|
+
**You change:** the `wippy/facade` `ns.dependency` in your app's deps `_index.yaml`. Add a `css_variables` parameter with a JSON-string mapping just the primary tokens.
|
|
47
|
+
|
|
48
|
+
**Result:** the entire host UI, every child iframe, and every web component picks up the new accent. Dark mode keeps working. No CSS edits anywhere.
|
|
49
|
+
|
|
50
|
+
### Sample
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
# src/<yourapp>/deps/_index.yaml
|
|
54
|
+
- name: facade
|
|
55
|
+
kind: ns.dependency
|
|
56
|
+
component: wippy/facade
|
|
57
|
+
parameters:
|
|
58
|
+
- name: server
|
|
59
|
+
value: app:gateway
|
|
60
|
+
- name: router
|
|
61
|
+
value: app:api.public
|
|
62
|
+
# Theming starts here:
|
|
63
|
+
- name: css_variables
|
|
64
|
+
value: '{"--p-primary":"#4f8ef7","--p-primary-color":"#4f8ef7","--p-primary-hover-color":"#5a9af8"}'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### What's actually happening
|
|
68
|
+
|
|
69
|
+
The facade reads `css_variables` (a JSON string), sends it down to the host as `AppConfig.customization.cssVariables`, the host writes those onto `:root` and forwards them into every child iframe via the proxy injection. So one place edits, everything updates.
|
|
70
|
+
|
|
71
|
+
> The default base color is `rgb(0, 95, 178)`. The full 11-step shade scale (50–950) **auto-derives** from your base via `color-mix()` — you only set `--p-primary`. See ["Choosing custom colors"](#reference--choosing-custom-colors-luminosity-wcag) for luminosity rules so light-mode buttons keep AA contrast against white text.
|
|
72
|
+
|
|
73
|
+
### When to graduate to Level 2
|
|
74
|
+
|
|
75
|
+
When you find yourself wanting any of:
|
|
76
|
+
|
|
77
|
+
- A complete surface scale (`--p-surface-0..950`) shifted to brand-specific neutrals
|
|
78
|
+
- Bespoke component CSS (custom button variants, badge styles, dialog padding)
|
|
79
|
+
- BEM-class overrides for the host chrome (sidebar, chat messages, splitter)
|
|
80
|
+
|
|
81
|
+
…you're at Level 2.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Level 2 — Full theming (palette + custom CSS, via facade)
|
|
86
|
+
|
|
87
|
+
**You want:** a fully-branded product. Custom palette, custom component variants, host chrome that matches your design system. Both light and dark modes look intentional.
|
|
88
|
+
|
|
89
|
+
**You change:** the same facade dependency, but with three richer scopes:
|
|
90
|
+
|
|
91
|
+
| Facade parameter | Reaches | Use for |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `css_variables` / `custom_css` | **Host + all children** (global) | App-wide tokens (`--p-*`), accent palette, global custom-css that has to apply identically in host and inside iframes (e.g. font family, kbd pill styling) |
|
|
94
|
+
| `host_css_variables` / `host_custom_css` | **Host UI only** | `--wippy-host-*` overrides; BEM class overrides scoped to `.wippy-host-app` (sidebar, chat messages, splitter) |
|
|
95
|
+
| `children_css_variables` / `children_custom_css` | **Child iframes only** | CSS that should apply inside child apps but not leak into host chrome |
|
|
96
|
+
|
|
97
|
+
> **Why three scopes:** `customCSS` injection is shared by default — a `.chat-message { ... }` rule written at the global scope will affect both the host's chat AND any child page that happens to have a `.chat-message` class. Splitting by scope avoids leak.
|
|
98
|
+
|
|
99
|
+
### Sample (drewaltizer-wippy reference implementation)
|
|
100
|
+
|
|
101
|
+
This is the canonical full-theming reference. See `C:/Projects/drewaltizer-wippy/src/drewapp/deps/_index.yaml` (the `facade` dependency entry) for the live source.
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
- name: facade
|
|
105
|
+
kind: ns.dependency
|
|
106
|
+
component: wippy/facade
|
|
107
|
+
parameters:
|
|
108
|
+
- name: server
|
|
109
|
+
value: app:gateway
|
|
110
|
+
- name: router
|
|
111
|
+
value: app:api.public
|
|
112
|
+
- name: app_title
|
|
113
|
+
value: Keeper
|
|
114
|
+
- name: app_icon
|
|
115
|
+
value: "tabler:camera"
|
|
116
|
+
- name: hide_nav_bar
|
|
117
|
+
value: "true"
|
|
118
|
+
|
|
119
|
+
# Full palette: neutrals + accents + severities + custom --k-* extensions
|
|
120
|
+
- name: css_variables
|
|
121
|
+
value: |
|
|
122
|
+
{
|
|
123
|
+
"--p-primary": "#4f8ef7",
|
|
124
|
+
"--p-primary-color": "#4f8ef7",
|
|
125
|
+
"--p-primary-hover-color": "#5a9af8",
|
|
126
|
+
"--p-info": "#4f8ef7",
|
|
127
|
+
"--p-danger": "#f44336",
|
|
128
|
+
"--p-warn": "#ffc107",
|
|
129
|
+
"--p-accent": "#ff9800",
|
|
130
|
+
"--p-success": "#4caf50",
|
|
131
|
+
"--p-surface-0": "#ffffff",
|
|
132
|
+
"--p-surface-50": "#fafafa",
|
|
133
|
+
"--p-surface-100": "#f2f2f2",
|
|
134
|
+
"--p-surface-200": "#e2e2e2",
|
|
135
|
+
"--p-surface-300": "#cccccc",
|
|
136
|
+
"--p-surface-400": "#9a9a9a",
|
|
137
|
+
"--p-surface-500": "#6b6b6b",
|
|
138
|
+
"--p-surface-600": "#4a4a4a",
|
|
139
|
+
"--p-surface-700": "#383838",
|
|
140
|
+
"--p-surface-800": "#2c2c2c",
|
|
141
|
+
"--p-surface-900": "#242424",
|
|
142
|
+
"--p-surface-950": "#1a1a1a",
|
|
143
|
+
"--p-text-muted-color": "#888888",
|
|
144
|
+
"--p-content-border-color": "var(--p-surface-700)",
|
|
145
|
+
"--p-form-field-focus-border-color": "var(--p-primary)",
|
|
146
|
+
"--p-form-field-border-radius": "5px",
|
|
147
|
+
"--p-button-border-radius": "4px",
|
|
148
|
+
"--p-card-border-radius": "6px",
|
|
149
|
+
"--p-dialog-border-radius": "10px",
|
|
150
|
+
"--p-chip-border-radius": "12px",
|
|
151
|
+
"--p-badge-border-radius": "10px",
|
|
152
|
+
"--k-surface-raised": "#1e1e1e",
|
|
153
|
+
"--k-wav": "#9c59d1",
|
|
154
|
+
"--k-avatar-gradient": "linear-gradient(135deg,#4f8ef7,#9c59d1)",
|
|
155
|
+
"--k-press-approved-bg": "#1a2e1a",
|
|
156
|
+
"--k-press-unapproved-bg": "#311515"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# App-wide font family + custom component variants. Applied to host AND children.
|
|
160
|
+
- name: custom_css
|
|
161
|
+
value: |
|
|
162
|
+
body, :root { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
163
|
+
|
|
164
|
+
/* Keyboard shortcut pill */
|
|
165
|
+
kbd {
|
|
166
|
+
display: inline-flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: center;
|
|
169
|
+
min-width: 20px;
|
|
170
|
+
height: 20px;
|
|
171
|
+
padding: 0 6px;
|
|
172
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
173
|
+
font-size: 10px;
|
|
174
|
+
color: var(--p-surface-900);
|
|
175
|
+
background: var(--p-surface-100);
|
|
176
|
+
border: 1px solid var(--p-surface-300);
|
|
177
|
+
border-radius: 3px;
|
|
178
|
+
}
|
|
179
|
+
@media (prefers-color-scheme: dark) {
|
|
180
|
+
kbd {
|
|
181
|
+
color: var(--p-surface-100);
|
|
182
|
+
background: var(--p-surface-800);
|
|
183
|
+
border-color: var(--p-surface-700);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Outlined.tinted button — persistent tint */
|
|
188
|
+
.p-button.p-button-outlined.p-button-tinted {
|
|
189
|
+
background: color-mix(in srgb, var(--p-primary) 13%, transparent);
|
|
190
|
+
}
|
|
191
|
+
.p-button.p-button-outlined.p-button-tinted:enabled:hover {
|
|
192
|
+
background: color-mix(in srgb, var(--p-primary) 20%, transparent);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Badge tint variants */
|
|
196
|
+
.p-badge.p-badge-success.p-badge-tinted {
|
|
197
|
+
background: color-mix(in srgb, var(--p-success) 20%, transparent);
|
|
198
|
+
color: var(--p-success);
|
|
199
|
+
border: 1px solid color-mix(in srgb, var(--p-success) 40%, transparent);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Host-only chrome tweaks. Use BEM classes scoped to `.wippy-host-app`.
|
|
203
|
+
- name: host_custom_css
|
|
204
|
+
value: |
|
|
205
|
+
.wippy-host-app .session-selector { display: none; }
|
|
206
|
+
.wippy-host-app .chat-message__footer { display: none; }
|
|
207
|
+
|
|
208
|
+
- name: host_css_variables
|
|
209
|
+
value: '{"--wippy-host-sidebar-width-open":"20rem","--wippy-host-message-radius":"0.5rem"}'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Custom `--k-*` tokens — extending the system
|
|
213
|
+
|
|
214
|
+
The `--p-*` namespace is reserved for PrimeVue-aligned tokens. For app-specific extensions, use a project-prefixed namespace like `--k-*` (Keeper), `--app-*`, etc. The drewaltizer sample defines `--k-surface-raised`, `--k-wav`, `--k-avatar-gradient`, `--k-press-approved-bg`, `--k-press-unapproved-bg` — they cascade like any CSS variable but don't collide with future `--p-*` additions.
|
|
215
|
+
|
|
216
|
+
### Component-level overrides via `custom_css`
|
|
217
|
+
|
|
218
|
+
PrimeVue components expose stable class names (`.p-button`, `.p-button-outlined`, `.p-badge.p-badge-tinted`, `.p-accordionheader`, etc.). You can target them in `custom_css` to add variants, change padding, change border-radius, hide built-ins, etc. See drewaltizer's `_index.yaml:212-409` for ~200 lines of examples covering buttons, badges, accordions, EXIF tables, and gallery tiles.
|
|
219
|
+
|
|
220
|
+
### Cross-iframe leak — scope with `.wippy-host-app`
|
|
221
|
+
|
|
222
|
+
`customCSS` is injected into both the host AND every child iframe. A bare `.chat-message { ... }` rule will hit child apps that happen to use the same class. To avoid that:
|
|
223
|
+
|
|
224
|
+
- Put host-only rules in `host_custom_css` (already scoped server-side)
|
|
225
|
+
- Or scope manually: `.wippy-host-app .my-thing { ... }`
|
|
226
|
+
- For child-only rules: `children_custom_css`
|
|
227
|
+
|
|
228
|
+
The host root element carries the `.wippy-host-app` class — child iframes do not — so prefixing with `.wippy-host-app ` is the canonical way to target host-only.
|
|
229
|
+
|
|
230
|
+
> **The drewaltizer-wippy `src/drewapp/deps/_index.yaml` file is the load-bearing reference.** When in doubt about full-theming syntax, read that file. It encodes the canonical mental model: facade-driven, three scopes, scoped BEM, custom `--k-*` extensions.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Level 3 — Per-page theme override (configOverrides / runtime)
|
|
235
|
+
|
|
236
|
+
**You want:** a single page (one of many) that looks different. A demo page with a contrasting palette, an A/B test variant, a brand take-over for one experimental embed.
|
|
237
|
+
|
|
238
|
+
**You change:** that page's `package.json` (declarative) **or** set runtime overrides before `proxy.js` loads.
|
|
239
|
+
|
|
240
|
+
**You do NOT change:** the facade. Per-page overrides are local to the iframe that hosts the page; nothing else is affected.
|
|
241
|
+
|
|
242
|
+
### Mechanism A — Declarative (registry entry YAML, primary)
|
|
243
|
+
|
|
244
|
+
The canonical place for per-page overrides is the `meta.config_overrides` block of the page's `registry.entry` YAML. Wippy compiles this block into the `WippyPageEntry.configOverrides` field that the host reads at request time and injects into the iframe srcdoc — no rebuild required to change theming.
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
# src/<yourapp>/views/_index.yaml
|
|
248
|
+
- name: iframe-demo-themed
|
|
249
|
+
kind: registry.entry
|
|
250
|
+
meta:
|
|
251
|
+
type: view.page
|
|
252
|
+
name: iframe-demo-themed
|
|
253
|
+
title: Iframe Demo (Custom Palette)
|
|
254
|
+
url: /app
|
|
255
|
+
base_path: app/iframe-demo
|
|
256
|
+
entry_point: app.html
|
|
257
|
+
config_overrides:
|
|
258
|
+
customization:
|
|
259
|
+
cssVariables:
|
|
260
|
+
"--p-primary": "#9c59d1"
|
|
261
|
+
"--p-primary-color": "#9c59d1"
|
|
262
|
+
"--p-content-background": "#1a0d22"
|
|
263
|
+
"@light":
|
|
264
|
+
"--p-content-background": "#faf5ff"
|
|
265
|
+
"--p-text-color": "#1a0d22"
|
|
266
|
+
"@dark":
|
|
267
|
+
"--p-content-background": "#1a0d22"
|
|
268
|
+
"--p-text-color": "#f4f0ff"
|
|
269
|
+
customCSS: |
|
|
270
|
+
.demo-banner { background: var(--p-primary-color); color: var(--p-primary-contrast-color); padding: 0.5rem 1rem; }
|
|
271
|
+
proxy:
|
|
272
|
+
enabled: true
|
|
273
|
+
css:
|
|
274
|
+
fonts: true
|
|
275
|
+
theme_config: true
|
|
276
|
+
iframe: true
|
|
277
|
+
prime_vue: true
|
|
278
|
+
custom_css: true
|
|
279
|
+
custom_variables: true
|
|
280
|
+
tailwind_config: true
|
|
281
|
+
iconify_icons: true
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
> **Shape note.** `config_overrides` (snake-case in YAML — Wippy converts to `configOverrides` for the JS API) matches the `AppConfigOverrides` interface from `@wippy-fe/proxy`: `cssVariables`, `customCSS`, `i18n`, `iconSets` all nest under `customization`. Sibling fields like `axiosDefaults`, `routePrefix`, `apiRoutes` sit at the top level of `config_overrides`. The CSS variable map keys themselves stay as written (`--p-*`, `@dark`, `@light`).
|
|
285
|
+
|
|
286
|
+
> **Why YAML over package.json.** YAML is registry-native — operators can patch `config_overrides` from a customer-specific overlay or a Wippy admin tool without rebuilding the FE bundle. Lua tools and other modules can also `registry:get` the entry and read the same block. package.json `wippy.configOverrides` (below) is a duplicate for the host-less rendering path, not the source of truth.
|
|
287
|
+
|
|
288
|
+
> **Dual-mode `@dark` / `@light` keys.** `cssVariables` accepts two special keys — `@dark` and `@light`. Their values are flat string maps that compile to `@media (prefers-color-scheme: dark) { :root { ... } }` and the matching light-mode media query. **They are NOT triggered by a `[data-theme="dark"]` attribute** — only by the OS preference. Use `@dark`/`@light` for the bulk of mode-specific theming (palette swaps, component-token overrides); the top level of `cssVariables` holds defaults that don't depend on mode.
|
|
289
|
+
|
|
290
|
+
### Mechanism A2 — package.json mirror (host-less mode)
|
|
291
|
+
|
|
292
|
+
Duplicate the same shape under `wippy.configOverrides` in the page's `package.json`:
|
|
293
|
+
|
|
294
|
+
```jsonc
|
|
295
|
+
{
|
|
296
|
+
"name": "@example/iframe-demo",
|
|
297
|
+
"wippy": {
|
|
298
|
+
"type": "page",
|
|
299
|
+
"title": "Iframe Demo",
|
|
300
|
+
"path": "dist/app.html",
|
|
301
|
+
"configOverrides": {
|
|
302
|
+
"customization": {
|
|
303
|
+
"cssVariables": { "...same as YAML...": "" }
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The package.json copy is what renders the page when no Wippy host is present — for example, the standalone dev-proxy preview, a unit test that mounts `app.html` directly, or any environment where the iframe is loaded without the host's srcdoc-injection step. With a host present, the YAML wins (because the host applies it via `WippyPageEntry.configOverrides` before the iframe boots); the package.json copy is then redundant but harmless.
|
|
311
|
+
|
|
312
|
+
**Keep both copies in sync.** When you edit one, edit the other. The YAML block is the canonical source.
|
|
313
|
+
|
|
314
|
+
### Mechanism B — Runtime (`window.__WIPPY_CONFIG_OVERRIDES__`)
|
|
315
|
+
|
|
316
|
+
Set the global before `proxy.js` runs. Useful when the override depends on a query param, a feature flag, or test harness state.
|
|
317
|
+
|
|
318
|
+
```html
|
|
319
|
+
<!-- app.html (head section, BEFORE the proxy script tag) -->
|
|
320
|
+
<script>
|
|
321
|
+
// Read a query param: ?theme=purple → load a purple variant
|
|
322
|
+
const params = new URLSearchParams(window.location.search)
|
|
323
|
+
if (params.get('theme') === 'purple') {
|
|
324
|
+
window.__WIPPY_CONFIG_OVERRIDES__ = {
|
|
325
|
+
customization: {
|
|
326
|
+
cssVariables: {
|
|
327
|
+
'--p-primary': '#9c59d1',
|
|
328
|
+
'--p-primary-color': '#9c59d1',
|
|
329
|
+
},
|
|
330
|
+
customCSS: '.demo-banner { background: var(--p-primary-color); }',
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
</script>
|
|
335
|
+
<!-- The host's @wippy/scripts injection happens here -->
|
|
336
|
+
<script type="text/javascript" data-role="@wippy/scripts"></script>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Merge semantics — what overrides what
|
|
340
|
+
|
|
341
|
+
When the host applies AppConfig (in order, last writer wins):
|
|
342
|
+
|
|
343
|
+
1. `theme-config.css` defaults (the dev-time fallback)
|
|
344
|
+
2. Facade `css_variables` / `custom_css` (host-wide)
|
|
345
|
+
3. Page `wippy.configOverrides` (declarative, baked into the page)
|
|
346
|
+
4. `window.__WIPPY_CONFIG_OVERRIDES__` (runtime, if set before proxy loads)
|
|
347
|
+
|
|
348
|
+
For object-shape fields:
|
|
349
|
+
|
|
350
|
+
| Field | Merge behavior |
|
|
351
|
+
|---|---|
|
|
352
|
+
| `cssVariables` | **Replace** — the override map fully replaces the AppConfig map (write the full set you want, don't expect partial merge) |
|
|
353
|
+
| `customCSS` | **Replace** — concat into one block when you write it; the override replaces wholesale |
|
|
354
|
+
| `icons` / `iconSets` | **Additive** — merged with facade-provided icons |
|
|
355
|
+
| `feature.*` | **Deep merge** — boolean flags merged per key |
|
|
356
|
+
|
|
357
|
+
### Sample wired into iframe-demo
|
|
358
|
+
|
|
359
|
+
`app-template/frontend/applications/iframe-demo/` is the working demo. Its `package.json` description says "theme override support"; the chart and mermaid pages use `var(--p-primary)`, `var(--p-danger)`, etc., so an override visibly flips them. Wire-up sample:
|
|
360
|
+
|
|
361
|
+
```jsonc
|
|
362
|
+
// app-template/frontend/applications/iframe-demo/package.json
|
|
363
|
+
{
|
|
364
|
+
"wippy": {
|
|
365
|
+
"type": "page",
|
|
366
|
+
"title": "Iframe Demo",
|
|
367
|
+
"configOverrides": {
|
|
368
|
+
"customization": {
|
|
369
|
+
"cssVariables": {
|
|
370
|
+
"--p-primary": "#9c59d1",
|
|
371
|
+
"--p-primary-color": "#9c59d1",
|
|
372
|
+
"--p-info": "#06b6d4",
|
|
373
|
+
"--p-danger": "#dc2626",
|
|
374
|
+
"--p-warn": "#facc15",
|
|
375
|
+
"--p-success": "#16a34a",
|
|
376
|
+
"--p-content-background": "#1a0d22",
|
|
377
|
+
"--p-text-color": "#f4f0ff"
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Open the iframe-demo page side-by-side with the main app — same component palette, fully different look. That's Level 3 in action.
|
|
386
|
+
|
|
387
|
+
### When NOT to use this
|
|
388
|
+
|
|
389
|
+
- **Don't ship per-page overrides as your *primary* theming**. If most of your app is purple-flavored, the facade is the right place — then per-page overrides are reserved for genuine exceptions.
|
|
390
|
+
- **Don't override `--p-surface-*` per-page unless you also override the semantic variables that depend on them**. The semantic vars (`--p-text-color`, `--p-content-background`) reference different `--p-surface-N` per mode; overriding only the surfaces, not the semantic mappings, can produce broken contrast in the opposite mode.
|
|
391
|
+
- **Don't use Mechanism B (runtime) when Mechanism A (declarative) works**. Declarative is reproducible across reloads, surfaces in code review, and doesn't depend on script-load order.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
# Reference
|
|
396
|
+
|
|
397
|
+
The remaining sections are the precise contract: which variables exist, which Tailwind utilities they back, how dark mode flips, and which colors actually pass WCAG. Use these to verify your overrides.
|
|
398
|
+
|
|
399
|
+
## Reference — CSS variables
|
|
400
|
+
|
|
401
|
+
All variables are defined in `theme-config.css` and set on `:root`. At runtime, the host injects the real theme — these serve as the dev-time fallback and contract.
|
|
402
|
+
|
|
403
|
+
### Primary palette (11 vars)
|
|
404
|
+
|
|
405
|
+
Base: `--p-primary` (default: `rgb(0, 95, 178)`)
|
|
406
|
+
|
|
407
|
+
| Variable | Value |
|
|
408
|
+
|---|---|
|
|
409
|
+
| `--p-primary-50` | `color-mix(in srgb, var(--p-primary) 5%, white)` |
|
|
410
|
+
| `--p-primary-100` | `color-mix(in srgb, var(--p-primary) 10%, white)` |
|
|
411
|
+
| `--p-primary-200` | `color-mix(in srgb, var(--p-primary) 20%, white)` |
|
|
412
|
+
| `--p-primary-300` | `color-mix(in srgb, var(--p-primary) 30%, white)` |
|
|
413
|
+
| `--p-primary-400` | `color-mix(in srgb, var(--p-primary) 40%, white)` |
|
|
414
|
+
| `--p-primary-500` | `var(--p-primary)` (base) |
|
|
415
|
+
| `--p-primary-600` | `color-mix(in srgb, var(--p-primary) 80%, black)` |
|
|
416
|
+
| `--p-primary-700` | `color-mix(in srgb, var(--p-primary) 70%, black)` |
|
|
417
|
+
| `--p-primary-800` | `color-mix(in srgb, var(--p-primary) 60%, black)` |
|
|
418
|
+
| `--p-primary-900` | `color-mix(in srgb, var(--p-primary) 50%, black)` |
|
|
419
|
+
| `--p-primary-950` | `color-mix(in srgb, var(--p-primary) 40%, black)` |
|
|
420
|
+
|
|
421
|
+
### Secondary palette (11 vars)
|
|
422
|
+
|
|
423
|
+
Base: `--p-secondary` (default: `#6f7385`)
|
|
424
|
+
|
|
425
|
+
Same numbered scale pattern as primary (50–950) using `color-mix`.
|
|
426
|
+
|
|
427
|
+
### Surface palette (13 vars)
|
|
428
|
+
|
|
429
|
+
| Variable | Light value | Dark value |
|
|
430
|
+
|---|---|---|
|
|
431
|
+
| `--p-surface-0` | `#fff` | `#fff` |
|
|
432
|
+
| `--p-surface-50` | `#fafafa` | `#fafafa` |
|
|
433
|
+
| `--p-surface-100` | `#f4f4f5` | `#f4f4f5` |
|
|
434
|
+
| `--p-surface-200` | `#e4e4e7` | `#e4e4e7` |
|
|
435
|
+
| `--p-surface-300` | `#d4d4d8` | `#d4d4d8` |
|
|
436
|
+
| `--p-surface-400` | `#a1a1aa` | `#a1a1aa` |
|
|
437
|
+
| `--p-surface-500` | `#71717a` | `#71717a` |
|
|
438
|
+
| `--p-surface-600` | `#52525b` | `#545250` (warm) |
|
|
439
|
+
| `--p-surface-700` | `#3f3f46` | `#403e3c` (warm) |
|
|
440
|
+
| `--p-surface-800` | `#27272a` | `#2b2927` (warm) |
|
|
441
|
+
| `--p-surface-900` | `#18181b` | `#1c1a19` (warm) |
|
|
442
|
+
| `--p-surface-950` | `#09090b` | `#0f0e0d` (warm) |
|
|
443
|
+
|
|
444
|
+
**Fixed light-to-dark scale** — 0 is always lightest, 950 always darkest. The scale does NOT flip with dark mode. In dark mode, only 600–950 get warmer undertones.
|
|
445
|
+
|
|
446
|
+
### Semantic variables (15 vars)
|
|
447
|
+
|
|
448
|
+
These **flip with dark mode** — use these for theme-dependent styling.
|
|
449
|
+
|
|
450
|
+
| Variable | Light | Dark |
|
|
451
|
+
|---|---|---|
|
|
452
|
+
| `--p-primary-color` | `primary-500` | `primary-400` |
|
|
453
|
+
| `--p-primary-contrast-color` | `surface-0` | `surface-900` |
|
|
454
|
+
| `--p-primary-hover-color` | `primary-600` | `primary-300` |
|
|
455
|
+
| `--p-primary-active-color` | `primary-700` | `primary-200` |
|
|
456
|
+
| `--p-text-color` | `surface-700` | `surface-0` |
|
|
457
|
+
| `--p-text-hover-color` | `surface-800` | `surface-0` |
|
|
458
|
+
| `--p-text-muted-color` | `surface-500` | `surface-400` |
|
|
459
|
+
| `--p-text-hover-muted-color` | `surface-600` | `surface-300` |
|
|
460
|
+
| `--p-content-background` | — | `surface-900` (via host) |
|
|
461
|
+
| `--p-content-border-color` | `surface-200` | `surface-700` |
|
|
462
|
+
| `--p-content-hover-background` | `surface-100` | `surface-800` |
|
|
463
|
+
| `--p-content-hover-color` | `surface-800` | `surface-0` |
|
|
464
|
+
| `--p-highlight-background` | `primary-50` | `primary-400 @ 16%` |
|
|
465
|
+
| `--p-highlight-color` | `primary-700` | `white @ 87%` |
|
|
466
|
+
| `--p-highlight-focus-background` | `primary-100` | `primary-400 @ 24%` |
|
|
467
|
+
| `--p-highlight-focus-color` | `primary-800` | `white @ 87%` |
|
|
468
|
+
| `--p-content-border-radius` | `6px` | `6px` |
|
|
469
|
+
|
|
470
|
+
## Reference — Tailwind utility classes
|
|
471
|
+
|
|
472
|
+
Provided by `tailwindcss-primeui` plugin (included in the shared Tailwind preset).
|
|
473
|
+
|
|
474
|
+
### Color utilities
|
|
475
|
+
|
|
476
|
+
Work with `bg-`, `text-`, `border-`, `outline-`, `ring-` prefixes:
|
|
477
|
+
|
|
478
|
+
| Class suffix | Maps to |
|
|
479
|
+
|---|---|
|
|
480
|
+
| `primary` | `--p-primary-color` |
|
|
481
|
+
| `primary-emphasis` | `--p-primary-hover-color` |
|
|
482
|
+
| `primary-emphasis-alt` | `--p-primary-active-color` |
|
|
483
|
+
| `primary-contrast` | `--p-primary-contrast-color` |
|
|
484
|
+
| `primary-{0,50,100,...,950}` | Full primary shade range |
|
|
485
|
+
| `surface-{0,50,100,...,950}` | Full surface shade range |
|
|
486
|
+
|
|
487
|
+
### Semantic utilities
|
|
488
|
+
|
|
489
|
+
| Class | Maps to |
|
|
490
|
+
|---|---|
|
|
491
|
+
| `.text-color` | `--p-text-color` |
|
|
492
|
+
| `.text-color-emphasis` | `--p-text-hover-color` |
|
|
493
|
+
| `.text-muted-color` | `--p-text-muted-color` |
|
|
494
|
+
| `.text-muted-color-emphasis` | `--p-text-hover-muted-color` |
|
|
495
|
+
| `.bg-emphasis` | Emphasis background + text |
|
|
496
|
+
| `.bg-highlight` | Highlighted state (selected items, active rows) |
|
|
497
|
+
| `.bg-highlight-emphasis` | Emphasized highlight |
|
|
498
|
+
| `.border-surface` | `--p-content-border-color` |
|
|
499
|
+
| `.rounded-border` | `--p-content-border-radius` |
|
|
500
|
+
|
|
501
|
+
### Animation utilities
|
|
502
|
+
|
|
503
|
+
| Class | Description |
|
|
504
|
+
|---|---|
|
|
505
|
+
| `.animate-fadein` | Fade in |
|
|
506
|
+
| `.animate-fadeout` | Fade out |
|
|
507
|
+
| `.animate-slidedown` | Slide down |
|
|
508
|
+
| `.animate-slideup` | Slide up |
|
|
509
|
+
| `.animate-scalein` | Scale in |
|
|
510
|
+
| `.animate-fadeinleft` | Fade in from left |
|
|
511
|
+
| `.animate-fadeinright` | Fade in from right |
|
|
512
|
+
| `.animate-fadeinup` | Fade in from below |
|
|
513
|
+
| `.animate-fadeindown` | Fade in from above |
|
|
514
|
+
| `.animate-duration-{ms}` | Animation duration |
|
|
515
|
+
| `.animate-delay-{ms}` | Animation delay |
|
|
516
|
+
| `.animate-ease-*` | Easing functions |
|
|
517
|
+
|
|
518
|
+
### Severity & accent color utilities (MANDATORY for semantic colors)
|
|
519
|
+
|
|
520
|
+
**Rule: Always use semantic severity classes over raw Tailwind color names when the color conveys meaning.** If a color represents an error, success, warning, info, or help state, you MUST use `danger-*`, `success-*`, `warn-*`, `info-*`, or `help-*` — never `red-*`, `green-*`, `orange-*`, `sky-*`, or `purple-*`. Raw Tailwind colors are only appropriate for purely decorative use where no semantic meaning is attached. Semantics first, decorative later.
|
|
521
|
+
|
|
522
|
+
Added by the shared Tailwind preset via `theme.extend.colors`. Each has a full 50–950 shade scale backed by CSS variables.
|
|
523
|
+
|
|
524
|
+
| Family | Base variable | Default color | Purpose |
|
|
525
|
+
|--------|--------------|---------------|---------|
|
|
526
|
+
| `danger` | `--p-danger` | `rgb(239, 68, 68)` (red-500) | Errors, destructive actions |
|
|
527
|
+
| `success` | `--p-success` | `rgb(34, 197, 94)` (green-500) | Success states, confirmations |
|
|
528
|
+
| `warn` | `--p-warn` | `rgb(249, 115, 22)` (orange-500) | Warnings, caution |
|
|
529
|
+
| `info` | `--p-info` | `rgb(14, 165, 233)` (sky-500) | Informational messages |
|
|
530
|
+
| `help` | `--p-help` | `rgb(168, 85, 247)` (purple-500) | Help, hints |
|
|
531
|
+
| `accent` | `--p-accent` | `rgb(20, 184, 166)` (teal-500) | Highlights, special callouts |
|
|
532
|
+
|
|
533
|
+
Usage with Tailwind prefixes (`bg-`, `text-`, `border-`, `outline-`, `ring-`):
|
|
534
|
+
|
|
535
|
+
```
|
|
536
|
+
bg-danger-500 text-success-700 border-warn-200
|
|
537
|
+
bg-info-50 text-help-400 border-accent-300
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
PrimeVue component CSS (button, tag, badge, toast, message, password) references these semantic names instead of hardcoded Tailwind color names.
|
|
541
|
+
|
|
542
|
+
**Overriding:** Set the base variable on `:root` to retheme all severity shades:
|
|
543
|
+
|
|
544
|
+
```css
|
|
545
|
+
:root {
|
|
546
|
+
--p-danger: rgb(220, 38, 38); /* custom danger base */
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
The full 50–950 scale auto-derives via `color-mix()`. No dark-mode override block is needed — component CSS picks the right shade per mode (e.g., `dark:bg-danger-400`). You may optionally override the base in `@media (prefers-color-scheme: dark)` for mode-specific tuning.
|
|
551
|
+
|
|
552
|
+
### Secondary color utilities
|
|
553
|
+
|
|
554
|
+
Added by the shared Tailwind preset via `theme.extend.colors`:
|
|
555
|
+
|
|
556
|
+
```
|
|
557
|
+
bg-secondary-{50..950}
|
|
558
|
+
text-secondary-{50..950}
|
|
559
|
+
border-secondary-{50..950}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Reference — Dark mode
|
|
563
|
+
|
|
564
|
+
Variables switch at `@media (prefers-color-scheme: dark)`. Key changes:
|
|
565
|
+
|
|
566
|
+
- `--p-primary` base shifts from `rgb(0, 95, 178)` to `rgb(0, 125, 178)` (brighter)
|
|
567
|
+
- `--p-primary-color` shifts from `primary-500` to `primary-400`
|
|
568
|
+
- `--p-content-background` shifts to `surface-900` (via host injection)
|
|
569
|
+
- `--p-text-color` shifts from `surface-700` to `surface-0`
|
|
570
|
+
- Surface 600–950 get warmer undertones in dark mode
|
|
571
|
+
|
|
572
|
+
### Rules
|
|
573
|
+
|
|
574
|
+
- **Use semantic variables** (`--p-text-color`, `--p-content-background`) for colors that should flip with dark mode.
|
|
575
|
+
- **Use `surface-0`/`surface-950`** only when you explicitly need the fixed shade regardless of mode.
|
|
576
|
+
- **For derived shades**: `color-mix(in srgb, var(--p-content-background) 85%, var(--p-text-color) 15%)`.
|
|
577
|
+
- **Never use `--p-surface-N`** for theme-dependent colors — the numbered scale does NOT flip.
|
|
578
|
+
|
|
579
|
+
## Reference — PrimeVue styling (opt-in)
|
|
580
|
+
|
|
581
|
+
The `primevue/` directory in `@wippy-fe/theme` contains CSS files that style PrimeVue component tags (`.p-button`, `.p-datatable`, etc.) using Tailwind `@apply` directives. These are **only needed if your component uses PrimeVue tags**.
|
|
582
|
+
|
|
583
|
+
### Setup
|
|
584
|
+
|
|
585
|
+
1. Add `primevue` as a dependency
|
|
586
|
+
2. Import `PrimeVuePlugin` from `@wippy-fe/theme/primevue-plugin` — installs PrimeVue with `{ theme: 'none' }`
|
|
587
|
+
3. Import PrimeVue CSS: `@import "@wippy-fe/theme/primevue/tailwind.css"` in your styles
|
|
588
|
+
4. Add `'primeVueCssUrl'` to `hostCssKeys` so the host injects PrimeVue CSS at runtime
|
|
589
|
+
|
|
590
|
+
### How it works
|
|
591
|
+
|
|
592
|
+
The CSS files use theme variables via Tailwind classes. Example from `button.css`:
|
|
593
|
+
|
|
594
|
+
```css
|
|
595
|
+
.p-button {
|
|
596
|
+
@apply bg-primary text-primary-contrast border-primary
|
|
597
|
+
enabled:hover:bg-primary-emphasis enabled:active:bg-primary-emphasis-alt ...
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
The `tailwind.css` master file imports all component CSS files organized by category (form, button, data, overlay, menu, panel, file, message, media, misc).
|
|
602
|
+
|
|
603
|
+
### Available PrimeVue component styles
|
|
604
|
+
|
|
605
|
+
**Form**: autocomplete, cascadeselect, checkbox, colorpicker, datepicker, iconfield, iftalabel, inputgroup, inputnumber, inputotp, inputtext, floatlabel, knob, listbox, multiselect, password, radiobutton, rating, select, selectbutton, slider, textarea, togglebutton, toggleswitch, treeselect
|
|
606
|
+
|
|
607
|
+
**Button**: button, buttongroup, speeddial, splitbutton
|
|
608
|
+
|
|
609
|
+
**Data**: datatable, dataview, paginator, picklist, orderlist, organizationchart, timeline, tree, treetable
|
|
610
|
+
|
|
611
|
+
**Overlay**: confirmdialog, confirmpopup, dialog, drawer, popover, tooltip
|
|
612
|
+
|
|
613
|
+
**Menu**: breadcrumb, contextmenu, dock, menu, menubar, megamenu, panelmenu, tieredmenu
|
|
614
|
+
|
|
615
|
+
**Panel**: accordion, card, divider, fieldset, panel, scrollpanel, splitter, stepper, tabs, toolbar
|
|
616
|
+
|
|
617
|
+
**File**: fileupload
|
|
618
|
+
|
|
619
|
+
**Message**: message, toast
|
|
620
|
+
|
|
621
|
+
**Media**: carousel, galleria, image, imagecompare
|
|
622
|
+
|
|
623
|
+
**Misc**: avatar, badge, blockui, chip, inplace, metergroup, overlaybadge, progressbar, progressspinner, ripple, scrolltop, skeleton, tag, terminal
|
|
624
|
+
|
|
625
|
+
## Reference — Choosing custom colors (luminosity, WCAG)
|
|
626
|
+
|
|
627
|
+
When overriding `--p-primary`, `--p-secondary`, or the surface palette, follow these guidelines to ensure contrast and readability across both modes.
|
|
628
|
+
|
|
629
|
+
### How the shade scale works
|
|
630
|
+
|
|
631
|
+
The `color-mix()` system blends your base color with white (lighter shades) or black (darker shades). The percentages are fixed:
|
|
632
|
+
|
|
633
|
+
| Shade | Blend |
|
|
634
|
+
|---|---|
|
|
635
|
+
| 50 | 5% base + 95% white |
|
|
636
|
+
| 100 | 10% base + 90% white |
|
|
637
|
+
| 200 | 20% base + 80% white |
|
|
638
|
+
| 300 | 30% base + 70% white |
|
|
639
|
+
| 400 | 40% base + 60% white |
|
|
640
|
+
| **500** | **100% base (no blend)** |
|
|
641
|
+
| 600 | 80% base + 20% black |
|
|
642
|
+
| 700 | 70% base + 30% black |
|
|
643
|
+
| 800 | 60% base + 40% black |
|
|
644
|
+
| 900 | 50% base + 50% black |
|
|
645
|
+
| 950 | 40% base + 60% black |
|
|
646
|
+
|
|
647
|
+
This means the base color's luminosity determines the contrast of the **entire scale**. A too-light base produces a washed-out 500 that can't contrast against white text; a too-dark base makes 400 too dim for dark mode.
|
|
648
|
+
|
|
649
|
+
### Primary color luminosity
|
|
650
|
+
|
|
651
|
+
The primary base is used at two critical contrast points:
|
|
652
|
+
|
|
653
|
+
1. **Light mode**: `bg-primary` (500) with `text-primary-contrast` (white) — buttons, badges, checkboxes
|
|
654
|
+
2. **Dark mode**: `bg-primary` (400 = 40% base + 60% white) with `text-primary-contrast` (surface-900, near-black)
|
|
655
|
+
|
|
656
|
+
**Recommended relative luminance for `--p-primary`: 0.05–0.15** (medium-dark, saturated).
|
|
657
|
+
|
|
658
|
+
| Example | RGB | Rel. luminance | Light contrast (500 vs white) | Dark contrast (400 vs black) |
|
|
659
|
+
|---|---|---|---|---|
|
|
660
|
+
| Default blue | `rgb(0, 95, 178)` | ~0.10 | ~8.5:1 (AAA) | ~6.8:1 (AA) |
|
|
661
|
+
| Deep teal | `rgb(0, 128, 128)` | ~0.14 | ~5.8:1 (AA) | ~8.7:1 (AAA) |
|
|
662
|
+
| Indigo | `rgb(79, 70, 229)` | ~0.07 | ~8.0:1 (AAA) | ~5.5:1 (AA) |
|
|
663
|
+
| Red | `rgb(185, 28, 28)` | ~0.05 | ~9.2:1 (AAA) | ~4.7:1 (borderline) |
|
|
664
|
+
| Forest green | `rgb(22, 101, 52)` | ~0.08 | ~7.2:1 (AAA) | ~6.1:1 (AA) |
|
|
665
|
+
| Orange | `rgb(194, 120, 3)` | ~0.18 | ~3.7:1 (FAIL) | ~9.5:1 (AAA) |
|
|
666
|
+
|
|
667
|
+
**Key constraints:**
|
|
668
|
+
- Luminance **> 0.18** → 500 shade is too light for white text (fails WCAG AA 4.5:1 in light mode)
|
|
669
|
+
- Luminance **< 0.04** → 400 shade is too dark for dark-on-light text (fails WCAG AA in dark mode)
|
|
670
|
+
- The sweet spot is **0.06–0.14** — both modes pass WCAG AA (4.5:1), with most passing AAA (7:1)
|
|
671
|
+
- Pure, saturated colors (high chroma) work best. Desaturated/muted colors lose contrast faster
|
|
672
|
+
|
|
673
|
+
**If your brand color is too light** (e.g., orange, yellow, lime): darken it for `--p-primary` and use the original as a secondary or accent. Light-mode buttons with white text will fail contrast otherwise.
|
|
674
|
+
|
|
675
|
+
**If your brand color is too dark** (e.g., navy, dark brown): lighten it slightly. The 400 shade in dark mode needs enough contrast against `surface-900`.
|
|
676
|
+
|
|
677
|
+
### Secondary color luminosity
|
|
678
|
+
|
|
679
|
+
Secondary is used for muted/deemphasized elements — less critical than primary but still appears in text and borders.
|
|
680
|
+
|
|
681
|
+
**Recommended relative luminance for `--p-secondary`: 0.10–0.25** (mid-range, desaturated).
|
|
682
|
+
|
|
683
|
+
The default `#6f7385` (luminance ~0.16) is a muted gray-purple that reads as neutral in both modes. Good secondary colors:
|
|
684
|
+
- Are **less saturated** than primary (avoid competing for attention)
|
|
685
|
+
- Have **mid-range luminance** so the full shade scale is usable
|
|
686
|
+
- Work as `text-secondary-600` on white and `text-secondary-300` on dark backgrounds
|
|
687
|
+
|
|
688
|
+
### Surface palette
|
|
689
|
+
|
|
690
|
+
Surfaces are a fixed grayscale ramp — they do NOT flip with dark mode. The semantic variables (`--p-content-background`, `--p-text-color`) reference different points on this scale per mode.
|
|
691
|
+
|
|
692
|
+
**Light mode assignments:**
|
|
693
|
+
|
|
694
|
+
| Role | Surface | Hex | Purpose |
|
|
695
|
+
|---|---|---|---|
|
|
696
|
+
| Page background | 0 | `#ffffff` | Main canvas |
|
|
697
|
+
| Card/container background | 0 | `#ffffff` | Content containers |
|
|
698
|
+
| Hover background | 100 | `#f4f4f5` | Interactive hover state |
|
|
699
|
+
| Border | 200 | `#e4e4e7` | Borders, dividers |
|
|
700
|
+
| Muted text | 500 | `#71717a` | Secondary text, labels |
|
|
701
|
+
| Body text | 700 | `#3f3f46` | Primary readable text |
|
|
702
|
+
| Strong emphasis | 800 | `#27272a` | Headings, hover text |
|
|
703
|
+
|
|
704
|
+
**Dark mode assignments:**
|
|
705
|
+
|
|
706
|
+
| Role | Surface | Hex | Purpose |
|
|
707
|
+
|---|---|---|---|
|
|
708
|
+
| Page background | 900 | `#1c1a19` (warm) | Main canvas |
|
|
709
|
+
| Card/container background | 800 | `#2b2927` (warm) | Elevated containers |
|
|
710
|
+
| Hover background | 800 | `#2b2927` (warm) | Interactive hover state |
|
|
711
|
+
| Border | 700 | `#403e3c` (warm) | Borders, dividers |
|
|
712
|
+
| Muted text | 400 | `#a1a1aa` | Secondary text, labels |
|
|
713
|
+
| Body text | 0 | `#ffffff` | Primary readable text |
|
|
714
|
+
| Strong emphasis | 0 | `#ffffff` | Headings, hover text |
|
|
715
|
+
|
|
716
|
+
**Dark mode warm tones** (600–950): The default surfaces shift from cool zinc grays to slightly warm brown-grays in dark mode. This reduces visual fatigue on dark backgrounds. The shift is subtle — `#545250` vs `#52525b` for surface-600 — but creates a warmer, more natural feel.
|
|
717
|
+
|
|
718
|
+
When providing custom surfaces, keep these rules:
|
|
719
|
+
- **0 must always be `#ffffff`** (or very near it) — used as contrast text in dark mode
|
|
720
|
+
- **950 must always be very dark** — used as the maximum-contrast anchor
|
|
721
|
+
- **The scale must increase monotonically in darkness** — no inversions
|
|
722
|
+
- **600–950 should shift warm** in dark mode for visual comfort (optional but recommended)
|
|
723
|
+
- **Don't change 0–500** between modes unless you have a specific reason — the semantic variables handle mode switching
|
|
724
|
+
|
|
725
|
+
### Contrast verification
|
|
726
|
+
|
|
727
|
+
After choosing colors, verify these critical pairs meet WCAG AA (4.5:1 minimum):
|
|
728
|
+
|
|
729
|
+
| Pair | Light mode | Dark mode |
|
|
730
|
+
|---|---|---|
|
|
731
|
+
| `bg-primary` + `text-primary-contrast` | primary-500 vs white | primary-400 vs surface-900 |
|
|
732
|
+
| `bg-highlight` + text | primary-50 vs primary-700 | primary-400@16% vs white@87% |
|
|
733
|
+
| Body text on background | surface-700 vs white | surface-0 vs surface-900 |
|
|
734
|
+
| Muted text on background | surface-500 vs white | surface-400 vs surface-900 |
|
|
735
|
+
|
|
736
|
+
Quick formula for relative luminance of an sRGB color:
|
|
737
|
+
```
|
|
738
|
+
L = 0.2126 * R' + 0.7152 * G' + 0.0722 * B'
|
|
739
|
+
where R' = (R/255)^2.2 (simplified gamma)
|
|
740
|
+
|
|
741
|
+
Contrast ratio = (L_lighter + 0.05) / (L_darker + 0.05)
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
WCAG AA requires 4.5:1 for normal text, 3:1 for large text (18px+ bold or 24px+ regular).
|
|
745
|
+
|
|
746
|
+
## Reference — Host UI customization (`--wippy-host-*` + BEM classes)
|
|
747
|
+
|
|
748
|
+
The Wippy host exposes CSS custom properties and BEM class names on its core UI components. Override these via the facade's `host_css_variables` / `host_custom_css` parameters (Level 2) or via `AppConfig.customization.cssVariables` / `customCSS` (Level 3) to restyle the host without forking.
|
|
749
|
+
|
|
750
|
+
> **Important:** Because `customCSS` and `cssVariables` are injected into both the host AND all nested child iframes by default, always scope host-only overrides to `.wippy-host-app`. For example, use `.wippy-host-app .chat-message { ... }` — never `.chat-message { ... }` alone, or the styles will leak into child iframes. (Putting them in `host_custom_css` does this server-side.)
|
|
751
|
+
|
|
752
|
+
### Layout & sidebar
|
|
753
|
+
|
|
754
|
+
| Variable | Default | Description |
|
|
755
|
+
|---|---|---|
|
|
756
|
+
| `--wippy-host-sidebar-width-open` | `16rem` | Sidebar width when expanded |
|
|
757
|
+
| `--wippy-host-sidebar-width-closed` | `3.5rem` | Sidebar width when collapsed |
|
|
758
|
+
|
|
759
|
+
**BEM classes** (scope with `.wippy-host-app`):
|
|
760
|
+
|
|
761
|
+
| Class | Element |
|
|
762
|
+
|---|---|
|
|
763
|
+
| `.layout` | Root layout wrapper |
|
|
764
|
+
| `.layout__sidebar` | Sidebar container |
|
|
765
|
+
| `.layout__sidebar-header` | Sidebar header (logo + toggle) |
|
|
766
|
+
| `.layout__sidebar-nav` | Navigation list area |
|
|
767
|
+
| `.layout__main` | Main content area (right of sidebar) |
|
|
768
|
+
|
|
769
|
+
### Splitter gutter
|
|
770
|
+
|
|
771
|
+
The resizable panel divider between main content and the right panel.
|
|
772
|
+
|
|
773
|
+
| Variable | Default | Description |
|
|
774
|
+
|---|---|---|
|
|
775
|
+
| `--wippy-host-splitter-width` | `1px` | Visible line width |
|
|
776
|
+
| `--wippy-host-splitter-hit-area` | `10px` | Draggable hit area width (transparent) |
|
|
777
|
+
| `--wippy-host-splitter-color` | `var(--p-surface-200)` (light) / `var(--p-surface-600)` (dark) | Line color |
|
|
778
|
+
|
|
779
|
+
### Chat messages
|
|
780
|
+
|
|
781
|
+
| Variable | Default | Description |
|
|
782
|
+
|---|---|---|
|
|
783
|
+
| `--wippy-host-message-radius` | `1rem` | Message bubble border radius |
|
|
784
|
+
| `--wippy-host-message-padding-x` | `1rem` | Message horizontal padding |
|
|
785
|
+
| `--wippy-host-message-padding-y` | `0.5rem` | Message vertical padding |
|
|
786
|
+
| `--wippy-host-message-user-bg` | `var(--p-primary-50)` | User message background |
|
|
787
|
+
| `--wippy-host-message-agent-bg` | `var(--p-yellow-50)` (light) / `var(--p-surface-800)` (dark) | Agent message background |
|
|
788
|
+
| `--wippy-host-tool-bg` | `var(--p-help-50)` | Tool call background |
|
|
789
|
+
| `--wippy-host-tool-border` | `var(--p-help-300)` | Tool call left border |
|
|
790
|
+
| `--wippy-host-avatar-size` | `2rem` | Message avatar diameter |
|
|
791
|
+
|
|
792
|
+
**BEM classes** (scope with `.wippy-host-app`):
|
|
793
|
+
|
|
794
|
+
| Class | Element |
|
|
795
|
+
|---|---|
|
|
796
|
+
| `.chat-message` | Message row container |
|
|
797
|
+
| `.chat-message--user` | User message modifier |
|
|
798
|
+
| `.chat-message--agent-message` | Agent message modifier |
|
|
799
|
+
| `.chat-message--tool` | Tool call message modifier |
|
|
800
|
+
| `.chat-message--error` | Error message modifier |
|
|
801
|
+
| `.chat-message__avatar` | Avatar wrapper |
|
|
802
|
+
| `.chat-message__avatar-icon` | Avatar icon circle |
|
|
803
|
+
| `.chat-message__content` | Message bubble |
|
|
804
|
+
| `.chat-message__body` | Message text content |
|
|
805
|
+
| `.chat-message__footer` | Timestamp row |
|
|
806
|
+
| `.chat-message__tool-name` | Tool name label |
|
|
807
|
+
| `.chat-message__tool-icon` | Tool icon |
|
|
808
|
+
| `.chat-message__agent-content` | Agent name system line |
|
|
809
|
+
| `.chat-message__model-content` | Model name system line |
|
|
810
|
+
| `.chat-message__files` | Attached files row |
|
|
811
|
+
| `.chat-tool-group` | Inline tool call badge group |
|
|
812
|
+
| `.chat-tool-group__badge` | Individual tool badge |
|
|
813
|
+
| `.chat-tool-group__badge--success` | Completed tool badge |
|
|
814
|
+
| `.chat-tool-group__badge--error` | Failed tool badge |
|
|
815
|
+
| `.chat-tool-group__badge--processing` | In-progress tool badge |
|
|
816
|
+
| `.chat-tool-group__icon` | Badge icon |
|
|
817
|
+
|
|
818
|
+
### Chat input
|
|
819
|
+
|
|
820
|
+
**BEM classes** (scope with `.wippy-host-app`):
|
|
821
|
+
|
|
822
|
+
| Class | Element |
|
|
823
|
+
|---|---|
|
|
824
|
+
| `.chat-input` | Input bar container |
|
|
825
|
+
| `.chat-input__group` | Input field + buttons wrapper |
|
|
826
|
+
| `.chat-input__textarea` | Message textarea |
|
|
827
|
+
| `.chat-input__attach-button` | Attachment button |
|
|
828
|
+
| `.chat-input__send-button` | Send button |
|
|
829
|
+
| `.chat-input__stop-button` | Stop generation button |
|
|
830
|
+
| `.chat-input__upload-list` | Upload queue list |
|
|
831
|
+
| `.chat-input__prompts` | Suggested prompts area |
|
|
832
|
+
|
|
833
|
+
### Chat container
|
|
834
|
+
|
|
835
|
+
**BEM classes** (scope with `.wippy-host-app`):
|
|
836
|
+
|
|
837
|
+
| Class | Element |
|
|
838
|
+
|---|---|
|
|
839
|
+
| `.chat-container` | Outer chat wrapper |
|
|
840
|
+
| `.chat-container--selected` | Has active session |
|
|
841
|
+
| `.chat-container--non-selected` | No session selected |
|
|
842
|
+
| `.chat-container__empty-state` | Empty state wrapper |
|
|
843
|
+
| `.chat-container__empty-state-icon` | Empty state icon |
|
|
844
|
+
| `.chat-container__empty-state-title` | Empty state heading |
|
|
845
|
+
| `.chat-container__empty-state-description` | Empty state text |
|
|
846
|
+
| `.chat-container__drop-zone` | File drag-and-drop overlay |
|
|
847
|
+
| `.chat-container__drop-zone-icon` | Drop zone icon |
|
|
848
|
+
|
|
849
|
+
### Session selector
|
|
850
|
+
|
|
851
|
+
**BEM classes** (scope with `.wippy-host-app`):
|
|
852
|
+
|
|
853
|
+
| Class | Element |
|
|
854
|
+
|---|---|
|
|
855
|
+
| `.session-selector` | Selector wrapper |
|
|
856
|
+
| `.session-selector__dropdown` | Dropdown component |
|
|
857
|
+
| `.session-selector__option` | Session option row |
|
|
858
|
+
| `.session-selector__active-dot` | Active session indicator |
|
|
859
|
+
|
|
860
|
+
### Root
|
|
861
|
+
|
|
862
|
+
| Class | Element |
|
|
863
|
+
|---|---|
|
|
864
|
+
| `.wippy-host-app` | Application root element — scope all host-only CSS overrides to this |
|
|
865
|
+
|
|
866
|
+
### Example: custom-styled host
|
|
867
|
+
|
|
868
|
+
```css
|
|
869
|
+
/* Via facade host_custom_css, OR via AppConfig.customization.customCSS */
|
|
870
|
+
|
|
871
|
+
/* Variables are already prefixed — no scoping needed */
|
|
872
|
+
:root {
|
|
873
|
+
--wippy-host-message-radius: 0.5rem;
|
|
874
|
+
--wippy-host-message-user-bg: #e0f2fe;
|
|
875
|
+
--wippy-host-sidebar-width-open: 20rem;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/* Class selectors MUST be scoped to .wippy-host-app */
|
|
879
|
+
.wippy-host-app .chat-message__footer {
|
|
880
|
+
display: none; /* hide timestamps */
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.wippy-host-app .session-selector {
|
|
884
|
+
display: none; /* hide session picker */
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
---
|
|
889
|
+
|
|
890
|
+
## Related docs
|
|
891
|
+
|
|
892
|
+
- **`README.md`** — frontend docs index
|
|
893
|
+
- **`app-guide.md`** — building Wippy web apps; covers `proxy.injections.css.themeConfig` and the standard injection block
|
|
894
|
+
- **`component-guide.md`** — building Wippy web components; `hostCssKeys` controls which platform CSS gets loaded into a component's Shadow DOM
|
|
895
|
+
- **`best-practices.md`** — general Vue/Tailwind conventions; defers theming to this doc
|
|
896
|
+
- **`host-spec.md`** — host runtime contract for `wippy.proxy.injections` keys
|
|
897
|
+
- **`proxy-api.md`** — runtime proxy API; documents which `--p-*` and `--wippy-host-*` vars `<wippy-loading>` and `<wippy-error>` consume
|
|
898
|
+
|
|
899
|
+
External:
|
|
900
|
+
|
|
901
|
+
- **`@wippy-fe/theme/THEMING.md`** — same content as this file, shipped with the theme package (`node_modules/@wippy-fe/theme/THEMING.md` after install)
|
|
902
|
+
- **drewaltizer-wippy** (`C:/Projects/drewaltizer-wippy/src/drewapp/deps/_index.yaml`) — full Level 2 reference implementation
|
|
903
|
+
- **`app-template/frontend/applications/iframe-demo/`** — Level 3 reference implementation
|
|
904
|
+
- **wippy.ai docs** (`https://wippy.ai/llms.txt`) — Lua-side facade documentation
|