@keenmate/web-multiselect 1.12.0-rc02 → 1.12.0-rc03

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.
@@ -0,0 +1,388 @@
1
+ # Theming
2
+
3
+ `@keenmate/web-multiselect` is styled entirely through CSS custom properties. The component uses Shadow DOM for encapsulation, but every visible color, size, and effect is exposed as a `--ms-*` variable that you can override from the light DOM.
4
+
5
+ For interactive theme exploration, use the [KeenMate Theme Designer](https://theme-designer.keenmate.dev).
6
+
7
+ ## Sizing
8
+
9
+ The component uses `--ms-rem` as a base unit for proportional scaling. Default is `10px`, meaning `calc(1.4 * var(--ms-rem))` equals `14px`.
10
+
11
+ **Global scaling:**
12
+
13
+ ```html
14
+ <!-- Compact (80%) -->
15
+ <web-multiselect style="--ms-rem: 8px;"></web-multiselect>
16
+
17
+ <!-- Default (100%) -->
18
+ <web-multiselect></web-multiselect>
19
+
20
+ <!-- Large (120%) -->
21
+ <web-multiselect style="--ms-rem: 12px;"></web-multiselect>
22
+
23
+ <!-- Pure Admin integration (inherits from html { font-size: 10px }) -->
24
+ <web-multiselect style="--ms-rem: 1rem;"></web-multiselect>
25
+ ```
26
+
27
+ **Via CSS class:**
28
+
29
+ ```css
30
+ web-multiselect.compact { --ms-rem: 8px; }
31
+ web-multiselect.large { --ms-rem: 12px; }
32
+ ```
33
+
34
+ > **Shadow DOM note:** CSS variables must be set on the `<web-multiselect>` element itself, not on wrapper divs.
35
+
36
+ **Fine-grained control:**
37
+
38
+ Override individual sizing variables for specific adjustments:
39
+
40
+ - `--ms-input-height` — input field height (default: 35px)
41
+ - `--ms-input-font-size` — input font size
42
+ - `--ms-input-padding` — input padding
43
+ - `--ms-badge-height` — badge height
44
+ - `--ms-option-height` — option height in dropdown
45
+
46
+ **Input size variants:**
47
+
48
+ Five size variants for consistent input sizing across KeenMate components:
49
+
50
+ | Size | Variable | Height | Base variable |
51
+ |------|----------|--------|---------------|
52
+ | XS | `--ms-input-size-xs-height` | 31px | `--base-input-size-xs-height` |
53
+ | SM | `--ms-input-size-sm-height` | 33px | `--base-input-size-sm-height` |
54
+ | MD | `--ms-input-size-md-height` | 35px | `--base-input-size-md-height` |
55
+ | LG | `--ms-input-size-lg-height` | 38px | `--base-input-size-lg-height` |
56
+ | XL | `--ms-input-size-xl-height` | 41px | `--base-input-size-xl-height` |
57
+
58
+ Heights reference `--base-input-size-*-height` from the [Theme Designer](https://theme-designer.keenmate.dev), ensuring consistent input heights across all KeenMate components.
59
+
60
+ ```css
61
+ /* Set consistent input heights across all components */
62
+ :root {
63
+ --base-input-size-md-height: 4.0; /* All components: 40px at 10px rem */
64
+ }
65
+ ```
66
+
67
+ ## Theme Designer integration
68
+
69
+ The easiest way to customize the appearance of this component is the **KeenMate Theme Designer** at [theme-designer.keenmate.dev](https://theme-designer.keenmate.dev).
70
+
71
+ 1. **Choose 3 base colors** — background, text, and accent.
72
+ 2. **Preview changes live** — see your theme applied instantly.
73
+ 3. **Fine-tune individual variables** — lock specific values while adjusting others.
74
+ 4. **Export your theme** — copy CSS, JSON, or SCSS to your project.
75
+
76
+ ### CSS variable layers
77
+
78
+ KeenMate components support a **two-layer theming architecture**:
79
+
80
+ **Standalone mode (simple)** — just override the component-specific variables you need:
81
+
82
+ ```css
83
+ :root {
84
+ --ms-accent-color: #your-brand-color;
85
+ --ms-primary-bg: #your-background;
86
+ --ms-text-primary: #your-text-color;
87
+ }
88
+ ```
89
+
90
+ **Cascading mode (multi-component)** — when using multiple KeenMate components, you can define a shared base layer:
91
+
92
+ ```css
93
+ :root {
94
+ /* Base layer - single source of truth */
95
+ --base-accent-color: #3b82f6;
96
+ --base-main-bg: #ffffff;
97
+ --base-hover-bg: #f3f4f6;
98
+ --base-text-color-1: #111827;
99
+
100
+ /* Components reference base layer */
101
+ --ms-accent-color: var(--base-accent-color);
102
+ --drp-accent-color: var(--base-accent-color);
103
+ }
104
+ ```
105
+
106
+ Change `--base-accent-color` once → all components update automatically.
107
+
108
+ ### Unified variable naming
109
+
110
+ All KeenMate components follow a consistent naming convention for **Tier 1 variables** (core theming):
111
+
112
+ | Purpose | web-multiselect | web-daterangepicker |
113
+ |---------|-----------------|---------------------|
114
+ | Brand color | `--ms-accent-color` | `--drp-accent-color` |
115
+ | Background | `--ms-primary-bg` | `--drp-primary-bg` |
116
+ | Text color | `--ms-text-primary` | `--drp-text-primary` |
117
+ | Text on accent | `--ms-text-color-on-accent` | `--drp-text-on-accent` |
118
+ | Border color | `--ms-border-color` | `--drp-border-color` |
119
+
120
+ Learn the pattern once, apply it across all components.
121
+
122
+ ### Component variables manifest
123
+
124
+ This package exports a `component-variables.manifest.json` file that documents all supported CSS variables for tooling integration (e.g., Theme Designer, IDE autocomplete):
125
+
126
+ ```javascript
127
+ import manifest from '@keenmate/web-multiselect/component-variables.manifest.json';
128
+ // manifest.baseVariables - list of --base-* variables the component responds to
129
+ // manifest.componentVariables - list of --ms-* component-specific variables
130
+ ```
131
+
132
+ ## CSS variables (no build system required)
133
+
134
+ You can customize the component using CSS variables even with just a `<script>` tag:
135
+
136
+ ```html
137
+ <style>
138
+ /* Override tooltip appearance */
139
+ web-multiselect {
140
+ --ms-tooltip-bg: #1f2937;
141
+ --ms-tooltip-color: #f9fafb;
142
+ --ms-tooltip-padding: 0.625rem 0.875rem;
143
+ --ms-tooltip-border-radius: 0.5rem;
144
+ --ms-tooltip-font-size: 0.8125rem;
145
+ --ms-tooltip-max-width: 24rem;
146
+ --ms-tooltip-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
147
+ --ms-tooltip-z-index: 10000;
148
+ }
149
+
150
+ /* Override "+X more" badge colors */
151
+ web-multiselect {
152
+ --ms-more-badge-bg: #dbeafe;
153
+ --ms-more-badge-hover-bg: #bfdbfe;
154
+ --ms-more-badge-active-bg: #93c5fd;
155
+ }
156
+
157
+ /* Size the component */
158
+ web-multiselect {
159
+ width: 100%;
160
+ max-width: 400px;
161
+ }
162
+ </style>
163
+ ```
164
+
165
+ ## Dark mode — supported signals
166
+
167
+ Since v1.12.0 the multiselect honors **five different signals** for switching to dark mode. Pick whichever fits your app; you don't need to wire them all up.
168
+
169
+ | # | Signal | Set by | Example |
170
+ |---|--------|--------|---------|
171
+ | 1 | OS preference + page `color-scheme` | App author | `html { color-scheme: light dark }` — multiselect picks the OS branch automatically. |
172
+ | 2 | Page-level `color-scheme: dark` | App author | `body { color-scheme: dark }` — flips every multiselect on the page to dark. |
173
+ | 3 | Framework data-attribute on ancestor | Bootstrap, Pure Admin, custom apps | `<html data-bs-theme="dark">` or `<div data-theme="dark">…</div>` |
174
+ | 4 | Framework class on ancestor | Tailwind, hand-rolled toggles | `<html class="dark">` |
175
+ | 5 | Per-instance attribute on host | App author, for one widget | `<web-multiselect data-theme="dark">` |
176
+
177
+ **Precedence** (highest wins): per-instance (#5) → framework ancestor (#3, #4) → page color-scheme (#1, #2).
178
+
179
+ ### Forcing a single widget back to light
180
+
181
+ If your page is dark but you want one multiselect to render light:
182
+
183
+ ```html
184
+ <!-- on a body { color-scheme: dark } page -->
185
+ <web-multiselect data-theme="light"></web-multiselect>
186
+ ```
187
+
188
+ This works for any of signals #3–#5. The symmetric `data-theme="light"`, `data-bs-theme="light"`, `.light` selectors restore the light palette inside the affected scope.
189
+
190
+ ## Available CSS variables
191
+
192
+ The component exposes **150+ CSS custom properties** defined at the `:host` level, making them inspectable and overridable. Below are the **50+ most commonly customized variables** organized by category.
193
+
194
+ ### Inspecting variables in DevTools
195
+
196
+ All CSS custom properties are defined at the `:host` level in the compiled CSS, making them visible in browser DevTools:
197
+
198
+ 1. Open DevTools (F12) and select the `<web-multiselect>` element.
199
+ 2. In the **Styles** panel, look for the `:host` selector.
200
+ 3. You'll see all 150+ variables with their default values.
201
+ 4. Edit values live to preview changes instantly.
202
+
203
+ **CSS variables work with Shadow DOM** because they inherit through the shadow boundary. This means you can customize the component from outside:
204
+
205
+ ```html
206
+ <style>
207
+ /* These variables will penetrate into the Shadow DOM */
208
+ web-multiselect {
209
+ --ms-accent-color: #10b981; /* Changes primary color */
210
+ --ms-input-border-radius: 0.5rem; /* Rounds input corners */
211
+ }
212
+ </style>
213
+ ```
214
+
215
+ For the complete list of all available CSS variables, see [`src/css/variables.css`](../src/css/variables.css) — all 150+ CSS custom properties at `:host` level.
216
+
217
+ ### Colors
218
+
219
+ | Variable | Default | Description |
220
+ |----------|---------|-------------|
221
+ | `--ms-accent-color` | `#3b82f6` | Primary accent color (blue) |
222
+ | `--ms-accent-color-hover` | `#2563eb` | Accent color on hover |
223
+ | `--ms-accent-color-active` | `#1d4ed8` | Accent color when active |
224
+ | `--ms-text-primary` | `#111827` | Primary text color |
225
+ | `--ms-text-secondary` | `#6b7280` | Secondary/muted text color |
226
+ | `--ms-border-color` | `#e5e7eb` | Default border color |
227
+ | `--ms-border` | `var(--base-border, 1px solid var(--ms-border-color))` | Default full border (inherits from theme-designer) |
228
+
229
+ ### Input component
230
+
231
+ | Variable | Default | Description |
232
+ |----------|---------|-------------|
233
+ | `--ms-input-bg` | `var(--base-input-bg, #ffffff)` | Input background |
234
+ | `--ms-input-text` | `#111827` | Input text color |
235
+ | `--ms-input-border` | `#d1d5db` | Input border color |
236
+ | `--ms-input-focus-border-color` | `#3b82f6` | Border color when focused |
237
+ | `--ms-input-padding-v` | `0.5rem` | Input vertical padding |
238
+ | `--ms-input-padding-h` | `0.75rem` | Input horizontal padding |
239
+ | `--ms-input-font-size` | `0.875rem` | Input font size |
240
+ | `--ms-input-border-radius` | `0.375rem` | Input border radius |
241
+ | `--ms-input-placeholder-color` | `#6b7280` | Placeholder text color |
242
+
243
+ ### Dropdown & options
244
+
245
+ | Variable | Default | Description |
246
+ |----------|---------|-------------|
247
+ | `--ms-dropdown-bg` | `var(--base-dropdown-bg, var(--base-elevated-bg, light-dark(#ffffff, #1a1a1a)))` | Dropdown background (auto-adapts to OS dark mode) |
248
+ | `--ms-dropdown-border` | `var(--ms-border-color)` | Dropdown border color |
249
+ | `--ms-dropdown-shadow` | (box shadow) | Dropdown shadow |
250
+ | `--ms-dropdown-max-height` | `20rem` | Max height of dropdown |
251
+ | `--ms-option-padding-v` | `0.5rem` | Option vertical padding |
252
+ | `--ms-option-padding-h` | `0.75rem` | Option horizontal padding |
253
+ | `--ms-option-hover-bg` | `#f9fafb` | Option background on hover |
254
+ | `--ms-option-color-hover` | `inherit` | Option text color on hover |
255
+ | `--ms-option-bg-selected` | (rgba accent) | Selected option background |
256
+ | `--ms-option-bg-focused` | `#f9fafb` | Focused option background (keyboard) |
257
+ | `--ms-option-color-focused` | `inherit` | Focused option text color |
258
+ | `--ms-option-bg-matched` | (accent 8%) | Matched option background (navigate mode) |
259
+ | `--ms-option-color-matched` | `inherit` | Matched option text color |
260
+
261
+ ### Badges
262
+
263
+ | Variable | Default | Description |
264
+ |----------|---------|-------------|
265
+ | `--ms-badge-text-bg` | `var(--ms-accent-color-light)` | Badge background color |
266
+ | `--ms-badge-text-color` | `var(--ms-accent-color)` | Badge text color |
267
+ | `--ms-badge-gap` | `0.5rem` | Gap between badges |
268
+ | `--ms-badge-height` | `1.5rem` | Height of badges |
269
+ | `--ms-badge-font-size` | `0.75rem` | Badge font size |
270
+ | `--ms-badge-border-radius` | `0.375rem` | Badge border radius |
271
+ | `--ms-badge-remove-bg` | `var(--ms-accent-color)` | Remove button background |
272
+ | `--ms-badge-remove-color` | `var(--ms-text-color-on-accent)` | Remove button (X) color — applied to the SVG via `currentColor` |
273
+ | `--ms-badge-remove-icon-size` | `calc(1.0 * var(--ms-rem))` | Size of the X glyph inside the remove button |
274
+ | `--ms-icon-remove` | (inline SVG `url(...)`) | The X mask SVG; override to swap the glyph shape (alpha-only — color comes from `--ms-badge-remove-color`) |
275
+ | `--ms-badge-counter-text-bg` | `var(--ms-primary-bg)` | BadgeCounter text background ("+X more") |
276
+ | `--ms-badge-counter-text-color` | `var(--ms-text-color-3)` | BadgeCounter text color |
277
+ | `--ms-badge-counter-remove-bg` | `var(--ms-text-color-3)` | BadgeCounter remove button background |
278
+ | `--ms-badge-counter-remove-color` | `var(--ms-text-color-on-accent)` | BadgeCounter remove button color |
279
+ | `--ms-badge-counter-border` | `1px solid var(--ms-border-color)` | BadgeCounter border |
280
+ | `--ms-badge-text-border` | `none` | Badge text border (e.g., `1px solid #3b82f6`) |
281
+
282
+ ### Checkboxes
283
+
284
+ | Variable | Default | Description |
285
+ |----------|---------|-------------|
286
+ | `--ms-checkbox-bg` | `var(--ms-input-bg)` | Checkbox background |
287
+ | `--ms-checkbox-border` | `1px solid var(--ms-border-color)` | Checkbox border |
288
+ | `--ms-checkbox-border-radius` | `0.3rem` | Checkbox border radius |
289
+ | `--ms-checkbox-checked-bg` | `var(--ms-accent-color)` | Background when checked |
290
+ | `--ms-checkbox-checked-border` | `1px solid var(--ms-accent-color)` | Border when checked |
291
+ | `--ms-checkbox-checkmark-color` | `var(--ms-text-color-on-accent)` | Checkmark color |
292
+ | `--ms-checkbox-hover-border-color` | `var(--ms-accent-color)` | Border on hover |
293
+ | `--ms-checkbox-disabled-bg` | `var(--ms-primary-bg)` | Disabled background |
294
+ | `--ms-checkbox-disabled-border` | `1px solid var(--ms-border-color)` | Disabled border |
295
+
296
+ ### Scrollbar
297
+
298
+ | Variable | Default | Description |
299
+ |----------|---------|-------------|
300
+ | `--ms-scrollbar-width` | `8px` | Scrollbar width |
301
+ | `--ms-scrollbar-track-bg` | `transparent` | Track background |
302
+ | `--ms-scrollbar-thumb-bg` | `var(--ms-border-color)` | Thumb color |
303
+ | `--ms-scrollbar-thumb-bg-hover` | `var(--ms-text-color-3)` | Thumb hover color |
304
+ | `--ms-scrollbar-thumb-border-radius` | `4px` | Thumb border radius |
305
+
306
+ ### Counter (in input)
307
+
308
+ | Variable | Default | Description |
309
+ |----------|---------|-------------|
310
+ | `--ms-counter-bg` | `#3b82f6` | Counter background |
311
+ | `--ms-counter-color` | `#ffffff` | Counter text color |
312
+ | `--ms-counter-font-size` | `0.75rem` | Counter font size |
313
+ | `--ms-counter-bg-hover` | `#2563eb` | Hover background color |
314
+
315
+ ### Tooltips
316
+
317
+ | Variable | Default | Description |
318
+ |----------|---------|-------------|
319
+ | `--ms-tooltip-bg` | `var(--base-tooltip-bg, var(--base-inverse-bg, light-dark(#333333, #f5f5f5)))` | Tooltip background (auto-adapts to OS dark mode) |
320
+ | `--ms-tooltip-color` | `var(--ms-tooltip-text-color)` | Tooltip text color |
321
+ | `--ms-tooltip-padding` | `0.5rem 0.75rem` | Tooltip padding |
322
+ | `--ms-tooltip-border-radius` | `0.375rem` | Tooltip border radius |
323
+ | `--ms-tooltip-font-size` | `0.875rem` | Tooltip font size |
324
+ | `--ms-tooltip-max-width` | `20rem` | Tooltip maximum width |
325
+ | `--ms-tooltip-shadow` | (box shadow) | Tooltip box shadow |
326
+ | `--ms-tooltip-z-index` | `10000` | Tooltip z-index |
327
+
328
+ ### Typography
329
+
330
+ | Variable | Default | Description |
331
+ |----------|---------|-------------|
332
+ | `--ms-font-size-xs` | `0.75rem` | Extra small font size |
333
+ | `--ms-font-size-sm` | `0.875rem` | Small font size |
334
+ | `--ms-font-size-base` | `1rem` | Base font size |
335
+ | `--ms-font-weight-medium` | `500` | Medium font weight |
336
+ | `--ms-font-weight-semibold` | `600` | Semibold font weight |
337
+
338
+ ### Effects & transitions
339
+
340
+ | Variable | Default | Description |
341
+ |----------|---------|-------------|
342
+ | `--ms-transition-fast` | `150ms` | Fast transition duration |
343
+ | `--ms-transition-normal` | `200ms` | Normal transition duration |
344
+ | `--ms-easing-snappy` | (cubic-bezier) | Snappy easing function |
345
+ | `--ms-shadow-md` | (box shadow) | Medium shadow |
346
+ | `--ms-shadow-xl` | (box shadow) | Extra large shadow |
347
+ | `--ms-disabled-opacity` | `0.5` | Opacity for disabled state |
348
+
349
+ ## Cascade layers / override contract
350
+
351
+ Since v1.12.0, the component's internal CSS is organized into named `@layer`s:
352
+
353
+ ```css
354
+ @layer variables, component, overrides;
355
+ ```
356
+
357
+ This gives consumers a predictable escape hatch when they need to override a rule from outside the shadow DOM (e.g. via `web-multiselect ::part(...)` or descendant selectors that reach into composed light DOM):
358
+
359
+ | Where your rule lives | Wins against |
360
+ |---|---|
361
+ | Unlayered consumer rule | Every internal layer (no `!important` needed) |
362
+ | Consumer `@layer overrides` block | Component's `overrides` layer if loaded later in the stylesheet stack |
363
+ | `:root { --base-* }` declaration | Component's `variables` layer trivially |
364
+ | `web-multiselect { --ms-* }` element selector | Same as above, with higher specificity |
365
+
366
+ In practice you rarely need to think about layers — variables-first theming (`--ms-*` and `--base-*` overrides) covers ~95% of customization. Layers exist for the residual 5% where you need to flip a property the variable system doesn't expose.
367
+
368
+ ### Visible surface (wrapper-host pattern)
369
+
370
+ The `:host` is intentionally transparent — the visible chrome is painted by `.ms__input`, which reads `--ms-input-bg`. This wrapper-host pattern keeps the host's outer boundary free of background so the component drops into any container without a colored box, while `--ms-input-bg` still gives consumers a single knob to theme the input surface. Override `--ms-input-bg` (or `--base-input-bg`) to change the painted color; the host doesn't need its own background.
371
+
372
+ ### Watch out — the unlayered-reset footgun
373
+
374
+ A consumer-side `* { margin: 0; padding: 0; ... }` (Bootstrap reboot, Tailwind preflight, hand-rolled) is *unlayered* and therefore beats every rule inside the component's `@layer component`. The component renders with mysteriously broken spacing even though variables resolved correctly. Wrap your universal reset in its own layer (`@layer reset { * { ... } }`) so the component's layered defaults can win. See the BlissFramework `css-structure.md` → "The unlayered-reset footgun" for the canonical write-up.
375
+
376
+ ## Advanced: direct CSS import
377
+
378
+ For users who want to import the raw CSS source files:
379
+
380
+ ```css
381
+ /* Import component CSS directly */
382
+ @import '@keenmate/web-multiselect/css';
383
+
384
+ /* Or import individual partials */
385
+ @import '@keenmate/web-multiselect/src/css/variables.css';
386
+ @import '@keenmate/web-multiselect/src/css/base.css';
387
+ /* ... etc */
388
+ ```