luxen-ui 0.18.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +1 -0
  2. package/cdn/color-scheme.d.ts +76 -0
  3. package/cdn/color-scheme.d.ts.map +1 -0
  4. package/cdn/color-scheme.js +2 -0
  5. package/cdn/color-scheme.js.map +1 -0
  6. package/cdn/config.d.ts +23 -0
  7. package/cdn/config.d.ts.map +1 -1
  8. package/cdn/custom-elements.json +142 -2
  9. package/cdn/elements/color-scheme-icon/color-scheme-icon.d.ts +55 -0
  10. package/cdn/elements/color-scheme-icon/color-scheme-icon.d.ts.map +1 -0
  11. package/cdn/elements/color-scheme-icon/color-scheme-icon.js +44 -0
  12. package/cdn/elements/color-scheme-icon/color-scheme-icon.js.map +1 -0
  13. package/cdn/elements/color-scheme-icon/index.d.ts +2 -0
  14. package/cdn/elements/color-scheme-icon/index.d.ts.map +1 -0
  15. package/cdn/elements/color-scheme-icon/index.js +2 -0
  16. package/cdn/elements/color-scheme-icon/index.js.map +1 -0
  17. package/cdn/elements/dropdown-item/dropdown-item.d.ts +9 -1
  18. package/cdn/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
  19. package/cdn/elements/dropdown-item/dropdown-item.js +32 -23
  20. package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
  21. package/cdn/elements/segmented-control/segmented-control.d.ts +2 -0
  22. package/cdn/elements/segmented-control/segmented-control.d.ts.map +1 -1
  23. package/cdn/elements/segmented-control/segmented-control.js +1 -1
  24. package/cdn/elements/segmented-control/segmented-control.js.map +1 -1
  25. package/cdn/elements/tabs/tabs.d.ts +28 -1
  26. package/cdn/elements/tabs/tabs.d.ts.map +1 -1
  27. package/cdn/elements/tabs/tabs.js +1 -1
  28. package/cdn/elements/tabs/tabs.js.map +1 -1
  29. package/cdn/index.d.ts +2 -0
  30. package/cdn/index.d.ts.map +1 -1
  31. package/cdn/index.js +1 -1
  32. package/cdn/registry.d.ts +1 -1
  33. package/cdn/registry.d.ts.map +1 -1
  34. package/cdn/registry.js.map +1 -1
  35. package/cdn/standalone.css +23 -4
  36. package/cdn/standalone.js.map +1 -1
  37. package/cdn/styles/elements/tabs/enclosed.css +9 -1
  38. package/cdn/styles/elements/tabs/line.css +14 -3
  39. package/dist/color-scheme.d.ts +76 -0
  40. package/dist/color-scheme.d.ts.map +1 -0
  41. package/dist/color-scheme.js +203 -0
  42. package/dist/config.d.ts +23 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/css/elements/tabs/enclosed.css +9 -1
  45. package/dist/css/elements/tabs/line.css +14 -3
  46. package/dist/custom-elements.json +142 -2
  47. package/dist/elements/color-scheme-icon/color-scheme-icon.css +98 -0
  48. package/dist/elements/color-scheme-icon/color-scheme-icon.d.ts +55 -0
  49. package/dist/elements/color-scheme-icon/color-scheme-icon.d.ts.map +1 -0
  50. package/dist/elements/color-scheme-icon/color-scheme-icon.js +138 -0
  51. package/dist/elements/color-scheme-icon/index.d.ts +2 -0
  52. package/dist/elements/color-scheme-icon/index.d.ts.map +1 -0
  53. package/dist/elements/color-scheme-icon/index.js +4 -0
  54. package/dist/elements/dropdown-item/dropdown-item.d.ts +9 -1
  55. package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
  56. package/dist/elements/dropdown-item/dropdown-item.js +49 -26
  57. package/dist/elements/segmented-control/segmented-control.d.ts +2 -0
  58. package/dist/elements/segmented-control/segmented-control.d.ts.map +1 -1
  59. package/dist/elements/segmented-control/segmented-control.js +11 -1
  60. package/dist/elements/tabs/tabs.d.ts +28 -1
  61. package/dist/elements/tabs/tabs.d.ts.map +1 -1
  62. package/dist/elements/tabs/tabs.js +121 -26
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1 -0
  66. package/dist/metadata/color-scheme-icon.json +66 -0
  67. package/dist/metadata/dropdown-item.json +9 -1
  68. package/dist/metadata/index.json +82 -3
  69. package/dist/metadata/tabs.json +6 -1
  70. package/dist/registry.d.ts +1 -1
  71. package/dist/registry.d.ts.map +1 -1
  72. package/dist/templates/elements/color-scheme-icon.md +279 -0
  73. package/dist/templates/elements/dropdown.md +54 -1
  74. package/dist/templates/elements/tabs.md +40 -7
  75. package/elements.json +8 -0
  76. package/package.json +5 -1
  77. package/vite-plugin.ts +71 -8
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.18.2",
2
+ "version": "0.19.0",
3
3
  "elements": [
4
4
  {
5
5
  "name": "alert",
@@ -2313,6 +2313,14 @@
2313
2313
  "reflects": true,
2314
2314
  "description": "Whether this item's submenu is open. Managed by the parent `l-dropdown`."
2315
2315
  },
2316
+ {
2317
+ "name": "checkPlacement",
2318
+ "attribute": "check-placement",
2319
+ "type": "'start' | 'end'",
2320
+ "default": "'start'",
2321
+ "reflects": true,
2322
+ "description": "Which side of a `type=\"checkbox\"` item carries the check mark. `start` (the\ndefault) puts it in the leading column, where it takes the place of any\n`prefix`. `end` moves it to the trailing edge, freeing that column so the\nitem can show its own icon alongside the state."
2323
+ },
2316
2324
  {
2317
2325
  "name": "hasSubmenu",
2318
2326
  "attribute": null,
@@ -2364,7 +2372,7 @@
2364
2372
  },
2365
2373
  {
2366
2374
  "name": "prefix",
2367
- "description": "Leading content (e.g. icon)."
2375
+ "description": "Leading content (e.g. icon). A `type=\"checkbox\"` item ignores it unless `check-placement=\"end\"` frees the leading column."
2368
2376
  },
2369
2377
  {
2370
2378
  "name": "suffix",
@@ -5325,7 +5333,7 @@
5325
5333
  "events": [
5326
5334
  {
5327
5335
  "name": "change",
5328
- "description": "Fired when the active tab changes. Bubbles. Properties: `index: number`, `name: string | null`.",
5336
+ "description": "Fired when the active tab changes. Bubbles. Not cancelable. Properties: `index: number`, `name: string | null`.",
5329
5337
  "cancelable": false
5330
5338
  }
5331
5339
  ],
@@ -5371,6 +5379,11 @@
5371
5379
  "title": "Mark the initially-active tab with `aria-selected=\"true\"` so it is styled as",
5372
5380
  "language": "html",
5373
5381
  "code": "selected before the script upgrades the element (progressive enhancement).\n```html\n<l-tabs variant=\"enclosed\">\n <div>\n <button aria-selected=\"true\">Tab 1</button>\n <button>Tab 2</button>\n </div>\n <div>Content 1</div>\n <div>Content 2</div>\n</l-tabs>\n```"
5382
+ },
5383
+ {
5384
+ "title": "A tab button marked `disabled` (or `aria-disabled=\"true\"`) is skipped by",
5385
+ "language": "html",
5386
+ "code": "arrow keys, `Home` and `End`, and cannot be selected by a click or by\n`value` — it still owns its panel, so the tab/panel order is unchanged.\n```html\n<button disabled>Billing</button>\n```"
5374
5387
  }
5375
5388
  ]
5376
5389
  },
@@ -5667,6 +5680,72 @@
5667
5680
  }
5668
5681
  ]
5669
5682
  },
5683
+ {
5684
+ "name": "color-scheme-icon",
5685
+ "displayName": "Color scheme icon",
5686
+ "type": "shadow",
5687
+ "isCustomElement": true,
5688
+ "tag": "l-color-scheme-icon",
5689
+ "nativeTag": null,
5690
+ "selector": "l-color-scheme-icon",
5691
+ "subItemOf": null,
5692
+ "summary": "A sun that morphs into a moon. Purely presentational: it shows a\ncolor scheme, it does not choose or store one.\n\nThe two glyphs are a single shape — the disc grows, a mask slides in to carve\nthe crescent, and the rays retract in a ripple — so the change reads as one\nobject turning rather than two icons swapping. That is why this is authored\nSVG rather than an `<l-icon>`: an Iconify icon is opaque, and you cannot grow\none into another or animate a mask inside it.\n\nUse it wherever a scheme needs a glyph and the surrounding element is already\nthe control — a `menuitemcheckbox` row, a button, a legend. With no `scheme`\nit follows the page's scheme from `luxen-ui/color-scheme`, so it needs no\nwiring; set `scheme` to pin it.",
5693
+ "status": "stable",
5694
+ "appearances": [],
5695
+ "import": {
5696
+ "css": null,
5697
+ "js": "luxen-ui/color-scheme-icon"
5698
+ },
5699
+ "properties": [
5700
+ {
5701
+ "name": "scheme",
5702
+ "attribute": "scheme",
5703
+ "type": "ColorScheme | undefined",
5704
+ "default": null,
5705
+ "reflects": true,
5706
+ "description": "Which glyph to show. Leave it unset and the icon follows the page's scheme\n(`colorScheme.current` — a stored override, else the OS preference), so it\nstays in step with every other control without wiring. Set it to pin the\nglyph, for a legend or a preview that must not move."
5707
+ },
5708
+ {
5709
+ "name": "label",
5710
+ "attribute": "label",
5711
+ "type": "string | undefined",
5712
+ "default": null,
5713
+ "reflects": false,
5714
+ "description": "Accessible label. When set, the glyph becomes meaningful (`role=\"img\"`);\nwhen absent it is decorative and hidden from assistive tech — the right\ndefault inside a row or button that already carries the name."
5715
+ }
5716
+ ],
5717
+ "attributes": [],
5718
+ "events": [],
5719
+ "methods": [],
5720
+ "slots": [],
5721
+ "cssClasses": [],
5722
+ "cssParts": [
5723
+ {
5724
+ "name": "base",
5725
+ "description": "The root `<svg>`, for overriding the morph's timing or tilt."
5726
+ }
5727
+ ],
5728
+ "cssProperties": [
5729
+ {
5730
+ "name": "--size",
5731
+ "default": "1em",
5732
+ "description": "Width and height of the glyph."
5733
+ },
5734
+ {
5735
+ "name": "--color",
5736
+ "default": "currentColor",
5737
+ "description": "Glyph color."
5738
+ }
5739
+ ],
5740
+ "commands": [],
5741
+ "examples": [
5742
+ {
5743
+ "title": "In a menu row, where the row is the control",
5744
+ "language": "html",
5745
+ "code": "```html\n<l-dropdown-item type=\"checkbox\" checked>\n<l-color-scheme-icon slot=\"prefix\" scheme=\"dark\"></l-color-scheme-icon>\nDark theme\n</l-dropdown-item>\n```"
5746
+ }
5747
+ ]
5748
+ },
5670
5749
  {
5671
5750
  "name": "toast",
5672
5751
  "displayName": "Toast",
@@ -55,7 +55,7 @@
55
55
  "events": [
56
56
  {
57
57
  "name": "change",
58
- "description": "Fired when the active tab changes. Bubbles. Properties: `index: number`, `name: string | null`.",
58
+ "description": "Fired when the active tab changes. Bubbles. Not cancelable. Properties: `index: number`, `name: string | null`.",
59
59
  "cancelable": false
60
60
  }
61
61
  ],
@@ -101,6 +101,11 @@
101
101
  "title": "Mark the initially-active tab with `aria-selected=\"true\"` so it is styled as",
102
102
  "language": "html",
103
103
  "code": "selected before the script upgrades the element (progressive enhancement).\n```html\n<l-tabs variant=\"enclosed\">\n <div>\n <button aria-selected=\"true\">Tab 1</button>\n <button>Tab 2</button>\n </div>\n <div>Content 1</div>\n <div>Content 2</div>\n</l-tabs>\n```"
104
+ },
105
+ {
106
+ "title": "A tab button marked `disabled` (or `aria-disabled=\"true\"`) is skipped by",
107
+ "language": "html",
108
+ "code": "arrow keys, `Home` and `End`, and cannot be selected by a click or by\n`value` — it still owns its panel, so the tab/panel order is unchanged.\n```html\n<button disabled>Billing</button>\n```"
104
109
  }
105
110
  ]
106
111
  }
@@ -1,5 +1,5 @@
1
1
  /** Every component base name the library ships. */
2
- export type ElementBaseName = 'alert' | 'alert-dialog' | 'avatar' | 'badge' | 'button-group' | 'carousel' | 'carousel-item' | 'combobox' | 'dialog' | 'divider' | 'drawer' | 'dropdown' | 'dropdown-item' | 'dropdown-label' | 'form-field' | 'icon' | 'input-group' | 'input-otp' | 'input-stepper' | 'popover' | 'prose-editor' | 'skeleton' | 'slider' | 'spinner' | 'rating' | 'segmented-control' | 'select' | 'sticky-bar' | 'stories' | 'story' | 'stories-viewer' | 'tabs' | 'tag' | 'toast' | 'toast-item' | 'tooltip' | 'tree' | 'tree-item';
2
+ export type ElementBaseName = 'alert' | 'alert-dialog' | 'avatar' | 'badge' | 'button-group' | 'carousel' | 'carousel-item' | 'combobox' | 'dialog' | 'divider' | 'drawer' | 'dropdown' | 'dropdown-item' | 'dropdown-label' | 'form-field' | 'icon' | 'input-group' | 'input-otp' | 'input-stepper' | 'popover' | 'prose-editor' | 'skeleton' | 'slider' | 'spinner' | 'rating' | 'segmented-control' | 'select' | 'sticky-bar' | 'stories' | 'story' | 'stories-viewer' | 'tabs' | 'tag' | 'color-scheme-icon' | 'toast' | 'toast-item' | 'tooltip' | 'tree' | 'tree-item';
3
3
  /** Set the global prefix. Must be called before defining elements. */
4
4
  export declare function setPrefix(prefix: {
5
5
  element: string;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/html/registry.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,cAAc,GACd,QAAQ,GACR,OAAO,GACP,cAAc,GACd,UAAU,GACV,eAAe,GACf,UAAU,GACV,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,YAAY,GACZ,MAAM,GACN,aAAa,GACb,WAAW,GACX,eAAe,GACf,SAAS,GACT,cAAc,GACd,UAAU,GACV,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,mBAAmB,GACnB,QAAQ,GACR,YAAY,GACZ,SAAS,GACT,OAAO,GACP,gBAAgB,GAChB,MAAM,GACN,KAAK,GACL,OAAO,GACP,YAAY,GACZ,SAAS,GACT,MAAM,GACN,WAAW,CAAC;AAMhB,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGxE;AAED,gCAAgC;AAChC,wBAAgB,SAAS,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAE5D;AAED,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAEzD;AAED,sEAAsE;AACtE,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAE9D"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/html/registry.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,cAAc,GACd,QAAQ,GACR,OAAO,GACP,cAAc,GACd,UAAU,GACV,eAAe,GACf,UAAU,GACV,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,YAAY,GACZ,MAAM,GACN,aAAa,GACb,WAAW,GACX,eAAe,GACf,SAAS,GACT,cAAc,GACd,UAAU,GACV,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,mBAAmB,GACnB,QAAQ,GACR,YAAY,GACZ,SAAS,GACT,OAAO,GACP,gBAAgB,GAChB,MAAM,GACN,KAAK,GACL,mBAAmB,GACnB,OAAO,GACP,YAAY,GACZ,SAAS,GACT,MAAM,GACN,WAAW,CAAC;AAMhB,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGxE;AAED,gCAAgC;AAChC,wBAAgB,SAAS,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAE5D;AAED,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAEzD;AAED,sEAAsE;AACtE,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAE9D"}
@@ -0,0 +1,279 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Color scheme icon
6
+
7
+ A sun that morphs into a moon.
8
+
9
+ **`<l-color-scheme-icon>`** — Custom Element · Shadow DOM
10
+
11
+ ```html
12
+ <l-color-scheme-icon scheme="dark"></l-color-scheme-icon>
13
+ ```
14
+
15
+ The two glyphs are one shape: the disc grows, a mask slides in to carve the crescent, and the rays retract in a ripple. Nothing is added or removed, so the change reads as a single object turning rather than two icons swapping.
16
+
17
+ Its home is an icon-only button in a header — the button owns the role, the name and the pressed state; the glyph shows which scheme is in effect.
18
+
19
+ ```html
20
+ <header
21
+ class="flex w-full items-center justify-between gap-4 rounded-xl border px-4 py-3 border-[var(--l-color-border)] bg-[var(--l-color-surface)]"
22
+ >
23
+ <div class="flex items-center gap-6">
24
+ <span class="text-sm font-semibold text-[var(--l-color-text-primary)]">Acme</span>
25
+
26
+ <nav class="flex gap-4 text-sm text-[var(--l-color-text-secondary)]">
27
+ <a href="#">Dashboard</a>
28
+ <a href="#">Reports</a>
29
+ <a href="#">Settings</a>
30
+ </nav>
31
+ </div>
32
+
33
+ <button
34
+ class="l-button"
35
+ data-icon-only
36
+ aria-pressed="false"
37
+ aria-label="Dark theme"
38
+ >
39
+ <l-color-scheme-icon></l-color-scheme-icon>
40
+ </button>
41
+ </header>
42
+ ```
43
+
44
+ It is **presentational only** — it shows a scheme, it never chooses or stores one. With no `scheme` it follows [`colorScheme.current`](/overview/color-scheme), so it stays in step with every other control on the page without wiring; set `scheme` to pin it.
45
+
46
+ The example above uses inline handlers to stay readable. In an application, the click and the pressed state are what you wire — the glyph needs nothing:
47
+
48
+ ```js
49
+ import { colorScheme } from 'luxen-ui/color-scheme';
50
+
51
+ const button = document.querySelector('[aria-label="Dark theme"]');
52
+
53
+ button.addEventListener('click', () => colorScheme.toggle());
54
+
55
+ // Called once with the current scheme, then on every change — no initial pass.
56
+ colorScheme.subscribe((scheme) => {
57
+ button.setAttribute('aria-pressed', String(scheme === 'dark'));
58
+ });
59
+ ```
60
+
61
+ ### Which scheme does the glyph show?
62
+
63
+ The one **in effect**, not the one a click would move to: dark mode shows the moon. That is what makes it correct inside a `role="switch"` thumb or beside an `aria-pressed` button, where the state is already announced and the glyph should agree with it.
64
+
65
+ To advertise the destination instead — a sun meaning "switch to light" — pass the opposite of your current scheme. Pick one convention and hold it across the app; mixing them is how a toggle stops being readable.
66
+
67
+ ## Options
68
+
69
+ ### Scheme
70
+
71
+ Leave `scheme` unset and the icon follows the page's [color scheme](/overview/color-scheme) — a stored override, else the OS preference — updating itself when it changes anywhere, including in another tab.
72
+
73
+ Set `scheme` and the store is out of the picture: the glyph shows what you tell it and nothing else moves it. That is the hook for an app that already owns its light/dark state — a framework store, a class on `<html>` written by the server, a user setting fetched from an API — and also what pins a glyph in a legend or a preview.
74
+
75
+ ```js
76
+ // Your state, your rules. The icon just draws it.
77
+ theme.subscribe((value) => (icon.scheme = value));
78
+ ```
79
+
80
+ ```html
81
+ <div class="flex items-center gap-8">
82
+ <div class="flex flex-col items-center gap-2">
83
+ <l-color-scheme-icon
84
+ scheme="light"
85
+ class="[--size:2rem]"
86
+ ></l-color-scheme-icon>
87
+ <code class="text-xs text-[var(--l-color-text-secondary)]">scheme="light"</code>
88
+ </div>
89
+
90
+ <div class="flex flex-col items-center gap-2">
91
+ <l-color-scheme-icon
92
+ scheme="dark"
93
+ class="[--size:2rem]"
94
+ ></l-color-scheme-icon>
95
+ <code class="text-xs text-[var(--l-color-text-secondary)]">scheme="dark"</code>
96
+ </div>
97
+
98
+ <div class="flex flex-col items-center gap-2">
99
+ <l-color-scheme-icon class="[--size:2rem]"></l-color-scheme-icon>
100
+ <code class="text-xs text-[var(--l-color-text-secondary)]">follows the page</code>
101
+ </div>
102
+ </div>
103
+ ```
104
+
105
+ ### Size & color
106
+
107
+ `--size` sets width and height (default `1em`, so it scales with the surrounding text). `--color` defaults to `currentColor`.
108
+
109
+ ```html
110
+ <div class="flex items-center gap-8">
111
+ <l-color-scheme-icon scheme="dark"></l-color-scheme-icon>
112
+
113
+ <l-color-scheme-icon
114
+ scheme="dark"
115
+ class="[--size:2rem]"
116
+ ></l-color-scheme-icon>
117
+
118
+ <l-color-scheme-icon
119
+ scheme="dark"
120
+ class="[--size:3rem] [--color:var(--l-color-text-info)]"
121
+ ></l-color-scheme-icon>
122
+ </div>
123
+ ```
124
+
125
+ ### Accessible name
126
+
127
+ The icon is decorative by default and hidden from assistive tech, which is what you want inside a row or button that already carries the name. Set `label` to make it meaningful — it then exposes `role="img"` with that name.
128
+
129
+ Both icons below look identical; the difference exists only in the accessibility tree.
130
+
131
+ ```html
132
+ <div class="flex items-center gap-8">
133
+ <button
134
+ class="l-button"
135
+ data-icon-only
136
+ aria-label="Dark theme"
137
+ >
138
+ <l-color-scheme-icon></l-color-scheme-icon>
139
+ </button>
140
+
141
+ <l-color-scheme-icon
142
+ label="Currently in dark theme"
143
+ scheme="dark"
144
+ class="[--size:2rem]"
145
+ ></l-color-scheme-icon>
146
+ </div>
147
+ ```
148
+
149
+ ## Examples
150
+
151
+ ### In a menu row
152
+
153
+ Put it in a `type="checkbox"` [dropdown item](/elements/dropdown#theme-row) and the row is the control: whole row clickable, `menuitemcheckbox` role, menu stays open. The icon stays decorative.
154
+
155
+ Add `check-placement="end"` so the check moves to the trailing edge and leaves the leading column free — the glyph then lines up with every other row's icon.
156
+
157
+ ```html
158
+ <header
159
+ class="flex w-full items-center justify-between gap-4 rounded-xl border px-4 py-3 border-[var(--l-color-border)] bg-[var(--l-color-surface)]"
160
+ >
161
+ <div class="flex items-center gap-6">
162
+ <span class="text-sm font-semibold text-[var(--l-color-text-primary)]">Acme</span>
163
+
164
+ <nav class="flex gap-4 text-sm text-[var(--l-color-text-secondary)]">
165
+ <a href="#">Dashboard</a>
166
+ <a href="#">Reports</a>
167
+ </nav>
168
+ </div>
169
+
170
+ <l-dropdown placement="bottom-end">
171
+ <l-avatar
172
+ slot="trigger"
173
+ interactive
174
+ name="Jane Cooper"
175
+ aria-label="Account"
176
+ class="[--appearance:circle] [--color:var(--l-color-blue-200)]"
177
+ ></l-avatar>
178
+
179
+ <div
180
+ slot="header"
181
+ class="flex flex-col px-2 py-1.5"
182
+ >
183
+ <span class="text-sm font-medium text-[var(--l-color-text-primary)]">Jane Cooper</span>
184
+ <span class="text-xs text-[var(--l-color-text-secondary)]">jane.cooper@acme.com</span>
185
+ </div>
186
+
187
+ <l-divider></l-divider>
188
+
189
+ <l-dropdown-item
190
+ type="checkbox"
191
+ check-placement="end"
192
+ value="theme"
193
+ >
194
+ <l-color-scheme-icon slot="prefix"></l-color-scheme-icon>
195
+ Dark theme
196
+ </l-dropdown-item>
197
+
198
+ <l-dropdown-item
199
+ type="checkbox"
200
+ check-placement="end"
201
+ value="compact"
202
+ checked
203
+ >
204
+ <iconify-icon
205
+ slot="prefix"
206
+ icon="lucide:rows-3"
207
+ ></iconify-icon>
208
+ Compact rows
209
+ </l-dropdown-item>
210
+
211
+ <l-divider></l-divider>
212
+
213
+ <l-dropdown-item value="signout">
214
+ <iconify-icon
215
+ slot="prefix"
216
+ icon="lucide:log-out"
217
+ ></iconify-icon>
218
+ Sign out
219
+ </l-dropdown-item>
220
+ </l-dropdown>
221
+ </header>
222
+ ```
223
+
224
+ Here the row carries the state, so `checked` is what you keep in step:
225
+
226
+ ```js
227
+ import { colorScheme } from 'luxen-ui/color-scheme';
228
+
229
+ const item = document.querySelector('l-dropdown-item[value="theme"]');
230
+
231
+ document.querySelector('l-dropdown').addEventListener('select', (event) => {
232
+ if (event.item === item) colorScheme.set(item.checked ? 'dark' : 'light');
233
+ });
234
+
235
+ colorScheme.subscribe((scheme) => (item.checked = scheme === 'dark'));
236
+ ```
237
+
238
+ Do not put a [switch](/elements/switch) in a menu row instead: a `role="menu"` may only own `menuitem`, `menuitemcheckbox` and `menuitemradio` children, and a nested control gives the row two competing click targets.
239
+
240
+ ## Accessibility
241
+
242
+ ### Criteria
243
+
244
+ - **Decorative by default** — With no `label` the icon is `aria-hidden` — the surrounding row or button carries the accessible name, so nothing is announced twice
245
+ - **Meaningful when named** — Setting `label` exposes `role=img` with that name, for the rare case where the glyph stands alone
246
+ - **Reduced motion** — The morph collapses to 0ms under `prefers-reduced-motion`; the glyph still changes, it just does not animate
247
+
248
+ ### Rules
249
+
250
+ - Leave the icon decorative when it sits inside a control that already has a name — a menu row, a button
251
+ - Never rely on the glyph alone to convey the state; pair it with a label or an `aria-checked` row
252
+ - Leave `scheme` unset unless the glyph must be pinned — it then follows the page and cannot disagree with it
253
+
254
+ ## API reference
255
+
256
+ ### Importing
257
+
258
+ ```js
259
+ import 'luxen-ui/color-scheme-icon';
260
+ ```
261
+
262
+ ### Properties
263
+
264
+ - **scheme**: `ColorScheme | undefined` — Which glyph to show. Leave it unset and the icon follows the page's scheme
265
+ (`colorScheme.current` — a stored override, else the OS preference), so it
266
+ stays in step with every other control without wiring. Set it to pin the
267
+ glyph, for a legend or a preview that must not move.
268
+ - **label**: `string | undefined` — Accessible label. When set, the glyph becomes meaningful (`role="img"`);
269
+ when absent it is decorative and hidden from assistive tech — the right
270
+ default inside a row or button that already carries the name.
271
+
272
+ ### CSS custom properties
273
+
274
+ - `--size` (default: `1em`) — Width and height of the glyph.
275
+ - `--color` (default: `currentColor`) — Glyph color.
276
+
277
+ ### CSS parts
278
+
279
+ - `base` — The root `<svg>`, for overriding the morph's timing or tilt.
@@ -394,6 +394,55 @@ Use the `header` slot for a profile row, the `prefix` slot on each `<l-dropdown-
394
394
  </l-dropdown>
395
395
  ```
396
396
 
397
+ ### Theme row {#theme-row}
398
+
399
+ A display setting belongs in the menu as a `type="checkbox"` item, not as a switch nested inside one. The whole row is then the control — one click target, `role="menuitemcheckbox"`, and the menu stays open so the change is visible immediately. A nested switch would give the row two competing targets, and `role="menu"` may only own `menuitem`, `menuitemcheckbox` and `menuitemradio` children.
400
+
401
+ A checkbox item puts its check in the leading column, which leaves no room for an icon. Set `check-placement="end"` to move the check to the trailing edge and free that column, so the row keeps its icon **and** shows whether it is on — and its icon lines up with every other row in the menu.
402
+
403
+ [`<l-color-scheme-icon>`](/elements/color-scheme-icon) in the `prefix` slot then morphs from sun to moon as the row toggles. It stays decorative: the row carries the name, the role and the state.
404
+
405
+ ```html
406
+ <l-dropdown>
407
+ <l-avatar
408
+ slot="trigger"
409
+ interactive
410
+ name="Jane Cooper"
411
+ aria-label="Account"
412
+ style="--appearance: circle; --color: var(--color-purple-200)"
413
+ ></l-avatar>
414
+
415
+ <l-dropdown-item value="whats-new">
416
+ <iconify-icon
417
+ slot="prefix"
418
+ icon="lucide:megaphone"
419
+ ></iconify-icon>
420
+ What's new
421
+ </l-dropdown-item>
422
+
423
+ <l-dropdown-item
424
+ type="checkbox"
425
+ check-placement="end"
426
+ value="theme"
427
+ >
428
+ <l-color-scheme-icon slot="prefix"></l-color-scheme-icon>
429
+ Dark theme
430
+ </l-dropdown-item>
431
+
432
+ <l-divider></l-divider>
433
+
434
+ <l-dropdown-item value="signout">
435
+ <iconify-icon
436
+ slot="prefix"
437
+ icon="lucide:log-out"
438
+ ></iconify-icon>
439
+ Sign out
440
+ </l-dropdown-item>
441
+ </l-dropdown>
442
+ ```
443
+
444
+ The row reflects and changes the page's [color scheme](/overview/color-scheme); the same glyph works in a header button, outside any menu.
445
+
397
446
  ## Accessibility
398
447
 
399
448
  ### Criteria
@@ -487,12 +536,16 @@ while open.
487
536
  - **variant**: `'destructive' | undefined` (default: `undefined`) — Visual variant. `destructive` marks a delete-style action in red.
488
537
  - **checked**: `boolean` (default: `false`) — Whether the checkbox item is checked.
489
538
  - **submenu-open**: `boolean` (default: `false`) — Whether this item's submenu is open. Managed by the parent `l-dropdown`.
539
+ - **check-placement**: `'start' | 'end'` (default: `'start'`) — Which side of a `type="checkbox"` item carries the check mark. `start` (the
540
+ default) puts it in the leading column, where it takes the place of any
541
+ `prefix`. `end` moves it to the trailing edge, freeing that column so the
542
+ item can show its own icon alongside the state.
490
543
  - **hasSubmenu**: `boolean` — Whether this item has nested `slot="submenu"` items.
491
544
 
492
545
  ### `dropdown-item` Slots
493
546
 
494
547
  - **(default)** — Label text.
495
- - **prefix** — Leading content (e.g. icon).
548
+ - **prefix** — Leading content (e.g. icon). A `type="checkbox"` item ignores it unless `check-placement="end"` frees the leading column.
496
549
  - **suffix** — Trailing content.
497
550
  - **submenu** — Nested `l-dropdown-item` elements rendered in a submenu panel anchored to this item. Drop an `<hr>` between them for a separator.
498
551
 
@@ -132,6 +132,37 @@ Set `value="1"` to activate a specific tab on load (0-based index).
132
132
  </l-tabs>
133
133
  ```
134
134
 
135
+ ### Disabled tab
136
+
137
+ Add `disabled` to a tab button to take it out of play. It is skipped by arrow keys, `Home` and `End`, it cannot be selected by a click or by `value`, and it never shows the hover pill. `aria-disabled="true"` behaves identically — reach for it only when the native attribute is out of reach (a framework binding, a non-`button` element).
138
+
139
+ `l-tabs` itself takes no `disabled` attribute. To freeze tab switching — while a page loads, say — put `inert` on the tablist `div`: the tabs leave the tab sequence and stop answering clicks, and the active panel stays reachable.
140
+
141
+ ```html
142
+ <l-tabs variant="line">
143
+ <div>
144
+ <button
145
+ name="account"
146
+ aria-selected="true"
147
+ >
148
+ Account
149
+ </button>
150
+ <button name="password">Password</button>
151
+ <button
152
+ name="billing"
153
+ disabled
154
+ >
155
+ Billing
156
+ </button>
157
+ <button name="notifications">Notifications</button>
158
+ </div>
159
+ <div class="p-4 text-sm text-secondary">Make changes to your account here.</div>
160
+ <div class="p-4 text-sm text-secondary">Change your password here.</div>
161
+ <div class="p-4 text-sm text-secondary">Upgrade your plan to manage billing.</div>
162
+ <div class="p-4 text-sm text-secondary">Manage your notification preferences.</div>
163
+ </l-tabs>
164
+ ```
165
+
135
166
  ## Accessibility
136
167
 
137
168
  ### Criteria
@@ -140,6 +171,7 @@ Set `value="1"` to activate a specific tab on load (0-based index).
140
171
  - **Linked controls** — Each tab has `aria-controls` pointing to its panel; each panel has `aria-labelledby` pointing back to its tab
141
172
  - **Selection state** — Active tab has `aria-selected="true"`; inactive tabs have `aria-selected="false"`
142
173
  - **Focus management** — Roving tabindex — active tab has `tabindex="0"`, others `tabindex="-1"`. Panels have `tabindex="0"` for keyboard access
174
+ - **Disabled tabs** — Tabs marked `disabled` or `aria-disabled="true"` are skipped by arrow keys, `Home` and `End`, and never take the roving `tabindex="0"` — so the tablist always keeps a reachable entry point
143
175
  - **Hidden content** — Inactive panels use the `hidden` attribute
144
176
  - **Motion** — Indicator animation respects `prefers-reduced-motion`
145
177
 
@@ -148,15 +180,16 @@ Set `value="1"` to activate a specific tab on load (0-based index).
148
180
  - The first child of `l-tabs` must be a `div` containing `button` elements for the tab triggers
149
181
  - Each remaining child `div` maps to a tab panel in order
150
182
  - Do not add `role` or `aria-*` attributes manually — the custom element sets them automatically
183
+ - `l-tabs` has no host-level `disabled` attribute — put `inert` on the tablist `div` to freeze tab switching
151
184
 
152
185
  ### Keyboard interactions
153
186
 
154
- - `ArrowRight` — Moves focus to the next tab and activates it (horizontal orientation)
155
- - `ArrowLeft` — Moves focus to the previous tab and activates it (horizontal orientation)
156
- - `ArrowDown` — Moves focus to the next tab and activates it (vertical orientation)
157
- - `ArrowUp` — Moves focus to the previous tab and activates it (vertical orientation)
158
- - `Home` — Moves focus to the first tab and activates it
159
- - `End` — Moves focus to the last tab and activates it
187
+ - `ArrowRight` — Moves focus to the next enabled tab and activates it (horizontal orientation)
188
+ - `ArrowLeft` — Moves focus to the previous enabled tab and activates it (horizontal orientation)
189
+ - `ArrowDown` — Moves focus to the next enabled tab and activates it (vertical orientation)
190
+ - `ArrowUp` — Moves focus to the previous enabled tab and activates it (vertical orientation)
191
+ - `Home` — Moves focus to the first enabled tab and activates it
192
+ - `End` — Moves focus to the last enabled tab and activates it
160
193
  - `Tab` — Moves focus out of the tablist to the active panel
161
194
 
162
195
  ## API reference
@@ -184,7 +217,7 @@ import 'luxen-ui/tabs';
184
217
 
185
218
  ### Events
186
219
 
187
- - **change** — Fired when the active tab changes. Bubbles. Properties: `index: number`, `name: string | null`.
220
+ - **change** — Fired when the active tab changes. Bubbles. Not cancelable. Properties: `index: number`, `name: string | null`.
188
221
 
189
222
  ### CSS custom properties
190
223
 
package/elements.json CHANGED
@@ -373,6 +373,14 @@
373
373
  "inMockups": true,
374
374
  "selector": ".l-textarea"
375
375
  },
376
+ {
377
+ "name": "color-scheme-icon",
378
+ "displayName": "Color scheme icon",
379
+ "kind": "element",
380
+ "type": "shadow",
381
+ "inSkill": true,
382
+ "inMockups": false
383
+ },
376
384
  {
377
385
  "name": "toast",
378
386
  "displayName": "Toast",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luxen-ui",
3
- "version": "0.18.2",
3
+ "version": "0.19.0",
4
4
  "description": "Modern web components and CSS-first UI library built with Lit. Framework-agnostic, customizable prefix, design tokens.",
5
5
  "keywords": [
6
6
  "custom-elements",
@@ -117,6 +117,10 @@
117
117
  "types": "./dist/shared/localize.d.ts",
118
118
  "import": "./dist/shared/localize.js"
119
119
  },
120
+ "./color-scheme": {
121
+ "types": "./dist/color-scheme.d.ts",
122
+ "import": "./dist/color-scheme.js"
123
+ },
120
124
  "./translations/*": {
121
125
  "types": "./dist/translations/*.d.ts",
122
126
  "import": "./dist/translations/*.js"